Skip to content

Commit bbcde7d

Browse files
committed
feat(cli): add builds command
1 parent 82add14 commit bbcde7d

9 files changed

Lines changed: 438 additions & 4 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ jobs:
8989
run: pnpm exec -- turbo run e2e --filter=@argos-ci/core --filter=@argos-ci/cli
9090
env:
9191
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
92+
ARGOS_BUILD_ID: ${{ vars.ARGOS_BUILD_ID }}
9293
NODE_VERSION: ${{ matrix.node-version }}
9394
OS: ${{ matrix.os }}
9495

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ dist/
99
# Turborepo
1010
.turbo
1111
# NPM
12-
.npmrc
12+
.npmrc
13+
# macOS
14+
.DS_Store

packages/api-client/src/schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,13 @@ export interface components {
236236
};
237237
};
238238
};
239+
/** @description Build git reference */
240+
BuildGitReference: {
241+
/** @description The commit SHA */
242+
sha: components["schemas"]["Sha1Hash"];
243+
/** @description The branch name */
244+
branch: string;
245+
};
239246
/** @description Build */
240247
Build: {
241248
id: components["schemas"]["BuildId"];

packages/cli/e2e/builds.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/**
2+
* E2E tests for `argos builds` commands.
3+
* Requires ARGOS_TOKEN and ARGOS_BUILD_ID env vars.
4+
* Optional: ARGOS_API_BASE_URL env var.
5+
*
6+
* Usage:
7+
* ARGOS_TOKEN=xxx ARGOS_BUILD_ID=<failingBuildId> node e2e/builds.js
8+
* ARGOS_TOKEN=xxx ARGOS_BUILD_ID=<failingBuildId> ARGOS_API_BASE_URL=https://api.argos-ci.com/v2 node e2e/builds.js
9+
*/
10+
11+
import { execFileSync } from "node:child_process";
12+
13+
const token = process.env.ARGOS_TOKEN;
14+
const apiBaseURL = process.env.ARGOS_API_BASE_URL;
15+
const buildId = process.env.ARGOS_BUILD_ID;
16+
const cliPath = "bin/argos-cli.js";
17+
18+
if (!token || !buildId) {
19+
console.error(
20+
"Usage: ARGOS_TOKEN=xxx ARGOS_BUILD_ID=<id> [ARGOS_API_BASE_URL=<url>] node e2e/builds.js",
21+
);
22+
process.exit(1);
23+
}
24+
25+
function envWith(overrides = {}) {
26+
return { ...process.env, ...overrides };
27+
}
28+
29+
const baseEnv = apiBaseURL
30+
? envWith({ ARGOS_API_BASE_URL: apiBaseURL })
31+
: process.env;
32+
33+
function run(args, env = process.env) {
34+
return execFileSync("node", [cliPath, ...args], {
35+
encoding: "utf8",
36+
env,
37+
});
38+
}
39+
40+
function assert(condition, message) {
41+
if (!condition) {
42+
console.error(`FAIL: ${message}`);
43+
process.exit(1);
44+
}
45+
console.log(`PASS: ${message}`);
46+
}
47+
48+
// --- Authentication tests ---
49+
50+
// No token → error + exit 1
51+
try {
52+
run(["builds", "get", buildId], { ...baseEnv, ARGOS_TOKEN: "" });
53+
assert(false, "Missing token should exit with code 1");
54+
} catch (err) {
55+
assert(err.status === 1, "Exit code 1 when no token");
56+
assert(
57+
err.stderr.includes("No Argos token found"),
58+
"Error message includes 'No Argos token found'",
59+
);
60+
}
61+
62+
// ARGOS_TOKEN env var
63+
const getOutputEnv = run(["builds", "get", buildId], {
64+
...baseEnv,
65+
ARGOS_TOKEN: token,
66+
});
67+
assert(getOutputEnv.includes("Build #"), "ARGOS_TOKEN env var authenticates");
68+
69+
// --token flag (takes precedence)
70+
const getOutputFlag = run(["builds", "get", buildId, "--token", token], {
71+
...baseEnv,
72+
ARGOS_TOKEN: "invalid",
73+
});
74+
assert(
75+
getOutputFlag.includes("Build #"),
76+
"--token flag takes precedence over env var",
77+
);
78+
79+
// --- builds get tests ---
80+
81+
// Formatted output
82+
assert(getOutputEnv.includes("Status:"), "builds get: formatted Status line");
83+
assert(getOutputEnv.includes("Branch:"), "builds get: formatted Branch line");
84+
assert(getOutputEnv.includes("Commit:"), "builds get: formatted Commit line");
85+
assert(getOutputEnv.includes("URL:"), "builds get: formatted URL line");
86+
assert(
87+
getOutputEnv.includes("Snapshots:"),
88+
"builds get: formatted Snapshots line",
89+
);
90+
91+
// --json flag
92+
const getJsonOutput = run(["builds", "get", buildId, "--json"], {
93+
...baseEnv,
94+
ARGOS_TOKEN: token,
95+
});
96+
const buildJson = JSON.parse(getJsonOutput);
97+
assert(buildJson.id !== undefined, "--json: has 'id' field");
98+
assert(buildJson.status !== undefined, "--json: has 'status' field");
99+
assert(
100+
buildJson.branch !== undefined || buildJson.branch === null,
101+
"--json: has 'branch' field",
102+
);
103+
assert(
104+
buildJson.commit !== undefined || buildJson.commit === null,
105+
"--json: has 'commit' field",
106+
);
107+
assert(buildJson.url !== undefined, "--json: has 'url' field");
108+
109+
// status reflects actual result
110+
assert(
111+
["failure", "success", "pending"].includes(buildJson.status),
112+
`--json: status is one of failure|success|pending (got: ${buildJson.status})`,
113+
);
114+
115+
// Unknown build ID
116+
try {
117+
run(["builds", "get", "unknown-build-id-9999"], {
118+
...baseEnv,
119+
ARGOS_TOKEN: token,
120+
});
121+
assert(false, "Unknown build ID should exit with code 1");
122+
} catch (err) {
123+
assert(err.status === 1, "Unknown build ID: exit code 1");
124+
assert(
125+
err.stderr.includes("Error:") || err.stderr.includes("not found"),
126+
"Unknown build ID: human-readable error message",
127+
);
128+
}
129+
130+
// --- builds snapshots tests ---
131+
132+
// List all (no filter)
133+
const snapshotsAll = run(["builds", "snapshots", buildId], {
134+
...baseEnv,
135+
ARGOS_TOKEN: token,
136+
});
137+
assert(typeof snapshotsAll === "string", "builds snapshots: returns output");
138+
139+
// --status failure filter
140+
const snapshotsFailure = run(
141+
["builds", "snapshots", buildId, "--status", "failure"],
142+
{ ...baseEnv, ARGOS_TOKEN: token },
143+
);
144+
const failureLines = snapshotsFailure.trim().split("\n").filter(Boolean);
145+
assert(
146+
failureLines.every((line) => line.includes("[failure]")),
147+
"--status failure: all lines have [failure] status",
148+
);
149+
150+
// --json output
151+
const snapshotsJson = run(["builds", "snapshots", buildId, "--json"], {
152+
...baseEnv,
153+
ARGOS_TOKEN: token,
154+
});
155+
const snapshotItems = JSON.parse(snapshotsJson);
156+
assert(Array.isArray(snapshotItems), "--json: returns an array");
157+
if (snapshotItems.length > 0) {
158+
const first = snapshotItems[0];
159+
assert(first.name !== undefined, "--json: each item has 'name' field");
160+
assert(first.status !== undefined, "--json: each item has 'status' field");
161+
assert(first.url !== undefined, "--json: each item has 'url' field");
162+
}
163+
164+
// Integration test: --status failure --json → every item has "status": "failure"
165+
const snapshotsFailureJson = run(
166+
["builds", "snapshots", buildId, "--status", "failure", "--json"],
167+
{ ...baseEnv, ARGOS_TOKEN: token },
168+
);
169+
const failureItems = JSON.parse(snapshotsFailureJson);
170+
assert(
171+
Array.isArray(failureItems),
172+
"integration: --status failure --json returns array",
173+
);
174+
assert(
175+
failureItems.every((item) => item.status === "failure"),
176+
'integration: every item in --status failure --json has "status": "failure"',
177+
);
178+
179+
// Empty result for build with no failures using --status failure should exit 0
180+
// (this test passes trivially if the build has failures and returns items,
181+
// but the logic is tested by checking exit code 0 above)
182+
183+
console.log("\nAll e2e tests passed!");

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
"access": "public"
3535
},
3636
"dependencies": {
37+
"@argos-ci/api-client": "workspace:*",
3738
"@argos-ci/core": "workspace:*",
3839
"commander": "^14.0.3",
3940
"ora": "^9.3.0",
4041
"update-notifier": "^7.3.1"
4142
},
4243
"scripts": {
4344
"build": "tsdown",
44-
"e2e": "node e2e/upload.js && node e2e/skip.js",
45+
"e2e": "node e2e/upload.js && node e2e/skip.js && node e2e/builds.js",
4546
"check-types": "tsc",
4647
"check-format": "prettier --check --ignore-unknown --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
4748
"lint": "eslint ."

0 commit comments

Comments
 (0)