task-pipeliner 0.3.0 → 0.3.2
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.ko.md +15 -7
- package/README.md +15 -7
- package/dist/index.cjs +76 -76
- package/package.json +19 -4
package/README.ko.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# task-pipeliner
|
|
2
2
|
|
|
3
|
-
> 조건 기반
|
|
3
|
+
> 조건 기반 실행과 아름다운 CLI 출력을 제공하는 강력한 워크플로우 오케스트레이션 도구
|
|
4
4
|
|
|
5
|
-
**버전:** 0.3.
|
|
5
|
+
**버전:** 0.3.2
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/task-pipeliner)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
**task-pipeliner**는 간단한 YAML 또는 JSON 파일로 복잡한
|
|
12
|
+
**task-pipeliner**는 간단한 YAML 또는 JSON 파일로 복잡한 워크플로우를 정의, 조율, 실행할 수 있는 현대적인 워크플로우 오케스트레이션 도구입니다. 조건부 실행, 병렬 작업, 대화형 프롬프트, 그리고 아름다운 터미널 출력을 제공하여 빌드 스크립트, 배포 워크플로우, CI/CD 파이프라인에 완벽합니다.
|
|
13
13
|
|
|
14
14
|
**README-Language-Map** [KR [한국어 버전]](https://github.com/racgoo/task-pipeliner/blob/main/README.ko.md) / [EN [English Version]](https://github.com/racgoo/task-pipeliner)
|
|
15
15
|
|
|
@@ -447,13 +447,13 @@ steps: # 필수: 실행할 단계 배열
|
|
|
447
447
|
- run: <command>
|
|
448
448
|
when?: <condition> # 선택: 조건이 충족될 때만 실행
|
|
449
449
|
timeout?: <number> # 선택: 타임아웃 (초 단위)
|
|
450
|
-
retry?: <number>
|
|
450
|
+
retry?: <number> | "Infinity" # 선택: 실패 시 재시도 횟수 (기본값: 0). "Infinity"로 무한 재시도 가능
|
|
451
451
|
shell?: <array> # 선택: 쉘 설정 (workflow.shell 오버라이드)
|
|
452
452
|
continue?: <bool> # 선택: 이 스텝 이후 다음 스텝으로 진행할지 여부 (성공/실패 무관)
|
|
453
453
|
onError?: # 선택: 에러 처리 동작
|
|
454
454
|
run: <command> # 메인 run 명령이 실패했을 때 실행할 대체 명령 (사이드 이펙트)
|
|
455
455
|
timeout?: <number> # 선택: 이 fallback 명령의 타임아웃
|
|
456
|
-
retry?: <number>
|
|
456
|
+
retry?: <number> | "Infinity" # 선택: 이 fallback 명령의 재시도 횟수. "Infinity"로 무한 재시도 가능
|
|
457
457
|
onError?: ... # 선택: 중첩 fallback (재귀 onError 체인)
|
|
458
458
|
```
|
|
459
459
|
|
|
@@ -461,7 +461,7 @@ steps: # 필수: 실행할 단계 배열
|
|
|
461
461
|
- `run` (필수): `string` - 실행할 셸 명령
|
|
462
462
|
- `when` (선택): `Condition` - 실행 전 확인할 조건
|
|
463
463
|
- `timeout` (선택): `number` - 최대 실행 시간 (초 단위). 이 시간을 초과하면 명령이 종료됩니다.
|
|
464
|
-
- `retry` (선택): `number` - 실패 시 재시도 횟수 (기본값: 0, 재시도 없음)
|
|
464
|
+
- `retry` (선택): `number | "Infinity"` - 실패 시 재시도 횟수 (기본값: 0, 재시도 없음). `"Infinity"`로 성공할 때까지 무한 재시도 가능
|
|
465
465
|
- `shell` (선택): `string`의 `array` - 이 스텝의 쉘 설정. 워크플로우의 전역 `shell`을 오버라이드합니다. 형식: `[프로그램, ...인자]`. 예: `[bash, -lc]`, `[zsh, -c]`.
|
|
466
466
|
- `continue` (선택): `boolean` - 이 스텝 완료 후 다음 스텝으로 진행할지 여부를 제어합니다 (성공/실패와 무관).
|
|
467
467
|
- `continue: true` - 항상 다음 스텝으로 진행 (이 스텝이 실패해도)
|
|
@@ -469,7 +469,7 @@ steps: # 필수: 실행할 단계 배열
|
|
|
469
469
|
- `continue` 미설정 (기본값) - 성공 시 진행, 실패 시 중단
|
|
470
470
|
- `onError.run` (선택): `string` - 메인 `run` 명령이 (자신의 재시도 후에도) 실패했을 때 실행할 대체 명령. **onError는 단순히 사이드 이펙트(예: 정리 작업, 롤백)를 수행하며, 스텝의 성공/실패 여부에는 영향을 주지 않습니다.** 메인 `run`이 실패하면 이 스텝은 실패로 간주됩니다.
|
|
471
471
|
- `onError.timeout` (선택): `number` - 이 fallback 명령의 타임아웃.
|
|
472
|
-
- `onError.retry` (선택): `number` - 이 fallback 명령의 재시도 횟수.
|
|
472
|
+
- `onError.retry` (선택): `number | "Infinity"` - 이 fallback 명령의 재시도 횟수. `"Infinity"`로 무한 재시도 가능.
|
|
473
473
|
|
|
474
474
|
**예제:**
|
|
475
475
|
```yaml
|
|
@@ -505,6 +505,14 @@ steps:
|
|
|
505
505
|
- run: npm install
|
|
506
506
|
retry: 3
|
|
507
507
|
|
|
508
|
+
# 무한 재시도 명령 (성공할 때까지 재시도)
|
|
509
|
+
- run: npm install
|
|
510
|
+
retry: Infinity
|
|
511
|
+
|
|
512
|
+
# PM2처럼 프로세스 관리: 서버가 죽으면 자동 재시작
|
|
513
|
+
- run: node server.js
|
|
514
|
+
retry: Infinity
|
|
515
|
+
|
|
508
516
|
# 타임아웃과 재시도 모두 사용
|
|
509
517
|
- run: npm install
|
|
510
518
|
timeout: 60
|
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# task-pipeliner
|
|
2
2
|
|
|
3
|
-
> A powerful
|
|
3
|
+
> A powerful workflow orchestration tool with condition-based execution and beautiful CLI output
|
|
4
4
|
|
|
5
|
-
**Version:** 0.3.
|
|
5
|
+
**Version:** 0.3.2
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/task-pipeliner)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
**task-pipeliner** is a modern workflow
|
|
12
|
+
**task-pipeliner** is a modern workflow orchestration tool that lets you define, coordinate, and execute complex workflows using simple YAML or JSON files. With conditional execution, parallel tasks, interactive prompts, and beautiful terminal output, it's perfect for build scripts, deployment workflows, and CI/CD pipelines.
|
|
13
13
|
|
|
14
14
|
**README-Language-Map** [KR [한국어 버전]](https://github.com/racgoo/task-pipeliner/blob/main/README.ko.md) / [EN [English Version]](https://github.com/racgoo/task-pipeliner)
|
|
15
15
|
|
|
@@ -447,13 +447,13 @@ Execute a shell command.
|
|
|
447
447
|
- run: <command>
|
|
448
448
|
when?: <condition> # Optional: Execute only if condition is met
|
|
449
449
|
timeout?: <number> # Optional: Timeout in seconds
|
|
450
|
-
retry?: <number>
|
|
450
|
+
retry?: <number> | "Infinity" # Optional: Number of retries on failure (default: 0). Use "Infinity" for infinite retries
|
|
451
451
|
shell?: <array> # Optional: Shell configuration (overrides workflow.shell)
|
|
452
452
|
continue?: <bool> # Optional: Continue to next step after this step completes (regardless of success/failure)
|
|
453
453
|
onError?: # Optional: Error handling behavior
|
|
454
454
|
run: <command> # Fallback command when main run command fails (side effect)
|
|
455
455
|
timeout?: <number> # Optional: Timeout for this fallback command
|
|
456
|
-
retry?: <number>
|
|
456
|
+
retry?: <number> | "Infinity" # Optional: Retry count for this fallback command. Use "Infinity" for infinite retries
|
|
457
457
|
onError?: ... # Optional: Nested fallback (recursive onError chain)
|
|
458
458
|
```
|
|
459
459
|
|
|
@@ -461,7 +461,7 @@ Execute a shell command.
|
|
|
461
461
|
- `run` (required): `string` - Shell command to execute
|
|
462
462
|
- `when` (optional): `Condition` - Condition to check before execution
|
|
463
463
|
- `timeout` (optional): `number` - Maximum execution time in seconds. Command will be killed if it exceeds this time.
|
|
464
|
-
- `retry` (optional): `number` - Number of retry attempts if command fails (default: 0, meaning no retry)
|
|
464
|
+
- `retry` (optional): `number | "Infinity"` - Number of retry attempts if command fails (default: 0, meaning no retry). Use `"Infinity"` for infinite retries until success
|
|
465
465
|
- `shell` (optional): `array` of `string` - Shell configuration for this step. Overrides workflow's global `shell`. Format: `[program, ...args]`. Example: `[bash, -lc]`, `[zsh, -c]`.
|
|
466
466
|
- `continue` (optional): `boolean` - Controls whether to proceed to the next step after this step completes, regardless of success or failure.
|
|
467
467
|
- `continue: true` - Always proceed to the next step (even if this step fails)
|
|
@@ -469,7 +469,7 @@ Execute a shell command.
|
|
|
469
469
|
- `continue` not set (default) - Proceed on success, stop on failure
|
|
470
470
|
- `onError.run` (optional): `string` - Fallback command executed when the main `run` command (after its retries) fails. **onError only performs side effects (e.g., cleanup, rollback) and does not affect the step's success/failure status.** If the main `run` fails, this step is considered failed regardless of onError execution.
|
|
471
471
|
- `onError.timeout` (optional): `number` - Timeout for this fallback command.
|
|
472
|
-
- `onError.retry` (optional): `number` - Retry count for this fallback command.
|
|
472
|
+
- `onError.retry` (optional): `number | "Infinity"` - Retry count for this fallback command. Use `"Infinity"` for infinite retries.
|
|
473
473
|
|
|
474
474
|
**Examples:**
|
|
475
475
|
```yaml
|
|
@@ -505,6 +505,14 @@ steps:
|
|
|
505
505
|
- run: npm install
|
|
506
506
|
retry: 3
|
|
507
507
|
|
|
508
|
+
# Command with infinite retry (retry until success)
|
|
509
|
+
- run: npm install
|
|
510
|
+
retry: Infinity
|
|
511
|
+
|
|
512
|
+
# PM2-like process manager: auto-restart crashed server
|
|
513
|
+
- run: node server.js
|
|
514
|
+
retry: Infinity
|
|
515
|
+
|
|
508
516
|
# Using both timeout and retry
|
|
509
517
|
- run: npm install
|
|
510
518
|
timeout: 60
|
package/dist/index.cjs
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";var
|
|
3
|
-
${
|
|
4
|
-
`;await(0,x.writeFile)(
|
|
5
|
-
`){i.length>0&&(g(),
|
|
6
|
-
`)),w=_}),d.stderr?.on("data"
|
|
7
|
-
`)),
|
|
8
|
-
`),
|
|
9
|
-
`);let
|
|
2
|
+
"use strict";var Yt=Object.create;var De=Object.defineProperty;var Ut=Object.getOwnPropertyDescriptor;var Jt=Object.getOwnPropertyNames;var qt=Object.getPrototypeOf,Gt=Object.prototype.hasOwnProperty;var Zt=(o,e)=>()=>(o&&(e=o(o=0)),e);var Xt=(o,e)=>{for(var t in e)De(o,t,{get:e[t],enumerable:!0})},Kt=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Jt(e))!Gt.call(o,n)&&n!==t&&De(o,n,{get:()=>e[n],enumerable:!(r=Ut(e,n))||r.enumerable});return o};var y=(o,e,t)=>(t=o!=null?Yt(qt(o)):{},Kt(e||!o||!o.__esModule?De(t,"default",{value:o,enumerable:!0}):t,o));var Qe={};Xt(Qe,{DAEMON_DIR:()=>q,getDaemonErrorLogPath:()=>Me,getDaemonPid:()=>Te,getDaemonStartTime:()=>Ke,getDaemonStatus:()=>R,isDaemonRunning:()=>A,readDaemonErrorLog:()=>je,removeDaemonPid:()=>se,saveDaemonPid:()=>er,writeDaemonError:()=>Ie});function Qt(o){try{return process.kill(o,0),!0}catch{return!1}}async function Te(){try{if(!(0,V.existsSync)(N))return null;let o=await(0,x.readFile)(N,"utf-8"),e=parseInt(o.trim(),10);return isNaN(e)?(await(0,x.unlink)(N),null):Qt(e)?e:(await(0,x.unlink)(N),null)}catch(o){if(o instanceof Error&&"code"in o&&o.code==="ENOENT")return null;throw o}}async function A(){return await Te()!==null}async function er(){await(0,x.mkdir)(q,{recursive:!0}),await(0,x.writeFile)(N,process.pid.toString(),"utf-8");let o=new Date().toISOString();await(0,x.writeFile)(oe,o,"utf-8")}async function Ie(o){try{await(0,x.mkdir)(q,{recursive:!0});let e=`${new Date().toISOString()} ${o.message}
|
|
3
|
+
${o.stack??""}
|
|
4
|
+
`;await(0,x.writeFile)(fe,e,"utf-8")}catch{}}async function se(){try{(0,V.existsSync)(N)&&await(0,x.unlink)(N),(0,V.existsSync)(oe)&&await(0,x.unlink)(oe)}catch{}}async function Ke(){try{if((0,V.existsSync)(oe)){let e=(await(0,x.readFile)(oe,"utf-8")).trim();if(e)return e}if((0,V.existsSync)(N)){let o=await(0,x.stat)(N);return new Date(o.mtime).toISOString()}return null}catch{return null}}function Me(){return fe}async function je(){try{return(0,V.existsSync)(fe)&&(await(0,x.readFile)(fe,"utf-8")).trim()||null}catch{return null}}async function R(){let o=await Te(),e=o?await Ke():null;return{running:o!==null,pid:o,startTime:e}}var V,x,Xe,ne,q,N,oe,fe,ie=Zt(()=>{"use strict";V=require("fs"),x=require("fs/promises"),Xe=require("os"),ne=require("path"),q=(0,ne.join)((0,Xe.homedir)(),".pipeliner","daemon"),N=(0,ne.join)(q,"scheduler.pid"),oe=(0,ne.join)(q,"scheduler.started");fe=(0,ne.join)(q,"error.log")});var Wt=require("child_process"),de=require("fs"),Pe=require("fs/promises"),_t=require("os"),U=require("path"),Vt=require("util"),Ze=y(require("boxen"),1),p=y(require("chalk"),1),Ht=require("commander"),zt=y(require("dayjs"),1);ie();var z=require("path"),lt=y(require("chalk"),1),Se=y(require("log-update"),1);var tt=y(require("readline"),1),D=y(require("chalk"),1),Ne=y(require("inquirer"),1),et=15,L=class{searchable;constructor(e=!1){this.searchable=e}async prompt(e,t){if(this.searchable)return this.promptWithSearch(e,t);let{choice:r}=await Ne.default.prompt([{type:"list",name:"choice",message:D.default.cyan(e),choices:t.map(s=>({name:s.label,value:s.id})),pageSize:et}]),n=t.find(s=>s.id===r);if(!n)throw new Error(`Invalid choice: ${r}`);return n}async promptWithSearch(e,t){return new Promise(r=>{let n="",s=0,i=[...t],a=tt.createInterface({input:process.stdin,output:process.stdout,terminal:!1});process.stdin.isTTY&&process.stdin.setRawMode(!0),process.stdout.write("\x1B[?1049h"),process.stdout.write("\x1B[?25l");let l=()=>{process.stdout.write("\x1B[H\x1B[2J"),console.log(D.default.cyan(`? ${e}`));let h=n?D.default.gray(` Filter: ${n}`)+D.default.gray(` (${i.length}/${t.length})`):D.default.gray(" Type to filter, \u2191\u2193 to navigate, Enter to select");console.log(h),console.log();let d=et,w=0,S=i.length;if(i.length>d){let v=Math.floor(d/2);w=Math.max(0,s-v),S=Math.min(i.length,w+d),S===i.length&&(w=Math.max(0,S-d))}if(i.length===0)console.log(D.default.yellow(" No matches found"));else{w>0&&console.log(D.default.gray(` \u2191 ${w} more above`));for(let v=w;v<S;v++){let $=i[v];console.log(v===s?D.default.cyan(`\u276F ${$.label}`):D.default.white(` ${$.label}`))}S<i.length&&console.log(D.default.gray(` \u2193 ${i.length-S} more below`))}},c=()=>{let h=n.toLowerCase();i=h?t.filter(d=>d.label.toLowerCase().includes(h)):[...t],s>=i.length&&(s=Math.max(0,i.length-1))},f=h=>{let d=h.toString();if(d===""&&(g(),process.exit(0)),d==="\r"||d===`
|
|
5
|
+
`){i.length>0&&(g(),r(i[s]));return}if(d==="\x1B"&&h.length===1){n&&(n="",c(),l());return}if(d==="\x1B[A"){i.length>0&&(s=s>0?s-1:i.length-1,l());return}if(d==="\x1B[B"){i.length>0&&(s=s<i.length-1?s+1:0,l());return}if(d==="\x7F"||d==="\b"){n.length>0&&(n=n.slice(0,-1),c(),l());return}d.length===1&&d>=" "&&d<="~"&&(n+=d,c(),l())},g=()=>{process.stdin.removeListener("data",f),process.stdin.isTTY&&process.stdin.setRawMode(!1),a.close(),process.stdout.write("\x1B[?25h"),process.stdout.write("\x1B[?1049l")};l(),process.stdin.on("data",f)})}},me=class{async prompt(e,t){let{value:r}=await Ne.default.prompt([{type:"input",name:"value",message:D.default.cyan(e),default:t}]);return r}};var he=y(require("boxen"),1),G=y(require("chalk"),1);function Ae(o,e,t,r={}){let{borderColor:n="cyan",isNested:s=!1}=r,i;e!==void 0&&(t?i=`line ${e} in ${t}`:i=`line ${e}`);let a=s?`\u2502 ${o}`:`> ${o}`;return(0,he.default)(a,{title:i,borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:n})}function Z(o,e=!1,t){let r=o?"\u2713 Completed":"\u2717 Failed",n=o?G.default.green(r):G.default.red(r);if(t!==void 0){let s=X(t);return`${n} ${G.default.gray(`(${s})`)}`}return n}function ae(o){return(0,he.default)(`\u2717 ${o}`,{borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:"red"})}function rt(o){return(0,he.default)(`> Starting parallel execution (${o} branches)`,{borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:"yellow"})}function ot(o){let e=o?"\u2713 All parallel branches completed":"\u2717 Some parallel branches failed";return o?G.default.green(e):G.default.red(e)}function Oe(o,e=!1){return`${e?"| \u2502 ":"\u2502 "}${o}`}function X(o){return`${(o/1e3).toFixed(3)}s`}var nt=require("fs"),st=require("path"),K=class{constructor(e){this.workspace=e}evaluate(e){return"var"in e||"has"in e?this.evaluateVarExists(e):"file"in e?this.evaluateFileExists(e):"choice"in e?this.evaluateChoice(e):"all"in e?this.evaluateAll(e):"any"in e?this.evaluateAny(e):"not"in e?this.evaluateNot(e):!1}evaluateVarExists(e){if(e.has)return this.workspace.hasVariable(e.has)||this.workspace.hasFact(e.has);if(!e.var)return!1;if(typeof e.var=="object"){for(let[r,n]of Object.entries(e.var)){let s=this.workspace.getVariable(r),i=this.workspace.getFact(r),a=s??(i!==void 0?i.toString():void 0);if(a===void 0||a!==n)return!1}return!0}let t=e.var;return this.workspace.hasVariable(t)||this.workspace.hasFact(t)}evaluateFileExists(e){try{let t=e.file.trim(),r=(0,st.resolve)(process.cwd(),t);return(0,nt.existsSync)(r)}catch{return!1}}evaluateChoice(e){return this.workspace.hasChoice(e.choice)}evaluateAll(e){return e.all.every(t=>this.evaluate(t))}evaluateAny(e){return e.any.some(t=>this.evaluate(t))}evaluateNot(e){return!this.evaluate(e.not)}};var T=require("fs/promises"),it=require("os"),H=require("path"),at=y(require("dayjs"),1),Q=(0,H.join)((0,it.homedir)(),".pipeliner","workflow-history"),ee=class{constructor(){}async saveHistory(e){await(0,T.mkdir)(Q,{recursive:!0});let t=(0,at.default)().format("YYYY-MM-DD_HH-mm-ss"),r=Math.random().toString(36).slice(2,6),n=(0,H.join)(Q,`workflow-${t}-${r}.json`);return await(0,T.writeFile)(n,JSON.stringify(e,null,2),{encoding:"utf8"}),n}async clearAllHistories(){await(0,T.rm)(Q,{recursive:!0,force:!0})}async removeHistory(e){await(0,T.rm)((0,H.join)(Q,e),{force:!0})}async getHistoryNames(){try{let t=(await(0,T.readdir)(Q)).map(r=>(0,H.basename)(r));return t.sort((r,n)=>{let s=l=>{let c=l.match(/workflow-(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})-/);return c?c[1]:""},i=s(r),a=s(n);return i===a?n.localeCompare(r):a.localeCompare(i)}),t}catch(e){if(e instanceof Error&&"code"in e&&e.code==="ENOENT")return[];throw e}}async getHistory(e){let t=await(0,T.readFile)((0,H.join)(Q,e),{encoding:"utf8"});return JSON.parse(t)}};var ge=class{records=[];initialTimestamp=Date.now();recordStartTimestamp=Date.now();constructor(){this.records=[]}recordStart(){this.recordStartTimestamp=Date.now()}recordEnd(e,t,r,n){let s=this.getDuration();return this.records.push({step:e,context:t,output:r,duration:s,status:n}),s}reset(){this.records=[],this.initialTimestamp=Date.now()}async save(){let e=new ee,t={initialTimestamp:this.initialTimestamp,records:this.records};return await e.saveHistory(t)}getDuration(){return Date.now()-this.recordStartTimestamp}};var Fe=require("child_process");var we=class{async run(e,t,r,n,s=!1,i=!1,a,l,c,f,g){return s?this.runBuffered(e,c,f,g):this.runRealtime(e,r||e,i,a,l,c,f,g)}async runBuffered(e,t,r,n){return new Promise((s,i)=>{let a=this.spawnWithShell(e,t,n),l=[],c=[],f="",g="",h=null;r&&r>0&&(h=setTimeout(()=>{a.kill("SIGTERM");let d=`Command timed out after ${r} seconds`;c.push(d),s({success:!1,stdout:l,stderr:c})},r*1e3)),a.stdout?.on("data",d=>{let w=d.toString(),{lines:S,remaining:v}=this.processStreamBuffer(w,f);l.push(...S),f=v}),a.stderr?.on("data",d=>{let w=d.toString(),{lines:S,remaining:v}=this.processStreamBuffer(w,g);c.push(...S),g=v}),a.on("close",d=>{h&&clearTimeout(h),f.trim()&&l.push(f),g.trim()&&c.push(g),s({success:d===0,stdout:l,stderr:c})}),a.on("error",d=>{h&&clearTimeout(h);let w=`Error: ${d.message}`;s({success:!1,stdout:l,stderr:[...c,w]})})})}async runRealtime(e,t,r,n,s,i,a,l){let f=Ae(t,n,s,{borderColor:r?"green":"cyan"});console.log(f);let g=Date.now();return new Promise(h=>{let d=this.spawnWithShell(e,i,l),w="",S="",v=null;a&&a>0&&(v=setTimeout(()=>{d.kill("SIGTERM");let $=`Command timed out after ${a} seconds`,C=ae($);console.error(C);let W=Date.now()-g,_=Z(!1,!1,W);console.log(_),h(!1)},a*1e3)),d.stdout?.on("data",$=>{let C=$.toString(),{lines:W,remaining:_}=this.processStreamBuffer(C,w);W.forEach(Ce=>process.stdout.write(`\u2502 ${Ce}
|
|
6
|
+
`)),w=_}),d.stderr?.on("data",$=>{let C=$.toString(),{lines:W,remaining:_}=this.processStreamBuffer(C,S);W.forEach(Ce=>process.stderr.write(`\u2502 ${Ce}
|
|
7
|
+
`)),S=_}),d.on("close",$=>{v&&clearTimeout(v),w.trim()&&process.stdout.write(`\u2502 ${w}
|
|
8
|
+
`),S.trim()&&process.stderr.write(`\u2502 ${S}
|
|
9
|
+
`);let C=$===0,W=Date.now()-g,_=Z(C,!1,W);console.log(_),h(C)}),d.on("error",$=>{v&&clearTimeout(v);let C=ae(`Error: ${$.message}`);console.error(C),h(!1)})})}createSpawnOptions(e){let t={stdio:["inherit","pipe","pipe"],shell:!0};return e&&(t.cwd=e),t}spawnWithShell(e,t,r){if(r&&r.length>0){let n=r[0],s=[...r.slice(1),e],i={stdio:["inherit","pipe","pipe"]};return t&&(i.cwd=t),(0,Fe.spawn)(n,s,i)}else{let n=process.env.SHELL||(process.platform==="win32"?"cmd.exe":"/bin/sh"),s=process.platform==="win32"?"/c":"-c",i={stdio:["inherit","pipe","pipe"]};return t&&(i.cwd=t),(0,Fe.spawn)(n,[s,e],i)}}processStreamBuffer(e,t){let r=t+e,n=[],s=r;for(;s.includes(`
|
|
10
10
|
`);){let i=s.indexOf(`
|
|
11
11
|
`),a=s.substring(0,i);s=s.substring(i+1),n.push(a)}return{lines:n,remaining:s}}formatNestedOutput(e,t){t?e.split(`
|
|
12
|
-
`).forEach(
|
|
13
|
-
`)}),e.stderr.forEach(l=>{let c=Oe(l,
|
|
14
|
-
`)});let a=Z(e.success,
|
|
15
|
-
Total execution time: ${i}`)),await
|
|
16
|
-
`)):(0,
|
|
17
|
-
`))}displayParallelResults(e,t,
|
|
12
|
+
`).forEach(r=>{r.trim()&&console.log(`| ${r}`)}):console.log(e)}displayBufferedOutput(e,t,r=!1,n,s){let i=Ae(t,n,s,{borderColor:"cyan",isNested:r});this.formatNestedOutput(i,r),e.stdout.forEach(l=>{let c=Oe(l,r);process.stdout.write(`${c}
|
|
13
|
+
`)}),e.stderr.forEach(l=>{let c=Oe(l,r);process.stderr.write(`${c}
|
|
14
|
+
`)});let a=Z(e.success,r);console.log(a)}};function tr(o,e,t){if(e.hasVariable(o)){let r=e.getVariable(o);return r??t}if(e.hasFact(o)){let r=e.getFact(o);return typeof r=="string"?r:String(r)}if(e.hasChoice(o)){let r=e.getChoice(o);return r??t}return t}function ye(o,e){let t=/\{\{\s*(\w+)\s*\}\}/g;return o.replace(t,(r,n)=>tr(n,e,r))}var be=class o{state;constructor(){this.state={facts:new Map,choices:new Map,variables:new Map,stepResults:new Map,lastStepIndex:-1}}hasFact(e){return this.state.facts.has(e)}getFact(e){return this.state.facts.get(e)}setFact(e,t){this.state.facts.set(e,t)}getFactStatus(e){if(!this.hasFact(e))return"pending";let t=this.getFact(e);return t===!1||t==="failed"?"failed":"ready"}getAllFacts(){return new Map(this.state.facts)}hasChoice(e){return this.state.choices.has(e)}getChoice(e){return this.state.choices.get(e)}setChoice(e,t){this.state.choices.set(e,t)}hasVariable(e){return this.state.variables.has(e)}getVariable(e){return this.state.variables.get(e)}setVariable(e,t){this.state.variables.set(e,t)}getAllVariables(){return new Map(this.state.variables)}setStepResult(e,t,r){this.state.stepResults.set(e,{success:t,exitCode:r}),this.state.lastStepIndex=e}getStepResult(e){return this.state.stepResults.get(e)}getLastStepResult(){if(this.state.lastStepIndex!==-1)return this.state.stepResults.get(this.state.lastStepIndex)}clone(){let e=new o;return e.state.facts=new Map(this.state.facts),e.state.choices=new Map(this.state.choices),e.state.variables=new Map(this.state.variables),e.state.stepResults=new Map(this.state.stepResults),e.state.lastStepIndex=this.state.lastStepIndex,e}};var te=class o{static PARALLEL_STEP_INDEX_MULTIPLIER=1e3;workspace;taskRunner;choicePrompt;textPrompt;baseDir;globalShell;constructor(){this.workspace=new be,this.taskRunner=new we,this.choicePrompt=new L,this.textPrompt=new me}resolveBaseDir(e){if(e.baseDir)if((0,z.isAbsolute)(e.baseDir))this.baseDir=e.baseDir;else if(e._filePath){let t=(0,z.dirname)(e._filePath);this.baseDir=(0,z.resolve)(t,e.baseDir)}else this.baseDir=(0,z.resolve)(process.cwd(),e.baseDir)}createStepContext(e,t){let r={workspace:this.workspace,stepIndex:e};return t._lineNumbers&&(r.lineNumber=t._lineNumbers.get(e)),t._fileName&&(r.fileName=t._fileName),r}evaluateStepCondition(e){return e.when?new K(this.workspace).evaluate(e.when):!0}calculateBaseStepIndex(e){return e.branchIndex===void 0?e.stepIndex:Math.floor(e.stepIndex/o.PARALLEL_STEP_INDEX_MULTIPLIER)}isRunStep(e){return"run"in e}async execute(e,t){if(t?.profileVars&&Object.keys(t.profileVars).length>0)for(let[a,l]of Object.entries(t.profileVars))this.workspace.setVariable(a,l);this.resolveBaseDir(e),this.globalShell=e.shell;let r=new ge,n=Date.now();for(let a=0;a<e.steps.length;a++){let l=e.steps[a],c=this.createStepContext(a,e),f=!!l.when;if(this.evaluateStepCondition(l)){r.recordStart();try{let g=await this.executeStep(l,c,!1,f);this.handleStepResult(l,c,a,g,r)}catch(g){throw this.handleStepError(l,c,a,g,r),g}}}let s=Date.now()-n,i=X(s);console.log(lt.default.cyan(`
|
|
15
|
+
Total execution time: ${i}`)),await r.save(),r.reset()}isStepSuccessful(e,t){return"run"in t?typeof e=="boolean"?e:e&&typeof e=="object"&&"success"in e?e.success:!1:!0}handleStepResult(e,t,r,n,s){let i=this.isRunStep(e)?(()=>{let c=this.workspace.getStepResult(r);return c?c.success:!0})():this.isStepSuccessful(n,e),a=i?"success":"failure",l=s.recordEnd(e,t,n,a);if(!this.isRunStep(e)){let c=Z(i,!1,l);console.log(c)}if(this.isRunStep(e)){if(e.continue===!1){let c=t.lineNumber?` (line ${t.lineNumber})`:"",f=i?`Step ${r}${c} completed, but workflow stopped due to continue: false`:`Step ${r}${c} failed`;throw new Error(f)}if(!i&&e.continue!==!0){let c=t.lineNumber?` (line ${t.lineNumber})`:"";throw new Error(`Step ${r}${c} failed`)}}}handleStepError(e,t,r,n,s){this.workspace.setStepResult(r,!1);let i=n instanceof Error?n.message:String(n),a={success:!1,stdout:[],stderr:[i]};s.recordEnd(e,t,a,"failure")}fixMalformedStep(e){let r=e;return"choose"in e&&r.choose===null&&"message"in e&&"options"in e?{choose:{message:r.message,options:r.options,as:r.as},when:r.when}:"prompt"in e&&r.prompt===null&&"message"in e&&"as"in e?{prompt:{message:r.message,as:r.as,default:r.default},when:r.when}:e}async executeStep(e,t,r=!1,n=!1){if(e=this.fixMalformedStep(e),"run"in e){let s=await this.executeRunStep(e,t,r,n);return r&&typeof s=="object"&&"stdout"in s,s}if("choose"in e){await this.executeChooseStep(e,t);return}if("prompt"in e){await this.executePromptStep(e,t);return}if("parallel"in e){await this.executeParallelStep(e,t);return}if("fail"in e){await this.executeFailStep(e,t);return}}async executeSingleRun(e,t,r=!1,n=!1){let s=this.calculateBaseStepIndex(t),i=ye(e.run,this.workspace),a=e.shell||this.globalShell,l=e.retry??0,c=l==="Infinity"||l===1/0,f=typeof l=="number"?l:0,g=e.timeout,h=!1,d=0;for(;c||d<=f;){let w=await this.taskRunner.run(i,s,i,t.branchIndex,r,n,t.lineNumber,t.fileName,this.baseDir,g,a),S=typeof w=="boolean"?w:w.success;if(h=w,S||!c&&d>=f)break;if(d++,c||d<=f){let v=Math.min(1e3*Math.pow(2,d-1),1e4);await new Promise($=>setTimeout($,v))}}return h}async executeRunStep(e,t,r=!1,n=!1){let s=await this.executeSingleRun({run:e.run,timeout:e.timeout,retry:e.retry,shell:e.shell},t,r,n),i=typeof s=="boolean"?s:s.success;if(this.workspace.setStepResult(t.stepIndex,i),i||!e.onError)return s;let a={run:e.onError.run,timeout:e.onError.timeout,retry:e.onError.retry,onError:e.onError.onError??void 0};return await this.executeRunChain(a,t,r,n)}async executeRunChain(e,t,r,n){let s=await this.executeSingleRun({run:e.run,timeout:e.timeout,retry:e.retry,shell:void 0},t,r,n);return(typeof s=="boolean"?s:s.success)||!e.onError?s:this.executeRunChain(e.onError,t,r,n)}async executeChooseStep(e,t){let r=e.choose.as,n=e.choose.options.map(a=>a.id);if(r&&this.workspace.hasVariable(r)){let a=this.workspace.getVariable(r)??"";if(n.includes(a)){this.workspace.setChoice(a,a),this.workspace.setStepResult(t.stepIndex,!0);return}}let s=await this.choicePrompt.prompt(e.choose.message,e.choose.options);if(!s?.id)throw new Error(`Invalid choice result: ${JSON.stringify(s)}`);let i=r??s.id;this.workspace.setChoice(s.id,s.id),this.workspace.setVariable(i,s.id),this.workspace.setStepResult(t.stepIndex,!0)}async executePromptStep(e,t){let r=e.prompt.as;if(this.workspace.hasVariable(r)){let a=this.workspace.getVariable(r)??"";this.workspace.setFact(r,a),this.workspace.setStepResult(t.stepIndex,!0);return}let n=ye(e.prompt.message,this.workspace),s=e.prompt.default?ye(e.prompt.default,this.workspace):void 0,i=await this.textPrompt.prompt(n,s);this.workspace.setVariable(r,i),this.workspace.setFact(r,i),this.workspace.setStepResult(t.stepIndex,!0)}createParallelContexts(e,t){return e.parallel.map((r,n)=>({workspace:this.workspace.clone(),stepIndex:t.stepIndex*o.PARALLEL_STEP_INDEX_MULTIPLIER+n,branchIndex:n,lineNumber:t.lineNumber,fileName:t.fileName}))}getBranchDisplayName(e,t){return"run"in e?e.run:"choose"in e?`Choose: ${e.choose.message}`:"prompt"in e?`Prompt: ${e.prompt.message}`:"fail"in e?`Fail: ${e.fail.message}`:`Branch ${t+1}`}async executeParallelBranches(e,t){let r=[],n=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],s=0;for(let c=0;c<e.length;c++){let f=e[c],g=t[c];if(f.when&&!new K(g.workspace).evaluate(f.when))continue;let h=this.getBranchDisplayName(f,c);r.push({index:c,name:h,status:"pending"})}let i=setInterval(()=>{s=(s+1)%n.length,this.updateParallelBranchesDisplay(r,n[s])},100),a=r.map(async c=>{let{index:f}=c,g=e[f],h=t[f];c.status="running";try{let d=await this.executeStep(g,h,!0);return c.status="success",this.updateParallelBranchesDisplay(r,n[s]),{index:f,result:d,context:h}}catch(d){h.workspace.setStepResult(h.stepIndex,!1);let w=d instanceof Error?d.message:String(d);return c.status="failed",c.error=w,this.updateParallelBranchesDisplay(r,n[s]),{index:f,error:d,context:h}}}),l=await Promise.all(a);return clearInterval(i),this.updateParallelBranchesDisplay(r,"",!0),Se.default.done(),l}updateParallelBranchesDisplay(e,t,r=!1){let n=e.map(s=>{let i=s.index+1,a="",l="";switch(s.status){case"pending":a="\u25CB",l=`Branch ${i}: ${s.name} - Pending`;break;case"running":a=t,l=`Branch ${i}: ${s.name} - Running...`;break;case"success":a="\u2713",l=`Branch ${i}: ${s.name} - Completed`;break;case"failed":a="\u2717",l=`Branch ${i}: ${s.name} - Failed${s.error?`: ${s.error}`:""}`;break}return`${a} ${l}`});r?(0,Se.default)(n.join(`
|
|
16
|
+
`)):(0,Se.default)(n.join(`
|
|
17
|
+
`))}displayParallelResults(e,t,r){let n=!0,s=!1;console.log("");for(let a of e){if(!a)continue;s=!0;let{index:l,result:c,error:f,context:g}=a;if(f){n=!1;let h=`Branch ${l+1} failed: ${f instanceof Error?f.message:String(f)}`,d=ae(h);console.error(d)}else if(c&&typeof c=="object"&&"stdout"in c){let h=c;if(n=n&&h.success,h.stdout.length>0||h.stderr.length>0||!h.success){let d=t[l],w=this.getBranchDisplayName(d,l);this.taskRunner.displayBufferedOutput(h,w,!1,g.lineNumber,g.fileName)}}}s||console.log("\u26A0\uFE0F All parallel branches were skipped (conditions not met)");let i=ot(n);return console.log(i),n}mergeParallelResults(e){for(let t of e){let r=t.workspace.getAllFacts(),n=t.workspace.getAllVariables();for(let[s,i]of r)this.workspace.setFact(s,i);for(let[s,i]of n)this.workspace.setVariable(s,i)}}countExecutableBranches(e,t){let r=0;for(let n=0;n<e.length;n++){let s=e[n],i=t[n];s.when&&!new K(i.workspace).evaluate(s.when)||r++}return r}async executeParallelStep(e,t){let r=this.createParallelContexts(e,t),n=this.countExecutableBranches(e.parallel,r),s=rt(n);console.log(s);let i=await this.executeParallelBranches(e.parallel,r),a=this.displayParallelResults(i,e.parallel,t);if(this.workspace.setStepResult(t.stepIndex,a),!a){let l=t.lineNumber?` (line ${t.lineNumber})`:"";throw new Error(`Parallel step ${t.stepIndex}${l} failed: one or more branches failed`)}this.mergeParallelResults(r)}async executeFailStep(e,t){let r=new Error(e.fail.message);throw r.stack=void 0,r}};var ft=require("yaml"),_e=require("zod");var u=require("zod"),rr=u.z.object({file:u.z.string()}),or=u.z.object({var:u.z.union([u.z.string(),u.z.record(u.z.string(),u.z.string())]).optional(),has:u.z.string().optional()}),nr=u.z.object({status:u.z.object({fact:u.z.string(),is:u.z.enum(["ready","failed","pending"])})}),sr=u.z.object({step:u.z.object({success:u.z.boolean()}).optional(),last_step:u.z.enum(["success","failure"]).optional()}),ir=u.z.object({choice:u.z.string()}),ar=u.z.union([rr,ir,or,nr,sr]),M=u.z.lazy(()=>u.z.union([ar,u.z.object({all:u.z.array(M)}),u.z.object({any:u.z.array(M)}),u.z.object({not:M})])),ct=u.z.lazy(()=>u.z.object({run:u.z.string(),timeout:u.z.number().optional(),retry:u.z.union([u.z.number(),u.z.literal("Infinity")]).optional(),onError:ct.optional()})),ut=u.z.object({run:u.z.string(),when:M.optional(),timeout:u.z.number().optional(),retry:u.z.union([u.z.number(),u.z.literal("Infinity")]).optional(),shell:u.z.array(u.z.string()).min(1,"shell must have at least one element").optional(),continue:u.z.boolean().optional(),onError:ct.optional()}),lr=u.z.object({choose:u.z.object({message:u.z.string(),options:u.z.array(u.z.object({id:u.z.string(),label:u.z.string()})),as:u.z.string().optional()}),when:M.optional()}),cr=u.z.object({prompt:u.z.object({message:u.z.string(),as:u.z.string(),default:u.z.string().optional(),validate:u.z.string().optional()}),when:M.optional()});function pt(o){if(!o||typeof o!="object")return{found:!1};let e=o;if("choose"in e)return{found:!0,type:"choose"};if("prompt"in e)return{found:!0,type:"prompt"};if("parallel"in e&&Array.isArray(e.parallel))for(let t of e.parallel){let r=pt(t);if(r.found)return r}return{found:!1}}var dt=u.z.lazy(()=>u.z.union([ut,u.z.object({parallel:u.z.array(u.z.lazy(()=>dt)),when:M.optional()}),u.z.object({fail:u.z.object({message:u.z.string()}),when:M.optional()})]).superRefine((o,e)=>{let t=pt(o);t.found&&e.addIssue({code:u.z.ZodIssueCode.custom,message:`'${t.type}' step is not allowed inside 'parallel' block (user input cannot run in parallel)`})})),ur=u.z.lazy(()=>u.z.union([ut,lr,cr,u.z.object({parallel:u.z.array(dt),when:M.optional()}),u.z.object({fail:u.z.object({message:u.z.string()}),when:M.optional()})])),pr=u.z.object({name:u.z.string().min(1,"Profile name must be non-empty"),var:u.z.record(u.z.string(),u.z.union([u.z.string(),u.z.number(),u.z.boolean()]).transform(String))}),dr=u.z.object({name:u.z.string().optional(),baseDir:u.z.string().optional(),shell:u.z.array(u.z.string()).min(1,"shell must have at least one element").optional(),profiles:u.z.array(pr).optional(),steps:u.z.array(ur).min(1,"Workflow must have at least one step")});function Le(o){return dr.parse(o)}function mt(o,e){let t=o.path;if(o.code==="custom"){let n=Be(t);return` - ${o.message}${n}`}if(o.message==="Invalid input"){let n=Be(t),s=fr(t,e);return s?` - ${s}${n}`:` - Invalid step type${n}`}let r=Be(t);return` - ${o.message}${r}`}function Be(o){if(o.length===0)return"";let e=[];for(let t=0;t<o.length;t++){let r=o[t],n=o[t+1];r==="steps"&&typeof n=="number"?(e.push(`step ${n+1}`),t++):r==="parallel"&&typeof n=="number"?(e.push(`parallel branch ${n+1}`),t++):typeof r=="string"&&r!=="steps"&&r!=="parallel"&&e.push(r)}return e.length>0?` (${e.join(" \u2192 ")})`:""}function b(o,e,t){let r=t?`
|
|
18
18
|
Reason: ${t}`:"";throw new Error(`Invalid workflow structure:
|
|
19
|
-
- ${e} (step ${
|
|
19
|
+
- ${e} (step ${o+1})${r}`)}function ht(o,e,t=!1,r=[]){let n=["run","choose","prompt","parallel","fail"],s=n.find(i=>i in o);if(!s){let i=Object.keys(o).filter(a=>a!=="when");b(e,`Unknown step type. Found keys: [${i.join(", ")}]. Valid types: ${n.join(", ")}`)}if(s==="run"){let i=o.run;if(typeof i!="string"&&b(e,"'run' must be a string command"),i===""&&b(e,"'run' command cannot be empty"),"shell"in o&&o.shell!==void 0){Array.isArray(o.shell)||b(e,"'shell' must be an array");let a=o.shell;a.length===0&&b(e,"'shell' cannot be empty","Shell configuration must have at least one element (program name)");for(let l=0;l<a.length;l++)typeof a[l]!="string"&&b(e,`'shell[${l}]' must be a string`)}}if(s==="choose"){if(t){let l=r.join(" \u2192 ");throw new Error(`Invalid workflow structure:
|
|
20
20
|
- 'choose' step is not allowed inside 'parallel' block (step ${e+1}, ${l})
|
|
21
|
-
Reason: User input prompts cannot run in parallel`)}let i=
|
|
21
|
+
Reason: User input prompts cannot run in parallel`)}let i=o.choose;(!i||typeof i!="object")&&b(e,"'choose' must be an object with 'message' and 'options'");let a=i;(!a.message||typeof a.message!="string")&&b(e,"'choose.message' is required and must be a string"),Array.isArray(a.options)||b(e,"'choose.options' is required and must be an array"),a.options.length===0&&b(e,"'choose.options' cannot be empty","At least one option is required");for(let l=0;l<a.options.length;l++){let c=a.options[l];(!c||typeof c!="object")&&b(e,`'choose.options[${l}]' must be an object with 'id' and 'label'`),(!c.id||typeof c.id!="string")&&b(e,`'choose.options[${l}].id' is required and must be a string`),(!c.label||typeof c.label!="string")&&b(e,`'choose.options[${l}].label' is required and must be a string`)}}if(s==="prompt"){if(t){let l=r.join(" \u2192 ");throw new Error(`Invalid workflow structure:
|
|
22
22
|
- 'prompt' step is not allowed inside 'parallel' block (step ${e+1}, ${l})
|
|
23
|
-
Reason: User input prompts cannot run in parallel`)}let i=
|
|
24
|
-
- Workflow must be an object`);let e=
|
|
23
|
+
Reason: User input prompts cannot run in parallel`)}let i=o.prompt;(!i||typeof i!="object")&&b(e,"'prompt' must be an object with 'message' and 'as'");let a=i;(!a.message||typeof a.message!="string")&&b(e,"'prompt.message' is required and must be a string"),(!a.as||typeof a.as!="string")&&b(e,"'prompt.as' is required and must be a string","The 'as' field specifies the variable name to store the user's input")}if(s==="parallel"){let i=o.parallel;Array.isArray(i)||b(e,"'parallel' must be an array of steps"),i.length===0&&b(e,"'parallel' cannot be empty","At least one step is required");for(let a=0;a<i.length;a++){let l=i[a];(!l||typeof l!="object")&&b(e,`'parallel[${a}]' must be a valid step object`);let c=[...r,`branch ${a+1}`];ht(l,e,!0,c)}}if(s==="fail"){let i=o.fail;(!i||typeof i!="object")&&b(e,"'fail' must be an object with 'message'");let a=i;(!a.message||typeof a.message!="string")&&b(e,"'fail.message' is required and must be a string")}}function gt(o){if(!o||typeof o!="object")throw new Error(`Invalid workflow structure:
|
|
24
|
+
- Workflow must be an object`);let e=o;if("name"in e&&e.name!==void 0&&typeof e.name!="string")throw new Error(`Invalid workflow structure:
|
|
25
25
|
- 'name' must be a string`);if("shell"in e&&e.shell!==void 0){if(!Array.isArray(e.shell))throw new Error(`Invalid workflow structure:
|
|
26
26
|
- 'shell' must be an array`);if(e.shell.length===0)throw new Error(`Invalid workflow structure:
|
|
27
27
|
- 'shell' cannot be empty
|
|
@@ -30,43 +30,43 @@ Total execution time: ${i}`)),await o.save(),o.reset()}isStepSuccessful(e,t){ret
|
|
|
30
30
|
- 'steps' is required`);if(!Array.isArray(e.steps))throw new Error(`Invalid workflow structure:
|
|
31
31
|
- 'steps' must be an array`);if(e.steps.length===0)throw new Error(`Invalid workflow structure:
|
|
32
32
|
- 'steps' cannot be empty
|
|
33
|
-
Reason: Workflow must have at least one step`);for(let t=0;t<e.steps.length;t++){let
|
|
34
|
-
- Step ${t+1} must be an object`);
|
|
33
|
+
Reason: Workflow must have at least one step`);for(let t=0;t<e.steps.length;t++){let r=e.steps[t];if(!r||typeof r!="object")throw new Error(`Invalid workflow structure:
|
|
34
|
+
- Step ${t+1} must be an object`);ht(r,t)}}function fr(o,e){try{let t=e;for(let s of o)if(typeof s!="symbol")if(t&&typeof t=="object")t=t[s];else return null;if(!t||typeof t!="object")return null;let n=Object.keys(t);if(n.length>0){let s=["run","choose","prompt","parallel","fail"];if(!n.some(a=>s.includes(a)))return`Unknown step type. Found keys: [${n.join(", ")}]. Valid types: run, choose, prompt, parallel, fail`}return null}catch{return null}}function Ve(o){let e=o;return"choose"in e&&(e.choose===null||e.choose===void 0)&&"message"in e&&"options"in e?{choose:{message:e.message,options:e.options,as:e.as},when:e.when}:"prompt"in e&&(e.prompt===null||e.prompt===void 0)&&"message"in e&&"as"in e?{prompt:{message:e.message,as:e.as,default:e.default,validate:e.validate},when:e.when}:"parallel"in e&&Array.isArray(e.parallel)?{...e,parallel:e.parallel.map(t=>Ve(t))}:o}var ve=class{parse(e){let t;try{t=(0,ft.parse)(e)}catch(r){throw new Error(`Invalid YAML format: ${r instanceof Error?r.message:String(r)}`)}if(t&&typeof t=="object"&&"steps"in t){let r=t;Array.isArray(r.steps)&&(r.steps=r.steps.map(n=>Ve(n)))}gt(t);try{return Le(t)}catch(r){if(r instanceof _e.ZodError){let n=r.issues.map(s=>mt(s,t)).filter(s=>s!==null).join(`
|
|
35
35
|
`);throw new Error(`Invalid workflow structure:
|
|
36
|
-
${n}`)}throw
|
|
37
|
-
`),n=0,s=!1;for(let i=0;i<
|
|
36
|
+
${n}`)}throw r}}extractStepLineNumbers(e){let t=new Map,r=e.split(`
|
|
37
|
+
`),n=0,s=!1;for(let i=0;i<r.length;i++){let a=r[i].trim();if(a==="steps:"||a.startsWith("steps:")){s=!0;continue}s&&a.startsWith("-")&&t.set(n++,i+1)}return t}},We=class{parse(e){let t;try{t=JSON.parse(e)}catch(r){throw new Error(`Invalid JSON format: ${r instanceof Error?r.message:String(r)}`)}if(t&&typeof t=="object"&&"steps"in t){let r=t;Array.isArray(r.steps)&&(r.steps=r.steps.map(n=>Ve(n)))}gt(t);try{return Le(t)}catch(r){if(r instanceof _e.ZodError){let n=r.issues.map(s=>mt(s,t)).filter(s=>s!==null).join(`
|
|
38
38
|
`);throw new Error(`Invalid workflow structure:
|
|
39
|
-
${n}`)}throw
|
|
40
|
-
`),n=0,s=!1,i=!1;for(let a=0;a<
|
|
41
|
-
`);console.log(`${(0,
|
|
39
|
+
${n}`)}throw r}}extractStepLineNumbers(e){let t=new Map,r=e.split(`
|
|
40
|
+
`),n=0,s=!1,i=!1;for(let a=0;a<r.length;a++){let c=r[a].trim();if(c.startsWith('"steps"')||c.startsWith("'steps'")){s=!0,c.includes("[")&&(i=!0);continue}if(s&&c==="["){i=!0;continue}if(i&&c==="]"){i=!1,s=!1;continue}i&&c.startsWith("{")&&t.set(n++,a+1)}return t}};function le(o){switch(o.toLowerCase().split(".").pop()){case"yaml":case"yml":return new ve;case"json":return new We;default:return new ve}}var St=require("fs/promises"),vt=require("path"),xe=y(require("boxen"),1),j=y(require("chalk"),1),ze=y(require("node-cron"),1);ie();var re=require("fs/promises"),yt=require("os"),He=require("path"),bt=(0,He.join)((0,yt.homedir)(),".pipeliner","schedules"),wt=(0,He.join)(bt,"schedules.json"),I=class{async loadSchedules(){try{let e=await(0,re.readFile)(wt,"utf-8");return JSON.parse(e).schedules||[]}catch(e){if(e instanceof Error&&"code"in e&&e.code==="ENOENT")return[];throw e}}async saveSchedules(e){await(0,re.mkdir)(bt,{recursive:!0}),await(0,re.writeFile)(wt,JSON.stringify({schedules:e},null,2),"utf-8")}async addSchedule(e){let t=await this.loadSchedules(),r=Math.random().toString(36).slice(2,10),n=new Date().toISOString(),s={id:r,createdAt:n,...e};return t.push(s),await this.saveSchedules(t),s}async removeSchedule(e){let t=await this.loadSchedules(),r=t.length,n=t.filter(s=>s.id!==e);return n.length===r?!1:(await this.saveSchedules(n),!0)}async updateLastRun(e){let t=await this.loadSchedules(),r=t.find(n=>n.id===e);r&&(r.lastRun=new Date().toISOString(),await this.saveSchedules(t))}async toggleSchedule(e,t){let r=await this.loadSchedules(),n=r.find(s=>s.id===e);return n?(n.enabled=t,await this.saveSchedules(r),!0):!1}async getSchedule(e){return(await this.loadSchedules()).find(r=>r.id===e)}};var mr=/^([+-])?(\d{1,2})(?::(\d{2}))?$/;function hr(o){let e=o.trim();if(e===""||e==="0"||e==="+0"||e==="-0")return 0;let t=e.match(mr);if(!t)return null;let r=t[1],n=parseInt(t[2],10);if(n>14)return null;let i=r==="-"?-n:n;return i<-12||i>14?null:i}function gr(o){if(o===0)return"UTC";let e=o>0?"-":"+",t=Math.abs(o);return`Etc/GMT${e}${t}`}function ke(o){if(!o?.trim())return;let e=hr(o);if(e!==null)return gr(e)}var B=class{scheduleManager;tasks=new Map;startOptions;constructor(){this.scheduleManager=new I}async start(e=!1,t){if(this.startOptions=t,!e&&await A()){let n=await R();throw new Error(`Scheduler daemon is already running (PID: ${n.pid}). Use "tp schedule stop" to stop it first.`)}if(e)console.log("\u{1F680} Starting scheduler daemon in background...");else{let n=j.default.bold("\u{1F680} Starting workflow scheduler...");console.log((0,xe.default)(n,{borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:"cyan"}))}if(await this.reload(),e){if(!process.env.TP_DAEMON_MODE){let n=[j.default.green("\u2713 Scheduler daemon started"),"",j.default.gray(`PID: ${process.pid}`),j.default.dim(" tp schedule stop stop daemon"),j.default.dim(" tp schedule status check status")].join(`
|
|
41
|
+
`);console.log(`${(0,xe.default)(n,{borderStyle:"round",padding:{top:1,bottom:1,left:2,right:2},borderColor:"green"})}
|
|
42
42
|
`)}}else{let n=[j.default.green("\u2713 Scheduler is running"),j.default.dim(" Press Ctrl+C to stop")].join(`
|
|
43
|
-
`);console.log((0,
|
|
44
|
-
\u23F9 Stopping scheduler...`),this.stop(),await se(),e||process.exit(0)};process.on("SIGINT",
|
|
45
|
-
`));return}for(let
|
|
46
|
-
\u23F0 Running scheduled workflow: ${t}`),console.log(` Time: ${new Date().toISOString()}`),e.profile&&console.log(` Profile: ${e.profile}`));try{let
|
|
47
|
-
`)}catch(
|
|
48
|
-
`))}}stop(){for(let e of this.tasks.values())e.stop();this.tasks.clear()}async stopDaemon(){let e=await
|
|
43
|
+
`);console.log((0,xe.default)(n,{borderStyle:"round",padding:{top:0,bottom:0,left:2,right:2},margin:{top:0,bottom:0,left:0,right:0},borderColor:"green"}))}let r=async()=>{e||console.log(`
|
|
44
|
+
\u23F9 Stopping scheduler...`),this.stop(),await se(),e||process.exit(0)};process.on("SIGINT",r),process.on("SIGTERM",r),e&&process.stdin.destroy()}async reload(){this.stop();let t=(await this.scheduleManager.loadSchedules()).filter(r=>r.enabled);if(t.length===0){console.log(j.default.gray(` No enabled schedules to load.
|
|
45
|
+
`));return}for(let r of t)try{this.startSchedule(r)}catch(n){console.error(j.default.red(` \u2717 Failed to start schedule ${r.id}:`),n)}}startSchedule(e){if(!ze.default.validate(e.cron)){console.error(` \u2717 Invalid cron expression for schedule ${e.id}: ${e.cron}`);return}let t={},r=ke(e.timezone);r&&(t.timezone=r);let n;try{n=ze.default.schedule(e.cron,async()=>{await this.executeSchedule(e)},t)}catch(s){throw console.error(` \u2717 Cron schedule failed for ${e.id} (timezone: ${r??"local"}).`,s instanceof Error?s.message:s),s}this.tasks.set(e.id,n),this.startOptions?.onScheduleStarted?.(e)}async executeSchedule(e){let t=e.name??e.workflowPath;e.silent||(console.log(`
|
|
46
|
+
\u23F0 Running scheduled workflow: ${t}`),console.log(` Time: ${new Date().toISOString()}`),e.profile&&console.log(` Profile: ${e.profile}`));try{let r=(0,vt.resolve)(e.workflowPath),n=le(r),s=await(0,St.readFile)(r,"utf-8"),i=n.parse(s),a=new te,l={};if(e.profile){if(!i.profiles)throw new Error(`Profile "${e.profile}" not found: no profiles defined in workflow`);let c=i.profiles.find(f=>f.name===e.profile);if(!c)throw new Error(`Profile "${e.profile}" not found. Available profiles: ${i.profiles.map(f=>f.name).join(", ")}`);l.profileVars=c.var}await a.execute(i,l),await this.scheduleManager.updateLastRun(e.id),e.silent||console.log(`\u2713 Scheduled workflow completed: ${t}
|
|
47
|
+
`)}catch(r){e.silent||(console.error(`\u2717 Scheduled workflow failed: ${t}`),console.error(` Error: ${r instanceof Error?r.message:String(r)}
|
|
48
|
+
`))}}stop(){for(let e of this.tasks.values())e.stop();this.tasks.clear()}async stopDaemon(){let e=await R();if(!e.running||!e.pid)return!1;let t=e.pid;try{if(process.kill(t,"SIGTERM"),await new Promise(r=>setTimeout(r,1e3)),await A()){try{process.kill(t,"SIGKILL")}catch{}await new Promise(r=>setTimeout(r,500))}return await se(),!0}catch{return await se(),!1}}};var Mt=require("child_process"),$e=require("fs"),O=require("path"),Re=y(require("boxen"),1),m=y(require("chalk"),1),jt=require("commander"),Ee=y(require("dayjs"),1),Y=y(require("inquirer"),1),ue=y(require("log-update"),1),Nt=y(require("node-cron"),1);ie();var xt=require("fs/promises"),$t=require("path"),Rt=require("yaml"),Et=require("zod");var P=require("zod"),wr=P.z.object({name:P.z.string().min(1,"Schedule name must be non-empty"),cron:P.z.string().min(1,"Cron expression is required"),workflow:P.z.string().min(1,"Workflow path is required"),baseDir:P.z.string().optional(),timezone:P.z.union([P.z.string(),P.z.number()]).transform(String).optional(),silent:P.z.boolean().optional(),profile:P.z.string().optional()}),yr=P.z.object({schedules:P.z.array(wr).min(1,"Schedule file must have at least one schedule")});function kt(o){return yr.parse(o)}async function Pt(o){let e=await(0,xt.readFile)(o,"utf-8"),t=(0,$t.extname)(o).toLowerCase(),r;try{if(t===".yaml"||t===".yml")r=(0,Rt.parse)(e);else if(t===".json")r=JSON.parse(e);else throw new Error(`Unsupported file format: ${t}. Use .yaml, .yml, or .json`)}catch(n){if(n instanceof Error&&n.message.startsWith("Unsupported"))throw n;let s=t===".json"?"JSON":"YAML";throw new Error(`Invalid ${s} format: ${n instanceof Error?n.message:String(n)}`)}try{return kt(r)}catch(n){if(n instanceof Et.ZodError){let s=n.issues.map(i=>` - ${i.message} (${i.path.join(".")})`).join(`
|
|
49
49
|
`);throw new Error(`Invalid schedule file structure:
|
|
50
|
-
${s}`)}throw n}}var
|
|
51
|
-
`);return(0,
|
|
52
|
-
Found ${
|
|
53
|
-
`);let i=[];for(let
|
|
50
|
+
${s}`)}throw n}}var Ct=y(require("boxen"),1),k=y(require("chalk"),1),Dt=y(require("cronstrue"),1),Ye=y(require("dayjs"),1),Ue=y(require("node-cron"),1);function br(o){try{return Dt.default.toString(o)}catch{return null}}function Sr(o){if(!Ue.default.validate(o.cron))return null;try{let e={},t=ke(o.timezone);t&&(e.timezone=t);let r=Ue.default.createTask(o.cron,()=>{},e),n=r.getNextRun();return r.destroy(),n}catch{return null}}function ce(o,e){let t=o,{daemonRunning:r}=e,n=t.enabled?k.default.green("enabled"):k.default.gray("disabled"),s=r&&t.enabled,i=s?k.default.green("\u25CF active"):k.default.gray("\u25CB inactive"),a=k.default.bold(t.name??t.workflowPath),l=Sr(t),c=l?(0,Ye.default)(l).format("YYYY-MM-DD HH:mm:ss"):k.default.dim("\u2014"),f=t.lastRun?(0,Ye.default)(t.lastRun).format("YYYY-MM-DD HH:mm:ss"):k.default.dim("never"),g=br(t.cron),h=t.timezone?t.timezone.startsWith("+")||t.timezone.startsWith("-")?`UTC${t.timezone}`:`UTC+${t.timezone}`:null,d=g?`${t.cron} ${k.default.dim(`\u2192 ${g}`)}`:t.cron,w=[[k.default.gray("Enabled"),n],[k.default.gray("Cron"),d],...h?[[k.default.gray("Timezone"),h]]:[],[k.default.gray("Workflow"),t.workflowPath],...t.profile?[[k.default.gray("Profile"),k.default.cyan(t.profile)]]:[],...t.silent?[[k.default.gray("Silent"),k.default.yellow("yes")]]:[],[k.default.gray("Last run"),f],[k.default.gray("Next run"),c]],S=[`${a} ${i}`,...w.map(([$,C])=>` ${$.padEnd(10)} ${C}`)].join(`
|
|
51
|
+
`);return(0,Ct.default)(S,{borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:s?"green":"gray"})}function At(){let o=new jt.Command("schedule").description("Manage workflow schedules").action(async()=>{await Tt()});return o.command("add [scheduleFile]").description("Add schedules from a schedule file (YAML or JSON)").action(async e=>{await vr(e)}),o.command("remove").alias("rm").description("Remove a workflow schedule").action(async()=>{await kr()}),o.command("remove-all").description("Remove all workflow schedules").action(async()=>{await Dr()}),o.command("list").alias("ls").description("List all workflow schedules").action(async()=>{await Tt()}),o.command("start").description("Start the scheduler daemon").option("-d, --daemon","Run in background daemon mode").action(async e=>{await xr(e.daemon??!1)}),o.command("stop").description("Stop the scheduler daemon").action(async()=>{await $r()}),o.command("status").description('View daemon and schedule status (does not start the daemon). In live mode, Ctrl+C only exits the status view; the daemon keeps running if it was started with "tp schedule start -d".').option("-n, --no-follow","Show status once and exit (no live refresh)").action(async e=>{let t=e.follow!==!1;await Pr(t)}),o.command("toggle").description("Enable or disable a schedule").action(async()=>{await Cr()}),o}function Je(o,e){let t=e.workflow;if((0,O.isAbsolute)(t))return t;let r=e.baseDir?(0,O.resolve)(e.baseDir):(0,O.dirname)(o);return(0,O.resolve)(r,t)}async function vr(o){let e=new I;if(!o){let{path:l}=await Y.default.prompt([{type:"input",name:"path",message:"Schedule file path (YAML or JSON):",validate:c=>{let f=(0,O.resolve)(c);return(0,$e.existsSync)(f)?!0:`File not found: ${f}`}}]);o=l}let t=(0,O.resolve)(o);(0,$e.existsSync)(t)||(console.error(`\u2717 File not found: ${t}`),process.exit(1));let r;try{r=await Pt(t)}catch(l){console.error(`\u2717 Failed to parse schedule file: ${l instanceof Error?l.message:String(l)}`),process.exit(1)}let n=r.schedules.filter(l=>!Nt.default.validate(l.cron));if(n.length>0){console.error("\u2717 Invalid cron expression(s):");for(let l of n)console.error(` - ${l.name}: "${l.cron}"`);process.exit(1)}let s=r.schedules.filter(l=>{let c=Je(t,l);return!(0,$e.existsSync)(c)});if(s.length>0){console.error("\u2717 Workflow file(s) not found:");for(let l of s){let c=Je(t,l);console.error(` - ${l.name}: ${l.workflow} (resolved: ${c})`)}process.exit(1)}console.log(`
|
|
52
|
+
Found ${r.schedules.length} schedule(s) in file.
|
|
53
|
+
`);let i=[];for(let l of r.schedules){let{alias:c}=await Y.default.prompt([{type:"input",name:"alias",message:`Alias for "${l.name}" (press Enter to use as-is):`,default:l.name}]),f=await e.addSchedule({name:c,workflowPath:Je(t,l),cron:l.cron,enabled:!0,timezone:l.timezone,silent:l.silent,profile:l.profile});i.push(f)}let a=await R();console.log(`
|
|
54
54
|
\u2713 Added ${i.length} schedule(s) successfully
|
|
55
|
-
`);for(let
|
|
55
|
+
`);for(let l of i)console.log(ce(l,{daemonRunning:a.running}));console.log(m.default.dim(' Tip: Run "tp schedule start" to start the scheduler daemon'))}async function kr(){let o=new I,e=await o.loadSchedules();if(e.length===0){console.log("No schedules found");return}let{scheduleId:t}=await Y.default.prompt([{type:"list",name:"scheduleId",message:"Select schedule to remove:",choices:e.map(s=>({name:`${s.name??s.workflowPath} (${s.cron}) ${s.enabled?"\u2713":"\u2717"}`,value:s.id}))}]),{confirm:r}=await Y.default.prompt([{type:"confirm",name:"confirm",message:"Are you sure you want to remove this schedule?",default:!1}]);if(!r){console.log("Cancelled");return}let n=await o.removeSchedule(t);console.log(n?"\u2713 Schedule removed successfully":"\u2717 Schedule not found")}async function Tt(){let e=await new I().loadSchedules();if(e.length===0){let i=[m.default.gray("No schedules registered."),"",m.default.dim(" tp schedule add <schedule.yaml> add from a schedule file")].join(`
|
|
56
56
|
`);console.log(`
|
|
57
57
|
${(0,Re.default)(i,{borderStyle:"round",padding:{top:1,bottom:1,left:2,right:2},margin:{top:0,bottom:0,left:0,right:0},borderColor:"gray"})}
|
|
58
|
-
`);return}let t=await
|
|
58
|
+
`);return}let t=await R(),r=t.running?m.default.green("\u25CF running"):m.default.gray("\u25CB stopped"),n=e.filter(i=>i.enabled).length,s=m.default.bold("\u{1F4C5} Workflow Schedules");console.log(s),console.log([m.default.gray(" Daemon: "),r,m.default.gray(` \xB7 Schedules: ${n}/${e.length} enabled`)].join(""));for(let i of e)console.log(ce(i,{daemonRunning:t.running}));console.log(m.default.dim(" Tip: tp schedule start \u2014 run scheduler daemon; tp schedule status \u2014 view live status"))}async function xr(o){if(await A()){let e=await R();console.error(`\u2717 Scheduler daemon is already running (PID: ${e.pid})`),console.error(' Run "tp schedule stop" to stop it first'),process.exit(1)}if(o)if(process.env.TP_DAEMON_MODE==="true")try{let{saveDaemonPid:e}=await Promise.resolve().then(()=>(ie(),Qe));await e(),await new B().start(!0),await new Promise(()=>{})}catch(e){await Ie(e instanceof Error?e:new Error(String(e))),process.exit(1)}else{let e=process.argv.slice(1);(0,Mt.spawn)(process.argv[0],e,{detached:!0,stdio:"ignore",env:{...process.env,TP_DAEMON_MODE:"true"}}).unref();let r=3,n=800,s=!1;for(let i=0;i<r;i++)if(await new Promise(a=>setTimeout(a,n)),await A()){s=!0;break}if(s){let i=await R();console.log(`\u2713 Scheduler daemon started in background (PID: ${i.pid})`),console.log(' Run "tp schedule stop" to stop the daemon'),console.log(' Run "tp schedule status" to check daemon status')}else{console.error("\u2717 Failed to start scheduler daemon");let i=await je();i?(console.error(m.default.dim(" Last error from daemon:")),console.error(m.default.red(i.split(`
|
|
59
59
|
`).map(a=>` ${a}`).join(`
|
|
60
|
-
`)))):console.error(
|
|
61
|
-
`)}else n=[`${
|
|
62
|
-
`);let s=(0,Re.default)(n,{title:
|
|
63
|
-
`)}function
|
|
64
|
-
Press Ctrl+C to exit this view (daemon keeps running in background)`):
|
|
65
|
-
Tip: To start the daemon, run: tp schedule start -d. Press Ctrl+C to exit this view.`);(0,
|
|
66
|
-
Press Ctrl+C to exit this view (daemon keeps running in background)`):
|
|
67
|
-
Tip: To start the daemon, run: tp schedule start -d. Press Ctrl+C to exit this view.`);(0,
|
|
68
|
-
Tip: To start the daemon, run: tp schedule start -d`);console.log(`${e}${
|
|
69
|
-
`)}}async function
|
|
60
|
+
`)))):console.error(m.default.dim(` Check ${Me()} for details`)),process.exit(1)}process.exit(0)}else await new B().start(!1,{onScheduleStarted:t=>console.log(ce(t,{daemonRunning:!0}))}),await new Promise(()=>{})}async function $r(){let o=await R();if(!o.running){console.log("Scheduler daemon is not running");return}console.log(`Stopping scheduler daemon (PID: ${o.pid})...`);let t=await new B().stopDaemon();console.log(t?"\u2713 Scheduler daemon stopped":"\u2717 Failed to stop scheduler daemon (process may have already exited)")}function Rr(o){if(!o)return"Unknown";let e=(0,Ee.default)(o),r=(0,Ee.default)().diff(e,"second"),n=Math.floor(r/86400),s=Math.floor(r%86400/3600),i=Math.floor(r%3600/60),a=r%60,l=[];return n>0&&l.push(`${n}d`),s>0&&l.push(`${s}h`),i>0&&l.push(`${i}m`),(a>0||l.length===0)&&l.push(`${a}s`),l.join(" ")}function Er(o,e){return ce(o,{daemonRunning:e})}async function qe(){let o=await R(),t=await new I().loadSchedules(),r=[],n;if(o.running&&o.pid){let i=Rr(o.startTime),a=o.startTime?(0,Ee.default)(o.startTime).format("YYYY-MM-DD HH:mm:ss"):"Unknown";n=[`${m.default.green("\u25CF")} ${m.default.green("active")} ${m.default.gray("(running)")}`,"",`${m.default.gray("Loaded:")} ${m.default.white(a)}`,`${m.default.gray("Active:")} ${m.default.green("active (running)")} since ${m.default.white(a)}`,`${m.default.gray("PID:")} ${m.default.white(o.pid.toString())}`,`${m.default.gray("Uptime:")} ${m.default.white(i)}`].join(`
|
|
61
|
+
`)}else n=[`${m.default.red("\u25CF")} ${m.default.red("inactive")} ${m.default.gray("(dead)")}`,"",`${m.default.gray("Loaded:")} ${m.default.gray("not found")}`,`${m.default.gray("Active:")} ${m.default.red("inactive (dead)")}`].join(`
|
|
62
|
+
`);let s=(0,Re.default)(n,{title:m.default.bold("task-pipeliner-scheduler.service"),titleAlignment:"left",borderStyle:"round",padding:{top:1,bottom:1,left:2,right:2},margin:{top:0,bottom:0,left:0,right:0},borderColor:o.running?"green":"red"});if(r.push(s),t.length>0){let i=t.filter(l=>l.enabled).length,a=m.default.bold(`Schedules: ${i}/${t.length} enabled`);r.push(a);for(let l of t)r.push(Er(l,o.running))}else{let i=(0,Re.default)(m.default.gray("No schedules configured"),{borderStyle:"round",padding:{top:0,bottom:0,left:1,right:1},margin:{top:0,bottom:0,left:0,right:0},borderColor:"gray"});r.push(i)}return r.join(`
|
|
63
|
+
`)}function It(){process.stdout.write("\x1B[2J\x1B[H")}async function Pr(o){if(o){let e=!0,t=()=>{e=!1,ue.default.done(),process.exit(0)};process.on("SIGINT",t),process.on("SIGTERM",t);let r=setInterval(async()=>{if(!e){clearInterval(r);return}try{let a=await qe(),c=(await R()).running?m.default.gray(`
|
|
64
|
+
Press Ctrl+C to exit this view (daemon keeps running in background)`):m.default.gray(`
|
|
65
|
+
Tip: To start the daemon, run: tp schedule start -d. Press Ctrl+C to exit this view.`);(0,ue.default)(`${a}${c}`)}catch(a){ue.default.done(),console.error("Error updating status:",a),clearInterval(r),process.exit(1)}},1e3);It();let n=await qe(),i=(await R()).running?m.default.gray(`
|
|
66
|
+
Press Ctrl+C to exit this view (daemon keeps running in background)`):m.default.gray(`
|
|
67
|
+
Tip: To start the daemon, run: tp schedule start -d. Press Ctrl+C to exit this view.`);(0,ue.default)(`${n}${i}`),await new Promise(()=>{})}else{It();let e=await qe(),r=(await R()).running?"":m.default.gray(`
|
|
68
|
+
Tip: To start the daemon, run: tp schedule start -d`);console.log(`${e}${r}
|
|
69
|
+
`)}}async function Cr(){let o=new I,e=await o.loadSchedules();if(e.length===0){console.log("No schedules found");return}let{scheduleId:t}=await Y.default.prompt([{type:"list",name:"scheduleId",message:"Select schedule to toggle:",choices:e.map(s=>({name:`${s.name??s.workflowPath} (${s.cron}) ${s.enabled?"\u2713":"\u2717"}`,value:s.id}))}]),r=e.find(s=>s.id===t);if(!r){console.log("\u2717 Schedule not found");return}let n=!r.enabled;await o.toggleSchedule(t,n),console.log(`\u2713 Schedule ${n?"enabled":"disabled"}: ${r.name??r.workflowPath}`)}async function Dr(){let o=new I,e=await o.loadSchedules();if(e.length===0){console.log("No schedules found");return}let{confirm:t}=await Y.default.prompt([{type:"confirm",name:"confirm",message:`Are you sure you want to remove all ${e.length} schedule(s)?`,default:!1}]);if(!t){console.log("Cancelled");return}await o.saveSchedules([]),console.log(`\u2713 Removed all ${e.length} schedule(s)`)}var F=require("fs"),E=require("path"),Ge=require("url"),Ot={};function Ft(){console.log=()=>{},console.error=()=>{},console.warn=()=>{},console.info=()=>{},process.stdout.write=()=>!0,process.stderr.write=()=>!0}function Lt(){return"0.3.2"}function Bt(o){let e=o?(0,E.resolve)(o):process.cwd(),t=50,r=0;for(;r<t;){let n=(0,E.resolve)(e,"tp");try{if((0,F.existsSync)(n)&&(0,F.statSync)(n).isDirectory())return n}catch{}let s=(0,E.dirname)(e);if(s===e)break;e=s,r++}return null}var pe=(0,U.join)((0,_t.homedir)(),".pipeliner"),Tr=(0,Vt.promisify)(Wt.exec),J=new Ht.Command;J.name("task-pipeliner").description(`A powerful task pipeline runner with condition-based workflow execution.
|
|
70
70
|
|
|
71
71
|
Define workflows in YAML or JSON files with conditional execution, parallel tasks,
|
|
72
72
|
interactive prompts, variable substitution, and cron-based scheduling.
|
|
@@ -119,7 +119,7 @@ Quick Start:
|
|
|
119
119
|
|
|
120
120
|
Note: After upgrading to a new version, if you see compatibility issues (e.g. schedules or daemon), run "tp clean" to reset ~/.pipeliner data.
|
|
121
121
|
|
|
122
|
-
`).version(
|
|
122
|
+
`).version(Lt()).addHelpText("after",`
|
|
123
123
|
Examples:
|
|
124
124
|
$ tp run workflow.yaml
|
|
125
125
|
$ tp run workflow.yaml --profile Production
|
|
@@ -164,44 +164,44 @@ Workflow File Structure:
|
|
|
164
164
|
\u2022 all/any/not: Combine conditions
|
|
165
165
|
|
|
166
166
|
Supported formats: YAML (.yaml, .yml) and JSON (.json)
|
|
167
|
-
See README.md for complete DSL documentation.`).action(async(
|
|
168
|
-
\u2717 No workflow file found`)),process.exit(1)),e.silent&&
|
|
167
|
+
See README.md for complete DSL documentation.`).action(async(o,e)=>{try{let t=o??await Mr()??null;t||(console.error(p.default.red(`
|
|
168
|
+
\u2717 No workflow file found`)),process.exit(1)),e.silent&&Ft();let r=le(t);console.log(p.default.blue(`Loading workflow from ${t}...`));let n=(0,de.readFileSync)(t,"utf-8"),s=r.parse(n);if(!s.steps||!Array.isArray(s.steps))throw new Error("Invalid workflow: steps array is required");let i;if(e.profile){let l=e.profile.trim();if(!s.profiles?.length)throw new Error(`Profile "${l}" requested but workflow has no "profiles" defined. Add a "profiles" section to your workflow file.`);let c=s.profiles.find(f=>f.name===l);if(!c){let f=s.profiles.map(g=>g.name).join(", ");throw new Error(`Profile "${l}" not found. Available profile(s): ${f}`)}i=c.var}s._lineNumbers=r.extractStepLineNumbers(n),s._fileName=jr(t),s._filePath=(0,U.resolve)(t),console.log(p.default.green(`Starting workflow execution...
|
|
169
169
|
`)),await new te().execute(s,i?{profileVars:i}:void 0),console.log(p.default.green(`
|
|
170
|
-
\u2713 Workflow completed successfully`))}catch(t){let
|
|
171
|
-
\u2717 Workflow failed: ${
|
|
170
|
+
\u2713 Workflow completed successfully`))}catch(t){let r=t instanceof Error?t.message:String(t);console.error(p.default.red(`
|
|
171
|
+
\u2717 Workflow failed: ${r}`)),process.exit(1)}});J.command("open").description("Open generator or docs website in browser").argument("<target>",'Target to open: "generator" or "docs"').addHelpText("after",`
|
|
172
172
|
Examples:
|
|
173
173
|
$ tp open generator
|
|
174
174
|
$ tp open docs
|
|
175
175
|
|
|
176
176
|
Targets:
|
|
177
177
|
generator Open the visual workflow generator (https://task-pipeliner-generator.racgoo.com/)
|
|
178
|
-
docs Open the documentation site (https://task-pipeliner.racgoo.com/)`).action(async
|
|
179
|
-
\u2717 Invalid target: ${
|
|
180
|
-
Valid targets:`)),console.log(p.default.yellow(" \u2022 generator - Open the visual workflow generator")),console.log(p.default.yellow(" \u2022 docs - Open the documentation site")),process.exit(1));try{let
|
|
181
|
-
\u2713 Opening ${
|
|
178
|
+
docs Open the documentation site (https://task-pipeliner.racgoo.com/)`).action(async o=>{let t={generator:"https://task-pipeliner-generator.racgoo.com/",docs:"https://task-pipeliner.racgoo.com/"}[o.toLowerCase()];t||(console.error(p.default.red(`
|
|
179
|
+
\u2717 Invalid target: ${o}`)),console.log(p.default.yellow(`
|
|
180
|
+
Valid targets:`)),console.log(p.default.yellow(" \u2022 generator - Open the visual workflow generator")),console.log(p.default.yellow(" \u2022 docs - Open the documentation site")),process.exit(1));try{let r=process.platform,n;r==="darwin"?n=`open "${t}"`:r==="win32"?n=`start "${t}"`:n=`xdg-open "${t}"`,await Tr(n),console.log(p.default.green(`
|
|
181
|
+
\u2713 Opening ${o==="generator"?"generator":"documentation"} in browser...`)),console.log(p.default.blue(` ${t}`))}catch(r){let n=r instanceof Error?r.message:String(r);console.error(p.default.red(`
|
|
182
182
|
\u2717 Failed to open browser: ${n}`)),console.log(p.default.yellow(`
|
|
183
|
-
Please visit manually: ${t}`)),process.exit(1)}});J.addCommand(
|
|
184
|
-
\u2717 Invalid choice`)),process.exit(1));let t=new ee;switch(e.id){case"show":{let
|
|
185
|
-
\u26A0 No history found`));return}let n=await
|
|
186
|
-
\u2717 Invalid choice`)),process.exit(1));try{let s=await t.getHistory(n.id);
|
|
187
|
-
\u2717 Failed to load history: ${i}`)),process.exit(1)}break}case"remove":{let
|
|
188
|
-
\u26A0 No history found`));return}let n=await
|
|
183
|
+
Please visit manually: ${t}`)),process.exit(1)}});J.addCommand(At());var Ir=J.command("history").description("Manage workflow execution history");Ir.action(async()=>{let o=new L,e=await o.prompt("Select an action",[{id:"show",label:"Show - View and select a history to view"},{id:"remove",label:"Remove - Delete a specific history file"},{id:"remove-all",label:"Remove All - Delete all history files"}]);e?.id||(console.error(p.default.red(`
|
|
184
|
+
\u2717 Invalid choice`)),process.exit(1));let t=new ee;switch(e.id){case"show":{let r=await t.getHistoryNames();if(r.length===0){console.log(p.default.yellow(`
|
|
185
|
+
\u26A0 No history found`));return}let n=await o.prompt("Select a history to view",r.map(s=>({id:s,label:s})));n?.id||(console.error(p.default.red(`
|
|
186
|
+
\u2717 Invalid choice`)),process.exit(1));try{let s=await t.getHistory(n.id);Nr(s,n.id)}catch(s){let i=s instanceof Error?s.message:String(s);console.error(p.default.red(`
|
|
187
|
+
\u2717 Failed to load history: ${i}`)),process.exit(1)}break}case"remove":{let r=await t.getHistoryNames();if(r.length===0){console.log(p.default.yellow(`
|
|
188
|
+
\u26A0 No history found`));return}let n=await o.prompt("Select a history to remove",r.map(s=>({id:s,label:s})));n?.id||(console.error(p.default.red(`
|
|
189
189
|
\u2717 Invalid choice`)),process.exit(1));try{await t.removeHistory(n.id),console.log(p.default.green(`
|
|
190
190
|
\u2713 Removed history: ${n.id}`))}catch(s){let i=s instanceof Error?s.message:String(s);console.error(p.default.red(`
|
|
191
|
-
\u2717 Failed to remove history: ${i}`)),process.exit(1)}break}case"remove-all":{if((await
|
|
191
|
+
\u2717 Failed to remove history: ${i}`)),process.exit(1)}break}case"remove-all":{if((await o.prompt("Are you sure you want to remove all histories?",[{id:"yes",label:"Yes, remove all"},{id:"no",label:"No, cancel"}]))?.id!=="yes"){console.log(p.default.yellow(`
|
|
192
192
|
\u2717 Cancelled`));return}try{await t.clearAllHistories(),console.log(p.default.green(`
|
|
193
193
|
\u2713 All histories removed`))}catch(n){let s=n instanceof Error?n.message:String(n);console.error(p.default.red(`
|
|
194
194
|
\u2717 Failed to remove histories: ${s}`)),process.exit(1)}break}default:console.error(p.default.red(`
|
|
195
|
-
\u2717 Unknown action: ${e.id}`)),process.exit(1)}});async function
|
|
196
|
-
\u2717 No tp directory found`)),null;try{let t=(await(0,Pe.readdir)(
|
|
197
|
-
\u2717 No workflow files found in ${
|
|
198
|
-
\u2717 Failed to read tp directory: ${t}`)),null}}function
|
|
199
|
-
`);let t=
|
|
200
|
-
`);console.log((0,
|
|
201
|
-
`);console.log((0,
|
|
202
|
-
`);console.log(p.default.green(" Output:")),console.log(e)}if(
|
|
203
|
-
`);console.log(p.default.red(" Errors:")),console.log(e)}}J.command("clean").description("Remove all data in ~/.pipeliner (schedules, daemon state, workflow history). Use after upgrades if data is incompatible.").action(async()=>{if((await new L().prompt(`This will remove all data in ${p.default.yellow(
|
|
204
|
-
\u2717 Cancelled`));return}try{if(await A()){let t=await
|
|
205
|
-
\u2713 Removed ${
|
|
206
|
-
${
|
|
207
|
-
\u2717 Clean failed: ${
|
|
195
|
+
\u2717 Unknown action: ${e.id}`)),process.exit(1)}});async function Mr(){let o=Bt();if(!o)return console.error(p.default.red(`
|
|
196
|
+
\u2717 No tp directory found`)),null;try{let t=(await(0,Pe.readdir)(o)).filter(i=>{let a=(0,U.extname)(i).toLowerCase();return[".yaml",".yml",".json"].includes(a)});if(t.length===0)return console.error(p.default.red(`
|
|
197
|
+
\u2717 No workflow files found in ${o}`)),null;let r=await Promise.all(t.map(async i=>{let a=(0,U.join)(o,i);try{let l=le(a),c=(0,de.readFileSync)(a,"utf-8"),g=l.parse(c).name??"Untitled";return{id:a,label:`${i} - ${g}`}}catch{return{id:a,label:i}}}));return(await new L(!0).prompt("Select a workflow to run",r)).id}catch(e){let t=e instanceof Error?e.message:String(e);return console.error(p.default.red(`
|
|
198
|
+
\u2717 Failed to read tp directory: ${t}`)),null}}function jr(o){return o.split("/").pop()??o}function Nr(o,e){console.log(`
|
|
199
|
+
`);let t=o.records.reduce((c,f)=>c+f.duration,0),r=o.records.filter(c=>c.status==="success").length,n=o.records.filter(c=>c.status==="failure").length,s=(0,zt.default)(o.initialTimestamp).format("YYYY-MM-DD HH:mm:ss"),a=X(t),l=[p.default.bold("Workflow Execution History"),"",`${p.default.cyan("File:")} ${e}`,`${p.default.cyan("Started:")} ${s}`,`${p.default.cyan("Total Duration:")} ${a}`,`${p.default.cyan("Total Steps:")} ${o.records.length}`,`${p.default.green("\u2713 Successful:")} ${r}`,n>0?`${p.default.red("\u2717 Failed:")} ${n}`:""].filter(Boolean).join(`
|
|
200
|
+
`);console.log((0,Ze.default)(l,{borderStyle:"round",padding:{top:1,bottom:1,left:2,right:2},margin:{top:0,bottom:1,left:0,right:0},borderColor:"cyan"})),o.records.forEach((c,f)=>{Ar(c,f+1,o.records.length)}),console.log("")}function Ar(o,e,t){let r=Or(o.step),n=Fr(o.step),s=o.status==="success"?p.default.green("\u2713"):p.default.red("\u2717"),i=o.status==="success"?p.default.green("Success"):p.default.red("Failed"),a=X(o.duration),l=[`${s} ${p.default.bold(`Step ${e}/${t}`)} - ${p.default.cyan(r)}`,`${p.default.gray("Duration:")} ${a} | ${p.default.gray("Status:")} ${i}`,"",p.default.white(n)].join(`
|
|
201
|
+
`);console.log((0,Ze.default)(l,{borderStyle:"round",padding:{top:1,bottom:1,left:2,right:2},margin:{top:0,bottom:1,left:0,right:0},borderColor:o.status==="success"?"green":"red"})),Lr(o.output)&&Br(o.output)}function Or(o){return"run"in o?"Run":"choose"in o?"Choose":"prompt"in o?"Prompt":"parallel"in o?"Parallel":"fail"in o?"Fail":"Unknown"}function Fr(o){return"run"in o?`Command: ${p.default.yellow(o.run)}`:"choose"in o?`Message: ${p.default.yellow(o.choose.message)}`:"prompt"in o?`Message: ${p.default.yellow(o.prompt.message)} | Variable: ${p.default.cyan(o.prompt.as)}`:"parallel"in o?`Parallel execution with ${o.parallel.length} branches`:"fail"in o?`Error: ${p.default.red(o.fail.message)}`:"Unknown step type"}function Lr(o){return typeof o=="object"&&o!==null&&"success"in o&&"stdout"in o&&"stderr"in o}function Br(o){if(o.stdout.length>0){let e=o.stdout.map(t=>p.default.gray(` ${t}`)).join(`
|
|
202
|
+
`);console.log(p.default.green(" Output:")),console.log(e)}if(o.stderr.length>0){let e=o.stderr.map(t=>p.default.gray(` ${t}`)).join(`
|
|
203
|
+
`);console.log(p.default.red(" Errors:")),console.log(e)}}J.command("clean").description("Remove all data in ~/.pipeliner (schedules, daemon state, workflow history). Use after upgrades if data is incompatible.").action(async()=>{if((await new L().prompt(`This will remove all data in ${p.default.yellow(pe)} (schedules, daemon PID, workflow history). Continue?`,[{id:"yes",label:"Yes, remove all"},{id:"no",label:"No, cancel"}]))?.id!=="yes"){console.log(p.default.yellow(`
|
|
204
|
+
\u2717 Cancelled`));return}try{if(await A()){let t=await R();console.log(p.default.gray(`Stopping scheduler daemon (PID: ${t.pid})...`)),await new B().stopDaemon(),console.log(p.default.gray(" Daemon stopped"))}(0,de.existsSync)(pe)?(await(0,Pe.rm)(pe,{recursive:!0}),console.log(p.default.green(`
|
|
205
|
+
\u2713 Removed ${pe}`))):console.log(p.default.gray(`
|
|
206
|
+
${pe} does not exist (already clean)`))}catch(t){let r=t instanceof Error?t.message:String(t);console.error(p.default.red(`
|
|
207
|
+
\u2717 Clean failed: ${r}`)),process.exit(1)}});J.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "task-pipeliner",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A task pipeline runner with condition-based workflow execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -10,12 +10,27 @@
|
|
|
10
10
|
"tp": "./dist/index.cjs"
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
|
-
"
|
|
14
|
-
"pipeline",
|
|
13
|
+
"workflow-orchestration",
|
|
15
14
|
"workflow",
|
|
15
|
+
"orchestration",
|
|
16
|
+
"task-runner",
|
|
17
|
+
"task-automation",
|
|
18
|
+
"ci-cd",
|
|
19
|
+
"build-automation",
|
|
20
|
+
"devops",
|
|
16
21
|
"automation",
|
|
22
|
+
"pipeline",
|
|
23
|
+
"workflow-engine",
|
|
24
|
+
"yaml",
|
|
25
|
+
"json",
|
|
26
|
+
"scheduler",
|
|
27
|
+
"cron",
|
|
17
28
|
"cli",
|
|
18
|
-
"interactive"
|
|
29
|
+
"interactive",
|
|
30
|
+
"makefile-alternative",
|
|
31
|
+
"bash-alternative",
|
|
32
|
+
"deployment",
|
|
33
|
+
"build-tool"
|
|
19
34
|
],
|
|
20
35
|
"author": "racgoo",
|
|
21
36
|
"license": "MIT",
|