Skip to content

Commit 32d4468

Browse files
committed
fix: make --metadata option repeatable instead of variadic
Also reject empty and duplicate metadata keys in parseMetadata.
1 parent c991e1c commit 32d4468

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

src/utils/commands.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { deleteDevbox } from "../commands/devbox/delete.js";
66
import { execCommand } from "../commands/devbox/exec.js";
77
import { uploadFile } from "../commands/devbox/upload.js";
88

9+
/** Helper for repeatable options (e.g., --metadata a=b --metadata c=d) */
10+
const collect = (val: string, prev: string[]) => [...prev, val];
11+
912
/**
1013
* Creates and configures the Commander program with all commands.
1114
* This is shared between the CLI and documentation generation.
@@ -487,7 +490,12 @@ export function createProgram(): Command {
487490
.option("--available-ports <ports...>", "Available ports")
488491
.option("--root", "Run as root")
489492
.option("--user <user:uid>", "Run as this user (format: username:uid)")
490-
.option("--metadata <tags...>", "Metadata tags (format: key=value)")
493+
.option(
494+
"--metadata <tag>",
495+
"Metadata tag (format: key=value), repeatable",
496+
collect,
497+
[],
498+
)
491499
.option(
492500
"-o, --output [format]",
493501
"Output format: text|json|yaml (default: json)",
@@ -578,7 +586,12 @@ export function createProgram(): Command {
578586
.option("--available-ports <ports...>", "Available ports")
579587
.option("--root", "Run as root")
580588
.option("--user <user:uid>", "Run as this user (format: username:uid)")
581-
.option("--metadata <tags...>", "Metadata tags (format: key=value)")
589+
.option(
590+
"--metadata <tag>",
591+
"Metadata tag (format: key=value), repeatable",
592+
collect,
593+
[],
594+
)
582595
.option(
583596
"--ttl <seconds>",
584597
"TTL in seconds for the build context object (default: 3600)",
@@ -664,7 +677,12 @@ export function createProgram(): Command {
664677
"Content type: unspecified|text|binary|gzip|tar|tgz",
665678
)
666679
.option("--public", "Make object publicly accessible")
667-
.option("--metadata <tags...>", "Metadata tags (format: key=value)")
680+
.option(
681+
"--metadata <tag>",
682+
"Metadata tag (format: key=value), repeatable",
683+
collect,
684+
[],
685+
)
668686
.option(
669687
"-o, --output [format]",
670688
"Output format: text|json|yaml (default: text)",
@@ -1143,7 +1161,12 @@ export function createProgram(): Command {
11431161
.option("--n-attempts <n>", "Number of attempts per scenario")
11441162
.option("--n-concurrent-trials <n>", "Number of concurrent trials")
11451163
.option("--timeout-multiplier <n>", "Timeout multiplier")
1146-
.option("--metadata <tags...>", "Metadata tags (format: key=value)")
1164+
.option(
1165+
"--metadata <tag>",
1166+
"Metadata tag (format: key=value), repeatable",
1167+
collect,
1168+
[],
1169+
)
11471170
.option(
11481171
"-o, --output [format]",
11491172
"Output format: text|json|yaml (default: text)",

src/utils/metadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ export function parseMetadata(metadata: string[]): Record<string, string> {
66
throw new Error(`Invalid metadata format: ${item}. Expected key=value`);
77
}
88
const key = item.substring(0, eqIndex);
9+
if (!key) {
10+
throw new Error(`Invalid metadata: key cannot be empty in "${item}"`);
11+
}
12+
if (key in result) {
13+
throw new Error(`Duplicate metadata key: "${key}"`);
14+
}
915
const value = item.substring(eqIndex + 1);
1016
result[key] = value;
1117
}

0 commit comments

Comments
 (0)