/*
* OpenDesk CI Pipeline(脚本式)
*
* 触发方式:GitCode PR Webhook → Generic Webhook Trigger Plugin
* 流程:拉取 main → merge PR 分支 → build → test → 回报结果(含日志)到 GitCode PR
*/
// ── 配置区(按实际修改) ──
def GITCODE_API_BASE = 'https://api.gitcode.com/api/v5'
def NODEJS_HOME = '/root/.nvm/versions/node/v25.9.0/'
node {
def ciLogDir = "${env.WORKSPACE}/ci-logs"
def buildResult = 'success'
// 从 Webhook 注入的 PR_REPO_URL 自动解析 owner/repo
// PR_REPO_URL 格式:https://gitcode.com/owner/repo.git
// 也可在 Generic Webhook Trigger 中额外配置 GITCODE_OWNER / GITCODE_REPO 变量
def repoOwner = env.GITCODE_OWNER ?: ''
def repoName = env.GITCODE_REPO ?: ''
if ((!repoOwner || !repoName) && env.PR_REPO_URL) {
def m = (env.PR_REPO_URL =~ /gitcode\.com\/([^\/]+)\/([^\/]+?)(\.git)?$/)
if (m.find()) {
repoOwner = repoOwner ?: m.group(1)
repoName = repoName ?: m.group(2)
}
}
withEnv([
"GITCODE_OWNER=${repoOwner}",
"GITCODE_REPO=${repoName}",
"GITCODE_API_BASE=${GITCODE_API_BASE}",
"CI_LOG_DIR=${ciLogDir}",
"PATH=${NODEJS_HOME}/bin:${env.PATH}"
]) {
withCredentials([string(credentialsId: 'gitcode-api-token', variable: 'GITCODE_TOKEN')]) {
timeout(time: 30, unit: 'MINUTES') {
timestamps {
try {
stage('Checkout') {
checkout scm
// 配置带 token 的 remote URL,确保后续 git fetch 能认证
sh '''
REPO_URL="${PR_REPO_URL:-$(git remote get-url origin)}"
AUTH_URL=$(echo "${REPO_URL}" | sed "s|https://|https://oauth2:${GITCODE_TOKEN}@|")
git remote set-url origin "${AUTH_URL}"
git config user.email "ci@jenkins"
git config user.name "Jenkins CI"
'''
}
stage('Prepare') {
sh '''
echo "=== 环境检查 ==="
node --version || { echo "错误: node 未找到,请检查 NODEJS_HOME"; exit 1; }
npm --version
git --version
echo ""
echo "=== Webhook 变量检查 ==="
echo "GITCODE_OWNER = ${GITCODE_OWNER:-<未设置>}"
echo "GITCODE_REPO = ${GITCODE_REPO:-<未设置>}"
echo "PR_NUMBER = ${PR_NUMBER:-<未设置>}"
echo "PR_SOURCE_BRANCH= ${PR_SOURCE_BRANCH:-<未设置>}"
echo "PR_TARGET_BRANCH= ${PR_TARGET_BRANCH:-<未设置>}"
echo "PR_COMMIT_SHA = ${PR_COMMIT_SHA:-<未设置>}"
echo "PR_REPO_URL = ${PR_REPO_URL:-<未设置>}"
echo "PR_SOURCE_REPO = ${PR_SOURCE_REPO_URL:-<未设置>}"
if [ -z "${PR_NUMBER:-}" ] || [ -z "${PR_SOURCE_BRANCH:-}" ] || [ -z "${PR_TARGET_BRANCH:-}" ]; then
echo ""
echo "警告: Webhook 变量不完整,可能是手动触发或 Webhook 尚未配置。"
echo "流水线将继续执行,但 GitCode API 回报和 PR merge 将被跳过。"
fi
mkdir -p "${CI_LOG_DIR}"
'''
sh 'bash ci/scripts/gitcode-notify.sh comment "⏳ CI 流水线已启动... [查看进度](${BUILD_URL})"'
}
def buildStatus = 'success'
def testStatus = 'skipped'
stage('Build') {
echo "=== Stage: Build ==="
try {
sh '''#!/usr/bin/env bash
set -euo pipefail
if [ -n "${PR_NUMBER:-}" ] && [ -n "${PR_SOURCE_BRANCH:-}" ] && [ -n "${PR_TARGET_BRANCH:-}" ]; then
echo "PR #${PR_NUMBER}: ${PR_SOURCE_BRANCH} → ${PR_TARGET_BRANCH}"
# 拉取目标分支
git fetch origin "${PR_TARGET_BRANCH}"
git checkout -B "ci-merge-${PR_NUMBER}" "origin/${PR_TARGET_BRANCH}"
# 如果有源仓库 URL(fork 场景),添加为临时 remote
if [ -n "${PR_SOURCE_REPO_URL:-}" ]; then
SOURCE_AUTH_URL=$(echo "${PR_SOURCE_REPO_URL}" | sed "s|https://|https://oauth2:${GITCODE_TOKEN}@|")
git remote remove pr-source 2>/dev/null || true
git remote add pr-source "${SOURCE_AUTH_URL}"
git fetch pr-source "${PR_SOURCE_BRANCH}"
git merge --no-ff "pr-source/${PR_SOURCE_BRANCH}" \
-m "CI: merge PR #${PR_NUMBER} (${PR_SOURCE_BRANCH}) into ${PR_TARGET_BRANCH}"
else
git fetch origin "${PR_SOURCE_BRANCH}"
git merge --no-ff "origin/${PR_SOURCE_BRANCH}" \
-m "CI: merge PR #${PR_NUMBER} (${PR_SOURCE_BRANCH}) into ${PR_TARGET_BRANCH}"
fi
else
echo "Webhook 变量不完整,跳过 PR merge,直接在当前分支构建"
fi
rm -rf dist || true
npm install 2>&1 | tee "${CI_LOG_DIR}/npm-install.log"
npm run build-cli 2>&1 | tee "${CI_LOG_DIR}/build.log"
echo "success" > "${CI_LOG_DIR}/build.status"
'''
archiveArtifacts artifacts: 'dist/cli/*', allowEmptyArchive: true, fingerprint: true
} catch (e) {
buildStatus = 'failure'
sh 'echo "failure" > "${CI_LOG_DIR}/build.status"'
throw e
}
}
stage('Test') {
echo "=== Stage: Test ==="
if (buildStatus == 'success') {
try {
sh '''#!/usr/bin/env bash
set -euo pipefail
{
echo "=== docs freshness check ==="
npm run check:docs
echo ""
echo "=== builtin plugin ZIP tests ==="
npx tsx --test \
src/tests/ut/node/packaging/builtin-plugin-zip.test.ts
echo ""
echo "=== unit tests ==="
npm run test:unit -- \
--json \
--outputFile "${CI_LOG_DIR}/jest-unit-results.json"
} 2>&1 | tee "${CI_LOG_DIR}/test.log"
'''
testStatus = 'success'
sh 'echo "success" > "${CI_LOG_DIR}/test.status"'
} catch (e) {
testStatus = 'failure'
sh 'echo "failure" > "${CI_LOG_DIR}/test.status"'
buildResult = 'failure'
currentBuild.result = 'FAILURE'
}
} else {
testStatus = 'skipped'
sh 'echo "skipped" > "${CI_LOG_DIR}/test.status"'
}
}
} catch (err) {
buildResult = 'failure'
currentBuild.result = 'FAILURE'
echo "Pipeline 失败: ${err.getMessage()}"
} finally {
// ── 回报结果到 GitCode(始终在 node 上下文内) ──
stage('Report') {
if (buildResult == 'success') {
sh 'bash ci/scripts/gitcode-notify.sh comment-with-logs success'
} else {
sh 'bash ci/scripts/gitcode-notify.sh comment-with-logs failure'
}
}
// ── 清理环境 ──
stage('Cleanup') {
sh '''
echo "=== 清理构建环境 ==="
rm -rf dist || true
git remote remove pr-source 2>/dev/null || true
if [ -n "${PR_NUMBER:-}" ]; then
git checkout --detach HEAD 2>/dev/null || true
git branch -D "ci-merge-${PR_NUMBER}" 2>/dev/null || true
fi
rm -rf "${CI_LOG_DIR}" || true
echo "环境清理完成"
'''
}
}
}
}
}
}
}