From 7b0e256c1321b31e36bc9a1edf2caf3bd84beaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Thu, 7 May 2026 14:07:30 +0000 Subject: [PATCH] feat(cli): add WORKFLOW_STORAGE_ROOT env var support Add user-facing WORKFLOW_STORAGE_ROOT environment variable to override the default storage directory (~/.uncaged/workflow). The existing UNCAGED_WORKFLOW_STORAGE_ROOT (internal/test) takes priority. - Update storage-env.ts with priority chain: internal > user > default - Add env var documentation to CLI help text - Add 5 tests covering all priority/fallback scenarios Fixes #63 --- .../__tests__/storage-env.test.ts | 54 +++++++++++++++++++ packages/cli-workflow/src/cli-dispatch.ts | 4 ++ packages/cli-workflow/src/storage-env.ts | 19 +++++-- 3 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 packages/cli-workflow/__tests__/storage-env.test.ts diff --git a/packages/cli-workflow/__tests__/storage-env.test.ts b/packages/cli-workflow/__tests__/storage-env.test.ts new file mode 100644 index 0000000..518ce8c --- /dev/null +++ b/packages/cli-workflow/__tests__/storage-env.test.ts @@ -0,0 +1,54 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { getDefaultWorkflowStorageRoot } from "@uncaged/workflow"; +import { resolveWorkflowStorageRoot } from "../src/storage-env.js"; + +describe("resolveWorkflowStorageRoot", () => { + let savedInternal: string | undefined; + let savedUser: string | undefined; + + beforeEach(() => { + savedInternal = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT; + savedUser = process.env.WORKFLOW_STORAGE_ROOT; + delete process.env.UNCAGED_WORKFLOW_STORAGE_ROOT; + delete process.env.WORKFLOW_STORAGE_ROOT; + }); + + afterEach(() => { + if (savedInternal === undefined) { + delete process.env.UNCAGED_WORKFLOW_STORAGE_ROOT; + } else { + process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = savedInternal; + } + if (savedUser === undefined) { + delete process.env.WORKFLOW_STORAGE_ROOT; + } else { + process.env.WORKFLOW_STORAGE_ROOT = savedUser; + } + }); + + test("returns default when no env vars are set", () => { + expect(resolveWorkflowStorageRoot()).toBe(getDefaultWorkflowStorageRoot()); + }); + + test("WORKFLOW_STORAGE_ROOT overrides default", () => { + process.env.WORKFLOW_STORAGE_ROOT = "/tmp/custom-storage"; + expect(resolveWorkflowStorageRoot()).toBe("/tmp/custom-storage"); + }); + + test("UNCAGED_WORKFLOW_STORAGE_ROOT takes priority over WORKFLOW_STORAGE_ROOT", () => { + process.env.WORKFLOW_STORAGE_ROOT = "/tmp/user-path"; + process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = "/tmp/internal-path"; + expect(resolveWorkflowStorageRoot()).toBe("/tmp/internal-path"); + }); + + test("ignores empty WORKFLOW_STORAGE_ROOT", () => { + process.env.WORKFLOW_STORAGE_ROOT = ""; + expect(resolveWorkflowStorageRoot()).toBe(getDefaultWorkflowStorageRoot()); + }); + + test("ignores empty UNCAGED_WORKFLOW_STORAGE_ROOT and falls through to WORKFLOW_STORAGE_ROOT", () => { + process.env.UNCAGED_WORKFLOW_STORAGE_ROOT = ""; + process.env.WORKFLOW_STORAGE_ROOT = "/tmp/user-fallback"; + expect(resolveWorkflowStorageRoot()).toBe("/tmp/user-fallback"); + }); +}); diff --git a/packages/cli-workflow/src/cli-dispatch.ts b/packages/cli-workflow/src/cli-dispatch.ts index 44ae43c..ea186e2 100644 --- a/packages/cli-workflow/src/cli-dispatch.ts +++ b/packages/cli-workflow/src/cli-dispatch.ts @@ -54,6 +54,10 @@ export function formatCliUsage(): string { "", " uncaged-workflow run [...] (shortcut for thread run)", " uncaged-workflow live [...] (shortcut for thread live)", + "", + "Environment variables:", + " WORKFLOW_STORAGE_ROOT Override storage directory (default: ~/.uncaged/workflow)", + " UNCAGED_WORKFLOW_STORAGE_ROOT Internal override (takes priority over WORKFLOW_STORAGE_ROOT)", ].join("\n"); } diff --git a/packages/cli-workflow/src/storage-env.ts b/packages/cli-workflow/src/storage-env.ts index 0a5721c..08d39ea 100644 --- a/packages/cli-workflow/src/storage-env.ts +++ b/packages/cli-workflow/src/storage-env.ts @@ -1,10 +1,21 @@ import { getDefaultWorkflowStorageRoot } from "@uncaged/workflow"; -/** Resolve storage root, honoring `UNCAGED_WORKFLOW_STORAGE_ROOT` for tests/tools. */ +/** + * Resolve storage root with env var override support. + * + * Priority (highest first): + * 1. `UNCAGED_WORKFLOW_STORAGE_ROOT` — internal/test override + * 2. `WORKFLOW_STORAGE_ROOT` — user-facing override + * 3. Default (`~/.uncaged/workflow`) + */ export function resolveWorkflowStorageRoot(): string { - const override = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT; - if (override !== undefined && override !== "") { - return override; + const internal = process.env.UNCAGED_WORKFLOW_STORAGE_ROOT; + if (internal !== undefined && internal !== "") { + return internal; + } + const userOverride = process.env.WORKFLOW_STORAGE_ROOT; + if (userOverride !== undefined && userOverride !== "") { + return userOverride; } return getDefaultWorkflowStorageRoot(); }