From 65294591a53d3c3b83af393a3d1d6148e0ce651f Mon Sep 17 00:00:00 2001 From: YG Park Date: Mon, 10 Aug 2026 17:08:21 +0900 Subject: [PATCH] feat: expose cvlt version and mixed-owner probe --- .github/workflows/release.yml | 9 ++++++++ README.md | 9 ++++++++ cli/src/index.ts | 2 ++ cli/src/version.test.ts | 18 ++++++++++++++++ package.json | 3 ++- scripts/e2e-vlt-mixed-owners.sh | 37 +++++++++++++++++++++++++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 cli/src/version.test.ts create mode 100644 scripts/e2e-vlt-mixed-owners.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a026d9..21b058a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -90,6 +90,15 @@ jobs: - name: Build run: bun build --compile --target=${{ matrix.bun_target }} cli/src/index.ts --outfile cvlt + - name: Test version + shell: bash + run: | + binary=./cvlt + if [[ "${{ matrix.os }}" == "windows-latest" ]]; then + binary=./cvlt.exe + fi + test "$("$binary" --version)" = "${{ needs.version.outputs.version }}" + - name: Re-sign darwin binary if: startsWith(matrix.bun_target, 'bun-darwin') run: | diff --git a/README.md b/README.md index b145347..4074a13 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,15 @@ cvlt item delete DB_PASSWORD --vault github.com/acme/api **Scope: personal by default, except `vlt://`.** General commands and `op://` references target your personal account unless you select an org with `--org ` (or `CRCL_ORG`). A `vlt://github.com//...` read automatically targets `` when it is an org accessible to the current Circles credential; otherwise it keeps the personal fallback. An explicit org must match the reference owner, and `run`/`inject` resolve mixed owners independently. CI via GitHub OIDC already has its org fixed by `OP_CONNECT_HOST`. +To exercise two accessible owners against a live development Vault without printing either value, provide distinct fixture references and run: + +```bash +CVLT_E2E_PROFILE=dev-profile \ +CVLT_E2E_REF_A=vlt://github.com/example-org-a/example-repo/MIXED_OWNER_PROBE \ +CVLT_E2E_REF_B=vlt://github.com/example-org-b/example-repo/MIXED_OWNER_PROBE \ +npm run test:e2e:mixed-owners +``` + ### Registering repos for CI access (operator-only) `cvlt vault create ` creates the op:// vault that stores the secrets; for a **repo** coordinate it also records the OIDC grant that lets that repo's CI read it (**creating it is the consent**). Grants are org-scoped, so pass `--org `. Once per repo: diff --git a/cli/src/index.ts b/cli/src/index.ts index 822db67..272988a 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,6 +1,7 @@ #!/usr/bin/env bun import { defineCommand, runMain } from "citty" +import pkg from "../package.json" import { api, apiOptional, @@ -1282,6 +1283,7 @@ export const main = defineCommand({ meta: { name: "cvlt", description: "1Password-style secrets CLI for Circles Vault", + version: (pkg as { version?: string }).version || "dev", }, args: { profile: { type: "string" as const, description: "Circles profile to use (default: shared current profile)" }, diff --git a/cli/src/version.test.ts b/cli/src/version.test.ts new file mode 100644 index 0000000..5dbe5e2 --- /dev/null +++ b/cli/src/version.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "bun:test" +import { spawnSync } from "node:child_process" +import { join } from "node:path" +import pkg from "../package.json" + +describe("CLI version", () => { + it("prints the package version without showing help", () => { + const result = spawnSync("bun", ["run", "cli/src/index.ts", "--version"], { + cwd: join(import.meta.dir, "../.."), + env: { ...process.env, CI: "1" }, + encoding: "utf8", + }) + + expect(result.status).toBe(0) + expect(result.stderr).toBe("") + expect(result.stdout.trim()).toBe((pkg as { version?: string }).version || "dev") + }) +}) diff --git a/package.json b/package.json index 2c10b1e..6142e31 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "build": "bun build --compile cli/src/index.ts --outfile cvlt", "build:sdk": "npm run build --workspace=@circlesac/vault", "dev": "bun run cli/src/index.ts", - "test": "bun test node/src/ cli/src/" + "test": "bun test node/src/ cli/src/", + "test:e2e:mixed-owners": "bash scripts/e2e-vlt-mixed-owners.sh" }, "devDependencies": { "@types/bun": "latest", diff --git a/scripts/e2e-vlt-mixed-owners.sh b/scripts/e2e-vlt-mixed-owners.sh new file mode 100644 index 0000000..6b658f4 --- /dev/null +++ b/scripts/e2e-vlt-mixed-owners.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${CVLT_E2E_REF_A:?Set CVLT_E2E_REF_A to the first vlt:// reference}" +: "${CVLT_E2E_REF_B:?Set CVLT_E2E_REF_B to the second vlt:// reference}" + +cvlt_bin=${CVLT_BIN:-cvlt} +profile_args=() +if [[ -n ${CVLT_E2E_PROFILE:-} ]]; then + profile_args=(--profile "$CVLT_E2E_PROFILE") +fi + +work_dir=$(mktemp -d) +trap 'rm -rf "$work_dir"' EXIT + +"$cvlt_bin" "${profile_args[@]}" read "$CVLT_E2E_REF_A" --out-file "$work_dir/a" +"$cvlt_bin" "${profile_args[@]}" read "$CVLT_E2E_REF_B" --out-file "$work_dir/b" +if cmp -s "$work_dir/a" "$work_dir/b"; then + echo "Mixed-owner fixtures must contain distinct values" >&2 + exit 1 +fi + +printf '{{%s}}\n---CVLT-MIXED-OWNER---\n{{%s}}' "$CVLT_E2E_REF_A" "$CVLT_E2E_REF_B" > "$work_dir/template" +{ + cat "$work_dir/a" + printf '\n---CVLT-MIXED-OWNER---\n' + cat "$work_dir/b" +} > "$work_dir/expected" +"$cvlt_bin" "${profile_args[@]}" inject --in-file "$work_dir/template" --out-file "$work_dir/injected" +cmp "$work_dir/expected" "$work_dir/injected" + +printf 'CVLT_E2E_A=%s\nCVLT_E2E_B=%s\n' "$CVLT_E2E_REF_A" "$CVLT_E2E_REF_B" > "$work_dir/env" +CVLT_E2E_A_FILE="$work_dir/a" CVLT_E2E_B_FILE="$work_dir/b" \ + "$cvlt_bin" "${profile_args[@]}" run --env-file "$work_dir/env" -- sh -c \ + 'printf %s "$CVLT_E2E_A" | cmp - "$CVLT_E2E_A_FILE" && printf %s "$CVLT_E2E_B" | cmp - "$CVLT_E2E_B_FILE"' + +echo "Mixed-owner read, inject, and run passed"