Merge remote-tracking branch 'origin/main' into feat/pipeline-driven-ship-commands

# Conflicts:
#	server/__tests__/lanes-cli.test.js
This commit is contained in:
2026-08-25 11:17:33 +07:00
12 changed files with 542 additions and 13 deletions
+80 -2
View File
@@ -126,11 +126,11 @@ function get(urlPath) {
// spawnSync would stall the event loop and the CLI child's request to
// 127.0.0.1 would never be served — a deadlock that looks like a network
// sandbox blocking loopback.
function cli(args, cwd) {
function cli(args, cwd, extraEnv = {}) {
return new Promise((resolve, reject) => {
const child = spawn(process.execPath, [CLI, ...args], {
cwd,
env: { ...process.env, CLAUDE_DASHBOARD_PORT: String(server.address().port) },
env: { ...process.env, CLAUDE_DASHBOARD_PORT: String(server.address().port), ...extraEnv },
});
let stdout = "";
let stderr = "";
@@ -573,3 +573,81 @@ describe("ccam ship-full / ccam ship-quick", () => {
assert.match(q.stderr, /usage: ccam ship-quick/);
});
});
describe("ccam lanes ci status", () => {
let restorePath;
let bin;
before(() => {
bin = fs.mkdtempSync(path.join(os.tmpdir(), "ccam-ci-status-cli-bin-"));
fs.writeFileSync(
path.join(bin, "gh"),
`#!/usr/bin/env bash
if [ "$1" = "run" ] && [ "$2" = "list" ]; then
if [ "$CI_STATUS_FIXTURE" = "failure" ]; then
echo '[{"databaseId":1,"status":"completed","conclusion":"failure"}]'
elif [ "$CI_STATUS_FIXTURE" = "pending" ]; then
echo '[]'
else
echo '[{"databaseId":1,"status":"completed","conclusion":"success"}]'
fi
exit 0
fi
if [ "$1" = "run" ] && [ "$2" = "view" ]; then
if [ "$CI_STATUS_FIXTURE" = "failure" ]; then
echo '{"jobs":[{"name":"tests","status":"completed","conclusion":"failure","url":"https://x/1"}]}'
else
echo '{"jobs":[{"name":"tests","status":"completed","conclusion":"success","url":"https://x/1"}]}'
fi
exit 0
fi
exit 1
`
);
fs.chmodSync(path.join(bin, "gh"), 0o755);
const originalPath = process.env.PATH;
process.env.PATH = `${bin}${path.delimiter}${originalPath}`;
restorePath = () => {
process.env.PATH = originalPath;
};
});
after(() => {
if (restorePath) restorePath();
fs.rmSync(bin, { recursive: true, force: true });
});
it("exits 0 and prints success state", async () => {
const r = await cli(["lanes", "ci", "status", "development"], LANE_DIR);
assert.equal(r.status, 0);
assert.match(r.stdout, /"state": "success"/);
});
it("exits 1 on a failed run", async () => {
const orig = process.env.CI_STATUS_FIXTURE;
try {
process.env.CI_STATUS_FIXTURE = "failure";
const r = await cli(["lanes", "ci", "status", "development"], LANE_DIR, {
CI_STATUS_FIXTURE: "failure",
});
assert.equal(r.status, 1);
assert.match(r.stdout, /"state": "failure"/);
} finally {
process.env.CI_STATUS_FIXTURE = orig;
}
});
it("exits 3 when no run exists yet", async () => {
const orig = process.env.CI_STATUS_FIXTURE;
try {
process.env.CI_STATUS_FIXTURE = "pending";
const r = await cli(["lanes", "ci", "status", "development"], LANE_DIR, {
CI_STATUS_FIXTURE: "pending",
});
assert.equal(r.status, 3);
assert.match(r.stdout, /"state": "pending"/);
} finally {
process.env.CI_STATUS_FIXTURE = orig;
}
});
});