|
| 1 | +import { initOptions, runInit } from "@fedify/init"; |
| 2 | +import { |
| 3 | + constant, |
| 4 | + merge, |
| 5 | + message, |
| 6 | + object, |
| 7 | + optionNames, |
| 8 | + type Parser, |
| 9 | +} from "@optique/core"; |
| 10 | +import { |
| 11 | + type CommandMetadata, |
| 12 | + type CommandPath, |
| 13 | + defineCommand, |
| 14 | + type StaticCommand, |
| 15 | +} from "@optique/discover"; |
| 16 | +import { benchMetadata, benchOptions } from "./bench/command.ts"; |
| 17 | +import { |
| 18 | + generateVocabMetadata, |
| 19 | + generateVocabOptions, |
| 20 | +} from "./generate-vocab/command.ts"; |
| 21 | +import { inboxMetadata, inboxOptions } from "./inbox/command.ts"; |
| 22 | +import { lookupMetadata, lookupOptions } from "./lookup/command.ts"; |
| 23 | +import { nodeInfoMetadata, nodeInfoOptions, runNodeInfo } from "./nodeinfo.ts"; |
| 24 | +import type { GlobalOptions } from "./options.ts"; |
| 25 | +import { relayMetadata, relayOptions } from "./relay/command.ts"; |
| 26 | +import { runTunnel, tunnelMetadata, tunnelOptions } from "./tunnel.ts"; |
| 27 | +import { webFingerMetadata, webFingerOptions } from "./webfinger/command.ts"; |
| 28 | + |
| 29 | +type CliCommandHandler<TValue extends object> = ( |
| 30 | + value: TValue & GlobalOptions, |
| 31 | +) => unknown | Promise<unknown>; |
| 32 | + |
| 33 | +export type CliStaticCommand<TValue extends object> = |
| 34 | + & Omit<StaticCommand<"sync", TValue>, "handler"> |
| 35 | + & { |
| 36 | + readonly handler: (value: never) => unknown | Promise<unknown>; |
| 37 | + readonly run: CliCommandHandler<TValue>; |
| 38 | + }; |
| 39 | + |
| 40 | +export type AnyCliStaticCommand = |
| 41 | + & Omit<StaticCommand<"sync", never>, "handler" | "parser"> |
| 42 | + & { |
| 43 | + readonly parser: Parser<"sync", unknown, unknown>; |
| 44 | + readonly handler: (value: never) => unknown | Promise<unknown>; |
| 45 | + readonly run: (value: never) => unknown | Promise<unknown>; |
| 46 | + }; |
| 47 | + |
| 48 | +export type CliCommandValue<TCommand extends AnyCliStaticCommand> = |
| 49 | + TCommand extends CliStaticCommand<infer TValue> ? TValue : never; |
| 50 | + |
| 51 | +function defineCliCommand<const TValue extends object>( |
| 52 | + command: { |
| 53 | + readonly path: CommandPath; |
| 54 | + readonly parser: Parser<"sync", TValue, unknown>; |
| 55 | + readonly metadata?: CommandMetadata; |
| 56 | + readonly run: CliCommandHandler<NoInfer<TValue>>; |
| 57 | + }, |
| 58 | +): CliStaticCommand<TValue> { |
| 59 | + const { run, ...definition } = command; |
| 60 | + return { |
| 61 | + ...defineCommand({ |
| 62 | + ...definition, |
| 63 | + handler: (_value: TValue) => {}, |
| 64 | + }), |
| 65 | + run, |
| 66 | + }; |
| 67 | +} |
| 68 | + |
| 69 | +const initParser = merge( |
| 70 | + initOptions, |
| 71 | + object({ command: constant("init") }), |
| 72 | +); |
| 73 | + |
| 74 | +const initMetadata = { |
| 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.`, |
| 85 | +}; |
| 86 | + |
| 87 | +export const generatingCommands = [ |
| 88 | + defineCliCommand({ |
| 89 | + path: ["init"], |
| 90 | + parser: initParser, |
| 91 | + metadata: initMetadata, |
| 92 | + run: runInit, |
| 93 | + }), |
| 94 | + defineCliCommand({ |
| 95 | + path: ["generate-vocab"], |
| 96 | + parser: generateVocabOptions, |
| 97 | + metadata: generateVocabMetadata, |
| 98 | + run: async (value) => { |
| 99 | + const { default: runGenerateVocab } = await import( |
| 100 | + "./generate-vocab/action.ts" |
| 101 | + ); |
| 102 | + return await runGenerateVocab(value); |
| 103 | + }, |
| 104 | + }), |
| 105 | +] satisfies readonly AnyCliStaticCommand[]; |
| 106 | + |
| 107 | +export const activityPubCommands = [ |
| 108 | + defineCliCommand({ |
| 109 | + path: ["webfinger"], |
| 110 | + parser: webFingerOptions, |
| 111 | + metadata: webFingerMetadata, |
| 112 | + run: async (value) => { |
| 113 | + const { default: runWebFinger } = await import("./webfinger/action.ts"); |
| 114 | + return await runWebFinger(value); |
| 115 | + }, |
| 116 | + }), |
| 117 | + defineCliCommand({ |
| 118 | + path: ["lookup"], |
| 119 | + parser: lookupOptions, |
| 120 | + metadata: lookupMetadata, |
| 121 | + run: async (value) => { |
| 122 | + const { runLookup } = await import("./lookup.ts"); |
| 123 | + return await runLookup(value); |
| 124 | + }, |
| 125 | + }), |
| 126 | + defineCliCommand({ |
| 127 | + path: ["inbox"], |
| 128 | + parser: inboxOptions, |
| 129 | + metadata: inboxMetadata, |
| 130 | + run: async (value) => { |
| 131 | + const { runInbox } = await import("./inbox.tsx"); |
| 132 | + return await runInbox(value); |
| 133 | + }, |
| 134 | + }), |
| 135 | + defineCliCommand({ |
| 136 | + path: ["nodeinfo"], |
| 137 | + parser: nodeInfoOptions, |
| 138 | + metadata: nodeInfoMetadata, |
| 139 | + run: runNodeInfo, |
| 140 | + }), |
| 141 | + defineCliCommand({ |
| 142 | + path: ["relay"], |
| 143 | + parser: relayOptions, |
| 144 | + metadata: relayMetadata, |
| 145 | + run: async (value) => { |
| 146 | + const { runRelay } = await import("./relay.ts"); |
| 147 | + return await runRelay(value); |
| 148 | + }, |
| 149 | + }), |
| 150 | + defineCliCommand({ |
| 151 | + path: ["bench"], |
| 152 | + parser: benchOptions, |
| 153 | + metadata: benchMetadata, |
| 154 | + run: async (value) => { |
| 155 | + const { runBench } = await import("./bench/mod.ts"); |
| 156 | + return await runBench(value); |
| 157 | + }, |
| 158 | + }), |
| 159 | +] satisfies readonly AnyCliStaticCommand[]; |
| 160 | + |
| 161 | +export const networkCommands = [ |
| 162 | + defineCliCommand({ |
| 163 | + path: ["tunnel"], |
| 164 | + parser: tunnelOptions, |
| 165 | + metadata: tunnelMetadata, |
| 166 | + run: runTunnel, |
| 167 | + }), |
| 168 | +] satisfies readonly AnyCliStaticCommand[]; |
| 169 | + |
| 170 | +export const cliCommands = [ |
| 171 | + ...generatingCommands, |
| 172 | + ...activityPubCommands, |
| 173 | + ...networkCommands, |
| 174 | +] as const satisfies readonly AnyCliStaticCommand[]; |
| 175 | + |
| 176 | +export type CliCommand = typeof cliCommands[number]; |
0 commit comments