@fedify/cli 2.0.0-pr.458.1796 → 2.0.0-pr.467.1876

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/cli",
3
- "version": "2.0.0-pr.458.1796+f091195d",
3
+ "version": "2.0.0-pr.467.1876+1fd56163",
4
4
  "license": "MIT",
5
5
  "exports": "./src/mod.ts",
6
6
  "imports": {
package/dist/deno.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  //#region deno.json
5
5
  var name = "@fedify/cli";
6
- var version = "2.0.0-pr.458.1796+f091195d";
6
+ var version = "2.0.0-pr.467.1876+1fd56163";
7
7
  var license = "MIT";
8
8
  var exports = "./src/mod.ts";
9
9
  var imports = {
@@ -1,21 +1,21 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
- import { command, constant, message, object, option } from "@optique/core";
4
+ import { argument, command, constant, message, object, option, withDefault } from "@optique/core";
5
5
  import { path } from "@optique/run";
6
6
 
7
7
  //#region src/generate-vocab/command.ts
8
- const schemaDir = option("-i", "--input", path({
8
+ const schemaDir = withDefault(option("-i", "--input", path({
9
9
  metavar: "DIR",
10
10
  type: "directory",
11
11
  mustExist: true
12
- }));
13
- const generatedPath = option("-o", "--output", path({
12
+ }), { description: message`Directory containing schema files.` }), ".");
13
+ const generatedPath = argument(path({
14
14
  metavar: "PATH",
15
15
  type: "file",
16
16
  allowCreate: true
17
- }));
18
- const generateVocabCommand = command("generate-vocab", object({
17
+ }), { description: message`Path to output the generated vocabulary classes. Should end with ${".ts"} suffix.` });
18
+ const generateVocabCommand = command("generate-vocab", object("Generation options", {
19
19
  command: constant("generate-vocab"),
20
20
  schemaDir,
21
21
  generatedPath
package/dist/inbox.js CHANGED
@@ -27,11 +27,14 @@ const logger = getLogger([
27
27
  const inboxCommand = command("inbox", merge(object("Inbox options", {
28
28
  command: constant("inbox"),
29
29
  follow: optional(multiple(option("-f", "--follow", string({ metavar: "URI" }), { description: message`Follow the given actor. The argument can be either an actor URI or a handle. Can be specified multiple times.` }))),
30
- acceptFollow: optional(multiple(option("-a", "--accept-follow", string({ metavar: "URI" }), { description: message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (*). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.` }))),
30
+ acceptFollow: optional(multiple(option("-a", "--accept-follow", string({ metavar: "URI" }), { description: message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.` }))),
31
31
  noTunnel: option("-T", "--no-tunnel", { description: message`Do not tunnel the ephemeral ActivityPub server to the public Internet.` }),
32
32
  actorName: withDefault(option("--actor-name", string({ metavar: "NAME" }), { description: message`Customize the actor display name.` }), "Fedify Ephemeral Inbox"),
33
33
  actorSummary: withDefault(option("--actor-summary", string({ metavar: "SUMMARY" }), { description: message`Customize the actor description.` }), "An ephemeral ActivityPub inbox for testing purposes.")
34
- }), debugOption), { description: message`Spins up an ephemeral server that serves the ActivityPub inbox with an one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.` });
34
+ }), debugOption), {
35
+ brief: message`Run an ephemeral ActivityPub inbox server.`,
36
+ description: message`Spins up an ephemeral server that serves the ActivityPub inbox with an one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`
37
+ });
35
38
  async function runInbox(command$1) {
36
39
  const fetch = createFetchHandler({
37
40
  actorName: command$1.actorName,
@@ -1,25 +1,33 @@
1
1
 
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
 
4
+ import { debugOption } from "../globals.js";
4
5
  import { KV_STORE, MESSAGE_QUEUE, PACKAGE_MANAGER, WEB_FRAMEWORK } from "./const.js";
5
- import { argument, choice, command, constant, message, object, option, optional } from "@optique/core";
6
+ import { argument, choice, command, constant, message, object, option, optionNames, optional } from "@optique/core";
6
7
  import { path } from "@optique/run";
7
8
 
8
9
  //#region src/init/command.ts
9
- const joinSep = (str) => str.join(" | ");
10
- const webFramework = optional(option("-w", "--web-framework", choice(WEB_FRAMEWORK, { metavar: `WEB_FRAMEWORK: ${joinSep(WEB_FRAMEWORK)}` })));
11
- const packageManager = optional(option("-p", "--package-manager", choice(PACKAGE_MANAGER, { metavar: `PACKAGE_MANAGER: ${joinSep(PACKAGE_MANAGER)}` })));
12
- const kvStore = optional(option("-k", "--kv-store", choice(KV_STORE, { metavar: `KV_STORE: ${joinSep(KV_STORE)}` })));
13
- const messageQueue = optional(option("-m", "--message-queue", choice(MESSAGE_QUEUE, { metavar: `MESSAGE_QUEUE: ${joinSep(MESSAGE_QUEUE)}` })));
14
- const initCommand = command("init", object({
10
+ const webFramework = optional(option("-w", "--web-framework", choice(WEB_FRAMEWORK, { metavar: "WEB_FRAMEWORK" }), { description: message`The web framework to integrate Fedify with.` }));
11
+ const packageManager = optional(option("-p", "--package-manager", choice(PACKAGE_MANAGER, { metavar: "PACKAGE_MANAGER" }), { description: message`The package manager to use for installing dependencies.` }));
12
+ const kvStore = optional(option("-k", "--kv-store", choice(KV_STORE, { metavar: "KV_STORE" }), { description: message`The key-value store to use for caching and some other features.` }));
13
+ const messageQueue = optional(option("-m", "--message-queue", choice(MESSAGE_QUEUE, { metavar: "MESSAGE_QUEUE" }), { description: message`The message queue to use for background tasks.` }));
14
+ const initCommand = command("init", object("Initialization options", {
15
15
  command: constant("init"),
16
- dir: optional(argument(path({ metavar: "DIRECTORY" }))),
16
+ dir: optional(argument(path({ metavar: "DIR" }), { description: message`The project directory to initialize. If a specified directory does not exist, it will be created.` })),
17
17
  webFramework,
18
18
  packageManager,
19
19
  kvStore,
20
20
  messageQueue,
21
- dryRun: option("-d", "--dry-run")
22
- }), { description: message`Initialize a new Fedify project directory.` });
21
+ dryRun: option("-d", "--dry-run", { description: message`Perform a trial run with no changes made.` }),
22
+ debugOption
23
+ }), {
24
+ brief: message`Initialize a new Fedify project directory.`,
25
+ description: message`Initialize a new Fedify project directory.
26
+
27
+ By default, it initializes the current directory. You can specify a different directory as an argument.
28
+
29
+ Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${optionNames(["-p", "--package-manager"])}, ${optionNames(["-k", "--kv-store"])}, and ${optionNames(["-m", "--message-queue"])}), it will prompt you to select the options interactively.`
30
+ });
23
31
 
24
32
  //#endregion
25
33
  export { initCommand };
@@ -0,0 +1,23 @@
1
+ import { createFederation, Person } from "@fedify/fedify";
2
+ import { getLogger } from "@logtape/logtape";
3
+ /* imports */
4
+
5
+ const logger = getLogger(/* logger */);
6
+
7
+ const federation = createFederation({
8
+ kv: /* kv */,
9
+ queue: /* queue */,
10
+ });
11
+
12
+ federation.setActorDispatcher(
13
+ "/users/{identifier}",
14
+ async (ctx, identifier) => {
15
+ return new Person({
16
+ id: ctx.getActorUri(identifier),
17
+ preferredUsername: identifier,
18
+ name: identifier,
19
+ });
20
+ },
21
+ );
22
+
23
+ export default federation;
@@ -0,0 +1,23 @@
1
+ import { configure, getConsoleSink } from "@logtape/logtape";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+
4
+ await configure({
5
+ contextLocalStorage: new AsyncLocalStorage(),
6
+ sinks: {
7
+ console: getConsoleSink(),
8
+ },
9
+ filters: {},
10
+ loggers: [
11
+ {
12
+ category: /* project name */,
13
+ lowestLevel: "debug",
14
+ sinks: ["console"],
15
+ },
16
+ { category: "fedify", lowestLevel: "info", sinks: ["console"] },
17
+ {
18
+ category: ["logtape", "meta"],
19
+ lowestLevel: "warning",
20
+ sinks: ["console"],
21
+ },
22
+ ],
23
+ });
@@ -0,0 +1,16 @@
1
+ import { integrateFederation } from "@fedify/express";
2
+ import { getLogger } from "@logtape/logtape";
3
+ import express from "express";
4
+ import federation from "./federation.ts";
5
+
6
+ const logger = getLogger("/* logger */");
7
+
8
+ export const app = express();
9
+
10
+ app.set("trust proxy", true);
11
+
12
+ app.use(integrateFederation(federation, () => undefined));
13
+
14
+ app.get("/", (_, res) => res.send("Hello, Fedify!"));
15
+
16
+ export default app;
@@ -0,0 +1,6 @@
1
+ import app from "./app.ts";
2
+ import "./logging.ts";
3
+
4
+ app.listen(8000, () => {
5
+ console.log("Server started at http://localhost:8000");
6
+ });
@@ -0,0 +1,14 @@
1
+ // @ts-nocheck this file is just a template
2
+ import { Hono } from "/* hono */";
3
+ import { federation } from "@fedify/hono";
4
+ import { getLogger } from "@logtape/logtape";
5
+ import fedi from "./federation.ts";
6
+
7
+ const logger = getLogger("/* logger */");
8
+
9
+ const app = new Hono();
10
+ app.use(federation(fedi, () => undefined));
11
+
12
+ app.get("/", (c) => c.text("Hello, Fedify!"));
13
+
14
+ export default app;
@@ -0,0 +1,10 @@
1
+ import { behindProxy } from "x-forwarded-fetch";
2
+ import app from "./app.tsx";
3
+ import "./logging.ts";
4
+
5
+ const server = Bun.serve({
6
+ port: 8000,
7
+ fetch: behindProxy(app.fetch.bind(app)),
8
+ });
9
+
10
+ console.log("Server started at", server.url.href);
@@ -0,0 +1,13 @@
1
+ import { behindProxy } from "@hongminhee/x-forwarded-fetch";
2
+ import "@std/dotenv/load";
3
+ import app from "./app.tsx";
4
+ import "./logging.ts";
5
+
6
+ Deno.serve(
7
+ {
8
+ port: 8000,
9
+ onListen: ({ port, hostname }) =>
10
+ console.log("Server started at http://" + hostname + ":" + port),
11
+ },
12
+ behindProxy(app.fetch.bind(app)),
13
+ );
@@ -0,0 +1,14 @@
1
+ // @ts-nocheck this file is just a template
2
+ import { serve } from "@hono/node-server";
3
+ import { behindProxy } from "x-forwarded-fetch";
4
+ import app from "./app.tsx";
5
+ import "./logging.ts";
6
+
7
+ serve(
8
+ {
9
+ port: 8000,
10
+ fetch: behindProxy(app.fetch.bind(app)),
11
+ },
12
+ (info) =>
13
+ console.log("Server started at http://" + info.address + ":" + info.port),
14
+ );
@@ -0,0 +1,45 @@
1
+ import { fedifyWith } from "@fedify/next";
2
+ import federation from "./federation";
3
+
4
+ export default fedifyWith(federation)(
5
+ /*
6
+ function (request: Request) {
7
+ // If you need to handle other requests besides federation
8
+ // requests in middleware, you can do it here.
9
+ // If you handle only federation requests in middleware,
10
+ // you don't need this function.
11
+ return NextResponse.next();
12
+ },
13
+ */
14
+ )
15
+
16
+ // This config needs because middleware process only requests with the
17
+ // "Accept" header matching the federation accept regex.
18
+ // More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional
19
+ export const config = {
20
+ runtime: "nodejs",
21
+ matcher: [
22
+ {
23
+ source: "/:path*",
24
+ has: [
25
+ {
26
+ type: "header",
27
+ key: "Accept",
28
+ value: ".*application\\/((jrd|activity|ld)\\+json|xrd\\+xml).*",
29
+ },
30
+ ],
31
+ },
32
+ {
33
+ source: "/:path*",
34
+ has: [
35
+ {
36
+ type: "header",
37
+ key: "content-type",
38
+ value: ".*application\\/((jrd|activity|ld)\\+json|xrd\\+xml).*",
39
+ },
40
+ ],
41
+ },
42
+ { source: "/.well-known/nodeinfo" },
43
+ { source: "/.well-known/x-nodeinfo2" },
44
+ ],
45
+ };
@@ -0,0 +1,5 @@
1
+ // https://nitro.unjs.io/config
2
+ export default defineNitroConfig({
3
+ srcDir: "server",
4
+ errorHandler: "~/error"
5
+ });
@@ -0,0 +1,3 @@
1
+ import { onError } from "@fedify/h3";
2
+
3
+ export default onError;
@@ -0,0 +1,8 @@
1
+
2
+ import { integrateFederation } from "@fedify/h3";
3
+ import federation from "../federation"
4
+
5
+ export default integrateFederation(
6
+ federation,
7
+ (event, request) => undefined,
8
+ );
package/dist/lookup.js CHANGED
@@ -6,7 +6,7 @@ import { configureLogging, debugOption } from "./globals.js";
6
6
  import { spawnTemporaryServer } from "./tempserver.js";
7
7
  import { colorEnabled, colors, formatObject } from "./utils.js";
8
8
  import { renderImages } from "./imagerenderer.js";
9
- import { argument, choice, command, constant, flag, float, map, merge, message, multiple, object, option, optional, or, string, withDefault } from "@optique/core";
9
+ import { argument, choice, command, constant, flag, float, map, merge, message, multiple, object, option, optionNames, optional, or, string, withDefault } from "@optique/core";
10
10
  import { path, print, printError } from "@optique/run";
11
11
  import process from "node:process";
12
12
  import { Application, Collection, CryptographicKey, Object as Object$1, generateCryptoKeyPair, getAuthenticatedDocumentLoader, lookupObject, respondWithObject, traverseCollection } from "@fedify/fedify";
@@ -22,7 +22,7 @@ const logger = getLogger([
22
22
  ]);
23
23
  const authorizedFetchOption = withDefault(object({
24
24
  authorizedFetch: flag("-a", "--authorized-fetch", { description: message`Sign the request with an one-time key.` }),
25
- firstKnock: withDefault(option("--first-knock", choice(["draft-cavage-http-signatures-12", "rfc9421"]), { description: message`The first-knock spec for -a/--authorized-fetch. It is used for the double-knocking technique.` }), "draft-cavage-http-signatures-12")
25
+ firstKnock: withDefault(option("--first-knock", choice(["draft-cavage-http-signatures-12", "rfc9421"]), { description: message`The first-knock spec for ${optionNames(["-a", "--authorized-fetch"])}. It is used for the double-knocking technique.` }), "draft-cavage-http-signatures-12")
26
26
  }), { authorizedFetch: false });
27
27
  const traverseOption = withDefault(object({
28
28
  traverse: flag("-t", "--traverse", { description: message`Traverse the given collection(s) to fetch all items.` }),
@@ -42,7 +42,12 @@ const lookupCommand = command("lookup", merge("Looking up options", object({ com
42
42
  min: 0,
43
43
  metavar: "SECONDS"
44
44
  }), { description: message`Set timeout for network requests in seconds.` }))
45
- })), { description: message`Lookup an Activity Streams object by URL or the actor handle. The argument can be either a URL or an actor handle (e.g., @username@domain), and it can be multiple.` });
45
+ })), {
46
+ brief: message`Look up Activity Streams objects.`,
47
+ description: message`Look up Activity Streams objects by URL or actor handle.
48
+
49
+ The arguments can be either URLs or actor handles (e.g., ${"@username@domain"}), and they can be multiple.`
50
+ });
46
51
  var TimeoutError = class extends Error {
47
52
  name = "TimeoutError";
48
53
  constructor(message$1) {
package/dist/nodeinfo.js CHANGED
@@ -27,15 +27,20 @@ const Jimp = createJimp({
27
27
  plugins: defaultPlugins
28
28
  });
29
29
  const nodeInfoOption = optional(or(object({ raw: flag("-r", "--raw", { description: message`Show NodeInfo document in the raw JSON format` }) }), object({
30
- bestEffort: optional(flag("-b", "--best-effort", { description: message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.` })),
30
+ bestEffort: optional(flag("-b", "--best-effort", { description: message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.` })),
31
31
  noFavicon: optional(flag("--no-favicon", { description: message`Disable fetching the favicon of the instance` })),
32
- metadata: optional(flag("-m", "--metadata", { description: message`show the extra metadata of the NodeInfo, i.e., the metadata field of the document.` }))
32
+ metadata: optional(flag("-m", "--metadata", { description: message`Show the extra metadata of the NodeInfo, i.e., the metadata field of the document.` }))
33
33
  })));
34
- const userAgentOption = optional(object({ userAgent: option("-u", "--user-agent", string()) }));
34
+ const userAgentOption = optional(object({ userAgent: option("-u", "--user-agent", string(), { description: message`The custom User-Agent header value.` }) }));
35
35
  const nodeInfoCommand = command("nodeinfo", merge(object({
36
36
  command: constant("nodeinfo"),
37
- host: argument(string({ metavar: "hostname or URL" }), { description: message`Bare hostname or a full URL of the instance` })
38
- }), debugOption, nodeInfoOption, userAgentOption), { description: message`Get information about a remote node using the NodeInfo protocol. The argument is the hostname of the remote node, or the URL of the remote node.` });
37
+ host: argument(string({ metavar: "HOST" }), { description: message`Bare hostname or a full URL of the instance` })
38
+ }), debugOption, nodeInfoOption, userAgentOption), {
39
+ brief: message`Get information about a remote node using the NodeInfo protocol`,
40
+ description: message`Get information about a remote node using the NodeInfo protocol.
41
+
42
+ The argument is the hostname of the remote node, or the URL of the remote node.`
43
+ });
39
44
  async function runNodeInfo(command$1) {
40
45
  const spinner = ora({
41
46
  text: "Fetching a NodeInfo document...",
package/dist/tunnel.js CHANGED
@@ -20,8 +20,13 @@ const tunnelCommand = command("tunnel", merge("Tunnel options", object({ command
20
20
  "localhost.run",
21
21
  "serveo.net",
22
22
  "pinggy.io"
23
- ]), { description: message`The localtunnel service to use.` }))
24
- }), debugOption), { description: message`Expose a local HTTP server to the public internet using a secure tunnel.\nNote that the HTTP requests through the tunnel have X-Forwarded-* headers.` });
23
+ ], { metavar: "SERVICE" }), { description: message`The tunneling service to use.` }))
24
+ }), debugOption), {
25
+ brief: message`Expose a local HTTP server to the public internet using a secure tunnel.`,
26
+ description: message`Expose a local HTTP server to the public internet using a secure tunnel.
27
+
28
+ Note that the HTTP requests through the tunnel have X-Forwarded-* headers.`
29
+ });
25
30
  async function runTunnel(command$1, deps = {
26
31
  openTunnel,
27
32
  ora,
@@ -10,11 +10,16 @@ const allowPrivateAddresses = optional(flag("-p", "--allow-private-address", { d
10
10
  const maxRedirection = withDefault(option("--max-redirection", integer({ min: 0 }), { description: message`Maximum number of redirections to follow.` }), 5);
11
11
  const webFingerCommand = command("webfinger", merge(object({
12
12
  command: constant("webfinger"),
13
- resources: multiple(argument(string({ metavar: "RESOURCE" }), { description: message`WebFinger resource(s) to look up.` })),
13
+ resources: multiple(argument(string({ metavar: "RESOURCE" }), { description: message`WebFinger resource(s) to look up.` }), { min: 1 }),
14
14
  userAgent,
15
15
  allowPrivateAddresses,
16
16
  maxRedirection
17
- }), debugOption), { description: message`Look up WebFinger resources. The argument can be multiple.` });
17
+ }), debugOption), {
18
+ brief: message`Look up WebFinger resources.`,
19
+ description: message`Look up WebFinger resources.
20
+
21
+ The argument can be multiple.`
22
+ });
18
23
 
19
24
  //#endregion
20
25
  export { webFingerCommand };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/cli",
3
- "version": "2.0.0-pr.458.1796+f091195d",
3
+ "version": "2.0.0-pr.467.1876+1fd56163",
4
4
  "description": "CLI toolchain for Fedify and debugging ActivityPub",
5
5
  "keywords": [
6
6
  "fedify",
@@ -71,16 +71,16 @@
71
71
  "ora": "^8.2.0",
72
72
  "shiki": "^1.6.4",
73
73
  "srvx": "^0.8.7",
74
- "@fedify/fedify": "2.0.0-pr.458.1796+f091195d",
75
- "@fedify/sqlite": "2.0.0-pr.458.1796+f091195d",
76
- "@fedify/vocab-runtime": "2.0.0-pr.458.1796+f091195d",
77
- "@fedify/vocab-tools": "2.0.0-pr.458.1796+f091195d"
74
+ "@fedify/sqlite": "2.0.0-pr.467.1876+1fd56163",
75
+ "@fedify/fedify": "2.0.0-pr.467.1876+1fd56163",
76
+ "@fedify/vocab-runtime": "2.0.0-pr.467.1876+1fd56163",
77
+ "@fedify/vocab-tools": "2.0.0-pr.467.1876+1fd56163"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/bun": "^1.2.23",
81
81
  "@types/node": "^22.17.0",
82
82
  "tsdown": "^0.12.9",
83
- "typescript": "^5.9.2"
83
+ "typescript": "^5.9.3"
84
84
  },
85
85
  "scripts": {
86
86
  "codegen": "deno task -f @fedify/fedify codegen",
package/scripts/pack.ts CHANGED
@@ -24,7 +24,9 @@ async function compile(os: OS, arch: Arch, into: string): Promise<void> {
24
24
  if (!target) {
25
25
  throw new Error(`Unsupported os/arch: ${os}/${arch}`);
26
26
  }
27
- await $`deno compile --allow-all --target=${target} --output=${into} ${
27
+ await $`deno compile --allow-all --include=${
28
+ join("src", "init", "templates")
29
+ } --target=${target} --output=${into} ${
28
30
  join(dirname(import.meta.dirname!), "src", "mod.ts")
29
31
  }`;
30
32
  }
@@ -51,10 +53,15 @@ async function pack(os: OS, arch: Arch): Promise<void> {
51
53
  }
52
54
  }
53
55
 
56
+ const osFilter = Deno.env.get("OS")?.toLowerCase();
57
+ const archFilter = Deno.env.get("ARCH")?.toLowerCase();
58
+
54
59
  const promises: Promise<void>[] = [];
55
60
  for (const osKey in triplets) {
56
61
  const os = osKey as OS;
62
+ if (osFilter != null && osFilter !== os) continue;
57
63
  for (const arch in triplets[os]) {
64
+ if (archFilter != null && archFilter !== arch) continue;
58
65
  const promise = pack(os, arch as Arch);
59
66
  promises.push(promise);
60
67
  }
@@ -1,27 +1,35 @@
1
1
  import {
2
+ argument,
2
3
  command,
3
4
  constant,
4
5
  type InferValue,
5
6
  message,
6
7
  object,
7
8
  option,
9
+ withDefault,
8
10
  } from "@optique/core";
9
11
  import { path } from "@optique/run";
10
12
 
11
- const schemaDir = option(
12
- "-i",
13
- "--input",
14
- path({ metavar: "DIR", type: "directory", mustExist: true }),
13
+ const schemaDir = withDefault(
14
+ option(
15
+ "-i",
16
+ "--input",
17
+ path({ metavar: "DIR", type: "directory", mustExist: true }),
18
+ { description: message`Directory containing schema files.` },
19
+ ),
20
+ ".",
15
21
  );
16
- const generatedPath = option(
17
- "-o",
18
- "--output",
22
+ const generatedPath = argument(
19
23
  path({ metavar: "PATH", type: "file", allowCreate: true }),
24
+ {
25
+ description:
26
+ message`Path to output the generated vocabulary classes. Should end with ${".ts"} suffix.`,
27
+ },
20
28
  );
21
29
 
22
30
  const generateVocabCommand = command(
23
31
  "generate-vocab",
24
- object({
32
+ object("Generation options", {
25
33
  command: constant("generate-vocab"),
26
34
  schemaDir,
27
35
  generatedPath,
package/src/inbox.tsx CHANGED
@@ -79,7 +79,7 @@ export const inboxCommand = command(
79
79
  multiple(
80
80
  option("-a", "--accept-follow", string({ metavar: "URI" }), {
81
81
  description:
82
- message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (*). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.`,
82
+ message`Accept follow requests from the given actor. The argument can be either an actor URI or a handle, or a wildcard (${"*"}). Can be specified multiple times. If a wildcard is specified, all follow requests will be accepted.`,
83
83
  }),
84
84
  ),
