From 1331993508aebbcc9c0d149c026d4d7ba1766a90 Mon Sep 17 00:00:00 2001 From: Runloop Agent Date: Fri, 13 Mar 2026 21:44:45 +0000 Subject: [PATCH] refactor: extract duplicate parseEnvVars/parseSecrets and reuse logFormatter - Add src/utils/parse.ts with shared parseEnvVars and parseSecrets functions - Remove duplicate definitions from devbox/create.ts and benchmark-job/run.ts - Replace duplicate formatLogLevel/formatTimestamp in blueprint/logs.ts with imports from logFormatter.ts (getLogLevelInfo and formatTimestamp) Co-Authored-By: Claude Sonnet 4.6 --- src/commands/benchmark-job/run.ts | 35 +---------------- src/commands/blueprint/logs.ts | 64 +++++++------------------------ src/commands/devbox/create.ts | 35 +---------------- src/utils/parse.ts | 37 ++++++++++++++++++ 4 files changed, 53 insertions(+), 118 deletions(-) create mode 100644 src/utils/parse.ts diff --git a/src/commands/benchmark-job/run.ts b/src/commands/benchmark-job/run.ts index a0e17f34..267ce081 100644 --- a/src/commands/benchmark-job/run.ts +++ b/src/commands/benchmark-job/run.ts @@ -10,6 +10,7 @@ import { } from "../../services/benchmarkService.js"; import { getClient } from "../../utils/client.js"; import { output, outputError } from "../../utils/output.js"; +import { parseEnvVars, parseSecrets } from "../../utils/parse.js"; // Secret name prefix for benchmark job secrets const SECRET_PREFIX = "BMJ_"; @@ -100,40 +101,6 @@ function parseAgentStrings(agentStrings: string[] | undefined): ParsedAgent[] { return agents; } -// Parse environment variables from KEY=value format -function parseEnvVars(envVars: string[]): Record { - const result: Record = {}; - for (const envVar of envVars) { - const eqIndex = envVar.indexOf("="); - if (eqIndex === -1) { - throw new Error( - `Invalid environment variable format: ${envVar}. Expected KEY=value`, - ); - } - const key = envVar.substring(0, eqIndex); - const value = envVar.substring(eqIndex + 1); - result[key] = value; - } - return result; -} - -// Parse secrets from ENV_VAR=SECRET_NAME format -function parseSecrets(secrets: string[]): Record { - const result: Record = {}; - for (const secret of secrets) { - const eqIndex = secret.indexOf("="); - if (eqIndex === -1) { - throw new Error( - `Invalid secret format: ${secret}. Expected ENV_VAR=SECRET_NAME`, - ); - } - const envVarName = secret.substring(0, eqIndex); - const secretName = secret.substring(eqIndex + 1); - result[envVarName] = secretName; - } - return result; -} - // Validate agent is supported function validateAgent(agent: string): asserts agent is SupportedAgent { if (!(agent in SUPPORTED_AGENTS)) { diff --git a/src/commands/blueprint/logs.ts b/src/commands/blueprint/logs.ts index 85726ab0..dbe8b7e0 100644 --- a/src/commands/blueprint/logs.ts +++ b/src/commands/blueprint/logs.ts @@ -9,6 +9,10 @@ import type { } from "@runloop/api-client/resources/blueprints"; import { getClient } from "../../utils/client.js"; import { output, outputError } from "../../utils/output.js"; +import { + formatTimestamp, + getLogLevelInfo, +} from "../../utils/logFormatter.js"; interface BlueprintLogsOptions { id: string; @@ -16,56 +20,16 @@ interface BlueprintLogsOptions { } function formatLogLevel(level: string): string { - const normalized = level.toUpperCase(); - switch (normalized) { - case "ERROR": - case "ERR": - return chalk.red.bold("ERROR"); - case "WARN": - case "WARNING": - return chalk.yellow.bold("WARN "); - case "INFO": - return chalk.blue("INFO "); - case "DEBUG": - return chalk.gray("DEBUG"); + const { name, color } = getLogLevelInfo(level); + switch (color) { + case "red": + return chalk.red.bold(name); + case "yellow": + return chalk.yellow.bold(name); + case "blue": + return chalk.blue(name); default: - return chalk.gray(normalized.padEnd(5)); - } -} - -function formatTimestamp(timestampMs: number): string { - const date = new Date(timestampMs); - const now = new Date(); - - const isToday = date.toDateString() === now.toDateString(); - const isThisYear = date.getFullYear() === now.getFullYear(); - - const time = date.toLocaleTimeString("en-US", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - const ms = date.getMilliseconds().toString().padStart(3, "0"); - - if (isToday) { - // Today: show time with milliseconds for fine granularity - return chalk.dim(`${time}.${ms}`); - } else if (isThisYear) { - // This year: show "Jan 5 15:44:03" - const monthDay = date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }); - return chalk.dim(`${monthDay} ${time}`); - } else { - // Older: show "Jan 5, 2024 15:44:03" - const fullDate = date.toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - }); - return chalk.dim(`${fullDate} ${time}`); + return chalk.gray(name); } } @@ -107,7 +71,7 @@ function formatLogEntry(log: BlueprintBuildLog): string { const parts: string[] = []; // Timestamp - parts.push(formatTimestamp(log.timestamp_ms)); + parts.push(chalk.dim(formatTimestamp(log.timestamp_ms))); // Level parts.push(formatLogLevel(log.level)); diff --git a/src/commands/devbox/create.ts b/src/commands/devbox/create.ts index 7377d573..e9044e7b 100644 --- a/src/commands/devbox/create.ts +++ b/src/commands/devbox/create.ts @@ -4,6 +4,7 @@ import { getClient } from "../../utils/client.js"; import { output, outputError } from "../../utils/output.js"; +import { parseEnvVars, parseSecrets } from "../../utils/parse.js"; interface CreateOptions { name?: string; @@ -29,40 +30,6 @@ interface CreateOptions { output?: string; } -// Parse environment variables from KEY=value format -function parseEnvVars(envVars: string[]): Record { - const result: Record = {}; - for (const envVar of envVars) { - const eqIndex = envVar.indexOf("="); - if (eqIndex === -1) { - throw new Error( - `Invalid environment variable format: ${envVar}. Expected KEY=value`, - ); - } - const key = envVar.substring(0, eqIndex); - const value = envVar.substring(eqIndex + 1); - result[key] = value; - } - return result; -} - -// Parse secrets from ENV_VAR=SECRET_NAME format -function parseSecrets(secrets: string[]): Record { - const result: Record = {}; - for (const secret of secrets) { - const eqIndex = secret.indexOf("="); - if (eqIndex === -1) { - throw new Error( - `Invalid secret format: ${secret}. Expected ENV_VAR=SECRET_NAME`, - ); - } - const envVarName = secret.substring(0, eqIndex); - const secretName = secret.substring(eqIndex + 1); - result[envVarName] = secretName; - } - return result; -} - // Parse code mounts from JSON format function parseCodeMounts(codeMounts: string[]): unknown[] { return codeMounts.map((mount) => { diff --git a/src/utils/parse.ts b/src/utils/parse.ts new file mode 100644 index 00000000..ed67d222 --- /dev/null +++ b/src/utils/parse.ts @@ -0,0 +1,37 @@ +/** + * Parse KEY=value format strings into a Record + */ +export function parseEnvVars(envVars: string[]): Record { + const result: Record = {}; + for (const envVar of envVars) { + const eqIndex = envVar.indexOf("="); + if (eqIndex === -1) { + throw new Error( + `Invalid environment variable format: ${envVar}. Expected KEY=value`, + ); + } + const key = envVar.substring(0, eqIndex); + const value = envVar.substring(eqIndex + 1); + result[key] = value; + } + return result; +} + +/** + * Parse ENV_VAR=SECRET_NAME format strings into a Record + */ +export function parseSecrets(secrets: string[]): Record { + const result: Record = {}; + for (const secret of secrets) { + const eqIndex = secret.indexOf("="); + if (eqIndex === -1) { + throw new Error( + `Invalid secret format: ${secret}. Expected ENV_VAR=SECRET_NAME`, + ); + } + const envVarName = secret.substring(0, eqIndex); + const secretName = secret.substring(eqIndex + 1); + result[envVarName] = secretName; + } + return result; +}