pipeline { agent none options { disableConcurrentBuilds() timestamps() } parameters { string(name: 'AGENT_LABEL', defaultValue: 'built-in', description: '构建节点标签') string(name: 'GENARRATIVE_WORKSPACE_ROOT', defaultValue: '', description: '源码根目录,留空则使用当前 Jenkins 工作区') string(name: 'BUILD_VERSION', defaultValue: '', description: '发布版本号,留空则使用 Jenkins BUILD_NUMBER') string(name: 'WEB_PORT', defaultValue: '25001', description: '发布包内静态网站端口,默认 25001') booleanParam(name: 'CLEAR_DATABASE', defaultValue: false, description: '部署时是否清空 SpacetimeDB 数据后再发布 wasm') booleanParam(name: 'RUN_NPM_CI', defaultValue: false, description: '构建前是否执行 npm ci') string(name: 'DEPLOY_JOB_NAME', defaultValue: 'Genarrative-Deploy', description: '部署流水线作业名') string(name: 'DEPLOY_DIRECTORY', defaultValue: '/var/lib/jenkins/deploy/Genarrative', description: '固定部署目录') booleanParam(name: 'RUN_DEPLOY_HOOKS_WITH_SUDO', defaultValue: true, description: 'start.sh / stop.sh 是否通过 sudo -n 执行') } stages { stage('构建发布包') { agent { label "${params.AGENT_LABEL}" } steps { script { // 统一在脚本块里计算版本号,避免 declarative environment 对表达式求值不一致。 env.EFFECTIVE_BUILD_VERSION = params.BUILD_VERSION?.trim() ? params.BUILD_VERSION.trim() : env.BUILD_NUMBER // 允许 Jenkins Job 直接指定固定源码目录,未指定时回退到当前工作区。 env.WORKSPACE_ROOT = params.GENARRATIVE_WORKSPACE_ROOT?.trim() ? params.GENARRATIVE_WORKSPACE_ROOT.trim() : pwd() def webPort = params.WEB_PORT?.trim() if (!webPort) { error('WEB_PORT 不能为空。') } if (!(webPort ==~ /^[0-9]+$/)) { error("WEB_PORT 必须是数字端口,当前值: ${webPort}") } if (webPort.length() > 5) { error("WEB_PORT 必须在 1-65535 之间,当前值: ${webPort}") } def parsedWebPort = webPort.toInteger() if (parsedWebPort < 1 || parsedWebPort > 65535) { error("WEB_PORT 必须在 1-65535 之间,当前值: ${webPort}") } // 后续构建与下游部署都使用校验后的同一端口值,避免参数空格导致上下游不一致。 env.EFFECTIVE_WEB_PORT = webPort // 记录当前构建节点名,部署阶段必须回到同一节点读取本地 build 目录。 env.SOURCE_NODE_NAME = env.NODE_NAME } dir("${env.WORKSPACE_ROOT}") { checkout scm sh ''' bash -lc ' set -euo pipefail # 构建前清理工作区内的 Git 变更和未跟踪文件,避免复用固定源码目录时受到上次构建残留影响。 # 这里不使用 -x,避免删除 node_modules 等忽略目录后与 RUN_NPM_CI=false 的配置冲突。 git reset --hard HEAD git clean -fd ' ''' script { // 是否重装依赖交给流水线参数决定,避免每次构建都重复执行 npm ci。 if (params.RUN_NPM_CI) { sh 'bash -lc "npm ci"' } } sh """ bash -lc ' set -euo pipefail # 构建并部署流水线显式透传 Web 端口,确保部署包默认监听 25001,同时允许 Jenkins 参数覆盖。 npm run deploy:rust:remote -- --skip-upload --name "${env.EFFECTIVE_BUILD_VERSION}" --web-port "${env.EFFECTIVE_WEB_PORT}" test -d "build/${env.EFFECTIVE_BUILD_VERSION}" ' """ archiveArtifacts artifacts: "build/${env.EFFECTIVE_BUILD_VERSION}/**", fingerprint: true } } } stage('触发部署') { steps { // 本阶段没有声明 agent,确保触发下游前已经释放构建节点,避免单执行器死锁。 build job: params.DEPLOY_JOB_NAME, wait: true, propagate: true, parameters: [ string(name: 'SOURCE_NODE_NAME', value: env.SOURCE_NODE_NAME), string(name: 'SOURCE_WORKSPACE_ROOT', value: env.WORKSPACE_ROOT), string(name: 'BUILD_VERSION', value: env.EFFECTIVE_BUILD_VERSION), string(name: 'DEPLOY_DIRECTORY', value: params.DEPLOY_DIRECTORY), string(name: 'WEB_PORT', value: env.EFFECTIVE_WEB_PORT), booleanParam(name: 'CLEAR_DATABASE', value: params.CLEAR_DATABASE), booleanParam(name: 'RUN_DEPLOY_HOOKS_WITH_SUDO', value: params.RUN_DEPLOY_HOOKS_WITH_SUDO), string(name: 'EXPECTED_UPSTREAM_JOB', value: env.JOB_NAME), ] } } } post { success { echo "构建并部署完成,版本号: ${env.EFFECTIVE_BUILD_VERSION}" } } }