-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: jobs #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feat: jobs #2
Changes from all commits
0ef766a
5cf77b3
dd6fa93
150d49a
65c41b0
8cb2e05
1b996ae
37b046b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| # Webhook-CD 配置文件示例 | ||
| # 已支持的特性: | ||
|
|
||
| name: webhook-cd-deployment-jobs | ||
|
|
||
| # 环境变量配置 | ||
| env: | ||
| # 全局环境变量 | ||
| DEPLOY_ENV: develop | ||
|
|
||
| # 分支配置 - 监听这些分支的合并请求,非必须 | ||
| jobs: | ||
| - on: | ||
| # 多个 Job 的目标分支不能重复 | ||
| branches: | ||
| - features/some-change | ||
| target: develop | ||
| deploy: | ||
| steps: | ||
| - name: 代码检查 | ||
| run: | ||
| - echo "检查完成" | ||
| - name: 构建项目 | ||
| run: | ||
| - echo "构建完成" | ||
| scripts: | ||
| pre_deploy: | ||
| - echo "开始部署前检查..." | ||
| - docker --version | ||
| - node --version | ||
| - npm --version | ||
|
|
||
| post_deploy: | ||
| - echo "部署完成,执行后续任务..." | ||
| - pm2 status | ||
| - df -h | ||
|
|
||
| cleanup: | ||
| - echo "执行清理任务..." | ||
| # - docker system prune -f | ||
| # - npm cache clean --force | ||
| - on: | ||
| branches: | ||
| - features/some-other-change | ||
| target: master | ||
| deploy: | ||
| steps: | ||
| - name: 代码检查 | ||
| run: | ||
| - echo "检查完成" | ||
| - name: 构建项目 | ||
| run: | ||
| - echo "构建完成" | ||
| scripts: | ||
| pre_deploy: | ||
| - echo "开始部署前检查..." | ||
| - docker --version | ||
| - node --version | ||
| - npm --version | ||
|
|
||
| post_deploy: | ||
| - echo "部署完成,执行后续任务..." | ||
| - pm2 status | ||
| - df -h | ||
|
|
||
| cleanup: | ||
| - echo "执行清理任务..." | ||
| # - docker system prune -f | ||
| # - npm cache clean --force |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| 'use strict'; | ||
|
|
||
| const is = require('@axiosleo/cli-tool/src/helper/is'); | ||
| const { _matchesBranch, _yaml } = require('./utils'); | ||
| const { _foreach, _shell, _exec } = require('@axiosleo/cli-tool/src/helper/cmd'); | ||
| const git = require('./git.js'); | ||
| const { debug } = require('@axiosleo/cli-tool'); | ||
| const { printer } = require('@axiosleo/cli-tool'); | ||
| const { _exists } = require('@axiosleo/cli-tool/src/helper/fs.js'); | ||
| const path = require('path'); | ||
|
|
||
| async function execSteps(label, scripts, cwd) { | ||
| if (!scripts) { | ||
| return true; | ||
| } | ||
| printer.yellow(label + ': ').println(); | ||
| try { | ||
| await _foreach(scripts, async (script) => { | ||
| if (is.string(script)) { | ||
| await _exec(script, cwd); | ||
| } else if (is.array(script)) { | ||
| await _foreach(script, async (line) => { | ||
| printer.yellow(line.name + ': ').println(); | ||
| await _exec(line.run, line.dir ? path.join(cwd, line.dir) : cwd); | ||
| }); | ||
| } else if (is.object(script) && !is.empty(script.run)) { | ||
| if (is.array(script.run)) { | ||
| await _foreach(script.run, async (line) => { | ||
| await _exec(line, script.dir ? path.join(cwd, script.dir) : cwd); | ||
| }); | ||
| } else { | ||
| await _exec(script.run, script.dir ? path.join(cwd, script.dir) : cwd); | ||
| } | ||
| } else { | ||
| debug.log({ script }); | ||
| printer.print('不支持的脚本类型: ').red(script).println(); | ||
| return false; | ||
| } | ||
| }); | ||
| return true; | ||
| } catch (err) { | ||
| printer.print('执行 ' + label + ' 脚本失败: ').red(err.message).println(); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| class Deployment { | ||
| /** | ||
| * @param {import("../index.d.ts").PlatformHandler} platformHandler | ||
| */ | ||
| constructor(platformHandler, options) { | ||
| this.platformHandler = platformHandler; | ||
| this.workspace = options.workspace; | ||
| this.cwd = options.cwd; | ||
| this.repo = options.repo; | ||
| this.platform = options.platform; | ||
| this.task = options.task; | ||
| this.status = options.status; | ||
| /** @type {import("../index.d.ts").DeployConfig} */ | ||
| this.deployConfig = options.deployConfig; | ||
|
|
||
| /** @type {import("../index.d.ts").DeployJob} */ | ||
| this.jobs = []; | ||
| } | ||
|
|
||
| async resolveJobs() { | ||
| const mergeList = (await this.platformHandler.getMergeRequest(this.task) || []).filter((i) => | ||
| !(i.source === i.target) | ||
| ); | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| if (!mergeList || !mergeList.length) { | ||
| throw new Error('没有需要部署的分支'); | ||
| } | ||
| if (this.deployConfig && is.array(this.deployConfig.jobs)) { | ||
| // 多个分支配置 | ||
| const jobs = this.deployConfig.jobs; | ||
| jobs.forEach((job) => { | ||
| const items = _matchesBranch(job.target, job, mergeList); | ||
| if (!items || !items.length) { | ||
| return; | ||
| } | ||
| this.jobs.push({ | ||
| target: job.target, | ||
|
cursor[bot] marked this conversation as resolved.
|
||
| items, | ||
| deployConfig: { | ||
| env: this.deployConfig.env, | ||
| ...job | ||
| } | ||
| }); | ||
| }); | ||
| } else { | ||
| const items = _matchesBranch(this.task.target, this.deployConfig, mergeList); | ||
| if (items && items.length) { | ||
| this.jobs.push({ | ||
| target: this.task.target, | ||
| items, | ||
| deployConfig: this.deployConfig | ||
| }); | ||
| } | ||
| } | ||
| return this.jobs; | ||
| } | ||
|
|
||
| async execJobs() { | ||
| if (!this.jobs || !this.jobs.length) { | ||
| throw new Error('没有需要部署的任务'); | ||
| } | ||
| // 检查多个 jobs 的 target 是否相同 | ||
| if (this.jobs.length > 1) { | ||
| const targets = this.jobs.map(job => job.target); | ||
| if (targets.length !== new Set(targets).size) { | ||
| throw new Error('多个 jobs 的 target 不能相同'); | ||
| } | ||
| } | ||
| let success = true; | ||
| await _foreach(this.jobs, async (job) => { | ||
| try { | ||
| // 基于 target 分支创建临时分支 | ||
| let { target, items, deployConfig } = job; | ||
| if (!target) { | ||
| target = this.task.target.indexOf('refs/heads/') === -1 ? this.task.target : this.task.target.replace('refs/heads/', ''); | ||
| } | ||
| let tmpBranch; | ||
| try { | ||
| await git.branch.checkout(target, true, this.cwd); | ||
| tmpBranch = `tmp/commit-${await git.commit.id(this.cwd)}`; | ||
| } catch (err) { | ||
| debug.log(err); | ||
| success = false; | ||
| return; | ||
| } | ||
| await git.branch.reset(target, this.cwd); | ||
| await git.branch.clear(this.cwd, false); | ||
| await _shell(`git checkout -b ${tmpBranch}`, this.cwd, false, false); | ||
|
|
||
| // 合并代码 | ||
| items = items.sort((a, b) => { | ||
| if (a.source === b.source) { | ||
| return 0; | ||
| } | ||
| return a.source > b.source ? 1 : -1; | ||
| }); | ||
| printer.yellow('需要合并的分支: ').println(); | ||
| items.forEach((item) => { | ||
| printer.yellow(item.source).print(' -> ').green(item.target).println(); | ||
| }); | ||
| let curr = '', last = null; | ||
| await _foreach(items, async (item) => { | ||
| curr = item; | ||
| let source = item.source.indexOf('refs/heads/') === -1 ? item.source : item.source.replace('refs/heads/', ''); | ||
| await _exec(`git merge origin/${source} -m 'merge: ${source}'`, this.cwd); | ||
| last = curr; | ||
| if (!await git.branch.exist(source, this.cwd)) { | ||
| printer.yellow('分支不存在: ').red(source).println(); | ||
| printer.print('Merge ').yellow(`${items.map(i => i.source).join(' | ')}`).println(' branches failed. last branch: ' + last.source); | ||
| return; | ||
| } | ||
| }); | ||
|
|
||
| // 合并代码后,再读一次 .cd.yml 文件,避免配置文件被修改,如果未修改,则与主分支形同 | ||
| const ymlConfigFile = path.join(this.cwd, '.cd.yml'); | ||
| if (!await _exists(ymlConfigFile)) { | ||
| printer.warning('没有找到 .cd.yml 文件,可能已被删除,请检查文件是否存在'); | ||
| return; | ||
| } | ||
| deployConfig = await _yaml(ymlConfigFile); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-reading YAML overwrites per-job config with top-level configHigh Severity When using multi-job configs, Additional Locations (1) |
||
| if (!deployConfig) { | ||
| printer.warning('读取 .cd.yml 文件失败'); | ||
| return; | ||
| } | ||
|
|
||
| // 合并配置 | ||
| const env = deployConfig.env || {}; | ||
| Object.keys(env).forEach((key) => { | ||
| process.env[key] = env[key]; | ||
| }); | ||
|
|
||
| const { pre_deploy, post_deploy, cleanup } = deployConfig.scripts || {}; | ||
| const deploy = deployConfig.deploy || []; | ||
| const steps = deploy.steps || []; | ||
|
|
||
| // 执行部署操作 | ||
| if (!await execSteps('执行预部署脚本', pre_deploy, this.cwd)) { | ||
| throw new Error('执行预部署脚本失败'); | ||
| } | ||
| if (!await execSteps('执行部署脚本', steps, this.cwd)) { | ||
| throw new Error('执行部署脚本失败'); | ||
| } | ||
| if (!await execSteps('执行后部署脚本', post_deploy, this.cwd)) { | ||
| throw new Error('执行后部署脚本失败'); | ||
| } | ||
| if (!await execSteps('执行清理脚本', cleanup, this.cwd)) { | ||
| throw new Error('执行清理脚本失败'); | ||
| } | ||
| } catch (err) { | ||
| debug.log(err); | ||
| printer.print('执行 ').yellow(job.target).print(' 部署操作失败: ').red(err.message).println(); | ||
| success = false; | ||
| return; | ||
| } | ||
| }); | ||
| return success; | ||
| } | ||
| } | ||
|
|
||
| module.exports = Deployment; | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DeployJobtype missingtargetfield used at runtimeMedium Severity
The
DeployJobinterface lacks atargetproperty, but the runtime code inresolveJobspushes objects withtarget: job.target, andexecJobsdestructurestargetfrom each job. Any consumer relying on this type definition would not knowtargetexists, leading to incorrect type-checking and missing IDE support for a critical field.