diff --git a/src/cli/nodes-camera.ts b/src/cli/nodes-camera.ts index ef06623a77..a31641fa91 100644 --- a/src/cli/nodes-camera.ts +++ b/src/cli/nodes-camera.ts @@ -1,8 +1,13 @@ -import { randomUUID } from "node:crypto"; import * as fs from "node:fs/promises"; -import * as os from "node:os"; import * as path from "node:path"; import { resolveCliName } from "./cli-name.js"; +import { + asBoolean, + asNumber, + asRecord, + asString, + resolveTempPathParts, +} from "./nodes-media-utils.js"; const MAX_CAMERA_URL_DOWNLOAD_BYTES = 250 * 1024 * 1024; @@ -24,22 +29,6 @@ export type CameraClipPayload = { hasAudio: boolean; }; -function asRecord(value: unknown): Record { - return typeof value === "object" && value !== null ? (value as Record) : {}; -} - -function asString(value: unknown): string | undefined { - return typeof value === "string" ? value : undefined; -} - -function asNumber(value: unknown): number | undefined { - return typeof value === "number" && Number.isFinite(value) ? value : undefined; -} - -function asBoolean(value: unknown): boolean | undefined { - return typeof value === "boolean" ? value : undefined; -} - export function parseCameraSnapPayload(value: unknown): CameraSnapPayload { const obj = asRecord(value); const format = asString(obj.format); @@ -73,10 +62,12 @@ export function cameraTempPath(opts: { tmpDir?: string; id?: string; }) { - const tmpDir = opts.tmpDir ?? os.tmpdir(); - const id = opts.id ?? randomUUID(); + const { tmpDir, id, ext } = resolveTempPathParts({ + tmpDir: opts.tmpDir, + id: opts.id, + ext: opts.ext, + }); const facingPart = opts.facing ? `-${opts.facing}` : ""; - const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`; const cliName = resolveCliName(); return path.join(tmpDir, `${cliName}-camera-${opts.kind}${facingPart}-${id}${ext}`); } diff --git a/src/cli/nodes-canvas.ts b/src/cli/nodes-canvas.ts index eb7a90236c..78c7a03029 100644 --- a/src/cli/nodes-canvas.ts +++ b/src/cli/nodes-canvas.ts @@ -1,21 +1,12 @@ -import { randomUUID } from "node:crypto"; -import * as os from "node:os"; import * as path from "node:path"; import { resolveCliName } from "./cli-name.js"; +import { asRecord, asString, resolveTempPathParts } from "./nodes-media-utils.js"; export type CanvasSnapshotPayload = { format: string; base64: string; }; -function asRecord(value: unknown): Record { - return typeof value === "object" && value !== null ? (value as Record) : {}; -} - -function asString(value: unknown): string | undefined { - return typeof value === "string" ? value : undefined; -} - export function parseCanvasSnapshotPayload(value: unknown): CanvasSnapshotPayload { const obj = asRecord(value); const format = asString(obj.format); @@ -27,9 +18,7 @@ export function parseCanvasSnapshotPayload(value: unknown): CanvasSnapshotPayloa } export function canvasSnapshotTempPath(opts: { ext: string; tmpDir?: string; id?: string }) { - const tmpDir = opts.tmpDir ?? os.tmpdir(); - const id = opts.id ?? randomUUID(); - const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`; + const { tmpDir, id, ext } = resolveTempPathParts(opts); const cliName = resolveCliName(); return path.join(tmpDir, `${cliName}-canvas-snapshot-${id}${ext}`); } diff --git a/src/cli/nodes-media-utils.test.ts b/src/cli/nodes-media-utils.test.ts new file mode 100644 index 0000000000..1204056f6b --- /dev/null +++ b/src/cli/nodes-media-utils.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vitest"; +import { + asBoolean, + asNumber, + asRecord, + asString, + resolveTempPathParts, +} from "./nodes-media-utils.js"; + +describe("cli/nodes-media-utils", () => { + it("parses primitive helper values", () => { + expect(asRecord({ a: 1 })).toEqual({ a: 1 }); + expect(asRecord("x")).toEqual({}); + expect(asString("x")).toBe("x"); + expect(asString(1)).toBeUndefined(); + expect(asNumber(1)).toBe(1); + expect(asNumber(Number.NaN)).toBeUndefined(); + expect(asBoolean(true)).toBe(true); + expect(asBoolean(1)).toBeUndefined(); + }); + + it("normalizes temp path parts", () => { + expect(resolveTempPathParts({ ext: "png", tmpDir: "/tmp", id: "id1" })).toEqual({ + tmpDir: "/tmp", + id: "id1", + ext: ".png", + }); + expect(resolveTempPathParts({ ext: ".jpg", tmpDir: "/tmp", id: "id2" }).ext).toBe(".jpg"); + }); +}); diff --git a/src/cli/nodes-media-utils.ts b/src/cli/nodes-media-utils.ts new file mode 100644 index 0000000000..eb8f853c6f --- /dev/null +++ b/src/cli/nodes-media-utils.ts @@ -0,0 +1,30 @@ +import { randomUUID } from "node:crypto"; +import * as os from "node:os"; + +export function asRecord(value: unknown): Record { + return typeof value === "object" && value !== null ? (value as Record) : {}; +} + +export function asString(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +export function asNumber(value: unknown): number | undefined { + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + +export function asBoolean(value: unknown): boolean | undefined { + return typeof value === "boolean" ? value : undefined; +} + +export function resolveTempPathParts(opts: { ext: string; tmpDir?: string; id?: string }): { + ext: string; + tmpDir: string; + id: string; +} { + return { + tmpDir: opts.tmpDir ?? os.tmpdir(), + id: opts.id ?? randomUUID(), + ext: opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`, + }; +} diff --git a/src/cli/nodes-screen.ts b/src/cli/nodes-screen.ts index 5d167e2f1e..d7712045d0 100644 --- a/src/cli/nodes-screen.ts +++ b/src/cli/nodes-screen.ts @@ -1,7 +1,6 @@ -import { randomUUID } from "node:crypto"; -import * as os from "node:os"; import * as path from "node:path"; import { writeBase64ToFile } from "./nodes-camera.js"; +import { asRecord, asString, resolveTempPathParts } from "./nodes-media-utils.js"; export type ScreenRecordPayload = { format: string; @@ -12,14 +11,6 @@ export type ScreenRecordPayload = { hasAudio?: boolean; }; -function asRecord(value: unknown): Record { - return typeof value === "object" && value !== null ? (value as Record) : {}; -} - -function asString(value: unknown): string | undefined { - return typeof value === "string" ? value : undefined; -} - export function parseScreenRecordPayload(value: unknown): ScreenRecordPayload { const obj = asRecord(value); const format = asString(obj.format); @@ -38,9 +29,7 @@ export function parseScreenRecordPayload(value: unknown): ScreenRecordPayload { } export function screenRecordTempPath(opts: { ext: string; tmpDir?: string; id?: string }) { - const tmpDir = opts.tmpDir ?? os.tmpdir(); - const id = opts.id ?? randomUUID(); - const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`; + const { tmpDir, id, ext } = resolveTempPathParts(opts); return path.join(tmpDir, `openclaw-screen-record-${id}${ext}`); }