airport2 0.0.2 → 0.0.4

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/CHANGELOG.md CHANGED
@@ -1,10 +1,31 @@
1
1
  # CHANGELOG
2
2
 
3
+
4
+ ## [0.0.4] - 2025-06-27
5
+
6
+ * 一些优化
7
+ * 更多范例详见`example`目录
8
+ * `run`函数默认注入父进程的`env`环境变量
9
+ * 统一Step和Task的`run`方法,支持直接运行`Step.run`和`Task.run`
10
+
11
+
12
+ ## [0.0.3] - 2025-06-26
13
+
14
+ * 日志染色,高亮关键信息,更容易区分日志信息
15
+ * 优化部分代码,提高可读性和可维护性
16
+ * Step添加`skip`属性,支持跳过执行,默认为`false`
17
+
18
+
19
+ ## [0.0.2] - 2025-06-26
20
+
21
+ * 一些优化
22
+
23
+
3
24
  ## [0.0.1] - 2025-06-25
4
25
 
5
26
  ### 初始版本发布
6
- - ✅ 实现Pipeline-Task-Step三级架构
7
- - ✅ 支持本地命令执行和远程SSH操作
8
- - ✅ 添加单元测试框架和示例测试
9
- - ✅ 实现测试模式,支持命令预览
10
- - ✅ 完成基础文档和开发计划
27
+ - 实现Pipeline-Task-Step三级架构
28
+ - 支持本地命令执行和远程SSH操作
29
+ - 添加单元测试框架和示例测试
30
+ - 实现测试模式,支持命令预览
31
+ - 完成基础文档和开发计划
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Airport
2
2
 
3
- 使用`ts`编写`Shell`管道
3
+ 使用`ts`编写流水线
4
4
 
5
5
  ```bash
6
6
  bun add airport2
@@ -32,7 +32,7 @@ bun airport.ts // 也可以将命令添加到package.json中
32
32
 
33
33
  ## Example
34
34
 
35
- 1、常规构建发布
35
+ **常规构建发布范例**
36
36
 
37
37
  ```ts
38
38
  import { name, version } from './package.json'
