Skip to content

Merge pull request #86 from Incanta/dependabot/npm_and_yarn/qs-6.15.2 #204

Merge pull request #86 from Incanta/dependabot/npm_and_yarn/qs-6.15.2

Merge pull request #86 from Incanta/dependabot/npm_and_yarn/qs-6.15.2 #204

Workflow file for this run

# ⚠️ IMPORTANT: When modifying this workflow, also update the corresponding
# Node.js test script at .github/workflows/test.js to keep them in sync.
name: CLI Integration Test
on:
push:
branches: [main, premium]
pull_request:
branches: [main, premium]
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
# ----------------------------------------------------------------
# Setup
# ----------------------------------------------------------------
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
- name: Enable Corepack
run: corepack enable
# ----------------------------------------------------------------
# Install & build Node.js services
# ----------------------------------------------------------------
- name: Install dependencies
run: yarn install
- name: Build services
run: yarn build
# ----------------------------------------------------------------
# Install C++ build dependencies
# ----------------------------------------------------------------
- name: Install C++ dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake g++ libcurl4-openssl-dev nlohmann-json3-dev
# ----------------------------------------------------------------
# Build the CLI
# ----------------------------------------------------------------
- name: Build CLI
run: |
cd src/clients/cli
mkdir -p build
cd build
cmake ..
cmake --build . --parallel
- name: Verify CLI builds
run: |
ls -la src/clients/cli/build/checkpoint
ls -la src/clients/cli/build/chk
src/clients/cli/build/chk --help
# ----------------------------------------------------------------
# Start services in background
# ----------------------------------------------------------------
- name: Start services
run: yarn dev --background
- name: Wait for services to be fully ready
run: |
echo "Waiting for services to be reachable..."
for i in $(seq 1 30); do
if curl -sf http://localhost:13000 > /dev/null 2>&1; then
echo "App is reachable"
break
fi
echo " Waiting for app... ($i/30)"
sleep 2
done
for i in $(seq 1 30); do
if curl -sf http://localhost:13010 > /dev/null 2>&1 || \
curl -sf "http://localhost:13010/workspaces.ops.list.local?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22daemonId%22%3A%22probe%22%7D%7D%7D" > /dev/null 2>&1; then
echo "Daemon is reachable"
break
fi
echo " Waiting for daemon... ($i/30)"
sleep 2
done
for i in $(seq 1 30); do
if curl -sf http://localhost:13001 > /dev/null 2>&1; then
echo "Server is reachable"
break
fi
echo " Waiting for server... ($i/30)"
sleep 2
done
echo "All services reachable!"
# ----------------------------------------------------------------
# Authenticate: dev login → write auth.json
# ----------------------------------------------------------------
- name: Create dev user and authenticate
id: auth
run: |
DAEMON_ID="ci-daemon-$(date +%s)"
echo "DAEMON_ID=$DAEMON_ID" >> "$GITHUB_OUTPUT"
# Dev login (public procedure, no auth needed)
# Next.js compiles route handlers on first request in dev mode,
# so we retry a few times to let it warm up.
DEV_LOGIN=""
for attempt in $(seq 1 10); do
HTTP_CODE=$(curl -s -o /tmp/devlogin_resp.json -w "%{http_code}" \
-X POST 'http://localhost:13000/api/trpc/auth.devLogin?batch=1' \
-H 'Content-Type: application/json' \
-d "{\"0\":{\"json\":{\"email\":\"ci-test@checkpoint.dev\",\"deviceCode\":\"$DAEMON_ID\",\"tokenName\":\"ci-token\"}}}")
echo "Attempt $attempt: HTTP $HTTP_CODE"
cat /tmp/devlogin_resp.json || true
echo ""
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
DEV_LOGIN=$(cat /tmp/devlogin_resp.json)
break
fi
echo " Retrying in 5s..."
sleep 5
done
if [ -z "$DEV_LOGIN" ]; then
echo "ERROR: devLogin failed after 10 attempts (last HTTP $HTTP_CODE)"
cat /tmp/devlogin_resp.json 2>/dev/null || true
exit 1
fi
echo "Dev login response: $DEV_LOGIN"
API_TOKEN=$(echo "$DEV_LOGIN" | jq -r '.[0].result.data.json.apiToken')
USER_ID=$(echo "$DEV_LOGIN" | jq -r '.[0].result.data.json.user.id')
if [ -z "$API_TOKEN" ] || [ "$API_TOKEN" = "null" ]; then
echo "ERROR: Failed to get API token"
exit 1
fi
echo "API_TOKEN=$API_TOKEN" >> "$GITHUB_OUTPUT"
echo "USER_ID=$USER_ID" >> "$GITHUB_OUTPUT"
echo "Authenticated as user: $USER_ID"
# Write auth.json for the daemon
mkdir -p ~/.checkpoint
cat > ~/.checkpoint/auth.json << EOF
{
"users": {
"$DAEMON_ID": {
"endpoint": "http://localhost:13000",
"apiToken": "$API_TOKEN"
}
}
}
EOF
echo "Wrote ~/.checkpoint/auth.json"
cat ~/.checkpoint/auth.json
# Warm up the user.me route on the app server so the daemon's
# auth.getUsers (which calls user.me to validate tokens) works
# on the first attempt from the CLI.
for i in $(seq 1 10); do
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
"http://localhost:13000/api/trpc/user.me?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D" \
-H "Authorization: Bearer $API_TOKEN")
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "user.me route warmed up (HTTP $HTTP_CODE)"
break
fi
echo " Warming up user.me... attempt $i (HTTP $HTTP_CODE)"
sleep 3
done
# ----------------------------------------------------------------
# Create org and repo via app API
# ----------------------------------------------------------------
- name: Create org
id: org
run: |
TOKEN="${{ steps.auth.outputs.API_TOKEN }}"
ORG=$(curl -sf -X POST 'http://localhost:13000/api/trpc/org.createOrg?batch=1' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"0":{"json":{"name":"ci-test-org"}}}')
echo "Create org response: $ORG"
ORG_ID=$(echo "$ORG" | jq -r '.[0].result.data.json.id')
if [ -z "$ORG_ID" ] || [ "$ORG_ID" = "null" ]; then
echo "ERROR: Failed to create org"
exit 1
fi
echo "ORG_ID=$ORG_ID" >> "$GITHUB_OUTPUT"
echo "Created org: $ORG_ID"
- name: Create repo
id: repo
run: |
TOKEN="${{ steps.auth.outputs.API_TOKEN }}"
ORG_ID="${{ steps.org.outputs.ORG_ID }}"
REPO=$(curl -sf -X POST 'http://localhost:13000/api/trpc/repo.createRepo?batch=1' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d "{\"0\":{\"json\":{\"name\":\"ci-test-repo\",\"orgId\":\"$ORG_ID\"}}}")
echo "Create repo response: $REPO"
REPO_ID=$(echo "$REPO" | jq -r '.[0].result.data.json.id')
if [ -z "$REPO_ID" ] || [ "$REPO_ID" = "null" ]; then
echo "ERROR: Failed to create repo"
exit 1
fi
echo "REPO_ID=$REPO_ID" >> "$GITHUB_OUTPUT"
echo "Created repo: $REPO_ID"
# ----------------------------------------------------------------
# Create workspace 1 via daemon, add files, submit
# ----------------------------------------------------------------
- name: Create workspace 1
id: ws1
run: |
WS_PATH="/tmp/ci-workspace-1"
mkdir -p "$WS_PATH"
cd "$WS_PATH"
$GITHUB_WORKSPACE/src/clients/cli/build/chk init ci-test-org/ci-test-repo
echo "WS_PATH=$WS_PATH" >> "$GITHUB_OUTPUT"
echo "Initialized workspace at $WS_PATH"
echo "=== workspace.json ==="
cat "$WS_PATH/.checkpoint/workspace.json"
- name: Create test files in workspace 1
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
echo "Hello from Checkpoint CI!" > "$WS_PATH/readme.txt"
mkdir -p "$WS_PATH/src"
echo "int main() { return 0; }" > "$WS_PATH/src/main.cpp"
echo "# Build\nrun make" > "$WS_PATH/src/notes.md"
echo "Created test files:"
find "$WS_PATH" -not -path '*/.checkpoint/*' -type f
- name: Wait for daemon to detect files
run: sleep 3
- name: CLI - check status in workspace 1
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk status ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk status || true
- name: CLI - add files in workspace 1
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk add readme.txt ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk add readme.txt src/main.cpp src/notes.md
- name: CLI - verify staged status
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk status (after add) ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk status
- name: CLI - submit from workspace 1
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk submit ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk submit --message "CI test: initial submit with 3 files"
- name: Verify submission via app API
run: |
TOKEN="${{ steps.auth.outputs.API_TOKEN }}"
REPO_ID="${{ steps.repo.outputs.REPO_ID }}"
# Wait a moment for the submission to propagate
sleep 2
# Query changelists via app API
INPUT=$(python3 -c "
import urllib.parse, json
data = {'0': {'json': {'repoId': '$REPO_ID', 'branchName': 'main', 'start': {'number': None, 'timestamp': None}, 'count': 100}}}
print(urllib.parse.quote(json.dumps(data)))
")
HISTORY=$(curl -sf "http://localhost:13000/api/trpc/changelist.getChangelists?batch=1&input=$INPUT" \
-H "Authorization: Bearer $TOKEN")
echo "Changelist history: $HISTORY"
# Should have changelist number 1 (initial is 0)
CL_COUNT=$(echo "$HISTORY" | jq '.[0].result.data.json | length')
echo "Changelist count: $CL_COUNT"
if [ "$CL_COUNT" -lt 2 ]; then
echo "ERROR: Expected at least 2 changelists (initial + our submit), got $CL_COUNT"
exit 1
fi
echo "Submission verified!"
# ----------------------------------------------------------------
# Create workspace 2, pull, verify
# ----------------------------------------------------------------
- name: Create workspace 2
id: ws2
run: |
WS_PATH="/tmp/ci-workspace-2"
mkdir -p "$WS_PATH"
cd "$WS_PATH"
$GITHUB_WORKSPACE/src/clients/cli/build/chk init ci-test-org/ci-test-repo
echo "WS_PATH=$WS_PATH" >> "$GITHUB_OUTPUT"
echo "Initialized workspace at $WS_PATH"
echo "=== workspace.json ==="
cat "$WS_PATH/.checkpoint/workspace.json"
- name: CLI - pull into workspace 2
run: |
WS_PATH="${{ steps.ws2.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk pull ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk pull
- name: Verify workspace 2 has the submitted files
run: |
WS1="${{ steps.ws1.outputs.WS_PATH }}"
WS2="${{ steps.ws2.outputs.WS_PATH }}"
echo "=== Workspace 1 files ==="
find "$WS1" -not -path '*/.checkpoint/*' -type f | sort
echo ""
echo "=== Workspace 2 files ==="
find "$WS2" -not -path '*/.checkpoint/*' -type f | sort
# Check each file exists and matches
echo ""
echo "=== Comparing files ==="
for file in readme.txt src/main.cpp src/notes.md; do
if [ ! -f "$WS2/$file" ]; then
echo "FAIL: $file missing in workspace 2"
exit 1
fi
if ! diff -q "$WS1/$file" "$WS2/$file" > /dev/null 2>&1; then
echo "FAIL: $file content differs between workspaces"
echo " WS1:" && cat "$WS1/$file"
echo " WS2:" && cat "$WS2/$file"
exit 1
fi
echo "OK: $file matches"
done
echo ""
echo "All files verified!"
- name: CLI - check status in workspace 2 (should be clean)
run: |
WS_PATH="${{ steps.ws2.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk status ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk status
# ----------------------------------------------------------------
# Workspace 2 modifies a file, submits; workspace 1 pulls
# ----------------------------------------------------------------
- name: Modify a file in workspace 2
run: |
WS_PATH="${{ steps.ws2.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk checkout readme.txt ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk checkout readme.txt
echo "Hello from workspace 2 — modified!" > "$WS_PATH/readme.txt"
echo "Wrote updated readme.txt:"
cat "$WS_PATH/readme.txt"
- name: Wait for daemon to detect modification
run: sleep 3
- name: CLI - add modified file in workspace 2
run: |
WS_PATH="${{ steps.ws2.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk add readme.txt ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk add readme.txt
echo ""
echo "=== chk status (after add) ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk status
- name: CLI - submit from workspace 2
run: |
WS_PATH="${{ steps.ws2.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk submit ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk submit --message "CI test: workspace 2 modifies readme.txt"
- name: CLI - pull into workspace 1
run: |
WS_PATH="${{ steps.ws1.outputs.WS_PATH }}"
cd "$WS_PATH"
echo "=== chk pull ==="
$GITHUB_WORKSPACE/src/clients/cli/build/chk pull
- name: Verify workspace 1 has the modified file
run: |
WS1="${{ steps.ws1.outputs.WS_PATH }}"
WS2="${{ steps.ws2.outputs.WS_PATH }}"
echo "=== Workspace 1 readme.txt ==="
cat "$WS1/readme.txt"
echo ""
echo "=== Workspace 2 readme.txt ==="
cat "$WS2/readme.txt"
echo ""
echo "=== Comparing ==="
if ! diff -q "$WS1/readme.txt" "$WS2/readme.txt" > /dev/null 2>&1; then
echo "FAIL: readme.txt content differs after ws2 submit + ws1 pull"
echo " WS1:" && cat "$WS1/readme.txt"
echo " WS2:" && cat "$WS2/readme.txt"
exit 1
fi
EXPECTED="Hello from workspace 2 — modified!"
ACTUAL=$(cat "$WS1/readme.txt")
if [ "$ACTUAL" != "$EXPECTED" ]; then
echo "FAIL: readme.txt does not contain the expected content"
echo " Expected: $EXPECTED"
echo " Actual: $ACTUAL"
exit 1
fi
echo "OK: readme.txt matches after round-trip modify"
echo ""
echo "All files verified!"
# ----------------------------------------------------------------
# Cleanup
# ----------------------------------------------------------------
- name: Stop services
if: always()
run: yarn dev stop || true
- name: Show logs on failure
if: failure()
run: |
echo "=== Service Logs ==="
for log in logs/*.log; do
if [ -f "$log" ]; then
echo ""
echo "============================================================"
echo " $log ($(wc -l < "$log") lines)"
echo "============================================================"
cat "$log"
echo ""
fi
done
echo ""
echo "=== auth.json ==="
cat ~/.checkpoint/auth.json 2>/dev/null || echo "(not found)"
echo ""
echo "=== daemon.json ==="
cat ~/.checkpoint/daemon.json 2>/dev/null || echo "(not found)"