Skip to content

Commit 2ba73ef

Browse files
stack72bixu
andauthored
fix: accept secret value as separate argument in vault put (#881)
## Summary - Changed `vault put` argument signature from `<vault_name> <key_value>` to `<vault_name> <key> [value]` so users can pass the secret as a separate positional argument - When 3 args given, treats arg2 as key and arg3 as value directly (no `KEY=VALUE` parsing needed) - When 2 args given, maintains full backward compatibility: `KEY=VALUE` format, stdin piping, and interactive prompt all still work - Updated error message to mention all three input methods including the new 3-arg form ## Test Plan - [x] All 3564 existing tests pass - [x] Updated test for new error message format (now mentions `<key> <value>` form) - [x] `deno check`, `deno lint`, `deno fmt` all clean - [ ] Manual test: `swamp vault put <vault> KEY value` works - [ ] Manual test: `swamp vault put <vault> KEY=value` still works (backward compat) - [ ] Manual test: `echo "value" | swamp vault put <vault> KEY` still works Closes #880 Co-authored-by: Blake Irvin <bixu@users.noreply.github.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Blake Irvin <bixu@users.noreply.github.com>
1 parent 0105b5a commit 2ba73ef

2 files changed

Lines changed: 47 additions & 36 deletions

File tree

src/cli/commands/vault_put.ts

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export function resolveKeyValue(
8383

8484
return {
8585
error: `Invalid argument format: ${argument}\n\n` +
86-
`Provide the value inline or via stdin:\n` +
86+
`Provide the value as a separate argument, inline, or via stdin:\n` +
87+
` swamp vault put <vault> ${argument} <value>\n` +
8788
` swamp vault put <vault> ${argument}=<value>\n` +
8889
` echo "<value>" | swamp vault put <vault> ${argument}`,
8990
};
@@ -109,13 +110,14 @@ type AnyOptions = any;
109110
export const vaultPutCommand = new Command()
110111
.name("put")
111112
.description("Store a secret in a vault")
112-
.arguments("<vault_name:string> <key_value:string>")
113+
.arguments("<vault_name:string> <key:string> [value:string]")
113114
.option("--repo-dir <dir:string>", "Repository directory", { default: "." })
114115
.option("-f, --force", "Skip confirmation prompt when overwriting")
115116
.action(async function (
116117
options: AnyOptions,
117118
vaultName: string,
118-
keyValue: string,
119+
keyOrKeyValue: string,
120+
valueArg: string | undefined,
119121
) {
120122
const cliCtx = createContext(options as GlobalOptions, ["vault", "put"]);
121123
cliCtx.logger.debug`Storing secret in vault: ${vaultName}`;
@@ -125,44 +127,52 @@ export const vaultPutCommand = new Command()
125127
outputMode: cliCtx.outputMode,
126128
});
127129

128-
// Parse KEY=VALUE argument, or KEY with value from stdin/interactive prompt.
129-
const parsed = parseKeyValue(keyValue);
130+
// Resolve key and value from arguments.
131+
// Priority: explicit value arg > KEY=VALUE format > stdin > interactive prompt.
130132
let key: string;
131133
let value: string;
132134
let stdinContent: string | null = null;
133135

134-
if (parsed) {
135-
key = parsed.key;
136-
value = parsed.value;
137-
} else if (!isStdinTty()) {
138-
stdinContent = await readStdin();
139-
const resolved = resolveKeyValue(keyValue, stdinContent);
140-
if ("error" in resolved) {
141-
throw new UserError(resolved.error);
142-
}
143-
key = resolved.key;
144-
value = resolved.value;
145-
} else if (cliCtx.outputMode === "log") {
146-
key = keyValue;
147-
if (key.length === 0) {
148-
throw new UserError("Key cannot be empty");
149-
}
150-
try {
151-
value = await readSecretFromTty(`Enter value for ${key}: `);
152-
} catch (err) {
153-
if (err instanceof Error && err.message === "Cancelled.") {
154-
renderVaultPutCancelled(cliCtx.outputMode);
155-
return;
156-
}
157-
throw err;
158-
}
136+
if (valueArg !== undefined) {
137+
// 3-arg form: swamp vault put <vault> <key> <value>
138+
key = keyOrKeyValue;
139+
value = valueArg;
159140
} else {
160-
const resolved = resolveKeyValue(keyValue, null);
161-
if ("error" in resolved) {
162-
throw new UserError(resolved.error);
141+
// 2-arg form: try KEY=VALUE, then stdin, then interactive prompt.
142+
const parsed = parseKeyValue(keyOrKeyValue);
143+
if (parsed) {
144+
key = parsed.key;
145+
value = parsed.value;
146+
} else if (!isStdinTty()) {
147+
stdinContent = await readStdin();
148+
const resolved = resolveKeyValue(keyOrKeyValue, stdinContent);
149+
if ("error" in resolved) {
150+
throw new UserError(resolved.error);
151+
}
152+
key = resolved.key;
153+
value = resolved.value;
154+
} else if (cliCtx.outputMode === "log") {
155+
key = keyOrKeyValue;
156+
if (key.length === 0) {
157+
throw new UserError("Key cannot be empty");
158+
}
159+
try {
160+
value = await readSecretFromTty(`Enter value for ${key}: `);
161+
} catch (err) {
162+
if (err instanceof Error && err.message === "Cancelled.") {
163+
renderVaultPutCancelled(cliCtx.outputMode);
164+
return;
165+
}
166+
throw err;
167+
}
168+
} else {
169+
const resolved = resolveKeyValue(keyOrKeyValue, null);
170+
if ("error" in resolved) {
171+
throw new UserError(resolved.error);
172+
}
173+
key = resolved.key;
174+
value = resolved.value;
163175
}
164-
key = resolved.key;
165-
value = resolved.value;
166176
}
167177
cliCtx.logger.debug`Parsed key: ${key}`;
168178

src/cli/commands/vault_put_test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ Deno.test("resolveKeyValue - error when no = and no stdin", () => {
9797
assertEquals("error" in result, true);
9898
});
9999

100-
Deno.test("resolveKeyValue - error message includes both usage formats", () => {
100+
Deno.test("resolveKeyValue - error message includes all usage formats", () => {
101101
const result = resolveKeyValue("MY_KEY", null);
102102
assertEquals("error" in result, true);
103103
if ("error" in result) {
104+
assertEquals(result.error.includes("MY_KEY <value>"), true);
104105
assertEquals(result.error.includes("MY_KEY=<value>"), true);
105106
assertEquals(result.error.includes("echo"), true);
106107
}

0 commit comments

Comments
 (0)