| import { posix } from "node:path"; |
| import { isServerId, type ServerId } from "@earendil-works/pi-protocol"; |
| import { type ParsedCommandInput, stringOption, valueOption } from "./command.ts"; |
|
|
| export type AuthInput = |
| | { readonly type: "token"; readonly token: string } |
| | { readonly type: "file"; readonly path: string }; |
|
|
| interface UnixTransportAddress { |
| readonly transport: "unix"; |
| readonly path: string; |
| } |
|
|
| interface RadiusTransportAddress { |
| readonly transport: "radius"; |
| readonly serverId: ServerId; |
| } |
|
|
| export type TransportAddress = UnixTransportAddress | RadiusTransportAddress; |
|
|
| export const authTokenOption = stringOption("--auth-token"); |
| export const authTokenFileOption = stringOption("--auth-token-file"); |
|
|
| function parseAuthInput(options: { readonly authToken?: string; readonly authTokenFile?: string }): { |
| auth?: AuthInput; |
| errors: string[]; |
| } { |
| if (options.authToken !== undefined && options.authTokenFile !== undefined) { |
| return { errors: ["--auth-token and --auth-token-file are mutually exclusive"] }; |
| } |
| if (options.authToken !== undefined) { |
| return { auth: { type: "token", token: options.authToken }, errors: [] }; |
| } |
| if (options.authTokenFile !== undefined) { |
| return { auth: { type: "file", path: options.authTokenFile }, errors: [] }; |
| } |
| return { errors: [] }; |
| } |
|
|
| function parseTransportAddress(value: string): { address?: TransportAddress; error?: string } { |
| let url: URL; |
| try { |
| url = new URL(value); |
| } catch { |
| return { error: `Invalid --connect address "${value}"` }; |
| } |
| if (url.protocol === "radius:") { |
| if ( |
| url.username || |
| url.password || |
| url.port || |
| (url.pathname !== "" && url.pathname !== "/") || |
| url.search || |
| url.hash || |
| value !== `radius://${url.hostname}${url.pathname}` |
| ) { |
| return { error: `Invalid --connect address "${value}"` }; |
| } |
| const serverId = url.hostname; |
| if (!isServerId(serverId)) { |
| return { error: "Radius transport address requires a lowercase UUIDv4 server ID" }; |
| } |
| return { address: { transport: "radius", serverId } }; |
| } |
| if (url.protocol !== "unix:") return { error: `Unsupported --connect transport "${url.protocol}"` }; |
| if (url.hostname || url.port || url.username || url.password) { |
| return { error: "Unix transport address must not include an authority" }; |
| } |
| if ( |
| !value.startsWith("unix:///") || |
| value.startsWith("unix:////") || |
| value.includes("?") || |
| value.includes("#") || |
| url.href !== value |
| ) { |
| return { error: `Invalid --connect address "${value}"` }; |
| } |
| let path: string; |
| try { |
| path = decodeURIComponent(url.pathname); |
| } catch { |
| return { error: `Invalid --connect address "${value}"` }; |
| } |
| if (path.includes("\0")) return { error: `Invalid --connect address "${value}"` }; |
| if (!posix.isAbsolute(path)) return { error: "Unix transport address requires an absolute path" }; |
| return { address: { transport: "unix", path } }; |
| } |
|
|
| export const connectOption = valueOption("--connect", (value) => { |
| const result = parseTransportAddress(value); |
| return result.address |
| ? { ok: true, value: result.address } |
| : { ok: false, error: result.error ?? `Invalid --connect address "${value}"` }; |
| }); |
|
|
| export function parseAuth(input: ParsedCommandInput): { auth?: AuthInput; errors: string[] } { |
| return parseAuthInput({ |
| authToken: input.value(authTokenOption), |
| authTokenFile: input.value(authTokenFileOption), |
| }); |
| } |
|
|
| export function unsupportedOptions(command: string, input: ParsedCommandInput): string[] { |
| if (input.remainingArgs.length === 0) return []; |
| return [`The experimental ${command} command does not support existing CLI options yet`]; |
| } |
|
|