@@ -74,14 +74,13 @@ const deployTask = new Task({
74
74
  PipeLine.run([deployTask])
75
75
  ```
76
76
 
77
- docker构建发布:
77
+ **docker构建范例**
78
78
 
79
79
  ```ts
80
80
  import { name, version } from './package.json'
81
81
  import { PipeLine, Task } from 'airport2'
82
82
 
83
- // 创建部署任务
84
- const deployTask = new Task({
83
+ const dockerTask = new Task({
85
84
  name: '部署docker应用',
86
85
  steps: [
87
86
  {
@@ -95,5 +94,5 @@ const deployTask = new Task({
95
94
  ],
96
95
  })
97
96
 
98
- PipeLine.run([deployTask])
97
+ dockerTask.run()
99
98
  ```
package/bun.lock CHANGED
@@ -3,6 +3,9 @@
3
3
  "workspaces": {
4
4
  "": {
5
5
  "name": "ts-cicd",
6
+ "dependencies": {
7
+ "chalk": "^5.4.1",
8
+ },
6
9
  "devDependencies": {
7
10
  "@types/bun": "latest",
8
11
  "@types/node": "^20.12.7",
@@ -136,6 +139,8 @@
136
139
 
137
140
  "chai": ["chai@4.5.0", "", { "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", "deep-eql": "^4.1.3", "get-func-name": "^2.0.2", "loupe": "^2.3.6", "pathval": "^1.1.1", "type-detect": "^4.1.0" } }, "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw=="],
138
141
 
142
+ "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
143
+
139
144
  "check-error": ["check-error@1.0.3", "", { "dependencies": { "get-func-name": "^2.0.2" } }, "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg=="],
140
145
 
141
146
  "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
@@ -1,5 +1,5 @@
1
- import { name, version } from './package.json'
2
- import { PipeLine, Task } from './src/airport'
1
+ import { name, version } from '../package.json'
2
+ import { PipeLine, Task } from '../src/airport'
3
3
 
4
4
  // 创建部署任务
5
5
  const deployTask = new Task({
@@ -1,6 +1,6 @@
1
1
  import { join } from 'path'
2
- import { name, version } from './package.json'
3
- import { PipeLine, Task, remote, exec, logError } from './src/airport'
2
+ import { name, version } from '../package.json'
3
+ import { PipeLine, Task, remote, logError } from '../src/airport'
4
4
 
5
5
  const file = [name, version].join('-') + '.tgz'
6
6
 
@@ -18,10 +18,8 @@ const deployTask = new Task({
18
18
  {
19
19
  name: '发布到远程服务器',
20
20
  async run() {
21
- exec(true, 'ls -al .cache')
22
21
  const ssh = remote('gavin@192.168.5.121')
23
22
  const dir = (path?: string) => join('/home/gavin/test', path ?? '')
24
-
25
23
  try {
26
24
  await ssh.run('rm -f', dir('latest'))
27
25
  await ssh.run('mkdir -p', dir(version))
@@ -0,0 +1,12 @@
1
+ import { Step } from '../src/airport'
2
+
3
+ // 创建部署任务
4
+ const step = new Step({
5
+ name: '构建项目',
6
+ run: [
7
+ 'mkdir -p .cache',
8
+ 'mv `npm pack` .cache/',
9
+ ],
10
+ })
11
+
12
+ step.run()
@@ -0,0 +1,38 @@
1
+ import { join } from 'path'
2
+ import { name, version } from '../package.json'
3
+ import { Task, remote, logError } from '../src/airport'
4
+
5
+ const file = [name, version].join('-') + '.tgz'
6
+
7
+ // 创建部署任务
8
+ const deployTask = new Task({
9
+ name: '部署应用',
10
+ steps: [
11
+ {
12
+ name: '构建项目',
13
+ run: [
14
+ 'mkdir -p .cache',
15
+ 'mv `npm pack` .cache/',
16
+ ],
17
+ },
18
+ {
19
+ name: '发布到远程服务器',
20
+ async run() {
21
+ const ssh = remote('gavin@192.168.5.121')
22
+ const dir = (path?: string) => join('/home/gavin/test', path ?? '')
23
+ try {
24
+ await ssh.run('rm -f', dir('latest'))
25
+ await ssh.run('mkdir -p', dir(version))
26
+ await ssh.scp(`.cache/${file}`, dir())
27
+ await ssh.run('tar -zxvf', dir(file), '-C', dir(version))
28
+ await ssh.run('ln -s', dir(version), dir('latest'))
29
+ }
30
+ catch (e) {
31
+ logError(e)
32
+ }
33
+ },
34
+ },
35
+ ],
36
+ })
37
+
38
+ deployTask.run()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airport2",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "main": "src/index.ts",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -33,5 +33,8 @@
33
33
  "@types/bun": "latest",
34
34
  "@types/node": "^20.12.7",
35
35
  "vitest": "^1.6.0"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^5.4.1"
36
39
  }
37
40
  }
@@ -1,5 +1,5 @@
1
1
  import { Task } from './task'
2
- import { log } from '../lib/log'
2
+ import { log, purple, gray } from '../lib'
3
3
 
4
4
  export class PipeLine {
5
5
  public name?: string
@@ -17,12 +17,12 @@ export class PipeLine {
17
17
  * 执行任务中的所有步骤
18
18
  */
19
19
  async run() {
20
- log('Start PipeLine: --------------------')
20
+ log(purple('Pipe'), gray('Start'), '-------------------------')
21
21
 
22
22
  for (const task of this.tasks) {
23
- await task.execute()
23
+ await task.run()
24
24
  }
25
25
 
26
- log('----------- End PipeLine -----------')
26
+ log(`------------- ${purple("Pipe")} ${gray('End')} -------------`)
27
27
  }
28
28
  }
@@ -1,21 +1,23 @@
1
- import { run } from '../lib/run'
1
+ import { log, run } from '../lib'
2
2
  import type { StepRaw } from '../types'
3
3
 
4
4
  export class Step {
5
5
  public name: string
6
- private test: boolean
6
+ public skip: boolean = false
7
7
  private step: StepRaw
8
+ private test: boolean
8
9
 
9
10
  constructor(step: StepRaw, test: boolean = false) {
10
11
  this.test = test
11
12
  this.step = step
12
13
  this.name = step.name
14
+ this.skip = step.skip ?? false
13
15
  }
14
16
 
15
17
  /**
16
18
  * 执行步骤
17
19
  */
18
- async execute(): Promise<any> {
20
+ async run(): Promise<any> {
19
21
  if (typeof this.step.run === 'string') {
20
22
  return this.runString()
21
23
  } else if (Array.isArray(this.step.run)) {
@@ -23,7 +25,7 @@ export class Step {
23
25
  } else {
24
26
  // 函数类型的步骤
25
27
  if (this.test) {
26
- console.log(`[TEST MODE] Would execute function: ${this.step.run.name}`)
28
+ log(`[TEST MODE] Would execute function: ${this.step.run.name}`)
27
29
  } else {
28
30
  // 实际执行函数
29
31
  return await this.step.run()
@@ -37,7 +39,7 @@ export class Step {
37
39
  private async runString() {
38
40
  const runString = this.step.run as string
39
41
  if (this.test) {
40
- console.log(`[TEST MODE] Would execute: ${runString}`)
42
+ log(`[TEST MODE] Would execute: ${runString}`)
41
43
  } else {
42
44
  return await run(runString)
43
45
  }
@@ -49,7 +51,7 @@ export class Step {
49
51
  private async runArray() {
50
52
  const runArray = this.step.run as string[]
51
53
  if (this.test) {
52
- console.log(`[TEST MODE] Would execute array: ${runArray}`)
54
+ log(`[TEST MODE] Would execute array: ${runArray}`)
53
55
  } else {
54
56
  for (const cmd of runArray) {
55
57
  await run(cmd)
@@ -1,5 +1,5 @@
1
1
  import { Step } from './step'
2
- import { log } from '../lib/log'
2
+ import { log, purple, gray, blue } from '../lib'
3
3
  import type { TaskOptions, StepRaw } from '../types'
4
4
 
5
5
  export class Task {
@@ -16,19 +16,19 @@ export class Task {
16
16
  /**
17
17
  * 执行任务中的所有步骤
18
18
  */
19
- async execute(test: boolean = false) {
19
+ async run(test: boolean = false) {
20
20
  const steps = this.steps.map((step) => new Step(step, test))
21
21
 
22
- log(`Start Task:`, this.name)
22
+ log(purple('Task'), gray('Start'), blue(this.name))
23
23
 
24
24
  for (const step of steps) {
25
- log(`Start Task.Step:`, step.name)
26
-
27
- await step.execute()
28
-
29
- log(`End Task.Step:`, step.name)
25
+ if (!step.skip) {
26
+ log(purple('Step'), gray('Start'), blue(step.name))
27
+ step.skip || (await step.run())
28
+ log(purple('Step'), gray('End'), blue(step.name))
29
+ }
30
30
  }
31
31
 
32
- log(`End Task:`, this.name)
32
+ log(purple('Task'), gray('End'), blue(this.name))
33
33
  }
34
34
  }
@@ -1,23 +1,4 @@
1
+ export * from './lib'
1
2
  export * from './air/step'
2
3
  export * from './air/task'
3
4
  export * from './air/pipeline'
4
- export * from './lib/run'
5
- export * from './lib/remote'
6
- export * from './lib/log'
7
-
8
- // 导出默认的核心API
9
- import { PipeLine } from './air/pipeline'
10
- import { Task } from './air/task'
11
- import { Step } from './air/step'
12
- import { run, exec } from './lib/run'
13
- import { log, logError } from './lib/log'
14
- import { remote } from './lib/remote'
15
-
16
- export default {
17
- PipeLine,
18
- Task,
19
- Step,
20
- remote,
21
- run, exec,
22
- log, logError,
23
- }
@@ -0,0 +1,10 @@
1
+ import color from 'chalk'
2
+
3
+ export { color }
4
+
5
+ export const gray = color.gray
6
+ export const blue = color.hex('#4EC4FF')
7
+ export const green = color.hex('#08cf08')
8
+ export const debug = color.hex('#4CD1E0')
9
+
10
+ export const purple = color.hex('#cf37f2')
@@ -0,0 +1,5 @@
1
+ export * from './color'
2
+ export * from './date'
3
+ export * from './log'
4
+ export * from './run'
5
+ export * from './remote'
@@ -1,7 +1,8 @@
1
+ import { gray } from './color'
1
2
  import { formatTimestamp } from './date'
2
3
 
3
4
  export const log = (...args: any[]) => {
4
- console.log(`[${formatTimestamp()}]`, ...args)
5
+ console.log(gray(`[${formatTimestamp()}]`), ...args)
5
6
  }
6
7
 
7
8
  export const logError = (...args: any[]) => {
@@ -21,6 +21,12 @@ class Remote {
21
21
  await run(`ssh ${this.opt.ssh} "${cmd.join(' ')}"`)
22
22
  }
23
23
 
24
+ /**
25
+ * scp远程复制文件
26
+ * @param src 本地路径 e.g. `.cache/app-0.0.1.tgz`
27
+ * @param dist 远程服务器绝对路径,支持文件夹和文件,e.g. `/tmp/test.txt` or `/tmp`
28
+ * @returns Promise<void>
29
+ */
24
30
  async scp(src: string, dist: string) {
25
31
  await run(`scp ${src} ${this.opt.ssh}:${dist}`)
26
32
  }
@@ -1,4 +1,5 @@
1
1
  import { log } from './log'
2
+ import { green, gray } from './color'
2
3
  import { spawn, execSync } from 'child_process'
3
4
 
4
5
  /**
@@ -10,10 +11,12 @@ import { spawn, execSync } from 'child_process'
10
11
  */
11
12
  export const run = (command: string) =>
12
13
  new Promise((resolve, reject) => {
13
- log('Running:', command)
14
+ log(gray('Running:'), green(command))
14
15
  const child = spawn(command, {
15
16
  shell: true,
16
17
  stdio: 'inherit',
18
+ env: process.env,
19
+ cwd: process.cwd(),
17
20
  timeout: 30_000 // 30秒超时
18
21
  })
19
22
  child.on('exit', resolve)
@@ -37,7 +40,7 @@ export function exec(print: boolean | string, command?: string): string {
37
40
  }
38
41
  let output = execSync(command ?? 'echo').toString()
39
42
  if (print) {
40
- log('Running:', command)
43
+ log(gray('Running:'), green(command))
41
44
  log(output)
42
45
  }
43
46
  return output
@@ -11,6 +11,7 @@ export interface StepResult {
11
11
  */
12
12
  export interface StepRaw {
13
13
  name: string
14
+ skip?: boolean
14
15
  run: string | string[] | ((...args: any[]) => Promise<any>)
15
16
  }
16
17