@ts-org/jenkins-cli 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -13
- package/dist/cli.js +23 -23
- package/package.json +4 -8
- package/schemas/jenkins.json +66 -0
- package/dist/index.d.ts +0 -71
- package/dist/index.js +0 -21
package/README.md
CHANGED
|
@@ -30,13 +30,15 @@ jenkins-cli
|
|
|
30
30
|
|
|
31
31
|
CLI 会自动加载配置,列出 Git 分支和部署环境供你选择。
|
|
32
32
|
|
|
33
|
-

|
|
33
|
+

|
|
34
34
|
|
|
35
35
|
### ⚙️ 配置
|
|
36
36
|
|
|
37
37
|
在项目根目录创建 `jenkins-cli.yaml`:
|
|
38
38
|
|
|
39
39
|
```yaml
|
|
40
|
+
# yaml-language-server: $schema=https://cdn.jsdelivr.net/npm/@ts-org/jenkins-cli@latest/schemas/jenkins.json
|
|
41
|
+
|
|
40
42
|
# Jenkins API 地址,格式: http(s)://username:token@host:port
|
|
41
43
|
apiToken: http://user:token@jenkins.example.com
|
|
42
44
|
|
|
@@ -49,15 +51,15 @@ modes:
|
|
|
49
51
|
- sit
|
|
50
52
|
- uat
|
|
51
53
|
```
|
|
54
|
+
建议在 `jenkins-cli.yaml` 顶部添加 `# yaml-language-server: $schema=https://cdn.jsdelivr.net/npm/@ts-org/jenkins-cli@latest/schemas/jenkins.json`,以有更好的开发体验,如示例。
|
|
52
55
|
|
|
53
56
|
#### 扩展交互参数
|
|
54
57
|
|
|
55
58
|
通过 `configs` 字段,你可以添加自定义的交互式提问,其结果将作为参数传递给 Jenkins。
|
|
56
59
|
|
|
57
|
-
- `type`: 支持 `input`, `
|
|
58
|
-
- `choices`, `default`, `validate`: 支持从本地 TS/JS 文件动态加载。
|
|
59
|
-
|
|
60
|
-
- `path:Var:varName`:获取 `varName` 变量的值 (支持 Promise)。
|
|
60
|
+
- `type`: 支持 `input`, `number`, `confirm`, `list`, `rawlist`, `checkbox`, `password`等类型。
|
|
61
|
+
- `choices`, `default`, `validate`, `when`, `filter`, `transformer`: 支持从本地 TS/JS 文件动态加载。
|
|
62
|
+
- `path:exportName`:自动判断并使用导出值;如果是函数则调用 (支持 async)。
|
|
61
63
|
- `name`: 参数名,注意不要使用 `branch`, `mode`, `modes` 等保留字。
|
|
62
64
|
|
|
63
65
|
**示例:**
|
|
@@ -68,30 +70,42 @@ configs:
|
|
|
68
70
|
name: version
|
|
69
71
|
message: '请输入版本号:'
|
|
70
72
|
default: '1.0.0'
|
|
73
|
+
filter: src/config.ts:filterVersion
|
|
74
|
+
transformer: src/config.ts:transformVersion
|
|
71
75
|
- type: list
|
|
72
76
|
name: service
|
|
73
77
|
message: '请选择服务:'
|
|
74
|
-
choices: src/config.ts:
|
|
78
|
+
choices: src/config.ts:getServices
|
|
75
79
|
- type: input
|
|
76
80
|
name: ticket
|
|
77
81
|
message: '请输入关联的工单号:'
|
|
78
|
-
validate: src/config.ts:
|
|
82
|
+
validate: src/config.ts:validateTicket
|
|
79
83
|
```
|
|
80
84
|
|
|
81
85
|
对应的 `src/config.ts`:
|
|
86
|
+
|
|
82
87
|
```ts
|
|
83
88
|
export async function getServices() {
|
|
84
|
-
return ['user-center', 'order-service']
|
|
89
|
+
return ['user-center', 'order-service']
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function filterVersion(input: string) {
|
|
93
|
+
return String(input ?? '').trim()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function transformVersion(input: string) {
|
|
97
|
+
const value = String(input ?? '')
|
|
98
|
+
return value ? `v${value}` : value
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
export function validateTicket(input: string) {
|
|
88
|
-
return /^(feat|fix|refactor)-[a-zA-Z0-9]+$/.test(input) || '工单号格式不正确'
|
|
102
|
+
return /^(feat|fix|refactor)-[a-zA-Z0-9]+$/.test(input) || '工单号格式不正确'
|
|
89
103
|
}
|
|
90
104
|
```
|
|
91
105
|
|
|
92
106
|
> **提示**: 配置也支持写在 `package.json` 的 `jenkins-cli` 字段里。
|
|
93
107
|
>
|
|
94
|
-
> **查找顺序**: `jenkins-cli.yaml` > `package.json` >
|
|
108
|
+
> **查找顺序**: `jenkins-cli.yaml` > `package.json` > 祖先目录的 `jenkins-cli.yaml`。
|
|
95
109
|
|
|
96
110
|
### 🤖 命令参考
|
|
97
111
|
|
|
@@ -141,8 +155,17 @@ jenkins-cli job info
|
|
|
141
155
|
# 查看指定构建号的日志
|
|
142
156
|
jenkins-cli log 1234
|
|
143
157
|
|
|
144
|
-
#
|
|
145
|
-
jenkins-cli log
|
|
158
|
+
# 查看最后一次构建日志
|
|
159
|
+
jenkins-cli log last
|
|
160
|
+
|
|
161
|
+
# 查看最后一次构建成功日志
|
|
162
|
+
jenkins-cli log last -s success
|
|
163
|
+
|
|
164
|
+
# 查看最后一次构建失败日志
|
|
165
|
+
jenkins-cli log last -s failed
|
|
166
|
+
|
|
167
|
+
# 仅输出最后 N 行
|
|
168
|
+
jenkins-cli log 1234 -t 200
|
|
146
169
|
```
|
|
147
170
|
|
|
148
171
|
#### `stop` - 停止构建
|
|
@@ -196,4 +219,4 @@ jenkins-cli whoami
|
|
|
196
219
|
|
|
197
220
|
**Q: `stop` 或 `queue cancel` 命令返回 403 Forbidden?**
|
|
198
221
|
|
|
199
|
-
A: 通常是 Jenkins 用户权限不足 (需要 `Job > Cancel` 权限)。请确保你使用的是 API Token 而非密码,并检查 Jenkins 的 CSRF 设置。工具会自动处理 CSRF,但权限问题需要手动排查。
|
|
222
|
+
A: 通常是 Jenkins 用户权限不足 (需要 `Job > Cancel` 权限)。请确保你使用的是 API Token 而非密码,并检查 Jenkins 的 CSRF 设置。工具会自动处理 CSRF,但权限问题需要手动排查。
|
package/dist/cli.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import Ye from 'inquirer';
|
|
4
|
+
import g from 'chalk';
|
|
5
|
+
import We from 'ora';
|
|
6
6
|
import { exec } from 'child_process';
|
|
7
|
-
import
|
|
7
|
+
import ze, { promisify } from 'util';
|
|
8
8
|
import C from 'fs-extra';
|
|
9
|
-
import
|
|
9
|
+
import Ne from 'js-yaml';
|
|
10
10
|
import S from 'path';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
11
|
+
import Fe from 'axios';
|
|
12
|
+
import _e from 'jiti';
|
|
13
13
|
import { fileURLToPath } from 'url';
|
|
14
14
|
import { XMLParser } from 'fast-xml-parser';
|
|
15
15
|
|
|
16
|
-
var
|
|
17
|
-
`).filter(
|
|
18
|
-
{PACKAGE_JSON}, and walking up from cwd. Required: apiToken, job, modes (non-empty array)`);return {...r,projectRoot:
|
|
16
|
+
var H="3.0.3",Y="Jenkins deployment CLI";var V=promisify(exec);async function z(){try{let{stdout:e}=await V("git branch --show-current"),t=e.trim();if(!t)throw new Error("Not on any branch (detached HEAD state)");return t}catch{throw new Error("Failed to get current branch. Are you in a git repository?")}}async function X(){try{let{stdout:e}=await V('git branch -r --format="%(refname:short)"');return e.split(`
|
|
17
|
+
`).filter(t=>t.includes("/")&&!t.includes("HEAD"))}catch{throw new Error("Failed to list branches. Are you in a git repository?")}}var B="jenkins-cli.yaml",T="package.json",U="jenkins-cli",Ee="jenkinsCli";function ee(e,t=process.cwd()){let n=t;for(;;){let r=S.join(n,e);if(C.existsSync(r))return r;let o=S.dirname(n);if(o===n)return null;n=o;}}function Be(e=process.cwd()){return ee(B,e)}function Le(e=process.cwd()){let t=ee(T,e);return t?S.dirname(t):null}function Ie(e){return !!(e.apiToken&&e.job&&Array.isArray(e.modes)&&e.modes.length>0)}async function Z(e){let t=await C.readFile(e,"utf-8"),n=Ne.load(t);if(!n||typeof n!="object")throw new Error(`Invalid config in ${B}: expected a YAML object`);return n}async function qe(e){let t=S.join(e,T);if(!await C.pathExists(t))return {};let n=await C.readFile(t,"utf-8"),r=JSON.parse(n),o=r[U]??r[Ee];if(o==null)return {};if(typeof o!="object"||Array.isArray(o))throw new Error(`Invalid ${T} config: "${U}" must be an object`);return o}async function te(){let e=process.cwd(),t=Le(e)??e,n=S.join(t,B),r={},o=S.dirname(t),a=Be(o);a&&await C.pathExists(a)&&(r=await Z(a));let i=await qe(t);r={...r,...i};let s=await C.pathExists(n)?await Z(n):{};if(r={...r,...s},!Ie(r))throw new Error(`Config incomplete or not found: tried ${B} in project root, "${U}" in $
|
|
18
|
+
{PACKAGE_JSON}, and walking up from cwd. Required: apiToken, job, modes (non-empty array)`);return {...r,projectRoot:t}}var D={maxRedirects:0,validateStatus:e=>e>=200&&e<400};function w(e){return `job/${e.split("/").map(n=>n.trim()).filter(Boolean).map(encodeURIComponent).join("/job/")}`}function W(e){let t=Array.isArray(e)?e.find(o=>o?.parameters):void 0,n=Array.isArray(t?.parameters)?t.parameters:[],r={};for(let o of n)o?.name&&(r[String(o.name)]=o?.value);return r}function Oe(e){try{let n=new URL(e).pathname.split("/").filter(Boolean),r=[];for(let o=0;o<n.length-1;o++)n[o]==="job"&&n[o+1]&&r.push(decodeURIComponent(n[o+1]));return r.join("/")}catch{return ""}}async function Te(e,t){let r=(await e.axios.get(new URL("api/json?tree=number,url,building,result,timestamp,duration,estimatedDuration,fullDisplayName,displayName,actions[parameters[name,value]]",t).toString())).data??{};return {...r,parameters:W(r.actions)}}function Ue(e,t=400){try{let r=(typeof e=="string"?e:JSON.stringify(e??"",null,2)).trim();return r?r.length>t?`${r.slice(0,t)}...`:r:""}catch{return ""}}function re(e){let t=e.match(/^(https?):\/\/([^:]+):([^@]+)@(.+)$/);if(!t)throw new Error("Invalid apiToken format. Expected: http(s)://username:token@host:port");let[,n,r,o,a]=t,i=`${n}://${a.replace(/\/$/,"")}/`,s=Fe.create({baseURL:i,auth:{username:r,password:o},proxy:!1,timeout:3e4});return {baseURL:i,auth:{username:r,password:o},axios:s,crumbForm:void 0}}async function oe(e,t=5e3){try{let n=await e.axios.get("crumbIssuer/api/json",{timeout:t}),{data:r}=n;r?.crumbRequestField&&r?.crumb&&(e.axios.defaults.headers.common[r.crumbRequestField]=r.crumb,e.crumbForm={field:r.crumbRequestField,value:r.crumb});let o=n.headers["set-cookie"];if(o){let a=Array.isArray(o)?o.map(i=>i.split(";")[0].trim()):[String(o).split(";")[0].trim()];e.axios.defaults.headers.common.Cookie=a.join("; ");}}catch{}}function M(e){return e.crumbPromise||(e.crumbPromise=oe(e,5e3)),e.crumbPromise}async function _(e){await M(e),e.crumbForm||(e.crumbPromise=oe(e,15e3),await e.crumbPromise);}async function $(e,t){let r=(await e.axios.get("queue/api/json?tree=items[id,task[name],actions[parameters[name,value]],why]")).data.items;if(t){let o=t.trim(),a=o.split("/").filter(Boolean).pop();return r.filter(i=>{let s=i.task?.name;return s===o||(a?s===a:!1)})}return r}async function v(e,t){let n=await e.axios.get(`${w(t)}/api/json?tree=builds[number,url,building,result,timestamp,duration,estimatedDuration,actions[parameters[name,value]]]`);return (Array.isArray(n.data?.builds)?n.data.builds:[]).filter(o=>o?.building).map(o=>({...o,parameters:W(o.actions)}))}async function L(e){let t=await e.axios.get("computer/api/json?tree=computer[displayName,executors[currentExecutable[number,url]],oneOffExecutors[currentExecutable[number,url]]]"),n=Array.isArray(t.data?.computer)?t.data.computer:[],r=[];for(let i of n){let s=Array.isArray(i?.executors)?i.executors:[],u=Array.isArray(i?.oneOffExecutors)?i.oneOffExecutors:[];for(let l of [...s,...u]){let c=l?.currentExecutable;c?.url&&r.push({number:Number(c.number),url:String(c.url)});}}let o=new Map;for(let i of r)i.url&&o.set(i.url,i);return (await Promise.all([...o.values()].map(async i=>{let s=await Te(e,i.url),u=Oe(String(s?.url??i.url));return {...s,job:u}}))).filter(i=>i?.building)}async function P(e,t){return await _(e),await e.axios.post("queue/cancelItem",new URLSearchParams({id:t.toString()}),D),!0}async function A(e,t,n){try{await _(e),await e.axios.post(`${w(t)}/${n}/stop/`,new URLSearchParams({}),D);}catch(r){let o=r.response?.status;if(o===403){let a=typeof r.response?.data=="string"?r.response.data.slice(0,200):JSON.stringify(r.response?.data??"").slice(0,200),i=/crumb|csrf/i.test(a);console.log(g.red("[Error] 403 Forbidden: Jenkins refused the stop request (likely permissions or CSRF).")),console.log(g.yellow('[Hint] Confirm the current user has "Job" -> "Cancel" permission on this job.')),console.log(i?"[Hint] Jenkins returned a crumb/CSRF error. Ensure crumb + session cookie are sent, or use API token (not password).":a?`[Hint] Jenkins response: ${a}`:'[Hint] If "Job/Cancel" is already granted, also try granting "Run" -> "Update" (some versions use it for stop).');return}if(o===404||o===400||o===500){console.log(`[Info] Build #${n} could not be stopped (HTTP ${o}). Assuming it finished.`);return}throw r}}async function I(e,t,n){let r="branch",o="mode",a=n[o];a&&console.log(`Checking for conflicting builds for mode: ${a}...`);let[i,s]=await Promise.all([$(e,t),v(e,t)]),u=(m,f)=>{let b=m.actions?.find(R=>R.parameters);return b?b.parameters.find(R=>R.name===f)?.value:void 0},l=n[r];if(a&&l){let m=i.find(f=>u(f,o)===a&&u(f,r)===l);if(m)return new URL(`queue/item/${m.id}/`,e.baseURL).toString()}let c=!1;if(a)for(let m of s)u(m,o)===a&&(console.log(`Stopping running build #${m.number} (mode=${a})...`),await A(e,t,m.number),c=!0);c&&await new Promise(m=>setTimeout(m,1e3));try{return await _(e),(await e.axios.post(`${w(t)}/buildWithParameters/`,new URLSearchParams({...n}),D)).headers.location||""}catch(m){let f=m?.response?.status,b=Ue(m?.response?.data);if(f===400){let y=[];y.push("Hint: Jenkins returned 400. Common causes: job is not parameterized, parameter names do not match, or the job endpoint differs (e.g. multibranch jobs)."),y.push(`Hint: This CLI sends parameters "${r}" and "${o}". Ensure your Jenkins Job has matching parameter names.`);let R=b?`
|
|
19
19
|
Jenkins response (snippet):
|
|
20
|
-
${
|
|
21
|
-
${
|
|
22
|
-
`)}`)}if(
|
|
20
|
+
${b}`:"";throw new Error(`Request failed with status code 400.${R}
|
|
21
|
+
${y.join(`
|
|
22
|
+
`)}`)}if(f){let y=b?`
|
|
23
23
|
Jenkins response (snippet):
|
|
24
|
-
${
|
|
24
|
+
${b}`:"";throw new Error(`Request failed with status code ${f}.${y}`)}throw m}}async function ie(e){return (await e.axios.get("whoAmI/api/json")).data}async function se(e){return (await e.axios.get("api/json?tree=jobs[name,url,color]")).data.jobs??[]}async function q(e,t){return (await e.axios.get(`${w(t)}/api/json?tree=name,fullName,url,buildable,inQueue,nextBuildNumber,color,healthReport[description,score],lastBuild[number,url,building,result,timestamp,duration],lastCompletedBuild[number,url,result,timestamp,duration],lastSuccessfulBuild[number,url,building,result,timestamp,duration],lastFailedBuild[number,url,building,result,timestamp,duration]`)).data}async function ae(e,t,n){let r=Math.max(1,n?.limit??20);return ((await e.axios.get(`${w(t)}/api/json?tree=builds[number,url,building,result,timestamp,duration,actions[parameters[name,value]]]{0,${r}}`)).data.builds??[]).map(i=>({number:i.number,result:i.result,building:i.building,timestamp:i.timestamp,duration:i.duration,url:i.url,parameters:W(i.actions)}))}async function ce(e,t,n){let r=await e.axios.get(`${w(t)}/${n}/consoleText`,{responseType:"text"});return String(r.data??"")}async function ue(e,t){return ((await e.axios.get(`${w(t)}/api/json?tree=property[parameterDefinitions[name,type,description,defaultParameterValue[value],choices]]`)).data?.property??[]).flatMap(a=>a?.parameterDefinitions??[]).filter(Boolean).map(a=>({name:a.name,type:a.type,description:a.description,default:a.defaultParameterValue?.value,choices:a.choices}))}async function le(e,t){let n=await e.axios.get(`${w(t)}/config.xml`,{responseType:"text"});return String(n.data??"")}async function p(){let e=We("Loading configuration...").start();try{let t=await te();e.succeed("Configuration loaded");let n=re(t.apiToken);return M(n),{config:t,jenkins:n}}catch(t){e.fail(g.red(t.message)),process.exit(1);}}var Ge=_e(fileURLToPath(import.meta.url),{interopDefault:!0});async function fe(e){return await Promise.resolve(e)}function Ke(e){let t=String(e??"").trim();if(!t)return null;let n=t.split(":");if(n.length<2)return null;if(n.length===2){let[a,i]=n;return !a||!i?null:{file:a,exportName:i}}let r=n.at(-1);if(!r)return null;let o=n.slice(0,-2).join(":");return o?{file:o,exportName:r}:null}function He(e,t){return S.isAbsolute(t)?t:S.join(e,t)}function pe(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}async function N(e,t){let n=Ke(t);if(!n)return null;let r=He(e,n.file);if(!await C.pathExists(r))throw new Error(`configs reference not found: ${n.file}`);let o=Ge(r),a=pe(o)?o:{default:o},i=n.exportName==="default"?a.default:a[n.exportName];if(i===void 0)throw new Error(`configs reference export not found: ${t}`);return {value:i}}async function de(e,t){if(typeof t!="string")return t;let n=await N(e,t);if(!n)return t;if(typeof n.value=="function"){let r=n.value();return await fe(r)}return await fe(n.value)}async function ge(e,t){let n=Array.isArray(e)?e:[];if(n.length===0)return [];let r=String(t.projectRoot??"").trim()||process.cwd(),o=[];for(let a of n){if(!pe(a))continue;let i={...a};if(i.choices!==void 0){i.choices=await de(r,i.choices);let s=String(i.type??"").toLowerCase(),u=i.choices;if((s==="list"||s==="rawlist"||s==="checkbox")&&typeof u!="function"&&!Array.isArray(u))if(u==null)i.choices=[];else if(typeof u=="string"||typeof u=="number"||typeof u=="boolean")i.choices=[String(u)];else if(typeof u[Symbol.iterator]=="function")i.choices=Array.from(u).map(c=>String(c));else throw new Error(`configs "${String(i.name??"")}" choices must resolve to an array (got ${typeof u})`)}if(i.default!==void 0&&(i.default=await de(r,i.default)),i.when!==void 0){let s=i.when;if(typeof s=="string"){let u=await N(r,s);if(!u)i.when=s;else {if(typeof u.value!="function"&&typeof u.value!="boolean")throw new Error(`when reference must be a function or boolean: ${s}`);i.when=u.value;}}else (typeof s=="function"||typeof s=="boolean")&&(i.when=s);}if(i.validate!==void 0){let s=i.validate;if(typeof s=="string"){let u=await N(r,s);if(!u)i.validate=s;else {if(typeof u.value!="function")throw new Error(`validate reference must be a function: ${s}`);i.validate=u.value;}}else typeof s=="function"&&(i.validate=s);}if(i.filter!==void 0){let s=i.filter;if(typeof s=="string"){let u=await N(r,s);if(!u)i.filter=s;else {if(typeof u.value!="function")throw new Error(`filter reference must be a function: ${s}`);i.filter=u.value;}}else typeof s=="function"&&(i.filter=s);}if(i.transformer!==void 0){let s=i.transformer;if(typeof s=="string"){let u=await N(r,s);if(!u)i.transformer=s;else {if(typeof u.value!="function")throw new Error(`transformer reference must be a function: ${s}`);i.transformer=u.value;}}else typeof s=="function"&&(i.transformer=s);}o.push(i);}return o}function be(e,t){let n=new Set(t),r={};for(let[o,a]of Object.entries(e))if(!n.has(o)&&a!==void 0){if(a===null){r[o]="";continue}if(Array.isArray(a)){r[o]=a.map(i=>String(i)).join(",");continue}if(typeof a=="object"){try{r[o]=JSON.stringify(a);}catch{r[o]=String(a);}continue}r[o]=String(a);}return r}async function ye(){console.log(g.bold.blue(`
|
|
25
25
|
\u{1F680} Jenkins CLI - Jenkins Deployment CLI
|
|
26
|
-
`));let{config:
|
|
27
|
-
\u{1F44B} Cancelled by user`)),process.exit(130);},
|
|
28
|
-
\u{1F44B} Cancelled by user`)),process.exit(0)),console.log(
|
|
29
|
-
\u274C Prompt failed: ${
|
|
30
|
-
`)),process.exit(1);}finally{process.off("SIGINT",o);}console.log();let
|
|
26
|
+
`));let{config:e,jenkins:t}=await p(),[n,r]=await Promise.all([X(),z()]),o=()=>{console.log(g.yellow(`
|
|
27
|
+
\u{1F44B} Cancelled by user`)),process.exit(130);},a,i={};try{process.prependOnceListener("SIGINT",o);let c=await ge(e.configs,{projectRoot:e.projectRoot}),m=new Set(["branch","modes","mode"]);for(let b of c){let y=String(b?.name??"").trim();if(m.has(y))throw new Error(`configs name "${y}" is reserved. Use another name.`)}let f=await Ye.prompt([{type:"list",name:"branch",message:"\u8BF7\u9009\u62E9\u8981\u6253\u5305\u7684\u5206\u652F:",choices:n,default:n.indexOf(r)},{type:"checkbox",name:"modes",message:"\u8BF7\u9009\u62E9\u8981\u6253\u5305\u7684\u73AF\u5883:",choices:e.modes,validate:b=>b.length===0?"You must select at least one environment":!0},...c]);a={branch:String(f.branch??""),modes:f.modes??[]},i=be(f,["branch","modes"]);}catch(c){let m=c,f=m?.message?String(m.message):String(m);((m?.name?String(m.name):"")==="ExitPromptError"||f.toLowerCase().includes("cancelled")||f.toLowerCase().includes("canceled"))&&(console.log(g.yellow(`
|
|
28
|
+
\u{1F44B} Cancelled by user`)),process.exit(0)),console.log(g.red(`
|
|
29
|
+
\u274C Prompt failed: ${f}
|
|
30
|
+
`)),process.exit(1);}finally{process.off("SIGINT",o);}console.log();let s=!1,u=()=>{s||(s=!0,console.log());},l=a.modes.map(async c=>{let m=We(`Triggering build for ${g.yellow(c)}...`).start();try{let f=await I(t,e.job,{...i,branch:a.branch,mode:c});u(),m.succeed(`${g.green(c)} - Triggered! Queue: ${f}`);}catch(f){throw u(),m.fail(`${g.red(c)} - ${f.message}`),f}});try{await Promise.all(l);}catch{console.log(g.bold.red(`
|
|
31
31
|
\u274C Some builds failed. Check the output above.
|
|
32
|
-
`)),process.exit(1);}}function
|
|
33
|
-
`);console.log(
|
|
34
|
-
`));return}process.stdout.write(a);});}function ke(
|
|
32
|
+
`)),process.exit(1);}}function h(e,t){return (t??"").trim()||e}function x(e,t){let n=Number(e);if(!Number.isInteger(n)||n<0)throw new Error(`${t} must be a non-negative integer`);return n}function we(e){let t=e.command("queue").description("Operations for the build queue");t.command("list").alias("ls").description("List items waiting in the build queue").option("-j, --job <job>","Show only queue items for the specified job").action(async n=>{let{config:r,jenkins:o}=await p(),a=n.job?h(r.job,n.job):void 0,i=await $(o,a);console.table((i??[]).map(s=>({id:s.id,job:s.task?.name,why:s.why})));}),t.command("cancel").description("Cancel a queued build item").argument("<id>").action(async n=>{let{jenkins:r}=await p(),o=await P(r,x(n,"id"));console.log(o?g.green("Cancelled"):g.red("Cancel failed"));});}function d(e){return {[ze.inspect.custom]:()=>e}}function k(e){let t=String(e??""),n=t.trim();if(!n)return d(g.gray("-"));let r=a=>d(a),o=n.toLowerCase();if(o.startsWith("http://")||o.startsWith("https://"))try{let i=(new URL(n).hostname??"").toLowerCase(),s=i==="localhost"||i==="127.0.0.1"||i==="::1",u=/^10\./.test(i)||/^192\.168\./.test(i)||/^127\./.test(i)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(i);return r(s||u?g.cyanBright(t):g.hex("#4EA1FF")(t))}catch{return r(g.hex("#4EA1FF")(t))}return o.startsWith("ws://")||o.startsWith("wss://")?r(g.cyan(t)):o.startsWith("ssh://")||o.startsWith("git+ssh://")||o.startsWith("git@")?r(g.magenta(t)):o.startsWith("file://")?r(g.yellow(t)):o.startsWith("mailto:")?r(g.green(t)):o.startsWith("javascript:")||o.startsWith("data:")?r(g.red(t)):n.startsWith("/")||n.startsWith("./")||n.startsWith("../")?r(g.cyan(t)):/^[a-z0-9.-]+(?::\d+)?(\/|$)/i.test(n)?r(g.blue(t)):r(t)}function J(e){let t=new Date(e),n=r=>String(r).padStart(2,"0");return `${t.getFullYear()}-${n(t.getMonth()+1)}-${n(t.getDate())} ${n(t.getHours())}:${n(t.getMinutes())}:${n(t.getSeconds())}`}function E(e,t){if(t===!0)return d(g.cyan("BUILDING"));let n=String(e??"").toUpperCase();return d(n?n==="SUCCESS"?g.green(n):n==="ABORTED"?g.gray(n):n==="FAILURE"?g.red(n):n==="UNSTABLE"?g.yellow(n):n==="NOT_BUILT"?g.gray(n):n:"-")}function Q(e){let t=String(e??"");if(!t)return d("-");let n=t.endsWith("_anime"),r=n?t.slice(0,-6):t,o=(()=>{switch(r){case"blue":return n?g.blueBright(t):g.blue(t);case"red":return n?g.redBright(t):g.red(t);case"yellow":return n?g.yellowBright(t):g.yellow(t);case"aborted":return g.gray(t);case"disabled":case"notbuilt":case"grey":case"gray":return g.gray(t);default:return t}})();return d(o)}function Xe(e){let t=Object.entries(e??{}).filter(([r])=>r);if(!t.length)return d("-");t.sort(([r],[o])=>r.localeCompare(o,"en",{sensitivity:"base"}));let n=t.map(([r,o])=>{if(o===void 0)return `${r}=`;if(o===null)return `${r}=null`;if(typeof o=="string"||typeof o=="number"||typeof o=="boolean")return `${r}=${o}`;try{return `${r}=${JSON.stringify(o)}`}catch{return `${r}=${String(o)}`}}).join(", ");return d(n)}function je(e){let t=e.command("builds").description("Build operations");t.command("list").alias("ls").description("List builds").option("-j, --job <job>","Specify job").option("-n, --limit <n>","Limit","20").action(async n=>{let{config:r,jenkins:o}=await p(),a=h(r.job,n.job),i=await ae(o,a,{limit:Number(n.limit)});console.table(i.map(s=>({number:s.number,result:E(s.result,s.building),building:s.building,durationS:Number((Number(s.duration??0)/1e3).toFixed(3)),date:d(J(Number(s.timestamp??0)))})));}),t.command("running").description("List running builds").option("-j, --job <job>","Specify job").option("-a, --all","Query all running builds on Jenkins").action(async n=>{let{config:r,jenkins:o}=await p(),a=n.all?await L(o):await v(o,h(r.job,n.job));console.table((a??[]).map(i=>({...n.all?{job:d(String(i.job??""))}:{},number:i.number,date:d(i.timestamp?J(Number(i.timestamp)):"-"),parameters:Xe(i.parameters),url:k(String(i.url??""))})));});}function xe(e){let t=e.command("log").description("Fetch build console log"),n=async(r,o,a)=>{let{jenkins:i}=await p(),s=await ce(i,r,o);if(a.tail){let u=x(a.tail,"tail"),l=s.split(`
|
|
33
|
+
`);console.log(l.slice(Math.max(0,l.length-u)).join(`
|
|
34
|
+
`));return}process.stdout.write(s);};t.argument("<id>").option("-j, --job <job>","Specify job").option("-t, --tail <n>","Only output the last N lines").action(async(r,o)=>{let{config:a}=await p(),i=h(a.job,o.job),s=x(r,"id");await n(i,s,o);}),t.command("last").description("Fetch the latest build log").option("-j, --job <job>","Specify job").option("-s, --status <status>","success | failed | any","any").option("-t, --tail <n>","Only output the last N lines").action(async r=>{let{config:o,jenkins:a}=await p(),i=h(o.job,r.job),s=String(r.status??"any").toLowerCase();if(!["any","success","failed"].includes(s))throw new Error(`Invalid status: ${r.status}. Use success | failed | any.`);let u=await q(a,i),l=s==="success"?u?.lastSuccessfulBuild:s==="failed"?u?.lastFailedBuild:u?.lastBuild,c=Number(l?.number);if(!c){let m=s==="success"?"successful":s==="failed"?"failed":"last";throw new Error(`No ${m} build found for job: ${i}`)}await n(i,c,r);});}function ke(e){e.command("stop").description("Clear queued builds and stop running builds").argument("[id]","Build number").option("-j, --job <job>","Specify Jenkins job").option("-a, --all","Cancel queued items for this job and stop all running builds for it").option("-A, --ALL","Cancel all queued items and stop all running builds on Jenkins").action(async(t,n)=>{let{config:r,jenkins:o}=await p(),a=h(r.job,n.job);if(n.ALL){let[i,s]=await Promise.all([$(o),L(o)]),u=(i??[]).map(c=>Number(c.id)).filter(c=>!Number.isNaN(c)),l=(s??[]).map(c=>({number:Number(c.number),job:String(c.job??"")})).filter(c=>c.job&&!Number.isNaN(c.number));for(let c of u)await P(o,c);for(let c of l)await A(o,c.job,c.number);console.log(g.green(`Requested: cancelled ${u.length} queue item(s), stopped ${l.length} running build(s).`));return}if(n.all){let[i,s]=await Promise.all([$(o,a),v(o,a)]),u=(i??[]).map(c=>Number(c.id)).filter(c=>!Number.isNaN(c)),l=(s??[]).map(c=>Number(c.number)).filter(c=>!Number.isNaN(c));for(let c of u)await P(o,c);for(let c of l)await A(o,a,c);console.log(g.green(`Requested: cancelled ${u.length} queue item(s), stopped ${l.length} running build(s).`));return}if(!t){console.log(g.red("Missing build id. Provide <id> or use -a/--all."));return}await A(o,a,x(t,"id")),console.log(g.green("Stop requested"));});}function Ce(e){e.command("params").description("Show job parameter definitions").option("-j, --job <job>","Specify job").action(async t=>{let{config:n,jenkins:r}=await p(),o=h(n.job,t.job),a=await ue(r,o),i=Math.max(0,...a.map(s=>String(s?.name??"").length));console.table((a??[]).map(s=>({name:d(String(s?.name??"").padEnd(i," ")),type:d(String(s?.type??"")),description:d(String(s?.description??"")),default:d(s?.default===void 0?"-":String(s.default)),choices:d(Array.isArray(s?.choices)?s.choices.join(", "):s?.choices??"-")})));});}function Se(e){e.command("whoami").description("Show Jenkins user info for the current token").action(async()=>{let{jenkins:t}=await p(),n=await ie(t),r=n&&typeof n=="object"?n:{value:n},o=(l,c)=>{let m=l.length;if(m>=c)return l;let f=c-m,b=Math.floor(f/2),y=f-b;return `${" ".repeat(b)}${l}${" ".repeat(y)}`},a=r.name??r.fullName??r.id??"",i=a==null?"":String(a),s=Math.max(20,i.length),u={};u.name=d(o(i,s));for(let l of Object.keys(r).sort((c,m)=>c.localeCompare(m,"en"))){if(l==="name")continue;let c=r[l],m=l.toLowerCase();if((m==="url"||m.endsWith("url")||m.includes("url"))&&(typeof c=="string"||typeof c=="number")){u[l]=k(String(c));continue}let b=c==null?"":typeof c=="object"?JSON.stringify(c):String(c);u[l]=d(b);}console.table([u]);});}function $e(e){e.command("config").description("Read Jenkins job configuration").command("read").description("Read job configuration").option("-j, --job <job>","Specify job").option("-f, --format <format>","Output format: xml or json","xml").action(async n=>{let{config:r,jenkins:o}=await p(),a=h(r.job,n.job),i=String(n.format??"xml").toLowerCase();if(i!=="xml"&&i!=="json")throw new Error(`Invalid format: ${n.format}. Expected: xml or json`);let s=await le(o,a);if(i==="xml"){process.stdout.write(s);return}let l=new XMLParser({ignoreAttributes:!1,attributeNamePrefix:"@_"}).parse(s);console.log(JSON.stringify(l,null,2));});}function Je(e){let t=e.command("job").description("Job operations"),n=(r,o)=>{let a=String(o??"").trim();if(!a)return !0;if(!/[*?]/.test(a))return r.includes(a);let u=`^${a.replace(/[$()*+.?[\\\]^{|}]/g,"\\$&").replace(/\\\*/g,".*").replace(/\\\?/g,".")}$`;try{return new RegExp(u).test(r)}catch{return r.includes(a)}};t.command("list").alias("ls").description("List all jobs").option("--search <keyword>","Filter by name (supports glob)").action(async r=>{let{jenkins:o}=await p(),a=await se(o),i=(r.search??"").trim(),s=i?a.filter(c=>n(String(c.name??""),i)):a;s.sort((c,m)=>String(c?.name??"").localeCompare(String(m?.name??""),"en",{sensitivity:"base"}));let u=Math.max(0,...s.map(c=>String(c?.name??"").length)),l=Math.max(0,...s.map(c=>String(c?.url??"").length));console.table((s??[]).map(c=>({name:d(String(c.name??"").padEnd(u," ")),color:Q(c.color),url:k(String(c.url??"").padEnd(l," "))})));}),t.command("info").description("Show job info").option("-j, --job <job>","Specify job").option("--json","Output raw JSON").action(async r=>{let{config:o,jenkins:a}=await p(),i=h(o.job,r.job),s=await q(a,i);if(r.json){console.log(JSON.stringify(s,null,2));return}let u=s?.lastBuild,l=s?.lastCompletedBuild,c=Array.isArray(s?.healthReport)?s.healthReport:[];console.table([{name:d(String(s?.name??"")),url:k(String(s?.url??"")),color:Q(s?.color),buildable:s?.buildable,inQueue:s?.inQueue,nextBuildNumber:s?.nextBuildNumber,healthScore:c[0]?.score??"-",lastBuild:d(u?.number?`#${u.number}`:"-"),lastResult:E(u?.result,u?.building),lastDurationS:u?.duration===void 0?"-":Number((Number(u.duration)/1e3).toFixed(3)),lastDate:d(u?.timestamp?J(Number(u.timestamp)):"-"),lastCompleted:d(l?.number?`#${l.number}`:"-"),lastCompletedResult:E(l?.result,!1),lastCompletedDate:d(l?.timestamp?J(Number(l.timestamp)):"-")}]);});}function tt(e){return e.split(",").map(t=>t.trim()).filter(Boolean)}function nt(e){let t=e.indexOf("=");if(t<=0)throw new Error(`Invalid --param: "${e}". Expected: key=value`);let n=e.slice(0,t).trim(),r=e.slice(t+1).trim();if(!n)throw new Error(`Invalid --param: "${e}". Key is empty`);return {key:n,value:r}}function Re(e){e.command("trigger").description("Trigger Jenkins builds non-interactively").requiredOption("-b, --branch <branch>","Branch (e.g., origin/develop)").option("-m, --mode <mode>","Deployment environment (repeatable or comma-separated: -m dev -m uat / -m dev,uat)",(t,n)=>n.concat(tt(t)),[]).option("--param <key=value>","Extra parameters (repeatable, e.g., --param foo=bar)",(t,n)=>n.concat(t),[]).option("-j, --job <job>","Specify job").action(async t=>{let{config:n,jenkins:r}=await p(),o=h(n.job,t.job),a=String(t.branch??"").trim();if(!a)throw new Error("branch is required");let i=(t.mode??[]).map(String).map(f=>f.trim()).filter(Boolean),s=Array.from(new Set(i));if(s.length===0)throw new Error("mode is required. Example: jenkins-cli trigger -b origin/develop -m dev -m uat");let u={};for(let f of t.param??[]){let{key:b,value:y}=nt(String(f));u[b]=y;}console.log();let l=!1,c=()=>{l||(l=!0,console.log());},m=s.map(async f=>{let b=We(`Triggering build for ${g.yellow(f)}...`).start();try{let y=await I(r,o,{...u,branch:a,mode:f});c(),b.succeed(`${g.green(f)} - Triggered! Queue: ${y}`);}catch(y){throw c(),b.fail(`${g.red(f)} - ${y.message}`),y}});try{await Promise.all(m);}catch{console.log(g.bold.red(`
|
|
35
35
|
\u274C Some builds failed. Check the output above.
|
|
36
|
-
`)),process.exit(1);}});}function ve(
|
|
36
|
+
`)),process.exit(1);}});}function ve(e){je(e),$e(e),Je(e),xe(e),Ce(e),we(e),ke(e),Re(e),Se(e);}program.name("jenkins-cli").description(Y).version(H,"-v, --version").helpOption("-h, --help").configureHelp({sortSubcommands:!0,sortOptions:!0}).action(ye);ve(program);program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-org/jenkins-cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Jenkins deployment CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
"dev": "NODE_ENV=development tsup --watch",
|
|
14
14
|
"prepublishOnly": "npm run build",
|
|
15
15
|
"link:test": "npm run build && npm link",
|
|
16
|
-
"format": "prettier . --write"
|
|
17
|
-
"format:check": "prettier . --check",
|
|
18
|
-
"lint": "eslint .",
|
|
19
|
-
"lint:fix": "eslint . --fix"
|
|
16
|
+
"format": "eslint . --fix && prettier . --write"
|
|
20
17
|
},
|
|
21
18
|
"keywords": [
|
|
22
19
|
"jenkins",
|
|
@@ -33,8 +30,7 @@
|
|
|
33
30
|
"homepage": "https://gitee.com/u-web/jenkins-cli",
|
|
34
31
|
"files": [
|
|
35
32
|
"dist",
|
|
36
|
-
"
|
|
37
|
-
"LICENSE",
|
|
33
|
+
"schemas/jenkins.json",
|
|
38
34
|
"docs/images/demo.png"
|
|
39
35
|
],
|
|
40
36
|
"dependencies": {
|
|
@@ -71,4 +67,4 @@
|
|
|
71
67
|
"doc": "docs"
|
|
72
68
|
},
|
|
73
69
|
"types": "./dist/index.d.ts"
|
|
74
|
-
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"apiToken": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"description": "Jenkins API Token, format: http(s)://username:token@host:port"
|
|
7
|
+
},
|
|
8
|
+
"job": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Jenkins job name"
|
|
11
|
+
},
|
|
12
|
+
"modes": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"description": "Available deployment environments, such as dev, sit, uat",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "string"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"configs": {
|
|
20
|
+
"type": "array",
|
|
21
|
+
"description": "Extra inquirer questions for interactive default command",
|
|
22
|
+
"items": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"required": ["type", "name", "message"],
|
|
25
|
+
"properties": {
|
|
26
|
+
"type": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"enum": ["input", "number", "confirm", "list", "rawlist", "checkbox", "password"]
|
|
29
|
+
},
|
|
30
|
+
"name": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Parameter name, cannot be branch, modes, mode"
|
|
33
|
+
},
|
|
34
|
+
"message": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Parameter message"
|
|
37
|
+
},
|
|
38
|
+
"default": {
|
|
39
|
+
"description": "Parameter default value"
|
|
40
|
+
},
|
|
41
|
+
"choices": {
|
|
42
|
+
"description": "Parameter choices, can be a reference string like: jenkins-utils.ts:choices or an array of strings"
|
|
43
|
+
},
|
|
44
|
+
"validate": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Parameter validate function, can be a reference string like: jenkins-utils.ts:validate"
|
|
47
|
+
},
|
|
48
|
+
"filter": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Parameter filter function, can be a reference string like: jenkins-utils.ts:filter"
|
|
51
|
+
},
|
|
52
|
+
"transformer": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Parameter transformer function, can be a reference string like: jenkins-utils.ts:transformer"
|
|
55
|
+
},
|
|
56
|
+
"when": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Parameter when function, can be a reference string like: jenkins-utils.ts:when"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"required": ["apiToken", "job", "modes"],
|
|
65
|
+
"additionalProperties": false
|
|
66
|
+
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { AxiosInstance } from 'axios';
|
|
2
|
-
|
|
3
|
-
interface JenkinsConfig {
|
|
4
|
-
apiToken: string;
|
|
5
|
-
job: string;
|
|
6
|
-
modes: string[];
|
|
7
|
-
/**
|
|
8
|
-
* Extra inquirer questions for interactive default command.
|
|
9
|
-
* Loaded from project root `jenkins-cli.yaml` (or package.json config).
|
|
10
|
-
*/
|
|
11
|
-
configs?: ExtraPromptConfig[];
|
|
12
|
-
/**
|
|
13
|
-
* Internal: resolved project root path used to locate `jenkins-cli.yaml`.
|
|
14
|
-
*/
|
|
15
|
-
projectRoot?: string;
|
|
16
|
-
}
|
|
17
|
-
interface BuildParams {
|
|
18
|
-
branch: string;
|
|
19
|
-
mode: string;
|
|
20
|
-
}
|
|
21
|
-
interface UserChoices {
|
|
22
|
-
branch: string;
|
|
23
|
-
modes: string[];
|
|
24
|
-
}
|
|
25
|
-
type InquirerType = 'input' | 'number' | 'confirm' | 'list' | 'rawlist' | 'checkbox' | 'password';
|
|
26
|
-
/**
|
|
27
|
-
* YAML-friendly prompt config (subset of inquirer question options).
|
|
28
|
-
*
|
|
29
|
-
* - `choices` can be an array, or a reference string like:
|
|
30
|
-
* - `src/utils/abc.ts:Fun:hello` (use `hello()` return value)
|
|
31
|
-
* - `src/utils/abc.ts:Var:hello` (use exported `hello` value)
|
|
32
|
-
* - `validate` can be a reference string in the same format.
|
|
33
|
-
*/
|
|
34
|
-
interface ExtraPromptConfig {
|
|
35
|
-
type: InquirerType;
|
|
36
|
-
name: string;
|
|
37
|
-
message: string;
|
|
38
|
-
choices?: unknown;
|
|
39
|
-
default?: unknown;
|
|
40
|
-
validate?: unknown;
|
|
41
|
-
/** Pass-through: any other inquirer options */
|
|
42
|
-
[key: string]: unknown;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
declare function loadConfig(): Promise<JenkinsConfig>;
|
|
46
|
-
|
|
47
|
-
declare function getCurrentBranch(): Promise<string>;
|
|
48
|
-
declare function getAllBranches(): Promise<string[]>;
|
|
49
|
-
|
|
50
|
-
interface JenkinsClient {
|
|
51
|
-
baseURL: string;
|
|
52
|
-
auth: {
|
|
53
|
-
username: string;
|
|
54
|
-
password: string;
|
|
55
|
-
};
|
|
56
|
-
axios: AxiosInstance;
|
|
57
|
-
/** crumb(部分 Jenkins 端点除 header 外也要求携带) */
|
|
58
|
-
crumbForm?: {
|
|
59
|
-
field: string;
|
|
60
|
-
value: string;
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
declare function createJenkinsClient(apiToken: string): JenkinsClient;
|
|
64
|
-
declare function triggerBuild(jenkins: JenkinsClient, job: string, params: BuildParams): Promise<string>;
|
|
65
|
-
|
|
66
|
-
declare function resolveExtraQuestions(configs: ExtraPromptConfig[] | undefined, options: {
|
|
67
|
-
projectRoot?: string;
|
|
68
|
-
}): Promise<Record<string, unknown>[]>;
|
|
69
|
-
declare function answersToJenkinsParams(answers: Record<string, unknown>, excludeKeys: string[]): Record<string, string>;
|
|
70
|
-
|
|
71
|
-
export { BuildParams, JenkinsConfig, UserChoices, answersToJenkinsParams, createJenkinsClient, getAllBranches, getCurrentBranch, loadConfig, resolveExtraQuestions, triggerBuild };
|
package/dist/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import d from 'fs-extra';
|
|
2
|
-
import B from 'js-yaml';
|
|
3
|
-
import g from 'path';
|
|
4
|
-
import { exec } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import K from 'axios';
|
|
7
|
-
import R from 'chalk';
|
|
8
|
-
import Z from 'jiti';
|
|
9
|
-
import { fileURLToPath } from 'url';
|
|
10
|
-
|
|
11
|
-
var h="jenkins-cli.yaml",b="package.json",x="jenkins-cli",N="jenkinsCli";function A(e,n=process.cwd()){let t=n;for(;;){let r=g.join(t,e);if(d.existsSync(r))return r;let o=g.dirname(t);if(o===t)return null;t=o;}}function I(e=process.cwd()){return A(h,e)}function F(e=process.cwd()){let n=A(b,e);return n?g.dirname(n):null}function T(e){return !!(e.apiToken&&e.job&&Array.isArray(e.modes)&&e.modes.length>0)}async function P(e){let n=await d.readFile(e,"utf-8"),t=B.load(n);if(!t||typeof t!="object")throw new Error(`Invalid config in ${h}: expected a YAML object`);return t}async function L(e){let n=g.join(e,b);if(!await d.pathExists(n))return {};let t=await d.readFile(n,"utf-8"),r=JSON.parse(t),o=r[x]??r[N];if(o==null)return {};if(typeof o!="object"||Array.isArray(o))throw new Error(`Invalid ${b} config: "${x}" must be an object`);return o}async function U(){let e=process.cwd(),n=F(e)??e,t=g.join(n,h),r={},o=g.dirname(n),s=I(o);s&&await d.pathExists(s)&&(r=await P(s));let i=await L(n);r={...r,...i};let a=await d.pathExists(t)?await P(t):{};if(r={...r,...a},!T(r))throw new Error(`Config incomplete or not found: tried ${h} in project root, "${x}" in $
|
|
12
|
-
{PACKAGE_JSON}, and walking up from cwd. Required: apiToken, job, modes (non-empty array)`);return {...r,projectRoot:n}}var J=promisify(exec);async function M(){try{let{stdout:e}=await J("git branch --show-current"),n=e.trim();if(!n)throw new Error("Not on any branch (detached HEAD state)");return n}catch{throw new Error("Failed to get current branch. Are you in a git repository?")}}async function D(){try{let{stdout:e}=await J('git branch -r --format="%(refname:short)"');return e.split(`
|
|
13
|
-
`).filter(n=>n.includes("/")&&!n.includes("HEAD"))}catch{throw new Error("Failed to list branches. Are you in a git repository?")}}var $={maxRedirects:0,validateStatus:e=>e>=200&&e<400};function k(e){return `job/${e.split("/").map(t=>t.trim()).filter(Boolean).map(encodeURIComponent).join("/job/")}`}function H(e){let n=Array.isArray(e)?e.find(o=>o?.parameters):void 0,t=Array.isArray(n?.parameters)?n.parameters:[],r={};for(let o of t)o?.name&&(r[String(o.name)]=o?.value);return r}function _(e,n=400){try{let r=(typeof e=="string"?e:JSON.stringify(e??"",null,2)).trim();return r?r.length>n?`${r.slice(0,n)}...`:r:""}catch{return ""}}function V(e){let n=e.match(/^(https?):\/\/([^:]+):([^@]+)@(.+)$/);if(!n)throw new Error("Invalid apiToken format. Expected: http(s)://username:token@host:port");let[,t,r,o,s]=n,i=`${t}://${s.replace(/\/$/,"")}/`,a=K.create({baseURL:i,auth:{username:r,password:o},proxy:!1,timeout:3e4});return {baseURL:i,auth:{username:r,password:o},axios:a,crumbForm:void 0}}async function G(e,n){let r=(await e.axios.get("queue/api/json?tree=items[id,task[name],actions[parameters[name,value]],why]")).data.items;if(n){let o=n.trim(),s=o.split("/").filter(Boolean).pop();return r.filter(i=>{let a=i.task?.name;return a===o||(s?a===s:!1)})}return r}async function Y(e,n){let t=await e.axios.get(`${k(n)}/api/json?tree=builds[number,url,building,result,timestamp,duration,estimatedDuration,actions[parameters[name,value]]]`);return (Array.isArray(t.data?.builds)?t.data.builds:[]).filter(o=>o?.building).map(o=>({...o,parameters:H(o.actions)}))}async function Q(e,n,t){try{await e.axios.post(`${k(n)}/${t}/stop/`,new URLSearchParams({}),$);}catch(r){let o=r.response?.status;if(o===403){let s=typeof r.response?.data=="string"?r.response.data.slice(0,200):JSON.stringify(r.response?.data??"").slice(0,200),i=/crumb|csrf/i.test(s);console.log(R.red("[Error] 403 Forbidden: Jenkins \u62D2\u7EDD\u505C\u6B62\u6784\u5EFA\u8BF7\u6C42\uFF08\u53EF\u80FD\u662F\u6743\u9650\u6216 CSRF\uFF09\u3002")),console.log(R.yellow('[Hint] \u8BF7\u786E\u8BA4\u5F53\u524D\u7528\u6237\u5BF9\u8BE5 Job \u62E5\u6709 "Job" -> "Cancel" \u6743\u9650\u3002')),console.log(i?"[Hint] Jenkins returned a crumb/CSRF error. Ensure crumb + session cookie are sent, or use API token (not password).":s?`[Hint] Jenkins response: ${s}`:'[Hint] If "Job/Cancel" is already granted, also try granting "Run" -> "Update" (some versions use it for stop).');return}if(o===404||o===400||o===500){console.log(`[Info] Build #${t} could not be stopped (HTTP ${o}). Assuming it finished.`);return}throw r}}async function z(e,n,t){return W(e,n,{branch:t.branch,mode:t.mode})}async function W(e,n,t){let r="branch",o="mode",s=t[o];s&&console.log(`Checking for conflicting builds for mode: ${s}...`);let[i,a]=await Promise.all([G(e,n),Y(e,n)]),u=(c,l)=>{let f=c.actions?.find(w=>w.parameters);return f?f.parameters.find(w=>w.name===l)?.value:void 0},y=t[r];if(s&&y){let c=i.find(l=>u(l,o)===s&&u(l,r)===y);if(c)return console.log(`Build already in queue (ID: ${c.id}). Skipping trigger.`),new URL(`queue/item/${c.id}/`,e.baseURL).toString()}let p=!1;if(s)for(let c of a)u(c,o)===s&&(console.log(`Stopping running build #${c.number} (mode=${s})...`),await Q(e,n,c.number),p=!0);p&&await new Promise(c=>setTimeout(c,1e3)),s&&console.log(`Triggering new build for mode=${s}...`);try{return (await e.axios.post(`${k(n)}/buildWithParameters/`,new URLSearchParams({...t}),$)).headers.location||""}catch(c){let l=c?.response?.status,f=_(c?.response?.data);if(l===400){let m=[];m.push("Hint: Jenkins returned 400. Common causes: job is not parameterized, parameter names do not match, or the job endpoint differs (e.g. multibranch jobs)."),m.push(`Hint: This CLI sends parameters "${r}" and "${o}". Ensure your Jenkins Job has matching parameter names.`);let w=f?`
|
|
14
|
-
Jenkins response (snippet):
|
|
15
|
-
${f}`:"";throw new Error(`Request failed with status code 400.${w}
|
|
16
|
-
${m.join(`
|
|
17
|
-
`)}`)}if(l){let m=f?`
|
|
18
|
-
Jenkins response (snippet):
|
|
19
|
-
${f}`:"";throw new Error(`Request failed with status code ${l}.${m}`)}throw c}}var ne=Z(fileURLToPath(import.meta.url),{interopDefault:!0});async function C(e){return await Promise.resolve(e)}function te(e){let n=String(e??"").trim();if(!n)return null;let t=n.split(":");if(t.length<2)return null;if(t.length===2){let[i,a]=t;return !i||!a?null:{file:i,kind:"Var",exportName:a}}let r=t.at(-2),o=t.at(-1);if(r!=="Fun"&&r!=="Var"||!o)return null;let s=t.slice(0,-2).join(":");return s?{file:s,kind:r,exportName:o}:null}function re(e,n){return g.isAbsolute(n)?n:g.join(e,n)}function j(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}async function S(e,n){let t=te(n);if(!t)return null;let r=re(e,t.file);if(!await d.pathExists(r))throw new Error(`configs reference not found: ${t.file}`);let o=ne(r),s=j(o)?o:{default:o},i=t.exportName==="default"?s.default:s[t.exportName];if(i===void 0)throw new Error(`configs reference export not found: ${n}`);return {kind:t.kind,value:i}}async function v(e,n){if(typeof n!="string")return n;let t=await S(e,n);if(!t)return n;if(t.kind==="Var"){if(typeof t.value=="function"){let o=t.value();return await C(o)}return await C(t.value)}if(typeof t.value!="function")throw new Error(`configs reference expected a function: ${n}`);let r=t.value();return await C(r)}async function oe(e,n){let t=Array.isArray(e)?e:[];if(t.length===0)return [];let r=String(n.projectRoot??"").trim()||process.cwd(),o=[];for(let s of t){if(!j(s))continue;let i={...s};if(i.choices!==void 0){i.choices=await v(r,i.choices);let a=String(i.type??"").toLowerCase(),u=i.choices;if((a==="list"||a==="rawlist"||a==="checkbox")&&typeof u!="function"&&!Array.isArray(u))if(u==null)i.choices=[];else if(typeof u=="string"||typeof u=="number"||typeof u=="boolean")i.choices=[String(u)];else if(typeof u[Symbol.iterator]=="function")i.choices=Array.from(u).map(p=>String(p));else throw new Error(`configs "${String(i.name??"")}" choices must resolve to an array (got ${typeof u})`)}if(i.default!==void 0&&(i.default=await v(r,i.default)),i.validate!==void 0){let a=i.validate;if(typeof a=="string"){let u=await S(r,a);if(!u)i.validate=a;else if(u.kind==="Var"){if(typeof u.value!="function")throw new Error(`validate reference must be a function: ${a}`);i.validate=u.value;}else {if(typeof u.value!="function")throw new Error(`validate reference must be a function: ${a}`);i.validate=(y,p)=>u.value(y,p);}}else typeof a=="function"&&(i.validate=a);}o.push(i);}return o}function se(e,n){let t=new Set(n),r={};for(let[o,s]of Object.entries(e))if(!t.has(o)&&s!==void 0){if(s===null){r[o]="";continue}if(Array.isArray(s)){r[o]=s.map(i=>String(i)).join(",");continue}if(typeof s=="object"){try{r[o]=JSON.stringify(s);}catch{r[o]=String(s);}continue}r[o]=String(s);}return r}
|
|
20
|
-
|
|
21
|
-
export { se as answersToJenkinsParams, V as createJenkinsClient, D as getAllBranches, M as getCurrentBranch, U as loadConfig, oe as resolveExtraQuestions, z as triggerBuild };
|