d3ployer 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -5
- package/dist/config.js +29 -6
- package/dist/configLoader.js +1 -3
- package/dist/def.d.ts +38 -5
- package/dist/defaultTasks.d.ts +3 -2
- package/dist/defaultTasks.js +55 -5
- package/dist/index.d.ts +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.js +106 -36
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -36,18 +36,23 @@ program
|
|
|
36
36
|
const scenarios = config.scenarios ?? {};
|
|
37
37
|
const scenarioKeys = Object.keys(scenarios);
|
|
38
38
|
if (scenarioKeys.length) {
|
|
39
|
-
for (const
|
|
40
|
-
|
|
39
|
+
for (const key of scenarioKeys) {
|
|
40
|
+
const s = scenarios[key];
|
|
41
|
+
const label = s.name !== key ? `${chalk.cyan(key)} (${s.name})` : chalk.cyan(key);
|
|
42
|
+
console.log(` ${label} → [${s.tasks.join(', ')}]`);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
else {
|
|
44
46
|
console.log(' (none)');
|
|
45
47
|
}
|
|
46
48
|
console.log(chalk.bold('\nTasks:'));
|
|
47
|
-
const
|
|
49
|
+
const tasks = config.tasks ?? {};
|
|
50
|
+
const taskKeys = Object.keys(tasks);
|
|
48
51
|
if (taskKeys.length) {
|
|
49
|
-
for (const
|
|
50
|
-
|
|
52
|
+
for (const key of taskKeys) {
|
|
53
|
+
const t = tasks[key];
|
|
54
|
+
const label = t.name !== key ? `${chalk.cyan(key)} (${t.name})` : chalk.cyan(key);
|
|
55
|
+
console.log(` ${label}`);
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
else {
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defaultsDeep } from 'lodash-es';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import { defaultTasks } from './defaultTasks.js';
|
|
3
|
+
import { defaultScenarios, defaultTasks } from './defaultTasks.js';
|
|
4
4
|
const SERVER_DEFAULTS = {
|
|
5
5
|
port: 22,
|
|
6
6
|
username: os.userInfo().username,
|
|
@@ -9,18 +9,41 @@ const SERVER_DEFAULTS = {
|
|
|
9
9
|
function resolveServer(input) {
|
|
10
10
|
return defaultsDeep({}, input, SERVER_DEFAULTS);
|
|
11
11
|
}
|
|
12
|
+
function normalizeTask(key, input) {
|
|
13
|
+
if (typeof input === 'function') {
|
|
14
|
+
return { name: key, fn: input };
|
|
15
|
+
}
|
|
16
|
+
return { name: input.name, fn: input.task };
|
|
17
|
+
}
|
|
18
|
+
function normalizeScenario(key, input) {
|
|
19
|
+
if (Array.isArray(input)) {
|
|
20
|
+
return { name: key, tasks: input };
|
|
21
|
+
}
|
|
22
|
+
return { name: input.name, tasks: input.tasks };
|
|
23
|
+
}
|
|
12
24
|
export function defineConfig(input) {
|
|
13
25
|
const servers = {};
|
|
14
26
|
for (const [name, serverInput] of Object.entries(input.servers)) {
|
|
15
27
|
servers[name] = resolveServer(serverInput);
|
|
16
28
|
}
|
|
29
|
+
const tasks = { ...defaultTasks };
|
|
30
|
+
if (input.tasks) {
|
|
31
|
+
for (const [key, taskInput] of Object.entries(input.tasks)) {
|
|
32
|
+
tasks[key] = normalizeTask(key, taskInput);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const scenarios = { ...defaultScenarios };
|
|
36
|
+
if (input.scenarios) {
|
|
37
|
+
for (const [key, scenarioInput] of Object.entries(input.scenarios)) {
|
|
38
|
+
scenarios[key] = normalizeScenario(key, scenarioInput);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
17
41
|
return {
|
|
18
|
-
|
|
42
|
+
packageManager: 'npm',
|
|
19
43
|
rootDir: '',
|
|
44
|
+
...input,
|
|
20
45
|
servers,
|
|
21
|
-
tasks
|
|
22
|
-
|
|
23
|
-
...input.tasks,
|
|
24
|
-
},
|
|
46
|
+
tasks,
|
|
47
|
+
scenarios,
|
|
25
48
|
};
|
|
26
49
|
}
|
package/dist/configLoader.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { defineConfig } from './config.js';
|
|
4
3
|
import { Exception } from './utils/Exception.js';
|
|
5
4
|
const CONFIG_FILENAME = 'deployer.config.ts';
|
|
6
5
|
export function findConfigFile(startDir = process.cwd()) {
|
|
@@ -23,8 +22,7 @@ export async function loadConfig(configPath) {
|
|
|
23
22
|
throw new Exception(`Config file not found: ${absolutePath}`, 1774741902017);
|
|
24
23
|
}
|
|
25
24
|
const module = await import(absolutePath);
|
|
26
|
-
const
|
|
27
|
-
const config = defineConfig(raw);
|
|
25
|
+
const config = module.default ?? module;
|
|
28
26
|
config.rootDir = path.dirname(absolutePath);
|
|
29
27
|
if (!config.servers || Object.keys(config.servers).length === 0) {
|
|
30
28
|
throw new Exception('Config must define at least one server', 1774741913430);
|
package/dist/def.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface ServerConfig {
|
|
|
9
9
|
password?: string;
|
|
10
10
|
agent?: string;
|
|
11
11
|
deployPath: string;
|
|
12
|
+
packageManager?: string;
|
|
13
|
+
initCmd?: string;
|
|
12
14
|
}
|
|
13
15
|
export type ServerConfigInput = Partial<ServerConfig> & Pick<ServerConfig, 'host' | 'deployPath'>;
|
|
14
16
|
export interface FilesConfig {
|
|
@@ -25,24 +27,55 @@ export interface Placeholders {
|
|
|
25
27
|
deployPath: string;
|
|
26
28
|
timestamp: string;
|
|
27
29
|
}
|
|
30
|
+
export type ExecResult = {
|
|
31
|
+
success: boolean;
|
|
32
|
+
code: number;
|
|
33
|
+
stdout: string;
|
|
34
|
+
stderr: string;
|
|
35
|
+
};
|
|
36
|
+
export type RunOptions = {
|
|
37
|
+
printOutput?: boolean;
|
|
38
|
+
ignoreError?: boolean;
|
|
39
|
+
};
|
|
28
40
|
export interface TaskContext {
|
|
29
41
|
server: ServerConfig & {
|
|
30
42
|
name: string;
|
|
31
43
|
};
|
|
32
44
|
ssh: SSH2Promise;
|
|
33
45
|
config: DeployerConfig;
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
runLocal: (cmd: string, options?: RunOptions) => Promise<ExecResult>;
|
|
47
|
+
testLocal: (cmd: string) => Promise<boolean>;
|
|
48
|
+
run: (cmd: string, options?: RunOptions) => Promise<ExecResult>;
|
|
49
|
+
test: (cmd: string) => Promise<boolean>;
|
|
36
50
|
}
|
|
37
51
|
export type TaskFn = (ctx: TaskContext, ph: Placeholders) => Promise<void>;
|
|
52
|
+
export interface TaskDef {
|
|
53
|
+
name: string;
|
|
54
|
+
fn: TaskFn;
|
|
55
|
+
}
|
|
56
|
+
export type TaskInput = TaskFn | {
|
|
57
|
+
name: string;
|
|
58
|
+
task: TaskFn;
|
|
59
|
+
};
|
|
60
|
+
export interface ScenarioDef {
|
|
61
|
+
name: string;
|
|
62
|
+
tasks: string[];
|
|
63
|
+
}
|
|
64
|
+
export type ScenarioInput = string[] | {
|
|
65
|
+
name: string;
|
|
66
|
+
tasks: string[];
|
|
67
|
+
};
|
|
38
68
|
export interface DeployerConfig {
|
|
39
69
|
rootDir: string;
|
|
40
70
|
servers: Record<string, ServerConfig>;
|
|
71
|
+
packageManager?: string;
|
|
41
72
|
files?: FilesConfig;
|
|
42
73
|
symlinks?: SymlinkConfig[];
|
|
43
|
-
tasks?: Record<string,
|
|
44
|
-
scenarios?: Record<string,
|
|
74
|
+
tasks?: Record<string, TaskDef>;
|
|
75
|
+
scenarios?: Record<string, ScenarioDef>;
|
|
45
76
|
}
|
|
46
|
-
export type DeployerConfigInput = Omit<DeployerConfig, 'servers' | 'rootDir'> & {
|
|
77
|
+
export type DeployerConfigInput = Omit<DeployerConfig, 'servers' | 'rootDir' | 'tasks' | 'scenarios'> & {
|
|
47
78
|
servers: Record<string, ServerConfigInput>;
|
|
79
|
+
tasks?: Record<string, TaskInput>;
|
|
80
|
+
scenarios?: Record<string, ScenarioInput>;
|
|
48
81
|
};
|
package/dist/defaultTasks.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const defaultTasks: Record<string,
|
|
1
|
+
import type { ScenarioDef, TaskDef } from './def.js';
|
|
2
|
+
export declare const defaultTasks: Record<string, TaskDef>;
|
|
3
|
+
export declare const defaultScenarios: Record<string, ScenarioDef>;
|
package/dist/defaultTasks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from 'chalk';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { Exception } from './utils/Exception.js';
|
|
4
5
|
function buildRsyncCommand(server, source, dest, files) {
|
|
5
6
|
const args = ['rsync', '-avz', '--delete', '--progress=info2'];
|
|
@@ -67,7 +68,7 @@ const uploadTask = async (ctx, ph) => {
|
|
|
67
68
|
const remotePath = ph.deployPath;
|
|
68
69
|
const dest = `${ctx.server.username}@${ctx.server.host}:${remotePath}`;
|
|
69
70
|
const source = localBase.endsWith('/') ? localBase : localBase + '/';
|
|
70
|
-
await ctx.
|
|
71
|
+
await ctx.run(`mkdir -p ${remotePath}`);
|
|
71
72
|
const command = buildRsyncCommand(ctx.server, source, dest, files);
|
|
72
73
|
await execRsync(command);
|
|
73
74
|
};
|
|
@@ -83,10 +84,59 @@ const symlinksTask = async (ctx, ph) => {
|
|
|
83
84
|
const path = link.path.startsWith('/')
|
|
84
85
|
? link.path
|
|
85
86
|
: `${ph.deployPath}/${link.path}`;
|
|
86
|
-
await ctx.
|
|
87
|
+
await ctx.run(`ln -sfn ${target} ${path}`);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const depInstallTask = async (ctx, ph) => {
|
|
91
|
+
const pm = ctx.server.packageManager ?? ctx.config.packageManager ?? 'npm';
|
|
92
|
+
const cmd = `${pm} install`;
|
|
93
|
+
await ctx.run(cmd);
|
|
94
|
+
};
|
|
95
|
+
const printDeploymentTask = async (ctx, ph) => {
|
|
96
|
+
console.log(chalk.cyan('Deployment directory'), ph.deployPath);
|
|
97
|
+
await ctx.run('ls -la .');
|
|
98
|
+
console.log(chalk.cyan('Directory size'));
|
|
99
|
+
await ctx.run('du -hd 1 .');
|
|
100
|
+
};
|
|
101
|
+
const pm2SetupTask = async (ctx, ph) => {
|
|
102
|
+
const pm2ConfigExists = await ctx.test('test -f pm2.config.js');
|
|
103
|
+
if (!pm2ConfigExists) {
|
|
104
|
+
console.log(chalk.yellow('pm2.config.js not found, skipping PM2 setup'));
|
|
105
|
+
return;
|
|
87
106
|
}
|
|
107
|
+
await ctx.run('pm2 start pm2.config.js --update-env');
|
|
108
|
+
await ctx.run('pm2 save');
|
|
88
109
|
};
|
|
89
110
|
export const defaultTasks = {
|
|
90
|
-
upload:
|
|
91
|
-
|
|
111
|
+
upload: {
|
|
112
|
+
name: 'Upload files',
|
|
113
|
+
fn: uploadTask,
|
|
114
|
+
},
|
|
115
|
+
symlinks: {
|
|
116
|
+
name: 'Create symlinks',
|
|
117
|
+
fn: symlinksTask,
|
|
118
|
+
},
|
|
119
|
+
depInstall: {
|
|
120
|
+
name: 'Install dependencies',
|
|
121
|
+
fn: depInstallTask,
|
|
122
|
+
},
|
|
123
|
+
printDeployment: {
|
|
124
|
+
name: 'Print deployment info',
|
|
125
|
+
fn: printDeploymentTask,
|
|
126
|
+
},
|
|
127
|
+
pm2Setup: {
|
|
128
|
+
name: 'PM2 setup',
|
|
129
|
+
fn: pm2SetupTask,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
export const defaultScenarios = {
|
|
133
|
+
deploy: {
|
|
134
|
+
name: 'Deploy',
|
|
135
|
+
tasks: [
|
|
136
|
+
'upload',
|
|
137
|
+
'symlinks',
|
|
138
|
+
'depInstall',
|
|
139
|
+
'pm2Setup',
|
|
140
|
+
],
|
|
141
|
+
},
|
|
92
142
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { AuthMethod, DeployerConfig, DeployerConfigInput, FilesConfig, Placeholders, ServerConfig, ServerConfigInput, SymlinkConfig, TaskContext, TaskFn, } from './def.js';
|
|
1
|
+
export type { AuthMethod, DeployerConfig, DeployerConfigInput, FilesConfig, Placeholders, ScenarioDef, ScenarioInput, ServerConfig, ServerConfigInput, SymlinkConfig, TaskContext, TaskDef, TaskFn, TaskInput, } from './def.js';
|
|
2
2
|
export { defineConfig } from './config.js';
|
|
3
3
|
export { runScenario, runTask } from './runner.js';
|
|
4
4
|
export { loadConfig, findConfigFile } from './configLoader.js';
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DeployerConfig, ServerConfig,
|
|
1
|
+
import type { DeployerConfig, ServerConfig, TaskDef } from './def.js';
|
|
2
2
|
export declare function resolveServers(config: DeployerConfig, serverNames?: string[]): Array<[string, ServerConfig]>;
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function resolveTaskDefs(taskNames: string[], allTasks: Record<string, TaskDef>): Array<[string, TaskDef]>;
|
|
4
4
|
export declare function runScenario(config: DeployerConfig, scenarioName: string, serverNames?: string[]): Promise<void>;
|
|
5
5
|
export declare function runTask(config: DeployerConfig, taskName: string, serverNames?: string[]): Promise<void>;
|
package/dist/runner.js
CHANGED
|
@@ -3,57 +3,89 @@ import { Listr } from 'listr2';
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { createSSHConnection } from './connection.js';
|
|
5
5
|
import { Exception } from './utils/Exception.js';
|
|
6
|
-
function execLocal(command) {
|
|
6
|
+
function execLocal(command, options) {
|
|
7
|
+
const result = {
|
|
8
|
+
success: undefined,
|
|
9
|
+
code: undefined,
|
|
10
|
+
stdout: '',
|
|
11
|
+
stderr: '',
|
|
12
|
+
};
|
|
7
13
|
return new Promise((resolve, reject) => {
|
|
8
14
|
const child = spawn('sh', ['-c', command], {
|
|
15
|
+
cwd: options.cwd,
|
|
9
16
|
stdio: ['inherit', 'pipe', 'pipe'],
|
|
10
17
|
});
|
|
11
|
-
const chunks = [];
|
|
12
18
|
child.stdout.on('data', (data) => {
|
|
13
19
|
const text = data.toString();
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
result.stdout += text;
|
|
21
|
+
if (options.printOutput) {
|
|
22
|
+
process.stdout.write(text);
|
|
23
|
+
}
|
|
16
24
|
});
|
|
17
25
|
child.stderr.on('data', (data) => {
|
|
18
26
|
const text = data.toString();
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
result.stderr += text;
|
|
28
|
+
if (options.printOutput) {
|
|
29
|
+
process.stderr.write(text);
|
|
30
|
+
}
|
|
21
31
|
});
|
|
22
32
|
child.on('close', (code) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
result.code = code;
|
|
34
|
+
result.success = code === 0;
|
|
35
|
+
if (code !== 0
|
|
36
|
+
&& !options.ignoreError) {
|
|
37
|
+
reject(new Exception(`Local command failed (exit ${code}): ${command}\n${result.stderr}`, 1774742010146));
|
|
26
38
|
return;
|
|
27
39
|
}
|
|
28
|
-
resolve(
|
|
40
|
+
resolve(result);
|
|
29
41
|
});
|
|
30
42
|
child.on('error', (err) => {
|
|
31
43
|
reject(new Exception(`Local command failed: ${command}\n${err.message}`, 1774742013175));
|
|
32
44
|
});
|
|
33
45
|
});
|
|
34
46
|
}
|
|
35
|
-
function execRemote(ssh, command) {
|
|
47
|
+
function execRemote(ssh, command, options) {
|
|
48
|
+
const result = {
|
|
49
|
+
success: undefined,
|
|
50
|
+
code: undefined,
|
|
51
|
+
stdout: '',
|
|
52
|
+
stderr: '',
|
|
53
|
+
};
|
|
54
|
+
const parts = [];
|
|
55
|
+
if (options.cwd) {
|
|
56
|
+
parts.push(`cd ${options.cwd}`);
|
|
57
|
+
}
|
|
58
|
+
if (options.initCmd) {
|
|
59
|
+
parts.push(options.initCmd);
|
|
60
|
+
}
|
|
61
|
+
parts.push(command);
|
|
62
|
+
const wrappedCommand = parts.join('; \\\n');
|
|
36
63
|
return new Promise((resolve, reject) => {
|
|
37
|
-
ssh.spawn(
|
|
64
|
+
ssh.spawn(wrappedCommand)
|
|
38
65
|
.then((stream) => {
|
|
39
|
-
const chunks = [];
|
|
40
66
|
stream.on('data', (data) => {
|
|
41
67
|
const text = data.toString();
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
result.stdout += text;
|
|
69
|
+
if (options.printOutput) {
|
|
70
|
+
process.stdout.write(text);
|
|
71
|
+
}
|
|
44
72
|
});
|
|
45
73
|
stream.stderr.on('data', (data) => {
|
|
46
74
|
const text = data.toString();
|
|
47
|
-
|
|
48
|
-
|
|
75
|
+
result.stderr += text;
|
|
76
|
+
if (options.printOutput) {
|
|
77
|
+
process.stderr.write(text);
|
|
78
|
+
}
|
|
49
79
|
});
|
|
50
80
|
stream.on('close', (code) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
81
|
+
result.code = code;
|
|
82
|
+
result.success = code === 0;
|
|
83
|
+
if (code !== 0
|
|
84
|
+
&& !options.ignoreError) {
|
|
85
|
+
reject(new Exception(`Remote command failed (exit ${code}): ${command}\n${result.stderr}`, 1774742047909));
|
|
54
86
|
return;
|
|
55
87
|
}
|
|
56
|
-
resolve(
|
|
88
|
+
resolve(result);
|
|
57
89
|
});
|
|
58
90
|
})
|
|
59
91
|
.catch((err) => {
|
|
@@ -73,8 +105,46 @@ function buildTaskContext(serverName, server, ssh, config) {
|
|
|
73
105
|
server: { ...server, name: serverName },
|
|
74
106
|
ssh,
|
|
75
107
|
config,
|
|
76
|
-
|
|
77
|
-
|
|
108
|
+
runLocal: (cmd, options = {}) => {
|
|
109
|
+
options = {
|
|
110
|
+
ignoreError: false,
|
|
111
|
+
printOutput: true,
|
|
112
|
+
...options,
|
|
113
|
+
};
|
|
114
|
+
return execLocal(cmd, {
|
|
115
|
+
cwd: config.rootDir,
|
|
116
|
+
...options,
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
testLocal: async (cmd) => {
|
|
120
|
+
const result = await execLocal(cmd, {
|
|
121
|
+
ignoreError: true,
|
|
122
|
+
printOutput: false,
|
|
123
|
+
cwd: config.rootDir,
|
|
124
|
+
});
|
|
125
|
+
return result.success;
|
|
126
|
+
},
|
|
127
|
+
run: (cmd, options = {}) => {
|
|
128
|
+
options = {
|
|
129
|
+
ignoreError: false,
|
|
130
|
+
printOutput: true,
|
|
131
|
+
...options,
|
|
132
|
+
};
|
|
133
|
+
return execRemote(ssh, cmd, {
|
|
134
|
+
cwd: server.deployPath,
|
|
135
|
+
initCmd: server.initCmd,
|
|
136
|
+
...options,
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
test: async (cmd) => {
|
|
140
|
+
const result = await execRemote(ssh, cmd, {
|
|
141
|
+
ignoreError: true,
|
|
142
|
+
printOutput: false,
|
|
143
|
+
cwd: config.rootDir,
|
|
144
|
+
initCmd: server.initCmd,
|
|
145
|
+
});
|
|
146
|
+
return result.success;
|
|
147
|
+
},
|
|
78
148
|
};
|
|
79
149
|
}
|
|
80
150
|
export function resolveServers(config, serverNames) {
|
|
@@ -93,14 +163,14 @@ export function resolveServers(config, serverNames) {
|
|
|
93
163
|
}
|
|
94
164
|
return result;
|
|
95
165
|
}
|
|
96
|
-
export function
|
|
166
|
+
export function resolveTaskDefs(taskNames, allTasks) {
|
|
97
167
|
return taskNames.map(name => {
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
168
|
+
const def = allTasks[name];
|
|
169
|
+
if (!def) {
|
|
100
170
|
const available = Object.keys(allTasks).join(', ');
|
|
101
171
|
throw new Exception(`Task "${name}" not found. Available: ${available}`, 1774742082083);
|
|
102
172
|
}
|
|
103
|
-
return [name,
|
|
173
|
+
return [name, def];
|
|
104
174
|
});
|
|
105
175
|
}
|
|
106
176
|
const listrOptions = {
|
|
@@ -121,9 +191,9 @@ function buildServerListr(serverName, server, config, tasks) {
|
|
|
121
191
|
ctx.ph = buildPlaceholders(serverName, server);
|
|
122
192
|
},
|
|
123
193
|
},
|
|
124
|
-
...tasks.map(([
|
|
125
|
-
title: chalk.bgCyan.black(` ${
|
|
126
|
-
task: async (ctx, task) =>
|
|
194
|
+
...tasks.map(([_key, taskDef]) => ({
|
|
195
|
+
title: chalk.bgCyan.black(` ${taskDef.name} `),
|
|
196
|
+
task: async (ctx, task) => taskDef.fn(ctx.taskCtx, ctx.ph),
|
|
127
197
|
options: listrOptions,
|
|
128
198
|
})),
|
|
129
199
|
{
|
|
@@ -136,13 +206,13 @@ function buildServerListr(serverName, server, config, tasks) {
|
|
|
136
206
|
], listrOptions);
|
|
137
207
|
}
|
|
138
208
|
export async function runScenario(config, scenarioName, serverNames) {
|
|
139
|
-
const
|
|
140
|
-
if (!
|
|
209
|
+
const scenarioDef = config.scenarios?.[scenarioName];
|
|
210
|
+
if (!scenarioDef) {
|
|
141
211
|
const available = Object.keys(config.scenarios ?? {}).join(', ') || 'none';
|
|
142
212
|
throw new Exception(`Scenario "${scenarioName}" not found. Available: ${available}`, 1774742090385);
|
|
143
213
|
}
|
|
144
214
|
const allTasks = config.tasks ?? {};
|
|
145
|
-
const tasks =
|
|
215
|
+
const tasks = resolveTaskDefs(scenarioDef.tasks, allTasks);
|
|
146
216
|
const servers = resolveServers(config, serverNames);
|
|
147
217
|
const listr = new Listr(servers.map(([name, server]) => ({
|
|
148
218
|
title: chalk.bgMagenta.black(` ${name} (${server.host}) `),
|
|
@@ -153,15 +223,15 @@ export async function runScenario(config, scenarioName, serverNames) {
|
|
|
153
223
|
}
|
|
154
224
|
export async function runTask(config, taskName, serverNames) {
|
|
155
225
|
const allTasks = config.tasks ?? {};
|
|
156
|
-
const
|
|
157
|
-
if (!
|
|
226
|
+
const taskDef = allTasks[taskName];
|
|
227
|
+
if (!taskDef) {
|
|
158
228
|
const available = Object.keys(allTasks).join(', ') || 'none';
|
|
159
229
|
throw new Exception(`Task "${taskName}" not found. Available: ${available}`, 1774742100356);
|
|
160
230
|
}
|
|
161
231
|
const servers = resolveServers(config, serverNames);
|
|
162
232
|
const listr = new Listr(servers.map(([name, server]) => ({
|
|
163
233
|
title: chalk.bgMagenta.black(` ${name} (${server.host}) `),
|
|
164
|
-
task: () => buildServerListr(name, server, config, [[taskName,
|
|
234
|
+
task: () => buildServerListr(name, server, config, [[taskName, taskDef]]),
|
|
165
235
|
options: listrOptions,
|
|
166
236
|
})), listrOptions);
|
|
167
237
|
await listr.run();
|