-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathJenkinsfile
More file actions
290 lines (248 loc) · 12.9 KB
/
Copy pathJenkinsfile
File metadata and controls
290 lines (248 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
pipeline {
agent { label 'agent'}
parameters {
booleanParam(name: 'PUBLISH', defaultValue: false, description: 'Trigger publish stage')
}
environment {
NPM_AUTH_TOKEN = credentials('Appcircle-CLI-NPM-Cred')
GITHUB_PAT = credentials('appcircle-cli-gh-repo-fg-pat')
}
stages {
stage('PR Validation') {
when {
changeRequest()
}
steps {
withCredentials([string(credentialsId: 'GithubPersonalToken', variable: 'GITHUB_TOKEN')]) {
sh '''#!/bin/bash
# shellcheck shell=bash
set -x
set -euo pipefail
echo "🔨 Starting PR Validation Pipeline 🔨"
echo "=================================="
# All Node tooling runs inside Docker so the host agent does not need Node.
echo "🐳 Building CI/test image..."
docker build -f Dockerfile.test -t ac-cli-test .
echo "⚙️ TypeScript compilation, 🧪 unit tests with coverage and 📊 threshold check..."
docker rm -f ac-cli-ci >/dev/null 2>&1 || true
if ! docker run --name ac-cli-ci ac-cli-test sh -c "yarn build && yarn test && node scripts/parse-coverage.js check-threshold && (node scripts/parse-coverage.js pr-comment > /app/pr-comment.txt 2>/dev/null || true)"; then
echo "❌ Build / unit tests / coverage threshold failed! 💔"
docker rm -f ac-cli-ci >/dev/null 2>&1 || true
docker image rm ac-cli-test >/dev/null 2>&1 || true
exit 1
fi
echo "✅ Build, unit tests and coverage thresholds passed! 🎯"
echo "📊 Posting coverage report to PR..."
# This section is optional and won't fail the build
set +e # Don't exit on error for coverage posting
docker cp ac-cli-ci:/app/pr-comment.txt ./pr-comment.txt >/dev/null 2>&1
if [ -n "${GITHUB_TOKEN:-}" ] && [ -s ./pr-comment.txt ]; then
COVERAGE_COMMENT=$(cat ./pr-comment.txt)
# Create JSON payload
JSON_PAYLOAD=$(jq -n --arg body "$COVERAGE_COMMENT" '{body: $body}' 2>/dev/null)
if [ $? -eq 0 ] && [ -n "$JSON_PAYLOAD" ]; then
# Post comment to PR
HTTP_CODE=$(curl -s -w "%{http_code}" -o /tmp/gh_response.json -X POST \
-H "Authorization: token ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/appcircleio/appcircle-cli/issues/${CHANGE_ID}/comments" \
-d "$JSON_PAYLOAD" 2>/dev/null)
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Coverage comment posted to PR #${CHANGE_ID}"
else
echo "⚠️ Failed to post coverage comment (HTTP ${HTTP_CODE:-unknown}), continuing..."
cat /tmp/gh_response.json 2>/dev/null || true
fi
else
echo "⚠️ Failed to create JSON payload, continuing..."
fi
else
echo "⚠️ No coverage comment generated or GITHUB_TOKEN not available, skipping coverage comment"
fi
# Cleanup
docker rm -f ac-cli-ci >/dev/null 2>&1 || true
docker image rm ac-cli-test >/dev/null 2>&1 || true
rm -f ./pr-comment.txt
set -e # Re-enable exit on error
echo "=================================="
echo "🎯 PR validation complete - All checks passed! 🚀"
'''
}
}
}
stage('Update Coverage Badges') {
when {
allOf {
branch 'develop'
not { changelog '.*\\[skip ci\\].*' }
}
}
steps {
sh '''#!/bin/bash
# shellcheck shell=bash
set -e
echo "🚀 Testing badge update automation..."
# Configuration
REPO="appcircleio/appcircle-cli"
BASE_BRANCH="develop"
GIT_USER_EMAIL="ozer@appcircle.io"
GIT_USER_NAME="Özer from Jenkins"
echo "🤖 Starting coverage badge update script..."
# Functions
die() {
echo "⚠️ $1"
docker rm -f ac-cli-badges >/dev/null 2>&1 || true
docker image rm ac-cli-test >/dev/null 2>&1 || true
[ -f README.md.bak ] && mv README.md.bak README.md
exit 0
}
api_call() {
local method=$1
local endpoint=$2
local data=$3
curl -s -X "$method" \\
-H "Authorization: token ${GITHUB_PAT}" \\
-H "Accept: application/vnd.github+json" \\
-H "X-GitHub-Api-Version: 2022-11-28" \\
${data:+-d "$data"} \\
"https://api.github.com/repos/${REPO}/${endpoint}"
}
# Main script
echo "📊 Updating coverage badges in README..."
# Run tests and generate badges inside Docker (no Node on the host agent)
echo "🐳 Building CI/test image..."
docker build -f Dockerfile.test -t ac-cli-test . || die "Failed to build test image"
echo "🧪 Running tests to generate coverage and badges..."
docker rm -f ac-cli-badges >/dev/null 2>&1 || true
docker run --name ac-cli-badges ac-cli-test sh -c '
TEST_OUTPUT=$(yarn test 2>&1) || { echo "$TEST_OUTPUT"; exit 1; }
TESTS_PASSED=$(echo "$TEST_OUTPUT" | grep -oE "Tests[[:space:]]+[0-9]+[[:space:]]+passed" | grep -oE "[0-9]+" | head -1)
if [ -n "$TESTS_PASSED" ]; then
node scripts/parse-coverage.js badges "{\\"passed\\":$TESTS_PASSED}" > /app/badges.txt 2>/dev/null
else
node scripts/parse-coverage.js badges > /app/badges.txt 2>/dev/null
fi
' || die "Tests failed, skipping badge update"
docker cp ac-cli-badges:/app/badges.txt ./badges.txt >/dev/null 2>&1 || die "Failed to read generated badges"
docker rm -f ac-cli-badges >/dev/null 2>&1 || true
docker image rm ac-cli-test >/dev/null 2>&1 || true
BADGES=$(cat ./badges.txt)
rm -f ./badges.txt
[ -z "$BADGES" ] && die "Failed to generate badges"
echo "Generated badges:"
echo "$BADGES"
# Update README
grep -q "^!\\[Coverage\\]" README.md || die "Coverage badge not found in README.md"
cp README.md README.md.bak
grep -v "^!\\[Coverage\\]\\|^!\\[Build\\]\\|^!\\[Tests\\]\\|^!\\[Branches\\]\\|^!\\[Functions\\]" README.md > README.md.tmp
LINE_NUM=$(grep -n "^!\\[NPM Version\\]" README.md.tmp | cut -d: -f1)
if [ -n "$LINE_NUM" ]; then
{ head -n "$LINE_NUM" README.md.tmp; echo "$BADGES"; tail -n +$((LINE_NUM + 1)) README.md.tmp; } > README.md
else
{ echo "$BADGES"; echo ""; cat README.md.tmp; } > README.md
fi
rm -f README.md.tmp
# Check for changes
if git diff --quiet README.md; then
echo "ℹ️ No changes to coverage badges"
rm -f README.md.bak
exit 0
fi
# Configure git and create PR
git config user.email "$GIT_USER_EMAIL"
git config user.name "$GIT_USER_NAME"
BRANCH_NAME="coverage-badges-$(date +%Y%m%d-%H%M%S)"
echo "📝 Creating branch: $BRANCH_NAME"
git checkout -b "$BRANCH_NAME" || die "Failed to create branch"
git add README.md
git commit -m "docs: update coverage badges [skip ci]" || die "Failed to commit changes"
echo "📤 Pushing branch to remote..."
git push "https://${GITHUB_PAT}@github.com/${REPO}.git" "$BRANCH_NAME" || die "Failed to push branch"
echo "🔀 Creating pull request..."
PR_RESPONSE=$(api_call POST pulls "{\\"title\\":\\"docs: update coverage badges [skip ci]\\",\\"body\\":\\"🤖 Automated coverage badge update from Jenkins build #${BUILD_NUMBER}\\",\\"head\\":\\"$BRANCH_NAME\\",\\"base\\":\\"$BASE_BRANCH\\"}")
# Extract PR number from response
PR_NUMBER=$(echo "$PR_RESPONSE" | sed -n 's/.*"number":[[:space:]]*\\([0-9]*\\).*/\\1/p' | head -1)
if [ -z "$PR_NUMBER" ]; then
echo "⚠️ Failed to create PR"
echo "Response: $PR_RESPONSE"
die "Could not extract PR number from response"
fi
echo "✅ Created PR #${PR_NUMBER}: https://github.com/${REPO}/pull/${PR_NUMBER}"
echo "🚀 Attempting to merge PR #${PR_NUMBER}..."
MERGE_RESPONSE=$(api_call PUT "pulls/${PR_NUMBER}/merge" '{"merge_method":"squash"}')
if echo "$MERGE_RESPONSE" | grep -q '"merged":true'; then
echo "✅ PR #${PR_NUMBER} merged successfully"
api_call DELETE "git/refs/heads/$BRANCH_NAME" > /dev/null
rm -f README.md.bak
else
echo "⚠️ Could not auto-merge PR #${PR_NUMBER}"
echo "💡 This may be due to:"
echo " - Branch protection rules requiring reviews"
echo " - Insufficient permissions on the PAT"
echo " - Required status checks not passing"
echo ""
echo "📋 PR is open and waiting for manual review/merge:"
echo " https://github.com/${REPO}/pull/${PR_NUMBER}"
echo ""
echo "Response: $MERGE_RESPONSE"
rm -f README.md.bak
fi
exit 0
'''
}
}
stage('Publish') {
when {
anyOf {
expression { params.PUBLISH == true }
buildingTag()
}
}
steps {
sh '''#!/bin/bash
# shellcheck shell=bash
set -x
set -euo pipefail
git fetch --tags --force
tag=$(git describe --tags --abbrev=0)
echo "Tag: ${tag}"
npmPublishCommand=""
if [[ "${tag}" == *"beta"* ]]; then
echo "Beta Release"
npmPublishCommand="npm publish --tag beta"
elif [[ "${tag}" == *"alpha"* ]]; then
echo "Alpha Release"
npmPublishCommand="npm publish --tag alpha"
else
echo "Production Release"
npmPublishCommand="npm publish"
fi
## Build the image and make it ready for publishing.
docker image build -t ac-cli .
## Print the bundled Node version (inside the image, not the host).
docker run --rm ac-cli node --version
## Publish the application.
publishStatus=0
# shellcheck disable=SC2086
if ! docker run --rm --env NPM_AUTH_TOKEN=${NPM_AUTH_TOKEN} ac-cli sh -c "npm config set //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} && ${npmPublishCommand}"; then
echo "Publishing failed"
publishStatus=1
fi
docker image rm ac-cli
exit $publishStatus
'''
}
}
}
post {
always {
script {
// CLI has no deployment image / security-scan reports, so pass an empty list
// (same path codepush uses when no report file exists). Produces the standard
// build-summary Slack notification used across the other projects.
sendBuildSummaryToSlack(currentBuild.currentResult, currentBuild.durationString, [])
}
}
}
}