@fedify/cli 2.0.7 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/dist/config.js +14 -3
- package/dist/deno.js +1 -1
- package/dist/docloader.js +19 -4
- package/dist/imagerenderer.js +47 -2
- package/dist/lookup.js +517 -64
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ command:
|
|
|
52
52
|
# Linux/macOS
|
|
53
53
|
deno install \
|
|
54
54
|
-A \
|
|
55
|
-
--unstable-fs --unstable-kv
|
|
55
|
+
--unstable-fs --unstable-kv \
|
|
56
56
|
-n fedify \
|
|
57
57
|
jsr:@fedify/cli
|
|
58
58
|
~~~~
|
|
@@ -61,11 +61,14 @@ deno install \
|
|
|
61
61
|
# Windows
|
|
62
62
|
deno install `
|
|
63
63
|
-A `
|
|
64
|
-
--unstable-fs --unstable-kv
|
|
64
|
+
--unstable-fs --unstable-kv `
|
|
65
65
|
-n fedify `
|
|
66
66
|
jsr:@fedify/cli
|
|
67
67
|
~~~~
|
|
68
68
|
|
|
69
|
+
On Deno versions earlier than 2.7.0, add `--unstable-temporal` to the install
|
|
70
|
+
command above.
|
|
71
|
+
|
|
69
72
|
[Deno]: https://deno.com/
|
|
70
73
|
|
|
71
74
|
### Downloading the executable
|
package/dist/config.js
CHANGED
|
@@ -6,7 +6,7 @@ import { printError } from "@optique/run";
|
|
|
6
6
|
import { readFileSync } from "node:fs";
|
|
7
7
|
import { parse } from "smol-toml";
|
|
8
8
|
import { createConfigContext } from "@optique/config";
|
|
9
|
-
import { array, boolean, number, object as object$1, optional as optional$1, picklist, string as string$1 } from "valibot";
|
|
9
|
+
import { array, boolean, check, forward, integer as integer$1, minValue, number, object as object$1, optional as optional$1, picklist, pipe, string as string$1 } from "valibot";
|
|
10
10
|
|
|
11
11
|
//#region src/config.ts
|
|
12
12
|
/**
|
|
@@ -19,11 +19,22 @@ const webfingerSchema = object$1({
|
|
|
19
19
|
/**
|
|
20
20
|
* Schema for the lookup command configuration.
|
|
21
21
|
*/
|
|
22
|
-
const lookupSchema = object$1({
|
|
22
|
+
const lookupSchema = pipe(object$1({
|
|
23
23
|
authorizedFetch: optional$1(boolean()),
|
|
24
24
|
firstKnock: optional$1(picklist(["draft-cavage-http-signatures-12", "rfc9421"])),
|
|
25
|
+
allowPrivateAddress: optional$1(boolean()),
|
|
25
26
|
traverse: optional$1(boolean()),
|
|
27
|
+
recurse: optional$1(picklist([
|
|
28
|
+
"replyTarget",
|
|
29
|
+
"quoteUrl",
|
|
30
|
+
"https://www.w3.org/ns/activitystreams#inReplyTo",
|
|
31
|
+
"https://www.w3.org/ns/activitystreams#quoteUrl",
|
|
32
|
+
"https://misskey-hub.net/ns#_misskey_quote",
|
|
33
|
+
"http://fedibird.com/ns#quoteUri"
|
|
34
|
+
])),
|
|
35
|
+
recurseDepth: optional$1(pipe(number(), integer$1(), minValue(1))),
|
|
26
36
|
suppressErrors: optional$1(boolean()),
|
|
37
|
+
reverse: optional$1(boolean()),
|
|
27
38
|
defaultFormat: optional$1(picklist([
|
|
28
39
|
"default",
|
|
29
40
|
"raw",
|
|
@@ -32,7 +43,7 @@ const lookupSchema = object$1({
|
|
|
32
43
|
])),
|
|
33
44
|
separator: optional$1(string$1()),
|
|
34
45
|
timeout: optional$1(number())
|
|
35
|
-
});
|
|
46
|
+
}), forward(check((input) => !(input.traverse === true && input.recurse != null), "lookup.traverse and lookup.recurse cannot be used together."), ["recurse"]), forward(check((input) => input.recurse != null || input.recurseDepth == null, "lookup.recurseDepth requires lookup.recurse."), ["recurseDepth"]));
|
|
36
47
|
/**
|
|
37
48
|
* Schema for the inbox command configuration.
|
|
38
49
|
*/
|
package/dist/deno.js
CHANGED
package/dist/docloader.js
CHANGED
|
@@ -7,11 +7,26 @@ import { getKvStore } from "#kv";
|
|
|
7
7
|
|
|
8
8
|
//#region src/docloader.ts
|
|
9
9
|
const documentLoaders = {};
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Returns a cache prefix that separates document-loader entries by user agent
|
|
12
|
+
* and private-address policy.
|
|
13
|
+
*/
|
|
14
|
+
function getDocumentLoaderCachePrefix(userAgent, allowPrivateAddress) {
|
|
15
|
+
return [
|
|
16
|
+
"_fedify",
|
|
17
|
+
"remoteDocument",
|
|
18
|
+
"cli",
|
|
19
|
+
userAgent ?? "",
|
|
20
|
+
allowPrivateAddress ? "allow-private" : "deny-private"
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
async function getDocumentLoader$1({ userAgent, allowPrivateAddress = false } = {}) {
|
|
24
|
+
const cacheKey = `${userAgent ?? ""}:${allowPrivateAddress}`;
|
|
25
|
+
if (documentLoaders[cacheKey]) return documentLoaders[cacheKey];
|
|
12
26
|
const kv = await getKvStore();
|
|
13
|
-
return documentLoaders[
|
|
27
|
+
return documentLoaders[cacheKey] = kvCache({
|
|
14
28
|
kv,
|
|
29
|
+
prefix: getDocumentLoaderCachePrefix(userAgent, allowPrivateAddress),
|
|
15
30
|
rules: [
|
|
16
31
|
[new URLPattern({
|
|
17
32
|
protocol: "http{s}?",
|
|
@@ -39,7 +54,7 @@ async function getDocumentLoader$1({ userAgent } = {}) {
|
|
|
39
54
|
}), { seconds: 0 }]
|
|
40
55
|
],
|
|
41
56
|
loader: getDocumentLoader({
|
|
42
|
-
allowPrivateAddress
|
|
57
|
+
allowPrivateAddress,
|
|
43
58
|
userAgent
|
|
44
59
|
})
|
|
45
60
|
});
|
package/dist/imagerenderer.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
import os from "node:os";
|
|
8
8
|
import process from "node:process";
|
|
9
|
+
import { validatePublicUrl } from "@fedify/vocab-runtime";
|
|
9
10
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
10
11
|
|
|
11
12
|
//#region src/imagerenderer.ts
|
|
@@ -18,6 +19,22 @@ const KITTY_IDENTIFIERS = [
|
|
|
18
19
|
"st",
|
|
19
20
|
"ghostty"
|
|
20
21
|
];
|
|
22
|
+
function getExtensionFromContentType(contentType) {
|
|
23
|
+
const mime = contentType?.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
24
|
+
switch (mime) {
|
|
25
|
+
case "image/jpeg":
|
|
26
|
+
case "image/jpg": return "jpg";
|
|
27
|
+
case "image/png": return "png";
|
|
28
|
+
case "image/gif": return "gif";
|
|
29
|
+
case "image/webp": return "webp";
|
|
30
|
+
case "image/avif": return "avif";
|
|
31
|
+
case "image/bmp": return "bmp";
|
|
32
|
+
case "image/svg+xml": return "svg";
|
|
33
|
+
default:
|
|
34
|
+
if (mime.startsWith("image/")) return mime.slice(6);
|
|
35
|
+
return "jpg";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
function detectTerminalCapabilities() {
|
|
22
39
|
const termProgram = (process.env.TERM_PROGRAM || "").toLowerCase();
|
|
23
40
|
if (KITTY_IDENTIFIERS.includes(termProgram)) return "kitty";
|
|
@@ -70,9 +87,37 @@ async function renderImageITerm2(imagePath) {
|
|
|
70
87
|
}
|
|
71
88
|
async function downloadImage(url) {
|
|
72
89
|
try {
|
|
73
|
-
|
|
90
|
+
let targetUrl = url;
|
|
91
|
+
let response = null;
|
|
92
|
+
for (let redirectCount = 0; redirectCount < 10; redirectCount++) {
|
|
93
|
+
await validatePublicUrl(targetUrl);
|
|
94
|
+
response = await fetch(targetUrl, { redirect: "manual" });
|
|
95
|
+
if (response.status === 301 || response.status === 302 || response.status === 303 || response.status === 307 || response.status === 308) {
|
|
96
|
+
const location = response.headers.get("location");
|
|
97
|
+
if (location == null) {
|
|
98
|
+
await response.body?.cancel();
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
await response.body?.cancel();
|
|
102
|
+
targetUrl = new URL(location, targetUrl).href;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (response == null) return null;
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
await response.body?.cancel();
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
74
112
|
const imageData = new Uint8Array(await response.arrayBuffer());
|
|
75
|
-
const
|
|
113
|
+
const pathname = new URL(targetUrl).pathname;
|
|
114
|
+
const lowerPathname = pathname.toLowerCase();
|
|
115
|
+
if (lowerPathname.includes("%2f") || lowerPathname.includes("%5c") || lowerPathname.includes("..")) return null;
|
|
116
|
+
const pathSegments = pathname.split("/").filter((segment) => segment !== "");
|
|
117
|
+
const filename = pathSegments[pathSegments.length - 1] ?? "";
|
|
118
|
+
const extension = filename.includes(".") ? path.extname(filename).slice(1) : getExtensionFromContentType(response.headers.get("content-type"));
|
|
119
|
+
if (extension.length < 1) return null;
|
|
120
|
+
if (extension.includes("/") || extension.includes("\\") || extension.includes("..")) return null;
|
|
76
121
|
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "fedify"));
|
|
77
122
|
const tempPath = path.join(tempDir, `image.${extension}`);
|
|
78
123
|
await fs.writeFile(tempPath, imageData);
|
package/dist/lookup.js
CHANGED
|
@@ -9,14 +9,15 @@ import { spawnTemporaryServer } from "./tempserver.js";
|
|
|
9
9
|
import { colorEnabled, colors, formatObject } from "./utils.js";
|
|
10
10
|
import { renderImages } from "./imagerenderer.js";
|
|
11
11
|
import process from "node:process";
|
|
12
|
-
import { argument, choice, command, constant, flag, float, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
13
|
-
import { path,
|
|
12
|
+
import { argument, choice, command, constant, flag, float, integer, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
|
|
13
|
+
import { path, printError } from "@optique/run";
|
|
14
14
|
import { createWriteStream } from "node:fs";
|
|
15
15
|
import { bindConfig } from "@optique/config";
|
|
16
16
|
import { generateCryptoKeyPair, getAuthenticatedDocumentLoader, respondWithObject } from "@fedify/fedify";
|
|
17
17
|
import { Application, Collection, CryptographicKey, Object as Object$1, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
18
18
|
import { getLogger } from "@logtape/logtape";
|
|
19
19
|
import ora from "ora";
|
|
20
|
+
import { UrlError } from "@fedify/vocab-runtime";
|
|
20
21
|
|
|
21
22
|
//#region src/lookup.ts
|
|
22
23
|
const logger = getLogger([
|
|
@@ -24,6 +25,28 @@ const logger = getLogger([
|
|
|
24
25
|
"cli",
|
|
25
26
|
"lookup"
|
|
26
27
|
]);
|
|
28
|
+
const IN_REPLY_TO_IRI = "https://www.w3.org/ns/activitystreams#inReplyTo";
|
|
29
|
+
const QUOTE_URL_IRI = "https://www.w3.org/ns/activitystreams#quoteUrl";
|
|
30
|
+
const MISSKEY_QUOTE_IRI = "https://misskey-hub.net/ns#_misskey_quote";
|
|
31
|
+
const FEDIBIRD_QUOTE_IRI = "http://fedibird.com/ns#quoteUri";
|
|
32
|
+
const recurseProperties = [
|
|
33
|
+
"replyTarget",
|
|
34
|
+
"quoteUrl",
|
|
35
|
+
IN_REPLY_TO_IRI,
|
|
36
|
+
QUOTE_URL_IRI,
|
|
37
|
+
MISSKEY_QUOTE_IRI,
|
|
38
|
+
FEDIBIRD_QUOTE_IRI
|
|
39
|
+
];
|
|
40
|
+
const suppressErrorsOption = bindConfig(flag("-S", "--suppress-errors", { description: message`Suppress partial errors during traversal or recursion.` }), {
|
|
41
|
+
context: configContext,
|
|
42
|
+
key: (config) => config.lookup?.suppressErrors ?? false,
|
|
43
|
+
default: false
|
|
44
|
+
});
|
|
45
|
+
const allowPrivateAddressOption = bindConfig(flag("-p", "--allow-private-address", { description: message`Allow private IP addresses for explicit lookup/traverse requests.` }), {
|
|
46
|
+
context: configContext,
|
|
47
|
+
key: (config) => config.lookup?.allowPrivateAddress ?? false,
|
|
48
|
+
default: false
|
|
49
|
+
});
|
|
27
50
|
const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
28
51
|
authorizedFetch: bindConfig(map(flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }), () => true), {
|
|
29
52
|
context: configContext,
|
|
@@ -40,25 +63,50 @@ const authorizedFetchOption = withDefault(object("Authorized fetch options", {
|
|
|
40
63
|
firstKnock: void 0,
|
|
41
64
|
tunnelService: void 0
|
|
42
65
|
});
|
|
43
|
-
const
|
|
66
|
+
const lookupModeOption = withDefault(or(object("Recurse options", {
|
|
67
|
+
traverse: constant(false),
|
|
68
|
+
recurse: bindConfig(option("--recurse", choice(recurseProperties, { metavar: "PROPERTY" }), { description: message`Recursively follow a relationship property.` }), {
|
|
69
|
+
context: configContext,
|
|
70
|
+
key: (config) => config.lookup?.recurse
|
|
71
|
+
}),
|
|
72
|
+
recurseDepth: withDefault(bindConfig(option("--recurse-depth", integer({
|
|
73
|
+
min: 1,
|
|
74
|
+
metavar: "DEPTH"
|
|
75
|
+
}), { description: message`Maximum recursion depth for ${optionNames(["--recurse"])}.` }), {
|
|
76
|
+
context: configContext,
|
|
77
|
+
key: (config) => config.lookup?.recurseDepth
|
|
78
|
+
}), 20),
|
|
79
|
+
suppressErrors: suppressErrorsOption
|
|
80
|
+
}), object("Traverse options", {
|
|
44
81
|
traverse: bindConfig(flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }), {
|
|
45
82
|
context: configContext,
|
|
46
83
|
key: (config) => config.lookup?.traverse ?? false,
|
|
47
84
|
default: false
|
|
48
85
|
}),
|
|
49
|
-
|
|
86
|
+
recurse: constant(void 0),
|
|
87
|
+
recurseDepth: constant(void 0),
|
|
88
|
+
suppressErrors: suppressErrorsOption
|
|
89
|
+
})), {
|
|
90
|
+
traverse: false,
|
|
91
|
+
recurse: void 0,
|
|
92
|
+
recurseDepth: void 0,
|
|
93
|
+
suppressErrors: false
|
|
94
|
+
});
|
|
95
|
+
const lookupCommand = command("lookup", merge(object({ command: constant("lookup") }), lookupModeOption, authorizedFetchOption, merge("Network options", userAgentOption, object({
|
|
96
|
+
allowPrivateAddress: allowPrivateAddressOption,
|
|
97
|
+
timeout: optional(bindConfig(option("-T", "--timeout", float({
|
|
98
|
+
min: 0,
|
|
99
|
+
metavar: "SECONDS"
|
|
100
|
+
}), { description: message`Set timeout for network requests in seconds.` }), {
|
|
50
101
|
context: configContext,
|
|
51
|
-
key: (config) => config.lookup?.
|
|
102
|
+
key: (config) => config.lookup?.timeout
|
|
103
|
+
}))
|
|
104
|
+
})), object("Arguments", { urls: multiple(argument(string({ metavar: "URL_OR_HANDLE" }), { description: message`One or more URLs or handles to look up.` }), { min: 1 }) }), object("Output options", {
|
|
105
|
+
reverse: bindConfig(flag("--reverse", { description: message`Reverse the output order of fetched objects or items.` }), {
|
|
106
|
+
context: configContext,
|
|
107
|
+
key: (config) => config.lookup?.reverse ?? false,
|
|
52
108
|
default: false
|
|
53
|
-
})
|
|
54
|
-
});
|
|
55
|
-
const lookupCommand = command("lookup", merge(object({ command: constant("lookup") }), traverseOption, authorizedFetchOption, merge("Network options", userAgentOption, object({ timeout: optional(bindConfig(option("-T", "--timeout", float({
|
|
56
|
-
min: 0,
|
|
57
|
-
metavar: "SECONDS"
|
|
58
|
-
}), { description: message`Set timeout for network requests in seconds.` }), {
|
|
59
|
-
context: configContext,
|
|
60
|
-
key: (config) => config.lookup?.timeout
|
|
61
|
-
})) })), object("Arguments", { urls: multiple(argument(string({ metavar: "URL_OR_HANDLE" }), { description: message`One or more URLs or handles to look up.` }), { min: 1 }) }), object("Output options", {
|
|
109
|
+
}),
|
|
62
110
|
format: bindConfig(optional(or(map(flag("-r", "--raw", { description: message`Print the fetched JSON-LD document as is.` }), () => "raw"), map(flag("-C", "--compact", { description: message`Compact the fetched JSON-LD document.` }), () => "compact"), map(flag("-e", "--expand", { description: message`Expand the fetched JSON-LD document.` }), () => "expand"))), {
|
|
63
111
|
context: configContext,
|
|
64
112
|
key: (config) => config.lookup?.defaultFormat ?? "default",
|
|
@@ -87,6 +135,55 @@ var TimeoutError = class extends Error {
|
|
|
87
135
|
this.name = "TimeoutError";
|
|
88
136
|
}
|
|
89
137
|
};
|
|
138
|
+
/**
|
|
139
|
+
* Error thrown when a recursive lookup target cannot be fetched.
|
|
140
|
+
*/
|
|
141
|
+
var RecursiveLookupError = class extends Error {
|
|
142
|
+
target;
|
|
143
|
+
constructor(target) {
|
|
144
|
+
super(`Failed to recursively fetch object: ${target}`);
|
|
145
|
+
this.name = "RecursiveLookupError";
|
|
146
|
+
this.target = target;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function writeToStream(stream, chunk) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const onError = (error) => {
|
|
152
|
+
stream.off("error", onError);
|
|
153
|
+
reject(error);
|
|
154
|
+
};
|
|
155
|
+
stream.once("error", onError);
|
|
156
|
+
try {
|
|
157
|
+
stream.write(chunk, (error) => {
|
|
158
|
+
stream.off("error", onError);
|
|
159
|
+
if (error != null) reject(error);
|
|
160
|
+
else resolve();
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
stream.off("error", onError);
|
|
164
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function endWritableStream(stream) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
const onError = (error) => {
|
|
171
|
+
stream.off("error", onError);
|
|
172
|
+
reject(error);
|
|
173
|
+
};
|
|
174
|
+
stream.once("error", onError);
|
|
175
|
+
try {
|
|
176
|
+
stream.end((error) => {
|
|
177
|
+
stream.off("error", onError);
|
|
178
|
+
if (error != null) reject(error);
|
|
179
|
+
else resolve();
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
stream.off("error", onError);
|
|
183
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
90
187
|
async function findAllImages(obj) {
|
|
91
188
|
const result = [];
|
|
92
189
|
const icon = await obj.getIcon();
|
|
@@ -95,8 +192,9 @@ async function findAllImages(obj) {
|
|
|
95
192
|
if (image && image.url instanceof URL) result.push(image.url);
|
|
96
193
|
return result;
|
|
97
194
|
}
|
|
98
|
-
async function writeObjectToStream(object$1, outputPath, format, contextLoader) {
|
|
99
|
-
const
|
|
195
|
+
async function writeObjectToStream(object$1, outputPath, format, contextLoader, stream) {
|
|
196
|
+
const localStream = stream ?? (outputPath ? createWriteStream(outputPath) : process.stdout);
|
|
197
|
+
const localFileStream = stream == null && outputPath != null ? localStream : void 0;
|
|
100
198
|
let content;
|
|
101
199
|
let json = true;
|
|
102
200
|
let imageUrls = [];
|
|
@@ -117,13 +215,37 @@ async function writeObjectToStream(object$1, outputPath, format, contextLoader)
|
|
|
117
215
|
content = object$1;
|
|
118
216
|
json = false;
|
|
119
217
|
}
|
|
120
|
-
const enableColors = colorEnabled &&
|
|
218
|
+
const enableColors = colorEnabled && localStream === process.stdout;
|
|
121
219
|
content = formatObject(content, enableColors, json);
|
|
122
220
|
const encoder = new TextEncoder();
|
|
123
221
|
const bytes = encoder.encode(content + "\n");
|
|
124
|
-
|
|
222
|
+
await writeToStream(localStream, bytes);
|
|
223
|
+
if (localFileStream != null) await endWritableStream(localFileStream);
|
|
125
224
|
if (object$1 instanceof Object$1) imageUrls = await findAllImages(object$1);
|
|
126
|
-
if (
|
|
225
|
+
if (localStream === process.stdout && imageUrls.length > 0) await renderImages(imageUrls);
|
|
226
|
+
}
|
|
227
|
+
async function closeWriteStream(stream) {
|
|
228
|
+
if (stream == null) return;
|
|
229
|
+
await endWritableStream(stream);
|
|
230
|
+
}
|
|
231
|
+
async function writeSeparator(separator, stream) {
|
|
232
|
+
await writeToStream(stream ?? process.stdout, `${separator}\n`);
|
|
233
|
+
}
|
|
234
|
+
function toPresentationOrder(items, reverse) {
|
|
235
|
+
if (reverse) return [...items].reverse();
|
|
236
|
+
return items;
|
|
237
|
+
}
|
|
238
|
+
async function collectAsyncItems(iterable) {
|
|
239
|
+
const items = [];
|
|
240
|
+
try {
|
|
241
|
+
for await (const item of iterable) items.push(item);
|
|
242
|
+
return { items };
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return {
|
|
245
|
+
items,
|
|
246
|
+
error
|
|
247
|
+
};
|
|
248
|
+
}
|
|
127
249
|
}
|
|
128
250
|
const signalTimers = /* @__PURE__ */ new WeakMap();
|
|
129
251
|
function createTimeoutSignal(timeoutSeconds) {
|
|
@@ -158,22 +280,152 @@ function handleTimeoutError(spinner, timeoutSeconds, url) {
|
|
|
158
280
|
spinner.fail(`Request timed out after ${timeoutSeconds} seconds${urlText}.`);
|
|
159
281
|
printError(message`Try increasing the timeout with -T/--timeout option or check network connectivity.`);
|
|
160
282
|
}
|
|
161
|
-
|
|
283
|
+
function isPrivateAddressError(error) {
|
|
284
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
285
|
+
const lowerMessage = errorMessage.toLowerCase();
|
|
286
|
+
if (error instanceof UrlError) return lowerMessage.includes("invalid or private address") || lowerMessage.includes("localhost is not allowed");
|
|
287
|
+
return lowerMessage.includes("private address") || lowerMessage.includes("private ip") || lowerMessage.includes("localhost") || lowerMessage.includes("loopback");
|
|
288
|
+
}
|
|
289
|
+
function getLookupFailureHint(error, options = {}) {
|
|
290
|
+
if (isPrivateAddressError(error)) return options.recursive ? "recursive-private-address" : "private-address";
|
|
291
|
+
return "authorized-fetch";
|
|
292
|
+
}
|
|
293
|
+
function shouldPrintLookupFailureHint(authLoader, hint) {
|
|
294
|
+
return hint !== "authorized-fetch" || authLoader == null;
|
|
295
|
+
}
|
|
296
|
+
function shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint) {
|
|
297
|
+
return authLoader != null && hint === "authorized-fetch";
|
|
298
|
+
}
|
|
299
|
+
function printLookupFailureHint(authLoader, error, options = {}) {
|
|
300
|
+
const hint = getLookupFailureHint(error, options);
|
|
301
|
+
if (!shouldPrintLookupFailureHint(authLoader, hint)) return;
|
|
302
|
+
switch (hint) {
|
|
303
|
+
case "private-address":
|
|
304
|
+
printError(message`The URL appears to be private or localhost. Try with -p/--allow-private-address.`);
|
|
305
|
+
return;
|
|
306
|
+
case "recursive-private-address":
|
|
307
|
+
printError(message`Recursive fetches do not allow private/localhost URLs. Use -S/--suppress-errors to skip blocked steps, or fetch those targets explicitly without --recurse.`);
|
|
308
|
+
return;
|
|
309
|
+
case "authorized-fetch":
|
|
310
|
+
printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Gets the next recursion target URL from an ActivityPub object.
|
|
316
|
+
*/
|
|
317
|
+
function getRecursiveTargetId(object$1, recurseProperty) {
|
|
318
|
+
switch (recurseProperty) {
|
|
319
|
+
case "replyTarget":
|
|
320
|
+
case IN_REPLY_TO_IRI: return object$1.replyTargetId;
|
|
321
|
+
case "quoteUrl":
|
|
322
|
+
case QUOTE_URL_IRI:
|
|
323
|
+
case MISSKEY_QUOTE_IRI:
|
|
324
|
+
case FEDIBIRD_QUOTE_IRI: {
|
|
325
|
+
const quoteUrl = object$1.quoteUrl;
|
|
326
|
+
return quoteUrl instanceof URL ? quoteUrl : null;
|
|
327
|
+
}
|
|
328
|
+
default: return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Collects recursively linked objects up to a depth limit.
|
|
333
|
+
*/
|
|
334
|
+
async function collectRecursiveObjects(initialObject, recurseProperty, recurseDepth, lookup, options) {
|
|
335
|
+
const visited = options.visited ?? /* @__PURE__ */ new Set();
|
|
336
|
+
const results = [];
|
|
337
|
+
let current = initialObject;
|
|
338
|
+
if (current.id != null) visited.add(current.id.href);
|
|
339
|
+
for (let depth = 0; depth < recurseDepth; depth++) {
|
|
340
|
+
const targetId = getRecursiveTargetId(current, recurseProperty);
|
|
341
|
+
if (targetId == null) break;
|
|
342
|
+
const target = targetId.href;
|
|
343
|
+
if (visited.has(target)) break;
|
|
344
|
+
let next;
|
|
345
|
+
try {
|
|
346
|
+
next = await lookup(target);
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (options.suppressErrors) {
|
|
349
|
+
logger.debug("Failed to recursively fetch object {target}, but suppressing error: {error}", {
|
|
350
|
+
target,
|
|
351
|
+
error
|
|
352
|
+
});
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
if (next == null) {
|
|
358
|
+
if (options.suppressErrors) {
|
|
359
|
+
logger.debug("Failed to recursively fetch object {target} (not found), but suppressing error.", { target });
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
throw new RecursiveLookupError(target);
|
|
363
|
+
}
|
|
364
|
+
results.push(next);
|
|
365
|
+
visited.add(target);
|
|
366
|
+
if (next.id != null) visited.add(next.id.href);
|
|
367
|
+
current = next;
|
|
368
|
+
}
|
|
369
|
+
return results;
|
|
370
|
+
}
|
|
371
|
+
async function runLookup(command$1, deps = {}) {
|
|
372
|
+
const effectiveDeps = {
|
|
373
|
+
lookupObject,
|
|
374
|
+
traverseCollection,
|
|
375
|
+
exit: (code) => process.exit(code),
|
|
376
|
+
...deps
|
|
377
|
+
};
|
|
162
378
|
if (command$1.urls.length < 1) {
|
|
163
379
|
printError(message`At least one URL or actor handle must be provided.`);
|
|
164
|
-
|
|
380
|
+
effectiveDeps.exit(1);
|
|
165
381
|
}
|
|
166
382
|
if (command$1.debug) await configureLogging();
|
|
167
383
|
const spinner = ora({
|
|
168
|
-
text: `Looking up the ${command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`,
|
|
384
|
+
text: `Looking up the ${command$1.recurse != null ? "object chain" : command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`,
|
|
169
385
|
discardStdin: false
|
|
170
386
|
}).start();
|
|
171
387
|
let server = void 0;
|
|
172
|
-
const baseDocumentLoader = await getDocumentLoader({
|
|
388
|
+
const baseDocumentLoader = await getDocumentLoader({
|
|
389
|
+
userAgent: command$1.userAgent,
|
|
390
|
+
allowPrivateAddress: command$1.allowPrivateAddress
|
|
391
|
+
});
|
|
173
392
|
const documentLoader = wrapDocumentLoaderWithTimeout(baseDocumentLoader, command$1.timeout);
|
|
174
|
-
const baseContextLoader = await getContextLoader({
|
|
393
|
+
const baseContextLoader = await getContextLoader({
|
|
394
|
+
userAgent: command$1.userAgent,
|
|
395
|
+
allowPrivateAddress: command$1.allowPrivateAddress
|
|
396
|
+
});
|
|
175
397
|
const contextLoader = wrapDocumentLoaderWithTimeout(baseContextLoader, command$1.timeout);
|
|
176
398
|
let authLoader = void 0;
|
|
399
|
+
let authIdentity = void 0;
|
|
400
|
+
let outputStream;
|
|
401
|
+
let outputStreamError;
|
|
402
|
+
const getOutputStream = () => {
|
|
403
|
+
if (command$1.output == null) return void 0;
|
|
404
|
+
if (outputStream == null) {
|
|
405
|
+
outputStream = createWriteStream(command$1.output);
|
|
406
|
+
outputStream.once("error", (error) => {
|
|
407
|
+
outputStreamError = error;
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (outputStreamError != null) throw outputStreamError;
|
|
411
|
+
return outputStream;
|
|
412
|
+
};
|
|
413
|
+
const finalizeAndExit = async (code) => {
|
|
414
|
+
let cleanupFailed = false;
|
|
415
|
+
try {
|
|
416
|
+
await closeWriteStream(outputStream);
|
|
417
|
+
} catch (error) {
|
|
418
|
+
cleanupFailed = true;
|
|
419
|
+
logger.error("Failed to close output stream during shutdown: {error}", { error });
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
await server?.close();
|
|
423
|
+
} catch (error) {
|
|
424
|
+
cleanupFailed = true;
|
|
425
|
+
logger.error("Failed to close temporary server during shutdown: {error}", { error });
|
|
426
|
+
}
|
|
427
|
+
effectiveDeps.exit(cleanupFailed && code === 0 ? 1 : code);
|
|
428
|
+
};
|
|
177
429
|
if (command$1.authorizedFetch) {
|
|
178
430
|
spinner.text = "Generating a one-time key pair...";
|
|
179
431
|
const key = await generateCryptoKeyPair();
|
|
@@ -205,26 +457,174 @@ async function runLookup(command$1) {
|
|
|
205
457
|
outbox: new URL("/outbox", serverUrl)
|
|
206
458
|
}), { contextLoader });
|
|
207
459
|
}, { service: command$1.tunnelService });
|
|
208
|
-
|
|
460
|
+
authIdentity = {
|
|
209
461
|
keyId: new URL("#main-key", server.url),
|
|
210
462
|
privateKey: key.privateKey
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
463
|
+
};
|
|
464
|
+
const baseAuthLoader = getAuthenticatedDocumentLoader(authIdentity, {
|
|
465
|
+
allowPrivateAddress: command$1.allowPrivateAddress,
|
|
466
|
+
userAgent: command$1.userAgent,
|
|
467
|
+
specDeterminer: {
|
|
468
|
+
determineSpec() {
|
|
469
|
+
return command$1.firstKnock;
|
|
470
|
+
},
|
|
471
|
+
rememberSpec() {}
|
|
472
|
+
}
|
|
473
|
+
});
|
|
217
474
|
authLoader = wrapDocumentLoaderWithTimeout(baseAuthLoader, command$1.timeout);
|
|
218
475
|
}
|
|
219
|
-
spinner.text = `Looking up the ${command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`;
|
|
476
|
+
spinner.text = `Looking up the ${command$1.recurse != null ? "object chain" : command$1.traverse ? "collection" : command$1.urls.length > 1 ? "objects" : "object"}...`;
|
|
477
|
+
if (command$1.recurse != null) {
|
|
478
|
+
const recursiveBaseDocumentLoader = await getDocumentLoader({
|
|
479
|
+
userAgent: command$1.userAgent,
|
|
480
|
+
allowPrivateAddress: false
|
|
481
|
+
});
|
|
482
|
+
const recursiveDocumentLoader = wrapDocumentLoaderWithTimeout(recursiveBaseDocumentLoader, command$1.timeout);
|
|
483
|
+
const recursiveBaseContextLoader = await getContextLoader({
|
|
484
|
+
userAgent: command$1.userAgent,
|
|
485
|
+
allowPrivateAddress: false
|
|
486
|
+
});
|
|
487
|
+
const recursiveContextLoader = wrapDocumentLoaderWithTimeout(recursiveBaseContextLoader, command$1.timeout);
|
|
488
|
+
const recursiveAuthLoader = command$1.authorizedFetch && authIdentity != null ? wrapDocumentLoaderWithTimeout(getAuthenticatedDocumentLoader(authIdentity, {
|
|
489
|
+
allowPrivateAddress: false,
|
|
490
|
+
userAgent: command$1.userAgent,
|
|
491
|
+
specDeterminer: {
|
|
492
|
+
determineSpec() {
|
|
493
|
+
return command$1.firstKnock;
|
|
494
|
+
},
|
|
495
|
+
rememberSpec() {}
|
|
496
|
+
}
|
|
497
|
+
}), command$1.timeout) : void 0;
|
|
498
|
+
const initialLookupDocumentLoader = authLoader ?? documentLoader;
|
|
499
|
+
const recursiveLookupDocumentLoader = recursiveAuthLoader ?? recursiveDocumentLoader;
|
|
500
|
+
let totalObjects = 0;
|
|
501
|
+
const recurseDepth = command$1.recurseDepth;
|
|
502
|
+
for (let urlIndex = 0; urlIndex < command$1.urls.length; urlIndex++) {
|
|
503
|
+
const visited = /* @__PURE__ */ new Set();
|
|
504
|
+
const url = command$1.urls[urlIndex];
|
|
505
|
+
if (urlIndex > 0) spinner.text = `Looking up object chain ${urlIndex + 1}/${command$1.urls.length}...`;
|
|
506
|
+
let current = null;
|
|
507
|
+
try {
|
|
508
|
+
current = await effectiveDeps.lookupObject(url, {
|
|
509
|
+
documentLoader: initialLookupDocumentLoader,
|
|
510
|
+
contextLoader,
|
|
511
|
+
userAgent: command$1.userAgent
|
|
512
|
+
});
|
|
513
|
+
} catch (error) {
|
|
514
|
+
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
515
|
+
else {
|
|
516
|
+
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
517
|
+
printLookupFailureHint(authLoader, error);
|
|
518
|
+
}
|
|
519
|
+
await finalizeAndExit(1);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (current == null) {
|
|
523
|
+
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
524
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
525
|
+
await finalizeAndExit(1);
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
visited.add(url);
|
|
529
|
+
if (current.id != null) visited.add(current.id.href);
|
|
530
|
+
if (!command$1.reverse) try {
|
|
531
|
+
if (totalObjects > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
532
|
+
await writeObjectToStream(current, command$1.output, command$1.format, contextLoader, getOutputStream());
|
|
533
|
+
totalObjects++;
|
|
534
|
+
} catch (error) {
|
|
535
|
+
logger.error("Failed to write lookup output: {error}", { error });
|
|
536
|
+
spinner.fail("Failed to write output.");
|
|
537
|
+
await finalizeAndExit(1);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
let chain = [];
|
|
541
|
+
try {
|
|
542
|
+
chain = await collectRecursiveObjects(current, command$1.recurse, recurseDepth, (target) => effectiveDeps.lookupObject(target, {
|
|
543
|
+
documentLoader: recursiveLookupDocumentLoader,
|
|
544
|
+
contextLoader: recursiveContextLoader,
|
|
545
|
+
userAgent: command$1.userAgent
|
|
546
|
+
}), {
|
|
547
|
+
suppressErrors: command$1.suppressErrors,
|
|
548
|
+
visited
|
|
549
|
+
});
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (command$1.reverse) try {
|
|
552
|
+
if (totalObjects > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
553
|
+
await writeObjectToStream(current, command$1.output, command$1.format, contextLoader, getOutputStream());
|
|
554
|
+
totalObjects++;
|
|
555
|
+
} catch (writeError) {
|
|
556
|
+
logger.error("Failed to write lookup output: {error}", { error: writeError });
|
|
557
|
+
spinner.fail("Failed to write output.");
|
|
558
|
+
await finalizeAndExit(1);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
logger.error("Failed to recursively fetch an object in chain: {error}", { error });
|
|
562
|
+
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout);
|
|
563
|
+
else if (error instanceof RecursiveLookupError) {
|
|
564
|
+
spinner.fail(`Failed to recursively fetch object: ${colors.red(error.target)}.`);
|
|
565
|
+
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
566
|
+
} else {
|
|
567
|
+
spinner.fail("Failed to recursively fetch object.");
|
|
568
|
+
const hint = getLookupFailureHint(error, { recursive: true });
|
|
569
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the -S/--suppress-errors option to suppress partial errors.`);
|
|
570
|
+
else printLookupFailureHint(authLoader, error, { recursive: true });
|
|
571
|
+
}
|
|
572
|
+
await finalizeAndExit(1);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (command$1.reverse) {
|
|
576
|
+
const chainEntries = [{
|
|
577
|
+
object: current,
|
|
578
|
+
objectContextLoader: contextLoader
|
|
579
|
+
}, ...chain.map((next) => ({
|
|
580
|
+
object: next,
|
|
581
|
+
objectContextLoader: recursiveContextLoader
|
|
582
|
+
}))];
|
|
583
|
+
for (let chainIndex = chainEntries.length - 1; chainIndex >= 0; chainIndex--) {
|
|
584
|
+
const entry = chainEntries[chainIndex];
|
|
585
|
+
try {
|
|
586
|
+
if (totalObjects > 0 || chainIndex < chainEntries.length - 1) await writeSeparator(command$1.separator, getOutputStream());
|
|
587
|
+
await writeObjectToStream(entry.object, command$1.output, command$1.format, entry.objectContextLoader, getOutputStream());
|
|
588
|
+
totalObjects++;
|
|
589
|
+
} catch (error) {
|
|
590
|
+
logger.error("Failed to write lookup output: {error}", { error });
|
|
591
|
+
spinner.fail("Failed to write output.");
|
|
592
|
+
await finalizeAndExit(1);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
const chainEntries = chain.map((next) => ({
|
|
598
|
+
object: next,
|
|
599
|
+
objectContextLoader: recursiveContextLoader
|
|
600
|
+
}));
|
|
601
|
+
for (let chainIndex = 0; chainIndex < chainEntries.length; chainIndex++) {
|
|
602
|
+
const entry = chainEntries[chainIndex];
|
|
603
|
+
try {
|
|
604
|
+
if (totalObjects > 0 || chainIndex > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
605
|
+
await writeObjectToStream(entry.object, command$1.output, command$1.format, entry.objectContextLoader, getOutputStream());
|
|
606
|
+
totalObjects++;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
logger.error("Failed to write lookup output: {error}", { error });
|
|
609
|
+
spinner.fail("Failed to write output.");
|
|
610
|
+
await finalizeAndExit(1);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
spinner.succeed("Successfully fetched all reachable objects in the chain.");
|
|
617
|
+
await finalizeAndExit(0);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
220
620
|
if (command$1.traverse) {
|
|
221
621
|
let totalItems = 0;
|
|
222
622
|
for (let urlIndex = 0; urlIndex < command$1.urls.length; urlIndex++) {
|
|
223
623
|
const url = command$1.urls[urlIndex];
|
|
224
624
|
if (urlIndex > 0) spinner.text = `Looking up collection ${urlIndex + 1}/${command$1.urls.length}...`;
|
|
225
|
-
let collection;
|
|
625
|
+
let collection = null;
|
|
226
626
|
try {
|
|
227
|
-
collection = await lookupObject(url, {
|
|
627
|
+
collection = await effectiveDeps.lookupObject(url, {
|
|
228
628
|
documentLoader: authLoader ?? documentLoader,
|
|
229
629
|
contextLoader,
|
|
230
630
|
userAgent: command$1.userAgent
|
|
@@ -233,33 +633,64 @@ async function runLookup(command$1) {
|
|
|
233
633
|
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
234
634
|
else {
|
|
235
635
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
236
|
-
|
|
636
|
+
printLookupFailureHint(authLoader, error);
|
|
237
637
|
}
|
|
238
|
-
await
|
|
239
|
-
|
|
638
|
+
await finalizeAndExit(1);
|
|
639
|
+
return;
|
|
240
640
|
}
|
|
241
641
|
if (collection == null) {
|
|
242
642
|
spinner.fail(`Failed to fetch object: ${colors.red(url)}.`);
|
|
243
643
|
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
244
|
-
await
|
|
245
|
-
|
|
644
|
+
await finalizeAndExit(1);
|
|
645
|
+
return;
|
|
246
646
|
}
|
|
247
647
|
if (!(collection instanceof Collection)) {
|
|
248
648
|
spinner.fail(`Not a collection: ${colors.red(url)}. The -t/--traverse option requires a collection.`);
|
|
249
|
-
await
|
|
250
|
-
|
|
649
|
+
await finalizeAndExit(1);
|
|
650
|
+
return;
|
|
251
651
|
}
|
|
252
652
|
spinner.succeed(`Fetched collection: ${colors.green(url)}.`);
|
|
253
653
|
try {
|
|
254
|
-
|
|
255
|
-
|
|
654
|
+
if (command$1.reverse) {
|
|
655
|
+
const { items: traversedItems, error: traversalError } = await collectAsyncItems(effectiveDeps.traverseCollection(collection, {
|
|
656
|
+
documentLoader: authLoader ?? documentLoader,
|
|
657
|
+
contextLoader,
|
|
658
|
+
suppressError: command$1.suppressErrors
|
|
659
|
+
}));
|
|
660
|
+
for (let index = traversedItems.length - 1; index >= 0; index--) {
|
|
661
|
+
const item = traversedItems[index];
|
|
662
|
+
try {
|
|
663
|
+
if (totalItems > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
664
|
+
await writeObjectToStream(item, command$1.output, command$1.format, contextLoader, getOutputStream());
|
|
665
|
+
} catch (error) {
|
|
666
|
+
logger.error("Failed to write output for {url}: {error}", {
|
|
667
|
+
url,
|
|
668
|
+
error
|
|
669
|
+
});
|
|
670
|
+
spinner.fail(`Failed to write output for: ${colors.red(url)}.`);
|
|
671
|
+
await finalizeAndExit(1);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
totalItems++;
|
|
675
|
+
}
|
|
676
|
+
if (traversalError != null) throw traversalError;
|
|
677
|
+
} else for await (const item of effectiveDeps.traverseCollection(collection, {
|
|
256
678
|
documentLoader: authLoader ?? documentLoader,
|
|
257
679
|
contextLoader,
|
|
258
680
|
suppressError: command$1.suppressErrors
|
|
259
681
|
})) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
682
|
+
try {
|
|
683
|
+
if (totalItems > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
684
|
+
await writeObjectToStream(item, command$1.output, command$1.format, contextLoader, getOutputStream());
|
|
685
|
+
} catch (error) {
|
|
686
|
+
logger.error("Failed to write output for {url}: {error}", {
|
|
687
|
+
url,
|
|
688
|
+
error
|
|
689
|
+
});
|
|
690
|
+
spinner.fail(`Failed to write output for: ${colors.red(url)}.`);
|
|
691
|
+
await finalizeAndExit(1);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
263
694
|
totalItems++;
|
|
264
695
|
}
|
|
265
696
|
} catch (error) {
|
|
@@ -270,19 +701,20 @@ async function runLookup(command$1) {
|
|
|
270
701
|
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
271
702
|
else {
|
|
272
703
|
spinner.fail(`Failed to complete the traversal for: ${colors.red(url)}.`);
|
|
273
|
-
|
|
274
|
-
|
|
704
|
+
const hint = getLookupFailureHint(error);
|
|
705
|
+
if (shouldSuggestSuppressErrorsForLookupFailure(authLoader, hint)) printError(message`Use the -S/--suppress-errors option to suppress partial errors.`);
|
|
706
|
+
else printLookupFailureHint(authLoader, error);
|
|
275
707
|
}
|
|
276
|
-
await
|
|
277
|
-
|
|
708
|
+
await finalizeAndExit(1);
|
|
709
|
+
return;
|
|
278
710
|
}
|
|
279
711
|
}
|
|
280
712
|
spinner.succeed("Successfully fetched all items in the collection.");
|
|
281
|
-
await
|
|
282
|
-
|
|
713
|
+
await finalizeAndExit(0);
|
|
714
|
+
return;
|
|
283
715
|
}
|
|
284
716
|
const promises = [];
|
|
285
|
-
for (const url of command$1.urls) promises.push(lookupObject(url, {
|
|
717
|
+
for (const url of command$1.urls) promises.push(effectiveDeps.lookupObject(url, {
|
|
286
718
|
documentLoader: authLoader ?? documentLoader,
|
|
287
719
|
contextLoader,
|
|
288
720
|
userAgent: command$1.userAgent
|
|
@@ -290,33 +722,54 @@ async function runLookup(command$1) {
|
|
|
290
722
|
if (error instanceof TimeoutError) handleTimeoutError(spinner, command$1.timeout, url);
|
|
291
723
|
throw error;
|
|
292
724
|
}));
|
|
293
|
-
let objects;
|
|
725
|
+
let objects = [];
|
|
294
726
|
try {
|
|
295
727
|
objects = await Promise.all(promises);
|
|
296
728
|
} catch (_error) {
|
|
297
|
-
await
|
|
298
|
-
|
|
729
|
+
await finalizeAndExit(1);
|
|
730
|
+
return;
|
|
299
731
|
}
|
|
300
732
|
spinner.stop();
|
|
301
733
|
let success = true;
|
|
302
|
-
let
|
|
303
|
-
|
|
734
|
+
let printedCount = 0;
|
|
735
|
+
const successfulObjects = [];
|
|
736
|
+
for (const [i, obj] of objects.entries()) {
|
|
304
737
|
const url = command$1.urls[i];
|
|
305
|
-
if (i > 0) print(message`${command$1.separator}`);
|
|
306
|
-
i++;
|
|
307
738
|
if (obj == null) {
|
|
308
739
|
spinner.fail(`Failed to fetch ${colors.red(url)}`);
|
|
309
740
|
if (authLoader == null) printError(message`It may be a private object. Try with -a/--authorized-fetch.`);
|
|
310
741
|
success = false;
|
|
311
742
|
} else {
|
|
312
743
|
spinner.succeed(`Fetched object: ${colors.green(url)}`);
|
|
313
|
-
|
|
314
|
-
if (i < command$1.urls.length - 1) print(message`${command$1.separator}`);
|
|
744
|
+
successfulObjects.push(obj);
|
|
315
745
|
}
|
|
316
746
|
}
|
|
747
|
+
for (const obj of toPresentationOrder(successfulObjects, command$1.reverse)) {
|
|
748
|
+
try {
|
|
749
|
+
if (printedCount > 0) await writeSeparator(command$1.separator, getOutputStream());
|
|
750
|
+
await writeObjectToStream(obj, command$1.output, command$1.format, contextLoader, getOutputStream());
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger.error("Failed to write lookup output: {error}", { error });
|
|
753
|
+
spinner.fail("Failed to write output.");
|
|
754
|
+
await finalizeAndExit(1);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
printedCount++;
|
|
758
|
+
}
|
|
317
759
|
if (success) spinner.succeed(command$1.urls.length > 1 ? "Successfully fetched all objects." : "Successfully fetched the object.");
|
|
318
|
-
|
|
319
|
-
|
|
760
|
+
if (!success) {
|
|
761
|
+
await finalizeAndExit(1);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
try {
|
|
765
|
+
await closeWriteStream(outputStream);
|
|
766
|
+
await server?.close();
|
|
767
|
+
} catch (error) {
|
|
768
|
+
logger.error("Failed to finalize lookup resources: {error}", { error });
|
|
769
|
+
spinner.fail("Failed to finalize output.");
|
|
770
|
+
await finalizeAndExit(1);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
320
773
|
if (success && command$1.output) spinner.succeed(`Successfully wrote output to ${colors.green(command$1.output)}.`);
|
|
321
774
|
}
|
|
322
775
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"README.md",
|
|
@@ -58,16 +58,16 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@fxts/core": "^1.20.0",
|
|
61
|
-
"@optique/config": "^0.10.
|
|
62
|
-
"@optique/core": "^0.10.
|
|
63
|
-
"@optique/run": "^0.10.
|
|
61
|
+
"@optique/config": "^0.10.7",
|
|
62
|
+
"@optique/core": "^0.10.7",
|
|
63
|
+
"@optique/run": "^0.10.7",
|
|
64
64
|
"@hongminhee/localtunnel": "^0.3.0",
|
|
65
65
|
"@inquirer/prompts": "^7.8.4",
|
|
66
66
|
"@jimp/core": "^1.6.0",
|
|
67
67
|
"@jimp/wasm-webp": "^1.6.0",
|
|
68
68
|
"@js-temporal/polyfill": "^0.5.1",
|
|
69
|
-
"@logtape/file": "^2.0.
|
|
70
|
-
"@logtape/logtape": "^2.0.
|
|
69
|
+
"@logtape/file": "^2.0.5",
|
|
70
|
+
"@logtape/logtape": "^2.0.5",
|
|
71
71
|
"@poppanator/http-constants": "^1.1.1",
|
|
72
72
|
"byte-encodings": "^1.0.11",
|
|
73
73
|
"chalk": "^5.6.2",
|
|
@@ -86,14 +86,14 @@
|
|
|
86
86
|
"smol-toml": "^1.6.0",
|
|
87
87
|
"srvx": "^0.8.7",
|
|
88
88
|
"valibot": "^1.2.0",
|
|
89
|
-
"@fedify/fedify": "2.0
|
|
90
|
-
"@fedify/init": "2.0
|
|
91
|
-
"@fedify/
|
|
92
|
-
"@fedify/sqlite": "2.0
|
|
93
|
-
"@fedify/vocab": "2.0
|
|
94
|
-
"@fedify/
|
|
95
|
-
"@fedify/
|
|
96
|
-
"@fedify/
|
|
89
|
+
"@fedify/fedify": "2.1.0",
|
|
90
|
+
"@fedify/init": "2.1.0",
|
|
91
|
+
"@fedify/vocab-runtime": "2.1.0",
|
|
92
|
+
"@fedify/sqlite": "2.1.0",
|
|
93
|
+
"@fedify/vocab-tools": "2.1.0",
|
|
94
|
+
"@fedify/vocab": "2.1.0",
|
|
95
|
+
"@fedify/webfinger": "2.1.0",
|
|
96
|
+
"@fedify/relay": "2.1.0"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
99
|
"@types/bun": "^1.2.23",
|