85
85
  ),
@@ -107,6 +107,7 @@ export const inboxCommand = command(
107
107
  debugOption,
108
108
  ),
109
109
  {
110
+ brief: message`Run an ephemeral ActivityPub inbox server.`,
110
111
  description:
111
112
  message`Spins up an ephemeral server that serves the ActivityPub inbox with an one-time actor, through a short-lived public DNS with HTTPS. You can monitor the incoming activities in real-time.`,
112
113
  },
@@ -8,6 +8,7 @@ import {
8
8
  object,
9
9
  option,
10
10
  optional,
11
+ optionNames,
11
12
  } from "@optique/core";
12
13
  import { path } from "@optique/run";
13
14
  import {
@@ -16,48 +17,71 @@ import {
16
17
  PACKAGE_MANAGER,
17
18
  WEB_FRAMEWORK,
18
19
  } from "./const.ts";
20
+ import { debugOption } from "../globals.ts";
19
21
 
20
- const joinSep = (str: readonly string[]) => str.join(" | ");
21
22
  const webFramework = optional(option(
22
23
  "-w",
23
24
  "--web-framework",
24
- choice(WEB_FRAMEWORK, {
25
- metavar: `WEB_FRAMEWORK: ${joinSep(WEB_FRAMEWORK)}`,
26
- }),
25
+ choice(WEB_FRAMEWORK, { metavar: "WEB_FRAMEWORK" }),
26
+ {
27
+ description: message`The web framework to integrate Fedify with.`,
28
+ },
27
29
  ));
28
30
  const packageManager = optional(option(
29
31
  "-p",
30
32
  "--package-manager",
31
- choice(PACKAGE_MANAGER, {
32
- metavar: `PACKAGE_MANAGER: ${joinSep(PACKAGE_MANAGER)}`,
33
- }),
33
+ choice(PACKAGE_MANAGER, { metavar: "PACKAGE_MANAGER" }),
34
+ {
35
+ description:
36
+ message`The package manager to use for installing dependencies.`,
37
+ },
34
38
  ));
35
39
  const kvStore = optional(option(
36
40
  "-k",
37
41
  "--kv-store",
38
- choice(KV_STORE, { metavar: `KV_STORE: ${joinSep(KV_STORE)}` }),
42
+ choice(KV_STORE, { metavar: "KV_STORE" }),
43
+ {
44
+ description:
45
+ message`The key-value store to use for caching and some other features.`,
46
+ },
39
47
  ));
40
48
  const messageQueue = optional(option(
41
49
  "-m",
42
50
  "--message-queue",
43
- choice(MESSAGE_QUEUE, {
44
- metavar: `MESSAGE_QUEUE: ${joinSep(MESSAGE_QUEUE)}`,
45
- }),
51
+ choice(MESSAGE_QUEUE, { metavar: "MESSAGE_QUEUE" }),
52
+ {
53
+ description: message`The message queue to use for background tasks.`,
54
+ },
46
55
  ));
47
56
 
48
57
  export const initCommand = command(
49
58
  "init",
50
- object({
59
+ object("Initialization options", {
51
60
  command: constant("init"),
52
- dir: optional(argument(path({ metavar: "DIRECTORY" }))),
61
+ dir: optional(argument(path({ metavar: "DIR" }), {
62
+ description:
63
+ message`The project directory to initialize. If a specified directory does not exist, it will be created.`,
64
+ })),
53
65
  webFramework,
54
66
  packageManager,
55
67
  kvStore,
56
68
  messageQueue,
57
- dryRun: option("-d", "--dry-run"),
69
+ dryRun: option("-d", "--dry-run", {
70
+ description: message`Perform a trial run with no changes made.`,
71
+ }),
72
+ debugOption,
58
73
  }),
59
74
  {
60
- description: message`Initialize a new Fedify project directory.`,
75
+ brief: message`Initialize a new Fedify project directory.`,
76
+ description: message`Initialize a new Fedify project directory.
77
+
78
+ By default, it initializes the current directory. You can specify a different directory as an argument.
79
+
80
+ Unless you specify all options (${optionNames(["-w", "--web-framework"])}, ${
81
+ optionNames(["-p", "--package-manager"])
82
+ }, ${optionNames(["-k", "--kv-store"])}, and ${
83
+ optionNames(["-m", "--message-queue"])
84
+ }), it will prompt you to select the options interactively.`,
61
85
  },
62
86
  );
63
87
 
package/src/lookup.ts CHANGED
@@ -28,6 +28,7 @@ import {
28
28
  object,
29
29
  option,
30
30
  optional,
31
+ optionNames,
31
32
  or,
32
33
  string,
33
34
  withDefault,
@@ -54,8 +55,9 @@ export const authorizedFetchOption = withDefault(
54
55
  "--first-knock",
55
56
  choice(["draft-cavage-http-signatures-12", "rfc9421"]),
56
57
  {
57
- description:
58
- message`The first-knock spec for -a/--authorized-fetch. It is used for the double-knocking technique.`,
58
+ description: message`The first-knock spec for ${
59
+ optionNames(["-a", "--authorized-fetch"])
60
+ }. It is used for the double-knocking technique.`,
59
61
  },
60
62
  ),
61
63
  "draft-cavage-http-signatures-12" as const,
@@ -147,8 +149,11 @@ export const lookupCommand = command(
147
149
  }),
148
150
  ),
149
151
  {
152
+ brief: message`Look up Activity Streams objects.`,
150
153
  description:
151
- message`Lookup an Activity Streams object by URL or the actor handle. The argument can be either a URL or an actor handle (e.g., @username@domain), and it can be multiple.`,
154
+ message`Look up Activity Streams objects by URL or actor handle.
155
+
156
+ The arguments can be either URLs or actor handles (e.g., ${"@username@domain"}), and they can be multiple.`,
152
157
  },
153
158
  );
154
159
 
package/src/nodeinfo.ts CHANGED
@@ -44,21 +44,23 @@ const nodeInfoOption = optional(
44
44
  object({
45
45
  bestEffort: optional(flag("-b", "--best-effort", {
46
46
  description:
47
- message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.`,
47
+ message`Parse the NodeInfo document with best effort. If the NodeInfo document is not well-formed, the option will try to parse it as much as possible.`,
48
48
  })),
49
49
  noFavicon: optional(flag("--no-favicon", {
50
50
  description: message`Disable fetching the favicon of the instance`,
51
51
  })),
52
52
  metadata: optional(flag("-m", "--metadata", {
53
53
  description:
54
- message`show the extra metadata of the NodeInfo, i.e., the metadata field of the document.`,
54
+ message`Show the extra metadata of the NodeInfo, i.e., the metadata field of the document.`,
55
55
  })),
56
56
  }),
57
57
  ),
58
58
  );
59
59
 
60
60
  const userAgentOption = optional(object({
61
- userAgent: option("-u", "--user-agent", string()),
61
+ userAgent: option("-u", "--user-agent", string(), {
62
+ description: message`The custom User-Agent header value.`,
63
+ }),
62
64
  }));
63
65
 
64
66
  export const nodeInfoCommand = Command(
@@ -66,7 +68,7 @@ export const nodeInfoCommand = Command(
66
68
  merge(
67
69
  object({
68
70
  command: constant("nodeinfo"),
69
- host: argument(string({ metavar: "hostname or URL" }), {
71
+ host: argument(string({ metavar: "HOST" }), {
70
72
  description: message`Bare hostname or a full URL of the instance`,
71
73
  }),
72
74
  }),
@@ -75,8 +77,12 @@ export const nodeInfoCommand = Command(
75
77
  userAgentOption,
76
78
  ),
77
79
  {
80
+ brief:
81
+ message`Get information about a remote node using the NodeInfo protocol`,
78
82
  description:
79
- message`Get information about a remote node using the NodeInfo protocol. The argument is the hostname of the remote node, or the URL of the remote node.`,
83
+ message`Get information about a remote node using the NodeInfo protocol.
84
+
85
+ The argument is the hostname of the remote node, or the URL of the remote node.`,
80
86
  },
81
87
  );
82
88
 
package/src/tunnel.ts CHANGED
@@ -32,18 +32,22 @@ export const tunnelCommand = command(
32
32
  option(
33
33
  "-s",
34
34
  "--service",
35
- choice(["localhost.run", "serveo.net", "pinggy.io"]),
36
- {
37
- description: message`The localtunnel service to use.`,
38
- },
35
+ choice(["localhost.run", "serveo.net", "pinggy.io"], {
36
+ metavar: "SERVICE",
37
+ }),
38
+ { description: message`The tunneling service to use.` },
39
39
  ),
40
40
  ),
41
41
  }),
42
42
  debugOption,
43
43
  ),
44
44
  {
45
+ brief:
46
+ message`Expose a local HTTP server to the public internet using a secure tunnel.`,
45
47
  description:
46
- message`Expose a local HTTP server to the public internet using a secure tunnel.\nNote that the HTTP requests through the tunnel have X-Forwarded-* headers.`,
48
+ message`Expose a local HTTP server to the public internet using a secure tunnel.
49
+
50
+ Note that the HTTP requests through the tunnel have X-Forwarded-* headers.`,
47
51
  },
48
52
  );
49
53
 
@@ -41,9 +41,12 @@ export const webFingerCommand = command(
41
41
  merge(
42
42
  object({
43
43
  command: constant("webfinger"),
44
- resources: multiple(argument(string({ metavar: "RESOURCE" }), {
45
- description: message`WebFinger resource(s) to look up.`,
46
- })),
44
+ resources: multiple(
45
+ argument(string({ metavar: "RESOURCE" }), {
46
+ description: message`WebFinger resource(s) to look up.`,
47
+ }),
48
+ { min: 1 },
49
+ ),
47
50
  userAgent,
48
51
  allowPrivateAddresses,
49
52
  maxRedirection,
@@ -51,8 +54,10 @@ export const webFingerCommand = command(
51
54
  debugOption,
52
55
  ),
53
56
  {
54
- description:
55
- message`Look up WebFinger resources. The argument can be multiple.`,
57
+ brief: message`Look up WebFinger resources.`,
58
+ description: message`Look up WebFinger resources.
59
+
60
+ The argument can be multiple.`,
56
61
  },
57
62
  );
58
63
 
package/tsdown.config.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { cp } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import { defineConfig } from "tsdown";
2
4
 
3
5
  export default defineConfig({
@@ -21,4 +23,13 @@ export default defineConfig({
21
23
  `;
22
24
  return outputOptions;
23
25
  },
26
+ hooks: {
27
+ "build:done": async (ctx) => {
28
+ await cp(
29
+ join("src", "init", "templates"),
30
+ join(ctx.options.outDir, "init", "templates"),
31
+ { recursive: true },
32
+ );
33
+ },
34
+ },
24
35
  });