Merge pull request #86 from Incanta/dependabot/npm_and_yarn/qs-6.15.2 #204
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # ⚠️ 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)" |