@fedify/cli 1.8.11 → 2.0.0-dev.1757
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/deno.json +72 -0
- package/dist/cache.js +17 -0
- package/dist/deno.js +72 -0
- package/dist/docloader.js +52 -0
- package/dist/globals.js +49 -0
- package/dist/imagerenderer.js +105 -0
- package/dist/inbox/rendercode.js +57 -0
- package/dist/inbox/view.js +508 -0
- package/dist/inbox.js +315 -0
- package/dist/init/action/configs.js +81 -0
- package/dist/init/action/deps.js +52 -0
- package/dist/init/action/dir.js +16 -0
- package/dist/init/action/env.js +13 -0
- package/dist/init/action/install.js +22 -0
- package/dist/init/action/mod.js +39 -0
- package/dist/init/action/notice.js +62 -0
- package/dist/init/action/patch.js +141 -0
- package/dist/init/action/precommand.js +23 -0
- package/dist/init/action/recommend.js +24 -0
- package/dist/init/action/set.js +31 -0
- package/dist/init/action/templates.js +57 -0
- package/dist/init/action/utils.js +50 -0
- package/dist/init/ask/dir.js +82 -0
- package/dist/init/ask/kv.js +33 -0
- package/dist/init/ask/mod.js +16 -0
- package/dist/init/ask/mq.js +33 -0
- package/dist/init/ask/pm.js +49 -0
- package/dist/init/ask/wf.js +29 -0
- package/dist/init/command.js +25 -0
- package/dist/init/const.js +31 -0
- package/dist/init/json/biome.js +24 -0
- package/dist/init/json/kv.js +53 -0
- package/dist/init/json/mq.js +72 -0
- package/dist/init/json/pm.js +44 -0
- package/dist/init/json/rt.js +39 -0
- package/dist/init/json/vscode-settings-for-deno.js +53 -0
- package/dist/init/json/vscode-settings.js +49 -0
- package/dist/init/lib.js +129 -0
- package/dist/init/mod.js +5 -0
- package/dist/init/webframeworks.js +133 -0
- package/dist/kv.bun.js +17 -0
- package/dist/kv.node.js +17 -0
- package/dist/log.js +52 -0
- package/dist/lookup.js +287 -0
- package/dist/mod.js +34 -0
- package/dist/nodeinfo.js +261 -0
- package/dist/table.js +24 -0
- package/dist/tempserver.js +71 -0
- package/dist/tunnel.js +21 -0
- package/dist/utils.js +67 -0
- package/dist/webfinger/action.js +44 -0
- package/dist/webfinger/command.js +20 -0
- package/dist/webfinger/error.js +47 -0
- package/dist/webfinger/lib.js +45 -0
- package/dist/webfinger/mod.js +5 -0
- package/package.json +64 -24
- package/scripts/pack.ts +64 -0
- package/src/cache.ts +17 -0
- package/src/docloader.ts +67 -0
- package/src/globals.ts +43 -0
- package/src/imagerenderer.ts +149 -0
- package/src/inbox/entry.ts +10 -0
- package/src/inbox/rendercode.ts +68 -0
- package/src/inbox/view.tsx +598 -0
- package/src/inbox.tsx +535 -0
- package/src/init/action/configs.ts +88 -0
- package/src/init/action/deps.ts +93 -0
- package/src/init/action/dir.ts +11 -0
- package/src/init/action/env.ts +14 -0
- package/src/init/action/install.ts +59 -0
- package/src/init/action/mod.ts +66 -0
- package/src/init/action/notice.ts +101 -0
- package/src/init/action/patch.ts +212 -0
- package/src/init/action/precommand.ts +22 -0
- package/src/init/action/recommend.ts +38 -0
- package/src/init/action/set.ts +78 -0
- package/src/init/action/templates.ts +95 -0
- package/src/init/action/utils.ts +64 -0
- package/src/init/ask/dir.ts +98 -0
- package/src/init/ask/kv.ts +39 -0
- package/src/init/ask/mod.ts +23 -0
- package/src/init/ask/mq.ts +37 -0
- package/src/init/ask/pm.ts +58 -0
- package/src/init/ask/wf.ts +27 -0
- package/src/init/command.ts +64 -0
- package/src/init/const.ts +4 -0
- package/src/init/json/biome.json +17 -0
- package/src/init/json/kv.json +39 -0
- package/src/init/json/mq.json +95 -0
- package/src/init/json/pm.json +47 -0
- package/src/init/json/rt.json +42 -0
- package/src/init/json/vscode-settings-for-deno.json +43 -0
- package/src/init/json/vscode-settings.json +41 -0
- package/src/init/lib.ts +220 -0
- package/src/init/mod.ts +2 -0
- package/src/init/templates/defaults/federation.ts.tpl +23 -0
- package/src/init/templates/defaults/logging.ts.tpl +23 -0
- package/src/init/templates/express/app.ts.tpl +16 -0
- package/src/init/templates/express/index.ts.tpl +6 -0
- package/src/init/templates/hono/app.tsx.tpl +14 -0
- package/src/init/templates/hono/index/bun.ts.tpl +10 -0
- package/src/init/templates/hono/index/deno.ts.tpl +13 -0
- package/src/init/templates/hono/index/node.ts.tpl +14 -0
- package/src/init/templates/next/middleware.ts.tpl +45 -0
- package/src/init/templates/nitro/nitro.config.ts.tpl +5 -0
- package/src/init/templates/nitro/server/error.ts.tpl +3 -0
- package/src/init/templates/nitro/server/middleware/federation.ts.tpl +8 -0
- package/src/init/types.ts +88 -0
- package/src/init/webframeworks.ts +151 -0
- package/src/kv.bun.ts +12 -0
- package/src/kv.node.ts +11 -0
- package/src/log.ts +64 -0
- package/src/lookup.test.ts +182 -0
- package/src/lookup.ts +558 -0
- package/src/mod.ts +45 -0
- package/src/nodeinfo.test.ts +229 -0
- package/src/nodeinfo.ts +447 -0
- package/src/table.ts +17 -0
- package/src/tempserver.ts +87 -0
- package/src/tunnel.ts +32 -0
- package/src/utils.ts +136 -0
- package/src/webfinger/action.ts +50 -0
- package/src/webfinger/command.ts +59 -0
- package/src/webfinger/error.ts +47 -0
- package/src/webfinger/lib.ts +37 -0
- package/src/webfinger/mod.test.ts +79 -0
- package/src/webfinger/mod.ts +2 -0
- package/tsdown.config.ts +24 -0
- package/src/install.mjs +0 -189
- package/src/run.mjs +0 -22
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { openTunnel } from "@hongminhee/localtunnel";
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
3
|
+
import { serve } from "srvx";
|
|
4
|
+
|
|
5
|
+
const logger = getLogger(["fedify", "cli", "tempserver"]);
|
|
6
|
+
|
|
7
|
+
export type SpawnTemporaryServerOptions = {
|
|
8
|
+
noTunnel?: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type TemporaryServer = {
|
|
12
|
+
url: URL;
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function spawnTemporaryServer(
|
|
17
|
+
fetch: (request: Request) => Promise<Response> | Response,
|
|
18
|
+
options: SpawnTemporaryServerOptions = {},
|
|
19
|
+
): Promise<TemporaryServer> {
|
|
20
|
+
if (options.noTunnel) {
|
|
21
|
+
const server = serve({
|
|
22
|
+
port: 0,
|
|
23
|
+
hostname: "::",
|
|
24
|
+
fetch: fetch,
|
|
25
|
+
});
|
|
26
|
+
await server.ready();
|
|
27
|
+
const url = new URL(server.url!);
|
|
28
|
+
const port = url.port;
|
|
29
|
+
logger.debug("Temporary server is listening on port {port}.", {
|
|
30
|
+
port: port,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
url: new URL(`http://localhost:${port}`),
|
|
35
|
+
async close() {
|
|
36
|
+
await server.close();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const server = serve({
|
|
42
|
+
fetch: (request) => {
|
|
43
|
+
const url = new URL(request.url);
|
|
44
|
+
url.protocol = "https:";
|
|
45
|
+
request = new Request(url, {
|
|
46
|
+
method: request.method,
|
|
47
|
+
headers: request.headers,
|
|
48
|
+
body: request.method === "GET" || request.method === "HEAD"
|
|
49
|
+
? null
|
|
50
|
+
: request.body,
|
|
51
|
+
referrer: request.referrer,
|
|
52
|
+
referrerPolicy: request.referrerPolicy,
|
|
53
|
+
mode: request.mode,
|
|
54
|
+
credentials: request.credentials,
|
|
55
|
+
cache: request.cache,
|
|
56
|
+
redirect: request.redirect,
|
|
57
|
+
integrity: request.integrity,
|
|
58
|
+
keepalive: request.keepalive,
|
|
59
|
+
signal: request.signal,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return new Response();
|
|
63
|
+
},
|
|
64
|
+
port: 0,
|
|
65
|
+
hostname: "::",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await server.ready();
|
|
69
|
+
|
|
70
|
+
const url = new URL(server.url!);
|
|
71
|
+
const port = url.port;
|
|
72
|
+
|
|
73
|
+
logger.debug("Temporary server is listening on port {port}.", { port });
|
|
74
|
+
const tun = await openTunnel({ port: parseInt(port) });
|
|
75
|
+
logger.debug(
|
|
76
|
+
"Temporary server is tunneled to {url}.",
|
|
77
|
+
{ url: tun.url.href },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
url: tun.url,
|
|
82
|
+
async close() {
|
|
83
|
+
await server.close();
|
|
84
|
+
await tun.close();
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
package/src/tunnel.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
argument,
|
|
3
|
+
command,
|
|
4
|
+
constant,
|
|
5
|
+
type InferValue,
|
|
6
|
+
integer,
|
|
7
|
+
merge,
|
|
8
|
+
message,
|
|
9
|
+
object,
|
|
10
|
+
} from "@optique/core";
|
|
11
|
+
import { debugOption } from "./globals.ts";
|
|
12
|
+
|
|
13
|
+
export const tunnelCommand = command(
|
|
14
|
+
"tunnel",
|
|
15
|
+
merge(
|
|
16
|
+
object({
|
|
17
|
+
command: constant("tunnel"),
|
|
18
|
+
port: argument(integer({ metavar: "PORT", min: 0, max: 65_535 })),
|
|
19
|
+
}),
|
|
20
|
+
debugOption,
|
|
21
|
+
),
|
|
22
|
+
{
|
|
23
|
+
description:
|
|
24
|
+
message`Expose a local HTTP server to the public internet using a secure tunnel.`,
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export function runTunnel(
|
|
29
|
+
command: InferValue<typeof tunnelCommand>,
|
|
30
|
+
) {
|
|
31
|
+
console.debug(command);
|
|
32
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isObject } from "@fxts/core";
|
|
2
|
+
import { Chalk } from "chalk";
|
|
3
|
+
import { highlight } from "cli-highlight";
|
|
4
|
+
import { toMerged } from "es-toolkit";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { writeFile } from "node:fs/promises";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
import util from "node:util";
|
|
9
|
+
|
|
10
|
+
export const colorEnabled: boolean = process.stdout.isTTY &&
|
|
11
|
+
!("NO_COLOR" in process.env && process.env.NO_COLOR !== "");
|
|
12
|
+
|
|
13
|
+
export const colors = new Chalk(colorEnabled ? {} : { level: 0 });
|
|
14
|
+
|
|
15
|
+
export function formatObject(
|
|
16
|
+
obj: unknown,
|
|
17
|
+
colors?: boolean,
|
|
18
|
+
json?: boolean,
|
|
19
|
+
): string {
|
|
20
|
+
const enableColors = colors ?? colorEnabled;
|
|
21
|
+
if (!json) return util.inspect(obj, { colors: enableColors });
|
|
22
|
+
const formatted = JSON.stringify(obj, null, 2);
|
|
23
|
+
if (enableColors) {
|
|
24
|
+
return highlight(formatted, { language: "json" });
|
|
25
|
+
}
|
|
26
|
+
return formatted;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const isPromise = <T>(a: unknown): a is Promise<T> =>
|
|
30
|
+
a instanceof Promise;
|
|
31
|
+
|
|
32
|
+
export function set<K extends PropertyKey, T extends object, S>(
|
|
33
|
+
key: K,
|
|
34
|
+
f: (value: T) => S,
|
|
35
|
+
): (
|
|
36
|
+
obj: T,
|
|
37
|
+
) => S extends Promise<infer U> ? Promise<T & { [P in K]: Awaited<U> }>
|
|
38
|
+
: T & { [P in K]: S } {
|
|
39
|
+
return ((obj) => {
|
|
40
|
+
const result = f(obj);
|
|
41
|
+
if (isPromise<S extends Promise<infer U> ? U : never>(result)) {
|
|
42
|
+
return result.then((value) => ({ ...obj, [key]: value })) as S extends
|
|
43
|
+
Promise<infer U> ? Promise<
|
|
44
|
+
T & { [P in K]: Awaited<U> }
|
|
45
|
+
>
|
|
46
|
+
: never;
|
|
47
|
+
}
|
|
48
|
+
return ({ ...obj, [key]: result }) as S extends Promise<infer _> ? never
|
|
49
|
+
: T & { [P in K]: S };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const merge =
|
|
54
|
+
(source: Parameters<typeof toMerged>[1] = {}) =>
|
|
55
|
+
(target: Parameters<typeof toMerged>[0] = {}) => toMerged(target, source);
|
|
56
|
+
|
|
57
|
+
export const isNotFoundError = (e: unknown): e is { code: "ENOENT" } =>
|
|
58
|
+
isObject(e) &&
|
|
59
|
+
"code" in e &&
|
|
60
|
+
e.code === "ENOENT";
|
|
61
|
+
|
|
62
|
+
export const runSubCommand = <Opt extends Parameters<typeof spawn>[2]>(
|
|
63
|
+
command: string[],
|
|
64
|
+
options: Opt,
|
|
65
|
+
): Promise<{
|
|
66
|
+
stdout: string;
|
|
67
|
+
stderr: string;
|
|
68
|
+
}> =>
|
|
69
|
+
new Promise((resolve, reject) => {
|
|
70
|
+
const child = spawn(command[0], command.slice(1), options);
|
|
71
|
+
|
|
72
|
+
let stdout = "";
|
|
73
|
+
let stderr = "";
|
|
74
|
+
|
|
75
|
+
child.stdout?.on("data", (data) => {
|
|
76
|
+
stdout += data.toString();
|
|
77
|
+
});
|
|
78
|
+
child.stderr?.on("data", (data) => {
|
|
79
|
+
stderr += data.toString();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
child.on("close", () => {
|
|
83
|
+
resolve({
|
|
84
|
+
stdout: stdout.trim(),
|
|
85
|
+
stderr: stderr.trim(),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
child.on("error", (error) => {
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export type RequiredNotNull<T> = {
|
|
95
|
+
[P in keyof T]: NonNullable<T[P]>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const getCwd = () => process.cwd();
|
|
99
|
+
|
|
100
|
+
export const replace = (
|
|
101
|
+
pattern: string | RegExp,
|
|
102
|
+
replacement: string | ((substring: string, ...args: unknown[]) => string),
|
|
103
|
+
) =>
|
|
104
|
+
(text: string): string => text.replace(pattern, replacement as string);
|
|
105
|
+
|
|
106
|
+
export const getOsType = () => process.platform;
|
|
107
|
+
|
|
108
|
+
export async function writeTextFile(
|
|
109
|
+
path: string,
|
|
110
|
+
content: string,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const encoder = new TextEncoder();
|
|
113
|
+
const data = encoder.encode(content);
|
|
114
|
+
return await writeFile(path, data);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const resolveProps = async <T extends object>(obj: T): Promise<
|
|
118
|
+
{ [P in keyof T]: Awaited<T[P]> }
|
|
119
|
+
> =>
|
|
120
|
+
Object.fromEntries(
|
|
121
|
+
await Array.fromAsync(
|
|
122
|
+
Object.entries(obj),
|
|
123
|
+
async ([k, v]) => [k, await v],
|
|
124
|
+
),
|
|
125
|
+
) as Promise<{ [P in keyof T]: Awaited<T[P]> }>;
|
|
126
|
+
|
|
127
|
+
export const formatJson = (obj: unknown) => JSON.stringify(obj, null, 2) + "\n";
|
|
128
|
+
|
|
129
|
+
export const notEmpty = <T extends string | { length: number }>(s: T) =>
|
|
130
|
+
s.length > 0;
|
|
131
|
+
|
|
132
|
+
export const notEmptyObj = <T extends Record<PropertyKey, never> | object>(
|
|
133
|
+
obj: T,
|
|
134
|
+
): obj is Exclude<T, Record<PropertyKey, never>> => Object.keys(obj).length > 0;
|
|
135
|
+
|
|
136
|
+
export const exit = (code: number) => process.exit(code);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ResourceDescriptor } from "@fedify/fedify";
|
|
2
|
+
import {
|
|
3
|
+
lookupWebFinger,
|
|
4
|
+
type LookupWebFingerOptions,
|
|
5
|
+
} from "@fedify/fedify/webfinger";
|
|
6
|
+
import { formatMessage, message } from "@optique/core/message";
|
|
7
|
+
import { print } from "@optique/run";
|
|
8
|
+
import ora from "ora";
|
|
9
|
+
import { formatObject } from "../utils.ts";
|
|
10
|
+
import type { WebFingerCommand } from "./command.ts";
|
|
11
|
+
import { getErrorMessage, NotFoundError } from "./error.ts";
|
|
12
|
+
import { convertUrlIfHandle } from "./lib.ts";
|
|
13
|
+
|
|
14
|
+
export default async function runWebFinger(
|
|
15
|
+
{ command: _, resources, ...options }: WebFingerCommand,
|
|
16
|
+
) {
|
|
17
|
+
await Array.fromAsync(
|
|
18
|
+
resources.map((resource) => ({ resource, ...options })),
|
|
19
|
+
spinnerWrapper(lookupSingleWebFinger),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function lookupSingleWebFinger<
|
|
24
|
+
T extends LookupWebFingerOptions & { resource: string },
|
|
25
|
+
>({ resource, ...options }: T): Promise<ResourceDescriptor> {
|
|
26
|
+
const url = convertUrlIfHandle(resource);
|
|
27
|
+
const webFinger = await lookupWebFinger(url, options) ??
|
|
28
|
+
new NotFoundError(resource).throw();
|
|
29
|
+
return webFinger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function spinnerWrapper<F extends typeof lookupSingleWebFinger>(
|
|
33
|
+
func: (...args: Parameters<F>) => ReturnType<F>,
|
|
34
|
+
) {
|
|
35
|
+
return async (...args: Parameters<F>) => {
|
|
36
|
+
const spinner = ora({
|
|
37
|
+
text: `Looking up WebFinger for ${args[0]}`,
|
|
38
|
+
discardStdin: false,
|
|
39
|
+
}).start();
|
|
40
|
+
try {
|
|
41
|
+
const result = await func(...args);
|
|
42
|
+
spinner.succeed(
|
|
43
|
+
formatMessage(message`WebFinger found for ${args[0].resource}:`),
|
|
44
|
+
);
|
|
45
|
+
print([{ type: "text", text: formatObject(result) }]);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
spinner.fail(formatMessage(getErrorMessage(args[0].resource, error)));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
argument,
|
|
3
|
+
command,
|
|
4
|
+
constant,
|
|
5
|
+
flag,
|
|
6
|
+
type InferValue,
|
|
7
|
+
integer,
|
|
8
|
+
merge,
|
|
9
|
+
message,
|
|
10
|
+
multiple,
|
|
11
|
+
object,
|
|
12
|
+
option,
|
|
13
|
+
optional,
|
|
14
|
+
string,
|
|
15
|
+
withDefault,
|
|
16
|
+
} from "@optique/core";
|
|
17
|
+
import { debugOption } from "../globals.ts";
|
|
18
|
+
|
|
19
|
+
const userAgent = optional(option(
|
|
20
|
+
"-u",
|
|
21
|
+
"--user-agent",
|
|
22
|
+
string({ metavar: "USER_AGENT" }),
|
|
23
|
+
{ description: message`The custom User-Agent header value.` },
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
const allowPrivateAddresses = optional(flag("-p", "--allow-private-address", {
|
|
27
|
+
description: message`Allow private IP addresses in the URL.`,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
const maxRedirection = withDefault(
|
|
31
|
+
option(
|
|
32
|
+
"--max-redirection",
|
|
33
|
+
integer({ min: 0 }),
|
|
34
|
+
{ description: message`Maximum number of redirections to follow.` },
|
|
35
|
+
),
|
|
36
|
+
5,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const webFingerCommand = command(
|
|
40
|
+
"webfinger",
|
|
41
|
+
merge(
|
|
42
|
+
object({
|
|
43
|
+
command: constant("webfinger"),
|
|
44
|
+
resources: multiple(argument(string({ metavar: "RESOURCE" }), {
|
|
45
|
+
description: message`WebFinger resource(s) to look up.`,
|
|
46
|
+
})),
|
|
47
|
+
userAgent,
|
|
48
|
+
allowPrivateAddresses,
|
|
49
|
+
maxRedirection,
|
|
50
|
+
}),
|
|
51
|
+
debugOption,
|
|
52
|
+
),
|
|
53
|
+
{
|
|
54
|
+
description:
|
|
55
|
+
message`Look up WebFinger resources. The argument can be multiple.`,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export type WebFingerCommand = InferValue<typeof webFingerCommand>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Message, message } from "@optique/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a user-friendly error message based on the type of error
|
|
5
|
+
* encountered during WebFinger lookup.
|
|
6
|
+
* @param {string} resource The resource being looked up.
|
|
7
|
+
* @param {unknown} error The error encountered.
|
|
8
|
+
* @returns {string} A descriptive error message.
|
|
9
|
+
*/
|
|
10
|
+
export const getErrorMessage = (resource: string, error: unknown): Message =>
|
|
11
|
+
error instanceof InvalidHandleError
|
|
12
|
+
? message`Invalid handle format: ${error.handle}`
|
|
13
|
+
: error instanceof NotFoundError
|
|
14
|
+
? message`Resource not found: ${error.resource}`
|
|
15
|
+
: error instanceof Error
|
|
16
|
+
? message`Failed to look up WebFinger for ${resource}: ${error.message}`
|
|
17
|
+
: message`Failed to look up WebFinger for ${resource}: ${String(error)}`;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Custom error class for invalid handle formats.
|
|
21
|
+
* @param {string} handle The invalid handle that caused the error.
|
|
22
|
+
* @extends {Error}
|
|
23
|
+
*/
|
|
24
|
+
export class InvalidHandleError extends Error {
|
|
25
|
+
constructor(public handle: string) {
|
|
26
|
+
super(`Invalid handle format: ${handle}`);
|
|
27
|
+
this.name = "InvalidHandleError";
|
|
28
|
+
}
|
|
29
|
+
throw(): never {
|
|
30
|
+
throw this;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Custom error class for not found resources.
|
|
36
|
+
* @param {string} resource The resource that was not found.
|
|
37
|
+
* @extends {Error}
|
|
38
|
+
*/
|
|
39
|
+
export class NotFoundError extends Error {
|
|
40
|
+
constructor(public resource: string) {
|
|
41
|
+
super(`Resource not found: ${resource}`);
|
|
42
|
+
this.name = "NotFoundError";
|
|
43
|
+
}
|
|
44
|
+
throw(): never {
|
|
45
|
+
throw this;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { toAcctUrl } from "@fedify/fedify";
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
3
|
+
import { InvalidHandleError } from "./error.ts";
|
|
4
|
+
|
|
5
|
+
export const logger = getLogger(["fedify", "cli", "webfinger"]);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Converts a handle or URL to a URL object.
|
|
9
|
+
* If the input is a valid URL, it returns the URL object.
|
|
10
|
+
* If the input is a handle in the format `@username@domain`, it converts it to a URL.
|
|
11
|
+
* @param handleOrUrl The handle or URL to convert.
|
|
12
|
+
* @returns A URL object representing the handle or URL.
|
|
13
|
+
*/
|
|
14
|
+
export function convertUrlIfHandle(handleOrUrl: string): URL {
|
|
15
|
+
try {
|
|
16
|
+
return new URL(handleOrUrl); // Try to convert the input to a URL
|
|
17
|
+
} catch {
|
|
18
|
+
return convertHandleToUrl(handleOrUrl); // If it fails, treat it as a handle
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converts a handle in the format `@username@domain` to a URL.
|
|
24
|
+
* The resulting URL will be in the format `https://domain/@username`.
|
|
25
|
+
* @param handle The handle to convert, in the format `@username@domain`.
|
|
26
|
+
* @returns A URL object representing the handle.
|
|
27
|
+
* @throws {Error} If the handle format is invalid.
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const url = convertHandleToUrl("@username@domain.com");
|
|
31
|
+
* console.log(url.toString()); // "https://domain.com/@username"
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function convertHandleToUrl(handle: string): URL {
|
|
35
|
+
return toAcctUrl(handle) ?? // Convert the handle to a URL
|
|
36
|
+
new InvalidHandleError(handle).throw(); // or throw an error if invalid
|
|
37
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { parse } from "@optique/core/parser";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { lookupSingleWebFinger } from "./action.ts";
|
|
5
|
+
import { webFingerCommand } from "./command.ts";
|
|
6
|
+
|
|
7
|
+
const COMMAND = "webfinger";
|
|
8
|
+
const USER_AGENT = "MyUserAgent/1.0";
|
|
9
|
+
const RESOURCES = [
|
|
10
|
+
"@hongminhee@hackers.pub",
|
|
11
|
+
"@fedify@hollo.social",
|
|
12
|
+
];
|
|
13
|
+
const ALIASES = [
|
|
14
|
+
"https://hackers.pub/ap/actors/019382d3-63d7-7cf7-86e8-91e2551c306c",
|
|
15
|
+
"https://hollo.social/@fedify",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
test("Test webFingerCommand", () => {
|
|
19
|
+
// Resources only
|
|
20
|
+
const argsWithResourcesOnly = [COMMAND, ...RESOURCES];
|
|
21
|
+
assert.deepEqual(
|
|
22
|
+
parse(webFingerCommand, argsWithResourcesOnly),
|
|
23
|
+
{
|
|
24
|
+
success: true,
|
|
25
|
+
value: {
|
|
26
|
+
debug: false,
|
|
27
|
+
command: COMMAND,
|
|
28
|
+
resources: RESOURCES,
|
|
29
|
+
allowPrivateAddresses: undefined,
|
|
30
|
+
maxRedirection: 5,
|
|
31
|
+
userAgent: undefined,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
// With options
|
|
36
|
+
const maxRedirection = 10;
|
|
37
|
+
assert.deepEqual(
|
|
38
|
+
parse(webFingerCommand, [
|
|
39
|
+
...argsWithResourcesOnly,
|
|
40
|
+
"-d",
|
|
41
|
+
"-u",
|
|
42
|
+
USER_AGENT,
|
|
43
|
+
"--max-redirection",
|
|
44
|
+
String(maxRedirection),
|
|
45
|
+
"--allow-private-address",
|
|
46
|
+
]),
|
|
47
|
+
{
|
|
48
|
+
success: true,
|
|
49
|
+
value: {
|
|
50
|
+
debug: true,
|
|
51
|
+
command: COMMAND,
|
|
52
|
+
resources: RESOURCES,
|
|
53
|
+
allowPrivateAddresses: true,
|
|
54
|
+
maxRedirection,
|
|
55
|
+
userAgent: USER_AGENT,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
// Wrong option
|
|
60
|
+
const wrongOptionResult = parse(webFingerCommand, [
|
|
61
|
+
...argsWithResourcesOnly,
|
|
62
|
+
"-Q",
|
|
63
|
+
]);
|
|
64
|
+
assert.ok(!wrongOptionResult.success);
|
|
65
|
+
// Wrong option value
|
|
66
|
+
const wrongOptionValueResult = parse(
|
|
67
|
+
webFingerCommand,
|
|
68
|
+
[...argsWithResourcesOnly, "--max-redirection", "-10"],
|
|
69
|
+
);
|
|
70
|
+
assert.ok(!wrongOptionValueResult.success);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("Test lookupSingleWebFinger", async () => {
|
|
74
|
+
const aliases = (await Array.fromAsync(
|
|
75
|
+
RESOURCES,
|
|
76
|
+
(resource) => lookupSingleWebFinger({ resource }),
|
|
77
|
+
)).map((w) => w?.aliases?.[0]);
|
|
78
|
+
assert.deepEqual(aliases, ALIASES);
|
|
79
|
+
});
|
package/tsdown.config.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { defineConfig } from "tsdown";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/mod.ts", "src/kv.bun.ts", "src/kv.node.ts"],
|
|
5
|
+
platform: "node",
|
|
6
|
+
unbundle: true,
|
|
7
|
+
inputOptions: {
|
|
8
|
+
onwarn(warning, defaultHandler) {
|
|
9
|
+
if (
|
|
10
|
+
warning.code === "UNRESOLVED_IMPORT" &&
|
|
11
|
+
["#kv", "bun:sqlite"].includes(warning.exporter ?? "")
|
|
12
|
+
) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
defaultHandler(warning);
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
outputOptions(outputOptions) {
|
|
19
|
+
outputOptions.intro = `
|
|
20
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
21
|
+
`;
|
|
22
|
+
return outputOptions;
|
|
23
|
+
},
|
|
24
|
+
});
|