121 lines
7.4 KiB
TypeScript
121 lines
7.4 KiB
TypeScript
import { defaultCodexPoolRuntimeTargetId } from "./runtime-target";
|
|
|
|
export type RuntimeAction = "list" | "get" | "errors" | "infrastructure" | "apply" | "delete";
|
|
|
|
export interface RuntimeOptions {
|
|
action: RuntimeAction;
|
|
account: string | null;
|
|
accounts: string[];
|
|
group: string | null;
|
|
allGroups: boolean;
|
|
platform: string | null;
|
|
pageToken: string | null;
|
|
template: string | null;
|
|
kind: "temp-unschedulable";
|
|
confirm: boolean;
|
|
full: boolean;
|
|
raw: boolean;
|
|
json: boolean;
|
|
since: string;
|
|
tail: number;
|
|
targetId: string;
|
|
}
|
|
|
|
export function parseRuntimeOptions(args: string[]): RuntimeOptions {
|
|
const [actionRaw = "list", ...rest] = args;
|
|
if (!(["list", "get", "errors", "infrastructure", "apply", "delete"] as string[]).includes(actionRaw)) {
|
|
throw new Error("runtime usage: list|get|errors|infrastructure|apply|delete [--account <name-or-id>|--accounts <selectors>] [--group <name-or-id>|--all-groups] [--platform <name>] [--page-token <token>] [--since 24h] [--tail 50000] [--template <id>] [--kind temp-unschedulable] [--confirm] [--target <id>] [--json|--full|--raw]");
|
|
}
|
|
let account: string | null = null;
|
|
let accounts: string[] = [];
|
|
let group: string | null = null;
|
|
let allGroups = false;
|
|
let platform: string | null = null;
|
|
let pageToken: string | null = null;
|
|
let template: string | null = null;
|
|
let kind = "temp-unschedulable" as const;
|
|
let confirm = false;
|
|
let full = false;
|
|
let raw = false;
|
|
let json = false;
|
|
let since = "24h";
|
|
let tail = 50_000;
|
|
let targetId = defaultCodexPoolRuntimeTargetId();
|
|
for (let index = 0; index < rest.length; index += 1) {
|
|
const arg = rest[index]!;
|
|
const readValue = (name: string): string => {
|
|
const value = rest[index + 1];
|
|
if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`);
|
|
index += 1;
|
|
return value.trim();
|
|
};
|
|
if (arg === "--confirm") confirm = true;
|
|
else if (arg === "--full") full = true;
|
|
else if (arg === "--raw") raw = true;
|
|
else if (arg === "--json") json = true;
|
|
else if (arg === "-o" && readValue("-o") === "json") json = true;
|
|
else if (arg === "--account") account = readValue("--account");
|
|
else if (arg.startsWith("--account=")) account = arg.slice("--account=".length).trim();
|
|
else if (arg === "--accounts") accounts = parseAccountSelectors(readValue("--accounts"));
|
|
else if (arg.startsWith("--accounts=")) accounts = parseAccountSelectors(arg.slice("--accounts=".length));
|
|
else if (arg === "--group") group = readValue("--group");
|
|
else if (arg.startsWith("--group=")) group = arg.slice("--group=".length).trim();
|
|
else if (arg === "--all-groups") allGroups = true;
|
|
else if (arg === "--platform") platform = readValue("--platform");
|
|
else if (arg.startsWith("--platform=")) platform = arg.slice("--platform=".length).trim();
|
|
else if (arg === "--page-token") pageToken = readValue("--page-token");
|
|
else if (arg.startsWith("--page-token=")) pageToken = arg.slice("--page-token=".length).trim();
|
|
else if (arg === "--template") template = readValue("--template");
|
|
else if (arg.startsWith("--template=")) template = arg.slice("--template=".length).trim();
|
|
else if (arg === "--kind") kind = parseRuntimeKind(readValue("--kind"));
|
|
else if (arg.startsWith("--kind=")) kind = parseRuntimeKind(arg.slice("--kind=".length));
|
|
else if (arg === "--target") targetId = readValue("--target");
|
|
else if (arg.startsWith("--target=")) targetId = arg.slice("--target=".length).trim();
|
|
else if (arg === "--since") since = readValue("--since");
|
|
else if (arg.startsWith("--since=")) since = arg.slice("--since=".length).trim();
|
|
else if (arg === "--tail") tail = parseRuntimeTail(readValue("--tail"));
|
|
else if (arg.startsWith("--tail=")) tail = parseRuntimeTail(arg.slice("--tail=".length));
|
|
else throw new Error(`unsupported runtime option: ${arg}`);
|
|
}
|
|
const action = actionRaw as RuntimeAction;
|
|
if (account !== null && accounts.length > 0) throw new Error("use only one of --account or --accounts");
|
|
if ((action === "get" || action === "infrastructure") && !account) throw new Error(`runtime ${action} requires --account <name-or-id>`);
|
|
if ((action === "apply" || action === "delete") && account === null && accounts.length === 0) throw new Error(`runtime ${action} requires --account or --accounts`);
|
|
if (accounts.length > 0 && action !== "apply" && action !== "delete") throw new Error(`runtime ${action} does not accept --accounts`);
|
|
if (account !== null && (account.length > 256 || /[\r\n]/u.test(account))) throw new Error("--account has an unsupported format");
|
|
if (group !== null && (group.length > 256 || /[\r\n]/u.test(group))) throw new Error("--group has an unsupported format");
|
|
if (platform !== null && (!/^[A-Za-z0-9._-]+$/u.test(platform) || platform.length > 64)) throw new Error("--platform has an unsupported format");
|
|
if (pageToken !== null && (!/^\d+$/u.test(pageToken) || pageToken.length > 20)) throw new Error("--page-token must be a numeric group cursor");
|
|
if (group !== null && allGroups) throw new Error("use only one of --group or --all-groups");
|
|
if ((allGroups || pageToken !== null) && action !== "errors") throw new Error(`runtime ${action} does not accept all-groups pagination options`);
|
|
if ((group !== null || platform !== null) && action !== "errors" && action !== "infrastructure") throw new Error(`runtime ${action} does not accept group scope options`);
|
|
if (pageToken !== null && !allGroups) throw new Error("--page-token requires --all-groups");
|
|
if (account !== null && allGroups) throw new Error("--account cannot be combined with --all-groups; use --group first");
|
|
if (action === "apply" && !template) throw new Error("runtime apply requires --template <id>");
|
|
if (action !== "apply" && template !== null) throw new Error(`runtime ${action} does not accept --template`);
|
|
if (action !== "errors" && action !== "infrastructure" && (since !== "24h" || tail !== 50_000)) throw new Error(`runtime ${action} does not accept --since or --tail`);
|
|
if (!/^\d+[mhd]$/u.test(since)) throw new Error("--since must use <number>m, <number>h, or <number>d");
|
|
if (["list", "get", "errors", "infrastructure"].includes(action) && confirm) throw new Error(`runtime ${action} does not accept --confirm`);
|
|
if ([full, raw, json].filter(Boolean).length > 1) throw new Error("use only one of --json, --full, or --raw");
|
|
return { action, account, accounts, group, allGroups, platform, pageToken, template, kind, confirm, full, raw, json, since, tail, targetId };
|
|
}
|
|
|
|
function parseAccountSelectors(value: string): string[] {
|
|
const selectors = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
if (selectors.length === 0 || selectors.length > 100) throw new Error("--accounts requires 1 to 100 comma-separated selectors");
|
|
if (selectors.some((item) => item.length > 256 || /[\r\n]/u.test(item))) throw new Error("--accounts has an unsupported selector");
|
|
if (new Set(selectors).size !== selectors.length) throw new Error("--accounts contains duplicate selectors");
|
|
return selectors;
|
|
}
|
|
|
|
function parseRuntimeKind(value: string): "temp-unschedulable" {
|
|
if (value !== "temp-unschedulable") throw new Error("--kind must be temp-unschedulable");
|
|
return value;
|
|
}
|
|
|
|
function parseRuntimeTail(value: string): number {
|
|
const parsed = Number(value);
|
|
if (!Number.isInteger(parsed) || parsed < 100 || parsed > 200_000) throw new Error("--tail must be an integer from 100 to 200000");
|
|
return parsed;
|
|
}
|