diff --git a/bun.lock b/bun.lock index 9c40d0243..e9086915d 100644 --- a/bun.lock +++ b/bun.lock @@ -11,7 +11,7 @@ }, "packages/cli": { "name": "base44", - "version": "0.0.50", + "version": "0.0.56", "bin": { "base44": "./bin/run.js", }, @@ -29,6 +29,7 @@ "@types/json-schema": "^7.0.15", "@types/jsonwebtoken": "^9.0.10", "@types/lodash": "^4.17.24", + "@types/ms": "^2.1.0", "@types/multer": "^2.0.0", "@types/node": "^22.10.5", "@vercel/detect-agent": "^1.1.0", @@ -50,6 +51,7 @@ "jsonwebtoken": "^9.0.3", "ky": "^1.14.2", "lodash": "^4.17.23", + "ms": "^2.1.3", "multer": "^2.0.0", "nanoid": "^5.1.6", "open": "^11.0.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index b81f167b7..7447a84fb 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -52,10 +52,10 @@ "@types/json-schema": "^7.0.15", "@types/jsonwebtoken": "^9.0.10", "@types/lodash": "^4.17.24", + "@types/ms": "^2.1.0", "@types/multer": "^2.0.0", "@types/node": "^22.10.5", "@vercel/detect-agent": "^1.1.0", - "outdent": "^0.8.0", "chalk": "^5.6.2", "chokidar": "^5.0.0", "commander": "^12.1.0", @@ -70,15 +70,18 @@ "globby": "^16.1.0", "http-proxy-middleware": "^3.0.5", "json-schema-to-typescript": "^15.0.4", - "jsonwebtoken": "^9.0.3", "json5": "^2.2.3", + "jsonwebtoken": "^9.0.3", "ky": "^1.14.2", "lodash": "^4.17.23", + "ms": "^2.1.3", "multer": "^2.0.0", "nanoid": "^5.1.6", "open": "^11.0.0", + "outdent": "^0.8.0", "p-wait-for": "^6.0.0", "posthog-node": "5.21.2", + "qs": "^6.12.3", "socket.io": "^4.8.3", "strip-ansi": "^7.1.2", "tar": "^7.5.4", @@ -86,7 +89,6 @@ "typescript": "^5.7.2", "vitest": "^4.0.16", "yaml": "^2.8.2", - "qs": "^6.12.3", "zod": "^4.3.5" }, "engines": { diff --git a/packages/cli/src/cli/commands/project/logs.ts b/packages/cli/src/cli/commands/project/logs.ts index 522feea5f..d8938ed1a 100644 --- a/packages/cli/src/cli/commands/project/logs.ts +++ b/packages/cli/src/cli/commands/project/logs.ts @@ -1,5 +1,6 @@ import type { Command } from "commander"; import { Option } from "commander"; +import ms, { type StringValue } from "ms"; import type { CLIContext, RunCommandResult } from "@/cli/types.js"; import { Base44Command } from "@/cli/utils/index.js"; import { ApiError, InvalidInputError } from "@/core/errors.js"; @@ -70,6 +71,10 @@ function parseFunctionNames(option: string | undefined): string[] { } function normalizeDatetime(value: string): string { + const duration = ms(value as StringValue); + if (duration !== undefined) { + return new Date(Date.now() - duration).toISOString(); + } if (/Z$|[+-]\d{2}:\d{2}$/.test(value)) return value; return `${value}Z`; } @@ -143,8 +148,9 @@ async function fetchLogsForFunctions( throw error; } - const entries = logs.map((entry) => normalizeLogEntry(entry, functionName)); - allEntries.push(...entries); + allEntries.push( + ...logs.map((entry) => normalizeLogEntry(entry, functionName)), + ); } // When fetching multiple functions, merge-sort the combined results @@ -216,7 +222,11 @@ async function logsAction( ? `${JSON.stringify(entries, null, 2)}\n` : formatLogs(entries); - return { outroMessage: "Fetched logs", stdout: logsOutput }; + const shouldOutputOutroMessage = !options.json; + return { + outroMessage: shouldOutputOutroMessage ? "Fetched logs" : undefined, + stdout: logsOutput, + }; } export function getLogsCommand(): Command { @@ -228,12 +238,12 @@ export function getLogsCommand(): Command { ) .option( "--since ", - "Show logs from this time (ISO format)", + "Show logs from this time. ISO datetime or relative shorthand (e.g. 1h, 30m, 2d)", normalizeDatetime, ) .option( "--until ", - "Show logs until this time (ISO format)", + "Show logs until this time. ISO datetime or relative shorthand (e.g. 1h, 30m, 2d)", normalizeDatetime, ) .addOption( @@ -245,5 +255,6 @@ export function getLogsCommand(): Command { .addOption( new Option("--order ", "Sort order").choices(["asc", "desc"]), ) + .option("--json", "Output as JSON (clean stdout, safe to pipe to jq)") .action(logsAction); } diff --git a/packages/cli/tests/cli/logs.spec.ts b/packages/cli/tests/cli/logs.spec.ts index ea0a87f09..ddcc71687 100644 --- a/packages/cli/tests/cli/logs.spec.ts +++ b/packages/cli/tests/cli/logs.spec.ts @@ -1,4 +1,4 @@ -import { describe, it } from "vitest"; +import { describe, expect, it } from "vitest"; import { fixture, setupCLITests } from "./testkit/index.js"; describe("logs command", () => { @@ -287,4 +287,38 @@ describe("logs command", () => { t.expectResult(result).toSucceed(); }); + + it("outputs valid JSON with --json", async () => { + await t.givenLoggedInWithProject(fixture("basic")); + t.api.mockFunctionLogs("my-function", [ + { time: "2024-01-15T10:30:00.000Z", level: "info", message: "Hello" }, + ]); + + const result = await t.run("logs", "--function", "my-function", "--json"); + + t.expectResult(result).toSucceed(); + const stdout = result.stdout ?? ""; + const jsonStart = stdout.indexOf("["); + const parsed = JSON.parse(stdout.slice(jsonStart)); + expect(parsed).toHaveLength(1); + expect(parsed[0].message).toContain("Hello"); + }); + + it("accepts relative time shortcuts for --since", async () => { + await t.givenLoggedInWithProject(fixture("basic")); + t.api.mockFunctionLogs("my-function", [ + { time: "2024-01-15T10:30:00.000Z", level: "info", message: "Recent" }, + ]); + + const result = await t.run( + "logs", + "--function", + "my-function", + "--since", + "1h", + ); + + t.expectResult(result).toSucceed(); + t.expectResult(result).toContain("Recent"); + }); });