@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 CHANGED
@@ -30,13 +30,15 @@ jenkins-cli
30
30
 
31
31
  CLI 会自动加载配置,列出 Git 分支和部署环境供你选择。
32
32
 
33
- ![Demo](docs/images/demo.png)
33
+ ![Demo](https://cdn.jsdelivr.net/npm/@ts-org/jenkins-cli@latest/docs/images/demo.png)
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`, `list`, `confirm` [Inquirer.js](https://github.com/SBoudrias/Inquirer.js/) 类型。
58
- - `choices`, `default`, `validate`: 支持从本地 TS/JS 文件动态加载。
59
- - `path:Fun:funcName`:调用 `funcName()` 函数获取值 (支持 async)。
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:Fun:getServices
78
+ choices: src/config.ts:getServices
75
79
  - type: input
76
80
  name: ticket
77
81
  message: '请输入关联的工单号:'
78
- validate: src/config.ts:Fun:validateTicket
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` > 父目录的 `jenkins-cli.yaml`。
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 1234 -f
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 He from 'inquirer';
4
- import f from 'chalk';
5
- import Me from 'ora';
3
+ import Ye from 'inquirer';
4
+ import g from 'chalk';
5
+ import We from 'ora';
6
6
  import { exec } from 'child_process';
7
- import Ye, { promisify } from 'util';
7
+ import ze, { promisify } from 'util';
8
8
  import C from 'fs-extra';
9
- import Re from 'js-yaml';
9
+ import Ne from 'js-yaml';
10
10
  import S from 'path';
11
- import Te from 'axios';
12
- import We from 'jiti';
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 Q="3.0.1",_="Jenkins deployment CLI";var G=promisify(exec);async function H(){try{let{stdout:n}=await G("git branch --show-current"),e=n.trim();if(!e)throw new Error("Not on any branch (detached HEAD state)");return e}catch{throw new Error("Failed to get current branch. Are you in a git repository?")}}async function V(){try{let{stdout:n}=await G('git branch -r --format="%(refname:short)"');return n.split(`
17
- `).filter(e=>e.includes("/")&&!e.includes("HEAD"))}catch{throw new Error("Failed to list branches. Are you in a git repository?")}}var P="jenkins-cli.yaml",q="package.json",F="jenkins-cli",Ee="jenkinsCli";function z(n,e=process.cwd()){let t=e;for(;;){let r=S.join(t,n);if(C.existsSync(r))return r;let o=S.dirname(t);if(o===t)return null;t=o;}}function Pe(n=process.cwd()){return z(P,n)}function Be(n=process.cwd()){let e=z(q,n);return e?S.dirname(e):null}function Le(n){return !!(n.apiToken&&n.job&&Array.isArray(n.modes)&&n.modes.length>0)}async function Y(n){let e=await C.readFile(n,"utf-8"),t=Re.load(e);if(!t||typeof t!="object")throw new Error(`Invalid config in ${P}: expected a YAML object`);return t}async function Ie(n){let e=S.join(n,q);if(!await C.pathExists(e))return {};let t=await C.readFile(e,"utf-8"),r=JSON.parse(t),o=r[F]??r[Ee];if(o==null)return {};if(typeof o!="object"||Array.isArray(o))throw new Error(`Invalid ${q} config: "${F}" must be an object`);return o}async function X(){let n=process.cwd(),e=Be(n)??n,t=S.join(e,P),r={},o=S.dirname(e),s=Pe(o);s&&await C.pathExists(s)&&(r=await Y(s));let i=await Ie(e);r={...r,...i};let a=await C.pathExists(t)?await Y(t):{};if(r={...r,...a},!Le(r))throw new Error(`Config incomplete or not found: tried ${P} in project root, "${F}" in $
18
- {PACKAGE_JSON}, and walking up from cwd. Required: apiToken, job, modes (non-empty array)`);return {...r,projectRoot:e}}var U={maxRedirects:0,validateStatus:n=>n>=200&&n<400};function h(n){return `job/${n.split("/").map(t=>t.trim()).filter(Boolean).map(encodeURIComponent).join("/job/")}`}function O(n){let e=Array.isArray(n)?n.find(o=>o?.parameters):void 0,t=Array.isArray(e?.parameters)?e.parameters:[],r={};for(let o of t)o?.name&&(r[String(o.name)]=o?.value);return r}function qe(n){try{let t=new URL(n).pathname.split("/").filter(Boolean),r=[];for(let o=0;o<t.length-1;o++)t[o]==="job"&&t[o+1]&&r.push(decodeURIComponent(t[o+1]));return r.join("/")}catch{return ""}}async function Fe(n,e){let r=(await n.axios.get(new URL("api/json?tree=number,url,building,result,timestamp,duration,estimatedDuration,fullDisplayName,displayName,actions[parameters[name,value]]",e).toString())).data??{};return {...r,parameters:O(r.actions)}}function Ue(n,e=400){try{let r=(typeof n=="string"?n:JSON.stringify(n??"",null,2)).trim();return r?r.length>e?`${r.slice(0,e)}...`:r:""}catch{return ""}}function ee(n){let e=n.match(/^(https?):\/\/([^:]+):([^@]+)@(.+)$/);if(!e)throw new Error("Invalid apiToken format. Expected: http(s)://username:token@host:port");let[,t,r,o,s]=e,i=`${t}://${s.replace(/\/$/,"")}/`,a=Te.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 te(n){try{let e=await n.axios.get("crumbIssuer/api/json"),{data:t}=e;t?.crumbRequestField&&t?.crumb&&(n.axios.defaults.headers.common[t.crumbRequestField]=t.crumb,n.crumbForm={field:t.crumbRequestField,value:t.crumb});let r=e.headers["set-cookie"];if(r){let o=Array.isArray(r)?r.map(s=>s.split(";")[0].trim()):[String(r).split(";")[0].trim()];n.axios.defaults.headers.common.Cookie=o.join("; ");}}catch{}}async function J(n,e){let r=(await n.axios.get("queue/api/json?tree=items[id,task[name],actions[parameters[name,value]],why]")).data.items;if(e){let o=e.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 N(n,e){let t=await n.axios.get(`${h(e)}/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:O(o.actions)}))}async function B(n){let e=await n.axios.get("computer/api/json?tree=computer[displayName,executors[currentExecutable[number,url]],oneOffExecutors[currentExecutable[number,url]]]"),t=Array.isArray(e.data?.computer)?e.data.computer:[],r=[];for(let i of t){let a=Array.isArray(i?.executors)?i.executors:[],l=Array.isArray(i?.oneOffExecutors)?i.oneOffExecutors:[];for(let u of [...a,...l]){let c=u?.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 a=await Fe(n,i.url),l=qe(String(a?.url??i.url));return {...a,job:l}}))).filter(i=>i?.building)}async function A(n,e){return await n.axios.post("queue/cancelItem",new URLSearchParams({id:e.toString()}),U),!0}async function R(n,e,t){try{await n.axios.post(`${h(e)}/${t}/stop/`,new URLSearchParams({}),U);}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(f.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(f.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 L(n,e,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([J(n,e),N(n,e)]),l=(m,p)=>{let y=m.actions?.find(v=>v.parameters);return y?y.parameters.find(v=>v.name===p)?.value:void 0},u=t[r];if(s&&u){let m=i.find(p=>l(p,o)===s&&l(p,r)===u);if(m)return console.log(`Build already in queue (ID: ${m.id}). Skipping trigger.`),new URL(`queue/item/${m.id}/`,n.baseURL).toString()}let c=!1;if(s)for(let m of a)l(m,o)===s&&(console.log(`Stopping running build #${m.number} (mode=${s})...`),await R(n,e,m.number),c=!0);c&&await new Promise(m=>setTimeout(m,1e3)),s&&console.log(`Triggering new build for mode=${s}...`);try{return (await n.axios.post(`${h(e)}/buildWithParameters/`,new URLSearchParams({...t}),U)).headers.location||""}catch(m){let p=m?.response?.status,y=Ue(m?.response?.data);if(p===400){let w=[];w.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)."),w.push(`Hint: This CLI sends parameters "${r}" and "${o}". Ensure your Jenkins Job has matching parameter names.`);let v=y?`
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
- ${y}`:"";throw new Error(`Request failed with status code 400.${v}
21
- ${w.join(`
22
- `)}`)}if(p){let w=y?`
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
- ${y}`:"";throw new Error(`Request failed with status code ${p}.${w}`)}throw m}}async function ne(n){return (await n.axios.get("whoAmI/api/json")).data}async function re(n){return (await n.axios.get("api/json?tree=jobs[name,url,color]")).data.jobs??[]}async function oe(n,e){return (await n.axios.get(`${h(e)}/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]`)).data}async function ie(n,e,t){let r=Math.max(1,t?.limit??20);return ((await n.axios.get(`${h(e)}/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:O(i.actions)}))}async function se(n,e,t){let r=await n.axios.get(`${h(e)}/${t}/consoleText`,{responseType:"text"});return String(r.data??"")}async function ae(n,e,t,r){let o=Math.max(0,r?.start??0),s=Math.max(200,r?.intervalMs??1e3);for(;;){let i=await n.axios.get(`${h(e)}/${t}/logText/progressiveText?start=${o}`,{responseType:"text"}),a=String(i.data??"");a&&process.stdout.write(a);let l=i.headers["x-text-size"],u=i.headers["x-more-data"],c=l?Number(l):NaN;if(Number.isNaN(c)||(o=c),!(String(u??"").toLowerCase()==="true"))break;await new Promise(p=>setTimeout(p,s));}}async function ce(n,e){return ((await n.axios.get(`${h(e)}/api/json?tree=property[parameterDefinitions[name,type,description,defaultParameterValue[value],choices]]`)).data?.property??[]).flatMap(s=>s?.parameterDefinitions??[]).filter(Boolean).map(s=>({name:s.name,type:s.type,description:s.description,default:s.defaultParameterValue?.value,choices:s.choices}))}async function le(n,e){let t=await n.axios.get(`${h(e)}/config.xml`,{responseType:"text"});return String(t.data??"")}async function g(){let n=Me("Loading configuration...").start();try{let e=await X();n.succeed("Configuration loaded");let t=ee(e.apiToken);return await te(t),{config:e,jenkins:t}}catch(e){n.fail(f.red(e.message)),process.exit(1);}}var Qe=We(fileURLToPath(import.meta.url),{interopDefault:!0});async function M(n){return await Promise.resolve(n)}function _e(n){let e=String(n??"").trim();if(!e)return null;let t=e.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 Ge(n,e){return S.isAbsolute(e)?e:S.join(n,e)}function de(n){return !!n&&typeof n=="object"&&!Array.isArray(n)}async function pe(n,e){let t=_e(e);if(!t)return null;let r=Ge(n,t.file);if(!await C.pathExists(r))throw new Error(`configs reference not found: ${t.file}`);let o=Qe(r),s=de(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: ${e}`);return {kind:t.kind,value:i}}async function me(n,e){if(typeof e!="string")return e;let t=await pe(n,e);if(!t)return e;if(t.kind==="Var"){if(typeof t.value=="function"){let o=t.value();return await M(o)}return await M(t.value)}if(typeof t.value!="function")throw new Error(`configs reference expected a function: ${e}`);let r=t.value();return await M(r)}async function fe(n,e){let t=Array.isArray(n)?n:[];if(t.length===0)return [];let r=String(e.projectRoot??"").trim()||process.cwd(),o=[];for(let s of t){if(!de(s))continue;let i={...s};if(i.choices!==void 0){i.choices=await me(r,i.choices);let a=String(i.type??"").toLowerCase(),l=i.choices;if((a==="list"||a==="rawlist"||a==="checkbox")&&typeof l!="function"&&!Array.isArray(l))if(l==null)i.choices=[];else if(typeof l=="string"||typeof l=="number"||typeof l=="boolean")i.choices=[String(l)];else if(typeof l[Symbol.iterator]=="function")i.choices=Array.from(l).map(c=>String(c));else throw new Error(`configs "${String(i.name??"")}" choices must resolve to an array (got ${typeof l})`)}if(i.default!==void 0&&(i.default=await me(r,i.default)),i.validate!==void 0){let a=i.validate;if(typeof a=="string"){let l=await pe(r,a);if(!l)i.validate=a;else if(l.kind==="Var"){if(typeof l.value!="function")throw new Error(`validate reference must be a function: ${a}`);i.validate=l.value;}else {if(typeof l.value!="function")throw new Error(`validate reference must be a function: ${a}`);i.validate=(u,c)=>l.value(u,c);}}else typeof a=="function"&&(i.validate=a);}o.push(i);}return o}function ge(n,e){let t=new Set(e),r={};for(let[o,s]of Object.entries(n))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}async function be(){console.log(f.bold.blue(`
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:n,jenkins:e}=await g(),[t,r]=await Promise.all([V(),H()]),o=()=>{console.log(f.yellow(`
27
- \u{1F44B} Cancelled by user`)),process.exit(130);},s,i={};try{process.prependOnceListener("SIGINT",o);let l=await fe(n.configs,{projectRoot:n.projectRoot}),u=new Set(["branch","modes","mode"]);for(let m of l){let p=String(m?.name??"").trim();if(u.has(p))throw new Error(`configs name "${p}" is reserved. Use another name.`)}let c=await He.prompt([{type:"list",name:"branch",message:"\u8BF7\u9009\u62E9\u8981\u6253\u5305\u7684\u5206\u652F:",choices:t,default:t.indexOf(r)},{type:"checkbox",name:"modes",message:"\u8BF7\u9009\u62E9\u8981\u6253\u5305\u7684\u73AF\u5883:",choices:n.modes,validate:m=>m.length===0?"You must select at least one environment":!0},...l]);s={branch:String(c.branch??""),modes:c.modes??[]},i=ge(c,["branch","modes"]);}catch(l){let u=l,c=u?.message?String(u.message):String(u);((u?.name?String(u.name):"")==="ExitPromptError"||c.toLowerCase().includes("cancelled")||c.toLowerCase().includes("canceled"))&&(console.log(f.yellow(`
28
- \u{1F44B} Cancelled by user`)),process.exit(0)),console.log(f.red(`
29
- \u274C Prompt failed: ${c}
30
- `)),process.exit(1);}finally{process.off("SIGINT",o);}console.log();let a=s.modes.map(async l=>{let u=Me(`Triggering build for ${f.yellow(l)}...`).start();try{let c=await L(e,n.job,{...i,branch:s.branch,mode:l});u.succeed(`${f.green(l)} - Triggered! Queue: ${c}`);}catch(c){throw u.fail(`${f.red(l)} - ${c.message}`),c}});try{await Promise.all(a),console.log();}catch{console.log(f.bold.red(`
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 b(n,e){return (e??"").trim()||n}function k(n,e){let t=Number(n);if(!Number.isInteger(t)||t<0)throw new Error(`${e} must be a non-negative integer`);return t}function he(n){let e=n.command("queue").description("\u7B49\u5F85\u6784\u5EFA\u961F\u5217\u76F8\u5173\u64CD\u4F5C");e.command("list").description("\u83B7\u53D6\u7B49\u5F85\u6784\u5EFA\u7684\u961F\u5217\u5217\u8868").option("-j, --job <job>","\u4EC5\u663E\u793A\u6307\u5B9A Job \u7684\u7B49\u5F85\u6784\u5EFA\u961F\u5217\u9879").action(async t=>{let{config:r,jenkins:o}=await g(),s=t.job?b(r.job,t.job):void 0,i=await J(o,s);console.table((i??[]).map(a=>({id:a.id,job:a.task?.name,why:a.why})));}),e.command("cancel").description("\u53D6\u6D88\u7B49\u5F85\u6784\u5EFA\u7684\u961F\u5217\u9879").argument("<id>").action(async t=>{let{jenkins:r}=await g(),o=await A(r,k(t,"id"));console.log(o?f.green("Cancelled"):f.red("Cancel failed"));});}function d(n){return {[Ye.inspect.custom]:()=>n}}function j(n){let e=String(n??""),t=e.trim();if(!t)return d(f.gray("-"));let r=s=>d(s),o=t.toLowerCase();if(o.startsWith("http://")||o.startsWith("https://"))try{let i=(new URL(t).hostname??"").toLowerCase(),a=i==="localhost"||i==="127.0.0.1"||i==="::1",l=/^10\./.test(i)||/^192\.168\./.test(i)||/^127\./.test(i)||/^172\.(1[6-9]|2\d|3[0-1])\./.test(i);return r(a||l?f.cyanBright(e):f.hex("#4EA1FF")(e))}catch{return r(f.hex("#4EA1FF")(e))}return o.startsWith("ws://")||o.startsWith("wss://")?r(f.cyan(e)):o.startsWith("ssh://")||o.startsWith("git+ssh://")||o.startsWith("git@")?r(f.magenta(e)):o.startsWith("file://")?r(f.yellow(e)):o.startsWith("mailto:")?r(f.green(e)):o.startsWith("javascript:")||o.startsWith("data:")?r(f.red(e)):t.startsWith("/")||t.startsWith("./")||t.startsWith("../")?r(f.cyan(e)):/^[a-z0-9.-]+(?::\d+)?(\/|$)/i.test(t)?r(f.blue(e)):r(e)}function $(n){let e=new Date(n),t=r=>String(r).padStart(2,"0");return `${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())} ${t(e.getHours())}:${t(e.getMinutes())}:${t(e.getSeconds())}`}function E(n,e){if(e===!0)return d(f.cyan("BUILDING"));let t=String(n??"").toUpperCase();return d(t?t==="SUCCESS"?f.green(t):t==="ABORTED"?f.gray(t):t==="FAILURE"?f.red(t):t==="UNSTABLE"?f.yellow(t):t==="NOT_BUILT"?f.gray(t):t:"-")}function D(n){let e=String(n??"");if(!e)return d("-");let t=e.endsWith("_anime"),r=t?e.slice(0,-6):e,o=(()=>{switch(r){case"blue":return t?f.blueBright(e):f.blue(e);case"red":return t?f.redBright(e):f.red(e);case"yellow":return t?f.yellowBright(e):f.yellow(e);case"aborted":return f.gray(e);case"disabled":case"notbuilt":case"grey":case"gray":return f.gray(e);default:return e}})();return d(o)}function ze(n){let e=Object.entries(n??{}).filter(([r])=>r);if(!e.length)return d("-");e.sort(([r],[o])=>r.localeCompare(o,"en",{sensitivity:"base"}));let t=e.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(t)}function we(n){let e=n.command("builds").description("\u6784\u5EFA\u76F8\u5173\u64CD\u4F5C");e.command("list").description("\u83B7\u53D6\u6784\u5EFA\u5217\u8868").option("-j, --job <job>","\u6307\u5B9A job").option("-n, --limit <n>","\u6570\u91CF","20").action(async t=>{let{config:r,jenkins:o}=await g(),s=b(r.job,t.job),i=await ie(o,s,{limit:Number(t.limit)});console.table(i.map(a=>({number:a.number,result:E(a.result,a.building),building:a.building,durationS:Number((Number(a.duration??0)/1e3).toFixed(3)),date:d($(Number(a.timestamp??0)))})));}),e.command("running").description("\u83B7\u53D6\u6B63\u5728\u8FD0\u884C\u7684\u6784\u5EFA").option("-j, --job <job>","\u6307\u5B9A job").option("-a, --all","\u67E5\u8BE2 Jenkins \u4E0B\u6240\u6709\u6B63\u5728\u8FD0\u884C\u7684\u6784\u5EFA").action(async t=>{let{config:r,jenkins:o}=await g(),s=t.all?await B(o):await N(o,b(r.job,t.job));console.table((s??[]).map(i=>({...t.all?{job:d(String(i.job??""))}:{},number:i.number,date:d(i.timestamp?$(Number(i.timestamp)):"-"),parameters:ze(i.parameters),url:j(String(i.url??""))})));});}function xe(n){n.command("log").description("\u83B7\u53D6\u6784\u5EFA\u63A7\u5236\u53F0\u65E5\u5FD7").argument("<id>").option("-j, --job <job>","\u6307\u5B9A job").option("--tail <n>","\u4EC5\u8F93\u51FA\u6700\u540E N \u884C").option("-f, --follow","\u6301\u7EED\u8F93\u51FA\uFF08\u76F4\u5230 Jenkins \u8868\u793A\u65E0\u66F4\u591A\u65E5\u5FD7\uFF09").option("--interval <ms>","follow \u62C9\u53D6\u95F4\u9694","1000").action(async(e,t)=>{let{config:r,jenkins:o}=await g(),s=b(r.job,t.job),i=k(e,"id");if(t.follow){await ae(o,s,i,{intervalMs:Number(t.interval)});return}let a=await se(o,s,i);if(t.tail){let l=k(t.tail,"tail"),u=a.split(`
33
- `);console.log(u.slice(Math.max(0,u.length-l)).join(`
34
- `));return}process.stdout.write(a);});}function ke(n){n.command("stop").description("\u6E05\u7406\u7B49\u5F85\u6784\u5EFA\u7684\u961F\u5217\u4E2D\u7684\u4EFB\u52A1\u5E76\u505C\u6B62\u6B63\u5728\u8FD0\u884C\u7684\u6784\u5EFA").argument("[id]","\u6784\u5EFA\u7F16\u53F7").option("-j, --job <job>","\u6307\u5B9A Jenkins Job").option("-a, --all","\u53D6\u6D88\u8BE5 Job \u7684\u7B49\u5F85\u6784\u5EFA\u961F\u5217\u9879\uFF0C\u5E76\u505C\u6B62\u8BE5 Jenkins Job \u4E0B\u6240\u6709\u6B63\u5728\u8FD0\u884C\u7684\u6784\u5EFA").option("-A, --ALL","\u53D6\u6D88 Jenkins \u4E0A\u6240\u6709\u7B49\u5F85\u6784\u5EFA\u961F\u5217\u9879\uFF0C\u5E76\u505C\u6B62\u6240\u6709\u6B63\u5728\u8FD0\u884C\u7684\u6784\u5EFA").action(async(e,t)=>{let{config:r,jenkins:o}=await g(),s=b(r.job,t.job);if(t.ALL){let[i,a]=await Promise.all([J(o),B(o)]),l=(i??[]).map(c=>Number(c.id)).filter(c=>!Number.isNaN(c)),u=(a??[]).map(c=>({number:Number(c.number),job:String(c.job??"")})).filter(c=>c.job&&!Number.isNaN(c.number));for(let c of l)await A(o,c);for(let c of u)await R(o,c.job,c.number);console.log(f.green(`Requested: cancelled ${l.length} queue item(s), stopped ${u.length} running build(s).`));return}if(t.all){let[i,a]=await Promise.all([J(o,s),N(o,s)]),l=(i??[]).map(c=>Number(c.id)).filter(c=>!Number.isNaN(c)),u=(a??[]).map(c=>Number(c.number)).filter(c=>!Number.isNaN(c));for(let c of l)await A(o,c);for(let c of u)await R(o,s,c);console.log(f.green(`Requested: cancelled ${l.length} queue item(s), stopped ${u.length} running build(s).`));return}if(!e){console.log(f.red("Missing build id. Provide <id> or use -a/--all."));return}await R(o,s,k(e,"id")),console.log(f.green("Stop requested"));});}function je(n){n.command("params").description("\u83B7\u53D6 job \u53C2\u6570\u5B9A\u4E49").option("-j, --job <job>","\u6307\u5B9A job").action(async e=>{let{config:t,jenkins:r}=await g(),o=b(t.job,e.job),s=await ce(r,o),i=Math.max(0,...s.map(a=>String(a?.name??"").length));console.table((s??[]).map(a=>({name:d(String(a?.name??"").padEnd(i," ")),type:d(String(a?.type??"")),description:d(String(a?.description??"")),default:d(a?.default===void 0?"-":String(a.default)),choices:d(Array.isArray(a?.choices)?a.choices.join(", "):a?.choices??"-")})));});}function Ce(n){n.command("whoami").description("\u67E5\u770B\u5F53\u524D Token \u5BF9\u5E94\u7684 Jenkins \u7528\u6237\u4FE1\u606F").action(async()=>{let{jenkins:e}=await g(),t=await ne(e),r=t&&typeof t=="object"?t:{value:t},o=(u,c)=>{let m=u.length;if(m>=c)return u;let p=c-m,y=Math.floor(p/2),w=p-y;return `${" ".repeat(y)}${u}${" ".repeat(w)}`},s=r.name??r.fullName??r.id??"",i=s==null?"":String(s),a=Math.max(20,i.length),l={};l.name=d(o(i,a));for(let u of Object.keys(r).sort((c,m)=>c.localeCompare(m,"en"))){if(u==="name")continue;let c=r[u],m=u.toLowerCase();if((m==="url"||m.endsWith("url")||m.includes("url"))&&(typeof c=="string"||typeof c=="number")){l[u]=j(String(c));continue}let y=c==null?"":typeof c=="object"?JSON.stringify(c):String(c);l[u]=d(y);}console.table([l]);});}function Se(n){n.command("config").description("\u8BFB\u53D6 Jenkins Job \u914D\u7F6E").command("read").description("\u8BFB\u53D6 job \u914D\u7F6E").option("-j, --job <job>","\u6307\u5B9A job").option("-f, --format <format>","\u8F93\u51FA\u683C\u5F0F\uFF1Axml \u6216 json","xml").action(async t=>{let{config:r,jenkins:o}=await g(),s=b(r.job,t.job),i=String(t.format??"xml").toLowerCase();if(i!=="xml"&&i!=="json")throw new Error(`Invalid format: ${t.format}. Expected: xml or json`);let a=await le(o,s);if(i==="xml"){process.stdout.write(a);return}let u=new XMLParser({ignoreAttributes:!1,attributeNamePrefix:"@_"}).parse(a);console.log(JSON.stringify(u,null,2));});}function Je(n){let e=n.command("job").description("Job \u76F8\u5173\u64CD\u4F5C"),t=(r,o)=>{let s=String(o??"").trim();if(!s)return !0;if(!/[*?]/.test(s))return r.includes(s);let l=`^${s.replace(/[$()*+.?[\\\]^{|}]/g,"\\$&").replace(/\\\*/g,".*").replace(/\\\?/g,".")}$`;try{return new RegExp(l).test(r)}catch{return r.includes(s)}};e.command("list").description("\u83B7\u53D6\u6240\u6709 job").option("--search <keyword>","\u6309\u540D\u79F0\u8FC7\u6EE4\uFF08\u652F\u6301 glob\uFF09").action(async r=>{let{jenkins:o}=await g(),s=await re(o),i=(r.search??"").trim(),a=i?s.filter(c=>t(String(c.name??""),i)):s;a.sort((c,m)=>String(c?.name??"").localeCompare(String(m?.name??""),"en",{sensitivity:"base"}));let l=Math.max(0,...a.map(c=>String(c?.name??"").length)),u=Math.max(0,...a.map(c=>String(c?.url??"").length));console.table((a??[]).map(c=>({name:d(String(c.name??"").padEnd(l," ")),color:D(c.color),url:j(String(c.url??"").padEnd(u," "))})));}),e.command("info").description("\u83B7\u53D6 job \u4FE1\u606F").option("-j, --job <job>","\u6307\u5B9A job").option("--json","\u8F93\u51FA\u539F\u59CB JSON").action(async r=>{let{config:o,jenkins:s}=await g(),i=b(o.job,r.job),a=await oe(s,i);if(r.json){console.log(JSON.stringify(a,null,2));return}let l=a?.lastBuild,u=a?.lastCompletedBuild,c=Array.isArray(a?.healthReport)?a.healthReport:[];console.table([{name:d(String(a?.name??"")),url:j(String(a?.url??"")),color:D(a?.color),buildable:a?.buildable,inQueue:a?.inQueue,nextBuildNumber:a?.nextBuildNumber,healthScore:c[0]?.score??"-",lastBuild:d(l?.number?`#${l.number}`:"-"),lastResult:E(l?.result,l?.building),lastDurationS:l?.duration===void 0?"-":Number((Number(l.duration)/1e3).toFixed(3)),lastDate:d(l?.timestamp?$(Number(l.timestamp)):"-"),lastCompleted:d(u?.number?`#${u.number}`:"-"),lastCompletedResult:E(u?.result,!1),lastCompletedDate:d(u?.timestamp?$(Number(u.timestamp)):"-")}]);});}function et(n){return n.split(",").map(e=>e.trim()).filter(Boolean)}function tt(n){let e=n.indexOf("=");if(e<=0)throw new Error(`Invalid --param: "${n}". Expected: key=value`);let t=n.slice(0,e).trim(),r=n.slice(e+1).trim();if(!t)throw new Error(`Invalid --param: "${n}". Key is empty`);return {key:t,value:r}}function $e(n){n.command("trigger").description("\u975E\u4EA4\u4E92\u89E6\u53D1 Jenkins \u6784\u5EFA").requiredOption("-b, --branch <branch>","\u5206\u652F\uFF08\u4F8B\u5982 origin/develop\uFF09").option("-m, --mode <mode>","\u90E8\u7F72\u73AF\u5883\uFF08\u53EF\u91CD\u590D\u4F20\u5165\uFF0C\u6216\u7528\u9017\u53F7\u5206\u9694\uFF1A-m dev -m uat / -m dev,uat\uFF09",(e,t)=>t.concat(et(e)),[]).option("--param <key=value>","\u989D\u5916\u53C2\u6570\uFF08\u53EF\u91CD\u590D\u4F20\u5165\uFF0C\u4F8B\u5982\uFF1A--param foo=bar\uFF09",(e,t)=>t.concat(e),[]).option("-j, --job <job>","\u6307\u5B9A job").action(async e=>{let{config:t,jenkins:r}=await g(),o=b(t.job,e.job),s=String(e.branch??"").trim();if(!s)throw new Error("branch is required");let i=(e.mode??[]).map(String).map(c=>c.trim()).filter(Boolean),a=Array.from(new Set(i));if(a.length===0)throw new Error("mode is required. Example: jenkins-cli trigger -b origin/develop -m dev -m uat");let l={};for(let c of e.param??[]){let{key:m,value:p}=tt(String(c));l[m]=p;}console.log();let u=a.map(async c=>{let m=Me(`Triggering build for ${f.yellow(c)}...`).start();try{let p=await L(r,o,{...l,branch:s,mode:c});m.succeed(`${f.green(c)} - Triggered! Queue: ${p}`);}catch(p){throw m.fail(`${f.red(c)} - ${p.message}`),p}});try{await Promise.all(u),console.log();}catch{console.log(f.bold.red(`
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(n){we(n),Se(n),Je(n),xe(n),je(n),he(n),ke(n),$e(n),Ce(n);}program.name("jenkins-cli").description(_).version(Q,"-v, --version").helpOption("-h, --help").action(be);ve(program);program.parse();
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.1",
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
- "README.md",
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 };