Skip to content

Commit bac6e8f

Browse files
MajorTalclaude
andcommitted
fix(cli): validate deploy list limit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c7bafa8 commit bac6e8f

2 files changed

Lines changed: 88 additions & 2 deletions

File tree

cli-e2e.test.mjs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,92 @@ describe("CLI e2e happy path", () => {
18621862
assert.equal(body.urls.deployment_id, "dpl_test456");
18631863
});
18641864

1865+
async function runDeployListLimit(limitValue) {
1866+
const { run } = await import("./cli/lib/deploy.mjs");
1867+
const { saveAllowance } = await import("./cli/lib/config.mjs");
1868+
await seedTestProject();
1869+
saveAllowance({
1870+
address: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
1871+
privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
1872+
created: "2026-03-15T00:00:00.000Z",
1873+
funded: true,
1874+
rail: "x402",
1875+
});
1876+
1877+
const prevFetch = globalThis.fetch;
1878+
let deployListCalled = false;
1879+
let seenLimit = null;
1880+
globalThis.fetch = (input, init) => {
1881+
const url = typeof input === "string" ? input : (input instanceof Request ? input.url : String(input));
1882+
if (url.includes("/deploy/v2/operations") && !url.includes("/deploy/v2/operations/")) {
1883+
deployListCalled = true;
1884+
seenLimit = new URL(url).searchParams.get("limit");
1885+
return Promise.resolve(json({
1886+
operations: [{
1887+
operation_id: "op_list_test",
1888+
project_id: TEST_PROJECT.project_id,
1889+
status: "ready",
1890+
created_at: "2026-03-15T12:00:00Z",
1891+
updated_at: "2026-03-15T12:00:00Z",
1892+
}],
1893+
cursor: null,
1894+
}));
1895+
}
1896+
return prevFetch(input, init);
1897+
};
1898+
1899+
const args = ["list", "--project", TEST_PROJECT.project_id, "--limit"];
1900+
if (limitValue !== undefined) args.push(limitValue);
1901+
let threw = null;
1902+
captureStart();
1903+
try {
1904+
await run(args);
1905+
} catch (e) {
1906+
threw = e;
1907+
} finally {
1908+
captureStop();
1909+
globalThis.fetch = prevFetch;
1910+
}
1911+
return {
1912+
threw,
1913+
deployListCalled,
1914+
seenLimit,
1915+
stdout: capturedStdout(),
1916+
stderr: capturedStderr(),
1917+
};
1918+
}
1919+
1920+
for (const badLimit of ["abc", "0", "-1", "1.5"]) {
1921+
it(`deploy list rejects --limit ${badLimit} before network (GH-265)`, async () => {
1922+
const result = await runDeployListLimit(badLimit);
1923+
assert.equal(result.threw?.message, "process.exit(1)");
1924+
assert.equal(result.deployListCalled, false, "must not call deploy list with invalid --limit");
1925+
const parsed = JSON.parse(result.stderr);
1926+
assert.equal(parsed.code, "BAD_USAGE");
1927+
assert.match(parsed.message, /--limit must be a positive integer/);
1928+
assert.deepEqual(parsed.details, { flag: "--limit", value: badLimit });
1929+
});
1930+
}
1931+
1932+
it("deploy list rejects missing --limit value before network (GH-265)", async () => {
1933+
const result = await runDeployListLimit(undefined);
1934+
assert.equal(result.threw?.message, "process.exit(1)");
1935+
assert.equal(result.deployListCalled, false, "must not call deploy list when --limit has no value");
1936+
const parsed = JSON.parse(result.stderr);
1937+
assert.equal(parsed.code, "BAD_USAGE");
1938+
assert.match(parsed.message, /--limit must be a positive integer/);
1939+
});
1940+
1941+
it("deploy list forwards a valid positive integer limit", async () => {
1942+
const result = await runDeployListLimit("7");
1943+
assert.equal(result.threw, null, `deploy list should succeed, got: ${result.stderr}`);
1944+
assert.equal(result.deployListCalled, true);
1945+
assert.equal(result.seenLimit, "7");
1946+
const body = JSON.parse(result.stdout);
1947+
assert.equal(body.status, "ok");
1948+
assert.equal(body.operations[0].operation_id, "op_list_test");
1949+
});
1950+
18651951
it("deploy release get wraps the inventory payload", async () => {
18661952
const { run } = await import("./cli/lib/deploy.mjs");
18671953
captureStart();

cli/lib/deploy-v2.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -725,15 +725,15 @@ async function listCmd(args) {
725725
for (let i = 0; i < args.length; i++) {
726726
if (args[i] === "--help" || args[i] === "-h") { console.log(LIST_HELP); process.exit(0); }
727727
if (args[i] === "--project" && args[i + 1]) { opts.project = args[++i]; continue; }
728-
if (args[i] === "--limit" && args[i + 1]) { opts.limit = Number(args[++i]); continue; }
728+
if (args[i] === "--limit") { opts.limit = parsePositiveInt(args[++i], "--limit"); continue; }
729729
}
730730

731731
const project = resolveProjectId(opts.project);
732732
allowanceAuthHeaders("/deploy/v2/operations");
733733

734734
try {
735735
const sdkOpts = { project };
736-
if (opts.limit !== null && Number.isFinite(opts.limit)) sdkOpts.limit = opts.limit;
736+
if (opts.limit !== null) sdkOpts.limit = opts.limit;
737737
const result = await getSdk().deploy.list(sdkOpts);
738738
console.log(JSON.stringify({ status: "ok", ...result }, null, 2));
739739
} catch (err) {

0 commit comments

Comments
 (0)