import { CheckExecutor } from './check-executor.ts'; import { DEFAULT_CONFIG_PATH, loadConfig, validateConfig, writeConfig } from './config.ts'; import type { IUptimeRunnerConfig, TCheckJob } from './interfaces.ts'; import { UptimeRunner } from './runner.ts'; import { UptimeRunnerSystemd } from './systemd.ts'; import denoConfig from '../deno.json' with { type: 'json' }; export class UptimeRunnerCli { public async parseAndExecute(argsArg: string[]): Promise { const command = argsArg[0] ?? 'help'; const commandArgs = argsArg.slice(1); switch (command) { case 'run': await this.run(commandArgs, false); break; case 'once': await this.run(commandArgs, true); break; case 'check': await this.check(commandArgs); break; case 'config': await this.config(commandArgs); break; case 'service': await this.service(commandArgs); break; case '--version': case '-v': case 'version': console.log(denoConfig.version); break; case 'help': case '--help': case '-h': this.showHelp(); break; default: throw new Error(`Unknown command: ${command}`); } } private async run(argsArg: string[], onceArg: boolean): Promise { const flags = parseFlags(argsArg); const config = await this.loadConfigFromFlags(flags); const runner = new UptimeRunner(config); if (onceArg) { const result = await runner.runOnce(); console.log(JSON.stringify(result, null, 2)); return; } await runner.run(); } private async check(argsArg: string[]): Promise { const url = argsArg.find((arg) => !arg.startsWith('--')); if (!url) { throw new Error('Usage: uptimerunner check '); } const check: TCheckJob = { id: `manual-${Date.now().toString(36)}`, type: 'http', url, expectedStatusCodes: [200], }; const executor = new CheckExecutor('manual'); const result = await executor.execute(check); console.log(JSON.stringify(result, null, 2)); if (result.status !== 'ok') { Deno.exit(2); } } private async config(argsArg: string[]): Promise { const subcommand = argsArg[0] ?? 'show'; const flags = parseFlags(argsArg.slice(1)); const configPath = flags.config ?? DEFAULT_CONFIG_PATH; switch (subcommand) { case 'show': { const config = await loadConfig(configPath); console.log(JSON.stringify({ ...config, token: mask(config.token) }, null, 2)); break; } case 'write': { const config: IUptimeRunnerConfig = { instanceUrl: requiredFlag(flags, 'url'), runnerId: requiredFlag(flags, 'runner-id'), token: requiredFlag(flags, 'token'), pollIntervalMs: flags.interval ? Number(flags.interval) : 30000, labels: flags.labels?.split(',').map((labelArg) => labelArg.trim()).filter(Boolean), maxConcurrentChecks: flags.concurrency ? Number(flags.concurrency) : 8, }; await writeConfig(config, configPath); console.log(`Config written to ${configPath}`); break; } default: throw new Error(`Unknown config command: ${subcommand}`); } } private async service(argsArg: string[]): Promise { const subcommand = argsArg[0] ?? 'status'; const systemd = new UptimeRunnerSystemd(); switch (subcommand) { case 'install': await systemd.install(); break; case 'uninstall': await systemd.uninstall(); break; case 'start': await systemd.start(); break; case 'stop': await systemd.stop(); break; case 'restart': await systemd.restart(); break; case 'status': await systemd.status(); break; case 'logs': await systemd.logs(); break; default: throw new Error(`Unknown service command: ${subcommand}`); } } private async loadConfigFromFlags( flagsArg: Record, ): Promise { const configPath = flagsArg.config ?? DEFAULT_CONFIG_PATH; const fileConfig = await loadConfig(configPath).catch((error) => { if (flagsArg.url && flagsArg.token && flagsArg['runner-id']) { return {} as Partial; } throw error; }); const config = { ...fileConfig, instanceUrl: flagsArg.url ?? fileConfig.instanceUrl, runnerId: flagsArg['runner-id'] ?? fileConfig.runnerId, token: flagsArg.token ?? fileConfig.token, pollIntervalMs: flagsArg.interval ? Number(flagsArg.interval) : fileConfig.pollIntervalMs, labels: flagsArg.labels ? flagsArg.labels.split(',').map((labelArg) => labelArg.trim()).filter(Boolean) : fileConfig.labels, maxConcurrentChecks: flagsArg.concurrency ? Number(flagsArg.concurrency) : fileConfig.maxConcurrentChecks, }; validateConfig(config); return config; } private showHelp(): void { console.log(`uptimerunner ${denoConfig.version} Usage: uptimerunner run [--config path] [--url https://uptime.link] [--token token] [--runner-id id] uptimerunner once [--config path] uptimerunner check uptimerunner config write --url https://uptime.link --runner-id edge-1 --token token uptimerunner service install|start|stop|restart|status|logs|uninstall Environment: UPTIMERUNNER_CONFIG UPTIMERUNNER_INSTANCE_URL UPTIMERUNNER_RUNNER_ID UPTIMERUNNER_TOKEN UPTIMERUNNER_LABELS `); } } function parseFlags(argsArg: string[]): Record { const flags: Record = {}; for (let index = 0; index < argsArg.length; index++) { const arg = argsArg[index]; if (!arg.startsWith('--')) { continue; } const [rawName, inlineValue] = arg.slice(2).split('=', 2); flags[rawName] = inlineValue ?? argsArg[++index]; } return flags; } function requiredFlag(flagsArg: Record, nameArg: string): string { const value = flagsArg[nameArg]; if (!value) { throw new Error(`Missing required --${nameArg} flag.`); } return value; } function mask(valueArg: string): string { return valueArg.length <= 8 ? '********' : `${valueArg.slice(0, 4)}...${valueArg.slice(-4)}`; }