airport2 0.0.5 → 0.0.6
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/example/airport-raw.ts +39 -0
- package/example/airport-test.ts +39 -0
- package/example/airport.ts +1 -0
- package/package.json +1 -1
- package/src/airport/air/pipeline.ts +38 -16
- package/src/airport/air/step.ts +48 -24
- package/src/airport/air/task.ts +52 -13
- package/src/airport/lib/log.ts +54 -2
- package/src/airport/types.ts +12 -9
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { name, version } from '../package.json'
|
|
3
|
+
import { PipeLine, remote, logError } from '../src/airport'
|
|
4
|
+
|
|
5
|
+
const file = [name, version].join('-') + '.tgz'
|
|
6
|
+
|
|
7
|
+
// 创建部署任务
|
|
8
|
+
const deployTask = {
|
|
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
|
+
// 执行流水线
|
|
39
|
+
PipeLine.run([deployTask])
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { join } from 'path'
|
|
2
|
+
import { name, version } from '../package.json'
|
|
3
|
+
import { PipeLine, 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
|
+
// 执行流水线
|
|
39
|
+
PipeLine.test([deployTask])
|
package/example/airport.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,36 +1,58 @@
|
|
|
1
1
|
import { Task } from './task'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
START,
|
|
5
|
-
END,
|
|
6
|
-
PIPE_TOP_LEFT,
|
|
7
|
-
PIPE_TOP_RIGHT,
|
|
8
|
-
PIPE_BOTTOM_LEFT,
|
|
9
|
-
PIPE_BOTTOM_RIGHT,
|
|
10
|
-
} from '../conf'
|
|
2
|
+
import { logPipeStart, logPipeEnd } from '../lib'
|
|
3
|
+
import type { TaskRaw } from '../types'
|
|
11
4
|
|
|
12
5
|
export class PipeLine {
|
|
13
6
|
public name?: string
|
|
7
|
+
// public log: boolean = true // 决定是否打印日志
|
|
14
8
|
private tasks: Task[]
|
|
15
9
|
|
|
16
|
-
static run(tasks: Task[]) {
|
|
10
|
+
static run(tasks: Task[] | TaskRaw[]) {
|
|
17
11
|
return new PipeLine(tasks).run()
|
|
18
12
|
}
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
static test(tasks: Task[] | TaskRaw[]) {
|
|
15
|
+
return new PipeLine(tasks).test()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
constructor(tasks: Task[] | TaskRaw[]) {
|
|
19
|
+
if (tasks.length > 0 && tasks[0] instanceof Task) {
|
|
20
|
+
this.tasks = tasks as Task[]
|
|
21
|
+
} else {
|
|
22
|
+
this.tasks = (tasks as TaskRaw[]).map((task) => new Task(task))
|
|
23
|
+
}
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* 执行任务中的所有步骤
|
|
26
28
|
*/
|
|
27
29
|
async run() {
|
|
28
|
-
|
|
30
|
+
this.beforeRun()
|
|
31
|
+
for (const [index, task] of this.tasks.entries()) {
|
|
32
|
+
if (!task.skip) {
|
|
33
|
+
task.index = index + 1
|
|
34
|
+
await task._runPipeLine()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
this.afterRun()
|
|
38
|
+
}
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
test() {
|
|
41
|
+
this.beforeRun()
|
|
42
|
+
for (const [index, task] of this.tasks.entries()) {
|
|
43
|
+
if (!task.skip) {
|
|
44
|
+
task.index = index + 1
|
|
45
|
+
task.test()
|
|
46
|
+
}
|
|
32
47
|
}
|
|
48
|
+
this.afterRun()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private beforeRun() {
|
|
52
|
+
logPipeStart()
|
|
53
|
+
}
|
|
33
54
|
|
|
34
|
-
|
|
55
|
+
private afterRun() {
|
|
56
|
+
logPipeEnd()
|
|
35
57
|
}
|
|
36
58
|
}
|
package/src/airport/air/step.ts
CHANGED
|
@@ -1,35 +1,48 @@
|
|
|
1
|
-
import { log, run } from '../lib'
|
|
2
1
|
import type { StepRaw } from '../types'
|
|
2
|
+
import { log, run, logStepStart, logStepEnd, logTesting } from '../lib'
|
|
3
3
|
|
|
4
4
|
export class Step {
|
|
5
5
|
public name: string
|
|
6
6
|
public skip: boolean = false
|
|
7
|
+
public index: number = 1 // 当前索引
|
|
7
8
|
private step: StepRaw
|
|
8
|
-
private
|
|
9
|
+
private testing: boolean = false
|
|
9
10
|
|
|
10
|
-
constructor(step: StepRaw
|
|
11
|
-
this.test = test
|
|
11
|
+
constructor(step: StepRaw) {
|
|
12
12
|
this.step = step
|
|
13
13
|
this.name = step.name
|
|
14
14
|
this.skip = step.skip ?? false
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
async run() {
|
|
18
|
+
this.beforeRun()
|
|
19
|
+
await this.exec()
|
|
20
|
+
this.afterRun()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async test() {
|
|
24
|
+
this.testing = true
|
|
25
|
+
this.beforeRun()
|
|
26
|
+
if (typeof this.step.run === 'string') {
|
|
27
|
+
this.runStringTesting()
|
|
28
|
+
} else if (Array.isArray(this.step.run)) {
|
|
29
|
+
this.runArrayTesting()
|
|
30
|
+
} else {
|
|
31
|
+
logTesting('[async function run]')
|
|
32
|
+
}
|
|
33
|
+
this.afterRun()
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
/**
|
|
18
37
|
* 执行步骤
|
|
19
38
|
*/
|
|
20
|
-
async
|
|
39
|
+
private async exec(): Promise<any> {
|
|
21
40
|
if (typeof this.step.run === 'string') {
|
|
22
41
|
return this.runString()
|
|
23
42
|
} else if (Array.isArray(this.step.run)) {
|
|
24
43
|
return this.runArray()
|
|
25
44
|
} else {
|
|
26
|
-
|
|
27
|
-
if (this.test) {
|
|
28
|
-
log(`[TEST MODE] Would execute function: ${this.step.run.name}`)
|
|
29
|
-
} else {
|
|
30
|
-
// 实际执行函数
|
|
31
|
-
return await this.step.run()
|
|
32
|
-
}
|
|
45
|
+
return await this.step.run()
|
|
33
46
|
}
|
|
34
47
|
}
|
|
35
48
|
|
|
@@ -38,11 +51,7 @@ export class Step {
|
|
|
38
51
|
*/
|
|
39
52
|
private async runString() {
|
|
40
53
|
const runString = this.step.run as string
|
|
41
|
-
|
|
42
|
-
log(`[TEST MODE] Would execute: ${runString}`)
|
|
43
|
-
} else {
|
|
44
|
-
return await run(runString)
|
|
45
|
-
}
|
|
54
|
+
return await run(runString)
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
@@ -50,13 +59,28 @@ export class Step {
|
|
|
50
59
|
*/
|
|
51
60
|
private async runArray() {
|
|
52
61
|
const runArray = this.step.run as string[]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} else {
|
|
56
|
-
for (const cmd of runArray) {
|
|
57
|
-
await run(cmd)
|
|
58
|
-
}
|
|
62
|
+
for (const cmd of runArray) {
|
|
63
|
+
await run(cmd)
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
|
-
}
|
|
62
66
|
|
|
67
|
+
private runStringTesting() {
|
|
68
|
+
const runString = this.step.run as string
|
|
69
|
+
logTesting(runString)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private runArrayTesting() {
|
|
73
|
+
const runArray = this.step.run as string[]
|
|
74
|
+
for (const cmd of runArray) {
|
|
75
|
+
logTesting(cmd)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private beforeRun() {
|
|
80
|
+
logStepStart(this)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private afterRun() {
|
|
84
|
+
logStepEnd(this)
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/airport/air/task.ts
CHANGED
|
@@ -1,35 +1,74 @@
|
|
|
1
1
|
import { Step } from './step'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { TaskOptions, StepRaw } from '../types'
|
|
2
|
+
import { logTaskStart, logTaskEnd } from '../lib'
|
|
3
|
+
import type { TaskRaw, StepRaw } from '../types'
|
|
5
4
|
|
|
6
5
|
export class Task {
|
|
6
|
+
// 任务名称
|
|
7
7
|
public name: string
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
/** Task元数据 */
|
|
10
|
+
public opt: TaskRaw
|
|
11
|
+
|
|
12
|
+
/** 在流水线中的任务索引 */
|
|
13
|
+
public index: number = 1 // 当前索引
|
|
14
|
+
|
|
15
|
+
/** 流水线中是否跳过当前任务 */
|
|
16
|
+
public skip: boolean = false
|
|
17
|
+
|
|
18
|
+
// 是否无头模式,是否自调用
|
|
19
|
+
// true: PipeLine模式,Task由PipeLine.run调用
|
|
20
|
+
// false: 自调用,独立任务模式,外部直接调用Task.run
|
|
21
|
+
public inPipeLine: boolean = false
|
|
22
|
+
|
|
23
|
+
// 任务步骤
|
|
9
24
|
private steps: StepRaw[]
|
|
10
25
|
|
|
11
|
-
constructor(opt:
|
|
26
|
+
constructor(opt: TaskRaw) {
|
|
12
27
|
this.opt = opt
|
|
13
28
|
this.name = opt.name
|
|
14
29
|
this.steps = opt.steps
|
|
30
|
+
this.skip = opt.skip ?? false
|
|
15
31
|
}
|
|
16
32
|
|
|
17
33
|
/**
|
|
18
34
|
* 执行任务中的所有步骤
|
|
19
35
|
*/
|
|
20
|
-
async run(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
logStart(purple('T'), START, blue(this.name))
|
|
36
|
+
async run() {
|
|
37
|
+
this.beforeRun()
|
|
38
|
+
const steps = this.steps.map((step) => new Step(step))
|
|
24
39
|
|
|
25
|
-
for (const step of steps) {
|
|
40
|
+
for (const [index, step] of steps.entries()) {
|
|
26
41
|
if (!step.skip) {
|
|
27
|
-
|
|
42
|
+
step.index = index + 1
|
|
28
43
|
step.skip || (await step.run())
|
|
29
|
-
logStart(purple('S'), END, blue(step.name))
|
|
30
44
|
}
|
|
31
45
|
}
|
|
46
|
+
this.afterRun()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test() {
|
|
50
|
+
this.inPipeLine = true
|
|
51
|
+
this.beforeRun()
|
|
52
|
+
const steps = this.steps.map((step) => new Step(step))
|
|
53
|
+
for (const [index, step] of steps.entries()) {
|
|
54
|
+
if (!step.skip) {
|
|
55
|
+
step.index = index + 1
|
|
56
|
+
step.skip || (step.test())
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.afterRun()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async _runPipeLine() {
|
|
63
|
+
this.inPipeLine = true
|
|
64
|
+
await this.run()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private beforeRun() {
|
|
68
|
+
logTaskStart(this)
|
|
69
|
+
}
|
|
32
70
|
|
|
33
|
-
|
|
71
|
+
private afterRun() {
|
|
72
|
+
logTaskEnd(this)
|
|
34
73
|
}
|
|
35
74
|
}
|
package/src/airport/lib/log.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { Writable } from 'stream'
|
|
2
|
-
import { gray } from './color'
|
|
2
|
+
import { blue, gray, purple } from './color'
|
|
3
3
|
import { formatTimestamp } from './date'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
START,
|
|
6
|
+
END,
|
|
7
|
+
PIPE_PREFIX,
|
|
8
|
+
PIPE_TOP_LEFT,
|
|
9
|
+
PIPE_TOP_RIGHT,
|
|
10
|
+
PIPE_BOTTOM_LEFT,
|
|
11
|
+
PIPE_BOTTOM_RIGHT,
|
|
12
|
+
} from '../conf'
|
|
5
13
|
|
|
6
14
|
export const log = (...args: any[]) => {
|
|
7
15
|
console.log(gray(`[${formatTimestamp()}]`), ...args)
|
|
@@ -19,6 +27,11 @@ export const logErrorStart = (...args: any[]) => {
|
|
|
19
27
|
logError(PIPE_PREFIX, ...args)
|
|
20
28
|
}
|
|
21
29
|
|
|
30
|
+
export const logTesting = (...args: any[]) => {
|
|
31
|
+
logStart(gray('Testing'), ...args)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 日志流、用于子进程直接输出日志到控制台
|
|
22
35
|
export const createLogStream = (prefix?: string) =>
|
|
23
36
|
new Writable({
|
|
24
37
|
write(chunk: Buffer, _encoding, callback) {
|
|
@@ -39,3 +52,42 @@ export const createLogStream = (prefix?: string) =>
|
|
|
39
52
|
callback()
|
|
40
53
|
},
|
|
41
54
|
})
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
// 流水线开始时日志
|
|
58
|
+
export const logPipeStart = () => {
|
|
59
|
+
log(PIPE_TOP_LEFT, purple('Airport PipeLine'), START, PIPE_TOP_RIGHT)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 流水线结束时日志
|
|
63
|
+
export const logPipeEnd = () => {
|
|
64
|
+
log(PIPE_BOTTOM_LEFT, purple('Airport PipeLine'), END, PIPE_BOTTOM_RIGHT)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// 任务开始时日志
|
|
69
|
+
export const logTaskStart = ({ name, index, inPipeLine }) => {
|
|
70
|
+
if (!inPipeLine) {
|
|
71
|
+
logPipeStart()
|
|
72
|
+
}
|
|
73
|
+
logStart(purple('T' + index), START, blue(name))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 任务结束时日志
|
|
77
|
+
export const logTaskEnd = ({ name, index, inPipeLine }) => {
|
|
78
|
+
logStart(purple('T' + index), END, blue(name))
|
|
79
|
+
if (!inPipeLine) {
|
|
80
|
+
logPipeEnd()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// 步骤开始时日志
|
|
86
|
+
export const logStepStart = ({ name, index }) => {
|
|
87
|
+
logStart(purple('S'+ index), START, blue(name))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 步骤结束时日志
|
|
91
|
+
export const logStepEnd = ({ name, index }) => {
|
|
92
|
+
logStart(purple('S'+ index), END, blue(name))
|
|
93
|
+
}
|
package/src/airport/types.ts
CHANGED
|
@@ -6,30 +6,33 @@ export interface StepResult {
|
|
|
6
6
|
output: string
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* 任务执行结果
|
|
11
12
|
*/
|
|
12
|
-
export interface
|
|
13
|
+
export interface TaskResult {
|
|
14
|
+
success: boolean
|
|
15
|
+
stepResults: StepResult[]
|
|
13
16
|
name: string
|
|
14
|
-
skip?: boolean
|
|
15
|
-
run: string | string[] | ((...args: any[]) => Promise<any>)
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* 步骤配置选项
|
|
21
22
|
*/
|
|
22
|
-
export interface
|
|
23
|
-
success: boolean
|
|
24
|
-
stepResults: StepResult[]
|
|
23
|
+
export interface StepRaw {
|
|
25
24
|
name: string
|
|
25
|
+
skip?: boolean
|
|
26
|
+
run: string | string[] | ((...args: any[]) => Promise<any>)
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* 任务配置选项
|
|
30
32
|
*/
|
|
31
|
-
export interface
|
|
33
|
+
export interface TaskRaw {
|
|
32
34
|
name: string
|
|
33
35
|
steps: StepRaw[]
|
|
34
36
|
test?: boolean
|
|
37
|
+
skip?: boolean
|
|
35
38
|
}
|