d3ployer 0.0.9 → 0.0.11
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 +9 -4
- package/dist/config.d.ts +0 -1
- package/dist/config.js +26 -16
- package/dist/def.d.ts +27 -18
- package/dist/defaultTasks.js +43 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/renderer.d.ts +23 -0
- package/dist/renderer.js +153 -0
- package/dist/runner.d.ts +8 -4
- package/dist/runner.js +45 -38
- package/dist/tasks/clearTarget.d.ts +2 -0
- package/dist/tasks/clearTarget.js +15 -0
- package/dist/tasks/download.d.ts +3 -0
- package/dist/tasks/download.js +27 -0
- package/dist/tasks/index.d.ts +6 -0
- package/dist/tasks/index.js +76 -0
- package/dist/tasks/installPackages.d.ts +3 -0
- package/dist/tasks/installPackages.js +38 -0
- package/dist/tasks/logsStream.d.ts +5 -0
- package/dist/tasks/logsStream.js +69 -0
- package/dist/tasks/printDeployment.d.ts +2 -0
- package/dist/tasks/printDeployment.js +8 -0
- package/dist/tasks/printLogs.d.ts +5 -0
- package/dist/tasks/printLogs.js +69 -0
- package/dist/tasks/setupDocker.d.ts +4 -0
- package/dist/tasks/setupDocker.js +33 -0
- package/dist/tasks/setupPm2.d.ts +3 -0
- package/dist/tasks/setupPm2.js +14 -0
- package/dist/tasks/symlinks.d.ts +3 -0
- package/dist/tasks/symlinks.js +18 -0
- package/dist/tasks/upload.d.ts +7 -0
- package/dist/tasks/upload.js +54 -0
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -6,19 +6,24 @@ import { runScenario, runTask } from './runner.js';
|
|
|
6
6
|
const program = new Command()
|
|
7
7
|
.name('deployer')
|
|
8
8
|
.description('TypeScript deployment tool')
|
|
9
|
-
.option('-c, --config <path>', 'path to deployer.config.ts')
|
|
9
|
+
.option('-c, --config <path>', 'path to deployer.config.ts')
|
|
10
|
+
.option('--skip <tasks>', 'comma-separated list of tasks to skip');
|
|
10
11
|
program
|
|
11
12
|
.argument('<name>', 'scenario or task name')
|
|
12
13
|
.argument('[servers...]', 'target server(s)')
|
|
13
14
|
.action(async (name, servers) => {
|
|
14
15
|
try {
|
|
15
|
-
const
|
|
16
|
+
const opts = program.opts();
|
|
17
|
+
const config = await loadConfig(opts.config);
|
|
16
18
|
const serverList = servers.length > 0 ? servers : undefined;
|
|
19
|
+
const skipTasks = opts.skip
|
|
20
|
+
? opts.skip.split(',').map((s) => s.trim()).filter(Boolean)
|
|
21
|
+
: [];
|
|
17
22
|
if (config.scenarios?.[name]) {
|
|
18
|
-
await runScenario(config, name, serverList);
|
|
23
|
+
await runScenario(config, name, serverList, { skip: skipTasks });
|
|
19
24
|
}
|
|
20
25
|
else {
|
|
21
|
-
await runTask(config, name, serverList);
|
|
26
|
+
await runTask(config, name, serverList, { skip: skipTasks });
|
|
22
27
|
}
|
|
23
28
|
}
|
|
24
29
|
catch (err) {
|
package/dist/config.d.ts
CHANGED
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 { defaultScenarios, defaultTasks } from './
|
|
3
|
+
import { defaultScenarios, defaultTasks } from './tasks/index.js';
|
|
4
4
|
const SERVER_DEFAULTS = {
|
|
5
5
|
port: 22,
|
|
6
6
|
username: os.userInfo().username,
|
|
@@ -35,9 +35,6 @@ function normalizeScenario(key, input) {
|
|
|
35
35
|
tasks: input.tasks,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
export function camelToColonCase(str) {
|
|
39
|
-
return str.replace(/([a-z0-9])([A-Z])/g, '$1:$2').toLowerCase();
|
|
40
|
-
}
|
|
41
38
|
export function defineConfig(input) {
|
|
42
39
|
const servers = {};
|
|
43
40
|
for (const [name, serverInput] of Object.entries(input.servers)) {
|
|
@@ -45,39 +42,52 @@ export function defineConfig(input) {
|
|
|
45
42
|
}
|
|
46
43
|
const tasks = {};
|
|
47
44
|
for (const [key, taskDef] of Object.entries(defaultTasks)) {
|
|
48
|
-
tasks[
|
|
45
|
+
tasks[key] = taskDef;
|
|
49
46
|
}
|
|
50
47
|
if (input.tasks) {
|
|
51
48
|
for (const [key, taskInput] of Object.entries(input.tasks)) {
|
|
52
|
-
tasks[
|
|
49
|
+
tasks[key] = normalizeTask(key, taskInput);
|
|
53
50
|
}
|
|
54
51
|
}
|
|
55
52
|
const scenarios = {};
|
|
56
53
|
for (const [key, scenarioDef] of Object.entries(defaultScenarios)) {
|
|
57
|
-
scenarios[
|
|
54
|
+
scenarios[key] = {
|
|
58
55
|
...scenarioDef,
|
|
59
|
-
tasks: scenarioDef.tasks
|
|
56
|
+
tasks: scenarioDef.tasks,
|
|
60
57
|
};
|
|
61
58
|
}
|
|
62
59
|
if (input.scenarios) {
|
|
63
60
|
for (const [key, scenarioInput] of Object.entries(input.scenarios)) {
|
|
64
61
|
const normalized = normalizeScenario(key, scenarioInput);
|
|
65
|
-
scenarios[
|
|
62
|
+
scenarios[key] = {
|
|
66
63
|
...normalized,
|
|
67
|
-
tasks: normalized.tasks
|
|
64
|
+
tasks: normalized.tasks,
|
|
68
65
|
};
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
|
-
return {
|
|
68
|
+
return defaultsDeep({
|
|
72
69
|
rootDir: '',
|
|
73
70
|
...input,
|
|
71
|
+
servers,
|
|
72
|
+
tasks,
|
|
73
|
+
scenarios,
|
|
74
|
+
}, {
|
|
74
75
|
packageManager: {
|
|
75
76
|
manager: 'npm',
|
|
76
77
|
productionOnly: true,
|
|
77
|
-
...input.packageManager,
|
|
78
78
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
pm2: {
|
|
80
|
+
logs: {
|
|
81
|
+
lines: 25,
|
|
82
|
+
time: 3,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
dockerCompose: {
|
|
86
|
+
configFiles: undefined,
|
|
87
|
+
logs: {
|
|
88
|
+
lines: 25,
|
|
89
|
+
time: 3,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
83
93
|
}
|
package/dist/def.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ListrTaskWrapper } from 'listr2';
|
|
1
2
|
import type SSH2Promise from 'ssh2-promise';
|
|
2
3
|
export type AuthMethod = 'key' | 'password' | 'agent';
|
|
3
4
|
export type PackageManager = 'npm' | 'yarn' | 'pnpm';
|
|
@@ -27,9 +28,34 @@ export interface SymlinkConfig {
|
|
|
27
28
|
path: string;
|
|
28
29
|
target: string;
|
|
29
30
|
}
|
|
31
|
+
export type ConfigOrDisable<T> = T | false;
|
|
30
32
|
export interface LogsConfig {
|
|
31
33
|
time?: number;
|
|
34
|
+
lines?: number;
|
|
32
35
|
}
|
|
36
|
+
export type Pm2Config = {
|
|
37
|
+
logs: ConfigOrDisable<LogsConfig>;
|
|
38
|
+
};
|
|
39
|
+
export type DockerComposeConfig = {
|
|
40
|
+
configFiles: string[];
|
|
41
|
+
logs: ConfigOrDisable<LogsConfig>;
|
|
42
|
+
};
|
|
43
|
+
export interface DeployerConfig {
|
|
44
|
+
rootDir: string;
|
|
45
|
+
servers: Record<string, ServerConfig>;
|
|
46
|
+
files?: FilesConfig;
|
|
47
|
+
symlinks?: SymlinkConfig[];
|
|
48
|
+
packageManager?: ConfigOrDisable<PackageManagerConfig>;
|
|
49
|
+
pm2?: ConfigOrDisable<Pm2Config>;
|
|
50
|
+
dockerCompose?: ConfigOrDisable<DockerComposeConfig>;
|
|
51
|
+
tasks?: Record<string, TaskDef>;
|
|
52
|
+
scenarios?: Record<string, ScenarioDef>;
|
|
53
|
+
}
|
|
54
|
+
export type DeployerConfigInput = Omit<DeployerConfig, 'servers' | 'rootDir' | 'tasks' | 'scenarios'> & {
|
|
55
|
+
servers: Record<string, ServerConfigInput>;
|
|
56
|
+
tasks?: Record<string, TaskInput>;
|
|
57
|
+
scenarios?: Record<string, ScenarioInput>;
|
|
58
|
+
};
|
|
33
59
|
export interface Placeholders {
|
|
34
60
|
serverName: string;
|
|
35
61
|
deployPath: string;
|
|
@@ -57,7 +83,7 @@ export interface TaskContext {
|
|
|
57
83
|
test: (cmd: string) => Promise<boolean>;
|
|
58
84
|
taskConfig?: any;
|
|
59
85
|
}
|
|
60
|
-
export type TaskFn = (ctx: TaskContext, ph: Placeholders) => Promise<void>;
|
|
86
|
+
export type TaskFn = (ctx: TaskContext, ph: Placeholders, task: ListrTaskWrapper<any, any, any>) => Promise<void>;
|
|
61
87
|
export type TaskSkipFn = (ctx: TaskContext, ph: Placeholders) => Promise<boolean | string> | boolean | string;
|
|
62
88
|
export interface TaskDef {
|
|
63
89
|
name: string;
|
|
@@ -79,20 +105,3 @@ export type ScenarioInput = string[] | {
|
|
|
79
105
|
name: string;
|
|
80
106
|
tasks: string[];
|
|
81
107
|
};
|
|
82
|
-
export interface DeployerConfig {
|
|
83
|
-
rootDir: string;
|
|
84
|
-
servers: Record<string, ServerConfig>;
|
|
85
|
-
files?: FilesConfig;
|
|
86
|
-
symlinks?: SymlinkConfig[];
|
|
87
|
-
packageManager?: PackageManagerConfig | false;
|
|
88
|
-
pm2?: boolean;
|
|
89
|
-
dockerCompose?: boolean;
|
|
90
|
-
logs?: LogsConfig | false;
|
|
91
|
-
tasks?: Record<string, TaskDef>;
|
|
92
|
-
scenarios?: Record<string, ScenarioDef>;
|
|
93
|
-
}
|
|
94
|
-
export type DeployerConfigInput = Omit<DeployerConfig, 'servers' | 'rootDir' | 'tasks' | 'scenarios'> & {
|
|
95
|
-
servers: Record<string, ServerConfigInput>;
|
|
96
|
-
tasks?: Record<string, TaskInput>;
|
|
97
|
-
scenarios?: Record<string, ScenarioInput>;
|
|
98
|
-
};
|
package/dist/defaultTasks.js
CHANGED
|
@@ -146,18 +146,38 @@ const pm2SetupTask = async (ctx) => {
|
|
|
146
146
|
await ctx.run('pm2 start pm2.config.* --update-env');
|
|
147
147
|
await ctx.run('pm2 save');
|
|
148
148
|
};
|
|
149
|
+
function buildDockerComposeTestCmd(dockerComposeConfig) {
|
|
150
|
+
if (dockerComposeConfig === false) {
|
|
151
|
+
return 'false';
|
|
152
|
+
}
|
|
153
|
+
const configFiles = dockerComposeConfig.configFiles ?? [
|
|
154
|
+
'docker-compose.yml',
|
|
155
|
+
'docker-compose.yaml',
|
|
156
|
+
'compose.yml',
|
|
157
|
+
'compose.yaml',
|
|
158
|
+
];
|
|
159
|
+
const testCmdPart = configFiles.map(f => `-f ${f}`);
|
|
160
|
+
return `test ${testCmdPart.join(' -o ')}`;
|
|
161
|
+
}
|
|
149
162
|
const dockerSetupSkip = async (ctx) => {
|
|
150
163
|
if (ctx.config.dockerCompose === false) {
|
|
151
164
|
return 'Docker Compose disabled';
|
|
152
165
|
}
|
|
153
|
-
const
|
|
166
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
167
|
+
const composeExists = await ctx.test(testCmd);
|
|
154
168
|
if (!composeExists) {
|
|
155
169
|
return 'Docker Compose config not found';
|
|
156
170
|
}
|
|
157
171
|
return false;
|
|
158
172
|
};
|
|
159
173
|
const dockerSetupTask = async (ctx) => {
|
|
160
|
-
|
|
174
|
+
if (ctx.config.dockerCompose === false) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const configFiles = ctx.config.dockerCompose?.configFiles ?? [];
|
|
178
|
+
const options = configFiles.map(f => `-f ${f}`).join(' ');
|
|
179
|
+
await ctx.run(`docker compose ${options} down --remove-orphans`);
|
|
180
|
+
await ctx.run(`docker compose ${options} up -d --build`);
|
|
161
181
|
};
|
|
162
182
|
const clearTargetTask = async (ctx, ph) => {
|
|
163
183
|
const confirmed = await confirm({
|
|
@@ -177,19 +197,22 @@ const printDeploymentTask = async (ctx, ph) => {
|
|
|
177
197
|
console.log(chalk.cyan('Directory size'));
|
|
178
198
|
await ctx.run('du -hd 1 .');
|
|
179
199
|
};
|
|
180
|
-
const
|
|
200
|
+
const logsStreamSkip = async (ctx) => {
|
|
181
201
|
if (ctx.config.logs === false) {
|
|
182
202
|
return 'Logs streaming disabled';
|
|
183
203
|
}
|
|
184
204
|
const hasPm2 = ctx.config.pm2 !== false && await ctx.test('test -f pm2.config.*');
|
|
185
|
-
|
|
186
|
-
|
|
205
|
+
let hasDocker = false;
|
|
206
|
+
if (ctx.config.dockerCompose) {
|
|
207
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
208
|
+
hasDocker = await ctx.test(testCmd);
|
|
209
|
+
}
|
|
187
210
|
if (!hasPm2 && !hasDocker) {
|
|
188
211
|
return 'No PM2 or Docker Compose detected';
|
|
189
212
|
}
|
|
190
213
|
return false;
|
|
191
214
|
};
|
|
192
|
-
const
|
|
215
|
+
const logsStreamTask = async (ctx) => {
|
|
193
216
|
const logsConfig = {
|
|
194
217
|
time: 3,
|
|
195
218
|
...ctx.config.logs,
|
|
@@ -197,8 +220,11 @@ const streamLogsTask = async (ctx) => {
|
|
|
197
220
|
const time = logsConfig.time;
|
|
198
221
|
const hasPm2 = ctx.config.pm2 !== false
|
|
199
222
|
&& await ctx.test('test -f pm2.config.*');
|
|
200
|
-
|
|
201
|
-
|
|
223
|
+
let hasDocker = false;
|
|
224
|
+
if (ctx.config.dockerCompose !== false) {
|
|
225
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
226
|
+
hasDocker = await ctx.test(testCmd);
|
|
227
|
+
}
|
|
202
228
|
if (hasPm2) {
|
|
203
229
|
const pm2ConfigRaw = await ctx.run('cat pm2.config.*', { printOutput: false });
|
|
204
230
|
const nameMatch = pm2ConfigRaw.stdout.match(/name: ['"](?<name>.+?)['"]/);
|
|
@@ -206,9 +232,11 @@ const streamLogsTask = async (ctx) => {
|
|
|
206
232
|
console.log(chalk.cyan(`Streaming PM2 logs for ${time}s...`));
|
|
207
233
|
await ctx.run(`timeout ${time} pm2 logs "${name}" || true`, { printOutput: true, ignoreError: true });
|
|
208
234
|
}
|
|
209
|
-
else if (hasDocker) {
|
|
235
|
+
else if (hasDocker && ctx.config.dockerCompose) {
|
|
236
|
+
const configFiles = ctx.config.dockerCompose.configFiles ?? [];
|
|
237
|
+
const options = configFiles.map(f => `-f ${f}`).join(' ');
|
|
210
238
|
console.log(chalk.cyan(`Streaming Docker Compose logs for ${time}s...`));
|
|
211
|
-
await ctx.run(`timeout ${time} docker compose logs --tail=
|
|
239
|
+
await ctx.run(`timeout ${time} docker compose ${options} logs --tail=10 -f || true`, {
|
|
212
240
|
printOutput: true,
|
|
213
241
|
ignoreError: true,
|
|
214
242
|
});
|
|
@@ -253,10 +281,10 @@ export const defaultTasks = {
|
|
|
253
281
|
name: 'Print deployment info',
|
|
254
282
|
task: printDeploymentTask,
|
|
255
283
|
},
|
|
256
|
-
|
|
257
|
-
name: '
|
|
258
|
-
skip:
|
|
259
|
-
task:
|
|
284
|
+
logsStream: {
|
|
285
|
+
name: 'Logs stream',
|
|
286
|
+
skip: logsStreamSkip,
|
|
287
|
+
task: logsStreamTask,
|
|
260
288
|
},
|
|
261
289
|
};
|
|
262
290
|
export const defaultScenarios = {
|
|
@@ -269,7 +297,7 @@ export const defaultScenarios = {
|
|
|
269
297
|
'pm2:setup',
|
|
270
298
|
'docker:setup',
|
|
271
299
|
'print:deployment',
|
|
272
|
-
'stream
|
|
300
|
+
'logs:stream',
|
|
273
301
|
],
|
|
274
302
|
},
|
|
275
303
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export type { AuthMethod, DeployerConfig, DeployerConfigInput, FilesConfig, Logs
|
|
|
2
2
|
export { defineConfig } from './config.js';
|
|
3
3
|
export { runScenario, runTask } from './runner.js';
|
|
4
4
|
export { loadConfig, findConfigFile } from './configLoader.js';
|
|
5
|
-
export { buildRsyncCommand, downloadSkip, downloadTask } from './
|
|
6
|
-
export type { RsyncOptions } from './
|
|
5
|
+
export { buildRsyncCommand, downloadSkip, downloadTask } from './tasks/index.js';
|
|
6
|
+
export type { RsyncOptions } from './tasks/upload.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { defineConfig } from './config.js';
|
|
2
2
|
export { runScenario, runTask } from './runner.js';
|
|
3
3
|
export { loadConfig, findConfigFile } from './configLoader.js';
|
|
4
|
-
export { buildRsyncCommand, downloadSkip, downloadTask } from './
|
|
4
|
+
export { buildRsyncCommand, downloadSkip, downloadTask } from './tasks/index.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare class CustomRenderer {
|
|
2
|
+
static nonTTY: boolean;
|
|
3
|
+
static rendererOptions: {
|
|
4
|
+
pausedTimer: {
|
|
5
|
+
field: (time: any) => string;
|
|
6
|
+
format: () => (text: string | number) => string;
|
|
7
|
+
condition?: boolean | ((args_0: number) => boolean);
|
|
8
|
+
args?: [number];
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
static rendererTaskOptions: {};
|
|
12
|
+
private tasks;
|
|
13
|
+
private options;
|
|
14
|
+
private logger;
|
|
15
|
+
private cache;
|
|
16
|
+
constructor(tasks: any, options: any);
|
|
17
|
+
end(): void;
|
|
18
|
+
render(): void;
|
|
19
|
+
private formatTitle;
|
|
20
|
+
renderer(tasks: any[]): void;
|
|
21
|
+
calculate(task: any): void;
|
|
22
|
+
reset(task: any): void;
|
|
23
|
+
}
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { color, LISTR_LOGGER_STDERR_LEVELS, LISTR_LOGGER_STYLE, ListrLogger, ListrLogLevels, ListrTaskEventType, ListrTaskState, PRESET_TIMER, } from 'listr2';
|
|
3
|
+
export class CustomRenderer {
|
|
4
|
+
static nonTTY = true;
|
|
5
|
+
static rendererOptions = {
|
|
6
|
+
pausedTimer: {
|
|
7
|
+
...PRESET_TIMER,
|
|
8
|
+
field: (time) => `${ListrLogLevels.PAUSED}:${time}`,
|
|
9
|
+
format: () => color.yellowBright,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
static rendererTaskOptions = {};
|
|
13
|
+
tasks;
|
|
14
|
+
options;
|
|
15
|
+
logger;
|
|
16
|
+
cache = {
|
|
17
|
+
rendererOptions: new Map(),
|
|
18
|
+
rendererTaskOptions: new Map(),
|
|
19
|
+
};
|
|
20
|
+
constructor(tasks, options) {
|
|
21
|
+
this.tasks = tasks;
|
|
22
|
+
this.options = {
|
|
23
|
+
...CustomRenderer.rendererOptions,
|
|
24
|
+
...options,
|
|
25
|
+
icon: {
|
|
26
|
+
...LISTR_LOGGER_STYLE.icon,
|
|
27
|
+
...options?.icon ?? {},
|
|
28
|
+
},
|
|
29
|
+
color: {
|
|
30
|
+
...LISTR_LOGGER_STYLE.color,
|
|
31
|
+
...options?.color ?? {},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
this.logger = this.options.logger ?? new ListrLogger({
|
|
35
|
+
useIcons: true,
|
|
36
|
+
toStderr: LISTR_LOGGER_STDERR_LEVELS,
|
|
37
|
+
});
|
|
38
|
+
this.logger.options.icon = this.options.icon;
|
|
39
|
+
this.logger.options.color = this.options.color;
|
|
40
|
+
if (this.options.timestamp) {
|
|
41
|
+
this.logger.options.fields.prefix.unshift(this.options.timestamp);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
end() { }
|
|
45
|
+
render() {
|
|
46
|
+
this.renderer(this.tasks);
|
|
47
|
+
}
|
|
48
|
+
formatTitle(title) {
|
|
49
|
+
return chalk.bgCyan.black(` ${title} `);
|
|
50
|
+
}
|
|
51
|
+
renderer(tasks) {
|
|
52
|
+
tasks.forEach((task) => {
|
|
53
|
+
this.calculate(task);
|
|
54
|
+
task.once(ListrTaskEventType.CLOSED, () => {
|
|
55
|
+
this.reset(task);
|
|
56
|
+
});
|
|
57
|
+
const rendererTaskOptions = this.cache.rendererTaskOptions.get(task.id);
|
|
58
|
+
task.on(ListrTaskEventType.SUBTASK, (subtasks) => {
|
|
59
|
+
this.renderer(subtasks);
|
|
60
|
+
});
|
|
61
|
+
task.on(ListrTaskEventType.STATE, (state) => {
|
|
62
|
+
if (!task.hasTitle())
|
|
63
|
+
return;
|
|
64
|
+
const title = this.formatTitle(task.title);
|
|
65
|
+
if (state === ListrTaskState.STARTED) {
|
|
66
|
+
this.logger.log(ListrLogLevels.STARTED, title);
|
|
67
|
+
}
|
|
68
|
+
// else if (state === ListrTaskState.COMPLETED) {
|
|
69
|
+
// const timer = rendererTaskOptions?.timer;
|
|
70
|
+
// this.logger.log(ListrLogLevels.COMPLETED, title, timer && {
|
|
71
|
+
// suffix: {
|
|
72
|
+
// ...timer,
|
|
73
|
+
// condition: !!task.message?.duration && timer.condition,
|
|
74
|
+
// args: [ task.message.duration ],
|
|
75
|
+
// },
|
|
76
|
+
// });
|
|
77
|
+
// }
|
|
78
|
+
else if (state === ListrTaskState.PROMPT) {
|
|
79
|
+
this.logger.process.hijack();
|
|
80
|
+
task.on(ListrTaskEventType.PROMPT, (prompt) => {
|
|
81
|
+
this.logger.process.toStderr(prompt, false);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else if (state === ListrTaskState.PROMPT_COMPLETED) {
|
|
85
|
+
task.off(ListrTaskEventType.PROMPT);
|
|
86
|
+
this.logger.process.release();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
task.on(ListrTaskEventType.OUTPUT, (output) => {
|
|
90
|
+
this.logger.log(ListrLogLevels.OUTPUT, output);
|
|
91
|
+
});
|
|
92
|
+
task.on(ListrTaskEventType.MESSAGE, (message) => {
|
|
93
|
+
const title = this.formatTitle(task.title);
|
|
94
|
+
if (message.error) {
|
|
95
|
+
this.logger.log(ListrLogLevels.FAILED, title, {
|
|
96
|
+
suffix: {
|
|
97
|
+
field: `${ListrLogLevels.FAILED}: ${message.error}`,
|
|
98
|
+
format: () => color.red,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else if (message.skip) {
|
|
103
|
+
process.stdout.write(chalk.gray(`Skipped: ${message.skip}\n`));
|
|
104
|
+
}
|
|
105
|
+
else if (message.rollback) {
|
|
106
|
+
this.logger.log(ListrLogLevels.ROLLBACK, title, {
|
|
107
|
+
suffix: {
|
|
108
|
+
field: `${ListrLogLevels.ROLLBACK}: ${message.rollback}`,
|
|
109
|
+
format: () => color.red,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else if (message.retry) {
|
|
114
|
+
this.logger.log(ListrLogLevels.RETRY, title, {
|
|
115
|
+
suffix: {
|
|
116
|
+
field: `${ListrLogLevels.RETRY}:${message.retry.count}`,
|
|
117
|
+
format: () => color.red,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else if (message.paused) {
|
|
122
|
+
const rendererOptions = this.cache.rendererOptions.get(task.id);
|
|
123
|
+
const timer = rendererOptions?.pausedTimer;
|
|
124
|
+
this.logger.log(ListrLogLevels.PAUSED, title, timer && {
|
|
125
|
+
suffix: {
|
|
126
|
+
...timer,
|
|
127
|
+
condition: !!message?.paused && timer.condition,
|
|
128
|
+
args: [message.paused - Date.now()],
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
calculate(task) {
|
|
136
|
+
if (this.cache.rendererOptions.has(task.id) && this.cache.rendererTaskOptions.has(task.id))
|
|
137
|
+
return;
|
|
138
|
+
const rendererOptions = {
|
|
139
|
+
...this.options,
|
|
140
|
+
...task.rendererOptions,
|
|
141
|
+
};
|
|
142
|
+
this.cache.rendererOptions.set(task.id, rendererOptions);
|
|
143
|
+
this.cache.rendererTaskOptions.set(task.id, {
|
|
144
|
+
...CustomRenderer.rendererTaskOptions,
|
|
145
|
+
timer: rendererOptions.timer,
|
|
146
|
+
...task.rendererTaskOptions,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
reset(task) {
|
|
150
|
+
this.cache.rendererOptions.delete(task.id);
|
|
151
|
+
this.cache.rendererTaskOptions.delete(task.id);
|
|
152
|
+
}
|
|
153
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { DeployerConfig, ServerConfig, TaskDef } from './def.js';
|
|
2
|
-
export declare function resolveServers(config: DeployerConfig, serverNames?: string[]):
|
|
3
|
-
export declare function resolveTaskDefs(taskNames: string[], allTasks: Record<string, TaskDef>):
|
|
4
|
-
export declare function runScenario(config: DeployerConfig, scenarioName: string, serverNames?: string[]
|
|
5
|
-
|
|
2
|
+
export declare function resolveServers(config: DeployerConfig, serverNames?: string[]): Record<string, ServerConfig>;
|
|
3
|
+
export declare function resolveTaskDefs(taskNames: string[], allTasks: Record<string, TaskDef>): Record<string, TaskDef>;
|
|
4
|
+
export declare function runScenario(config: DeployerConfig, scenarioName: string, serverNames?: string[], options?: {
|
|
5
|
+
skip?: string[];
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function runTask(config: DeployerConfig, taskName: string, serverNames?: string[], options?: {
|
|
8
|
+
skip?: string[];
|
|
9
|
+
}): Promise<void>;
|
package/dist/runner.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import { Listr } from 'listr2';
|
|
2
|
+
import { CustomRenderer } from './renderer.js';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { createSSHConnection } from './connection.js';
|
|
5
5
|
import { Exception } from './utils/Exception.js';
|
|
@@ -148,39 +148,32 @@ function buildTaskContext(serverName, server, ssh, config) {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
export function resolveServers(config, serverNames) {
|
|
151
|
-
const allEntries = Object.entries(config.servers);
|
|
152
151
|
if (!serverNames || serverNames.length === 0) {
|
|
153
|
-
return
|
|
152
|
+
return config.servers;
|
|
154
153
|
}
|
|
155
|
-
const result =
|
|
154
|
+
const result = {};
|
|
156
155
|
for (const name of serverNames) {
|
|
157
156
|
const server = config.servers[name];
|
|
158
157
|
if (!server) {
|
|
159
158
|
const available = Object.keys(config.servers).join(', ');
|
|
160
159
|
throw new Exception(`Server "${name}" not found. Available: ${available}`, 1774742073310);
|
|
161
160
|
}
|
|
162
|
-
result
|
|
161
|
+
result[name] = server;
|
|
163
162
|
}
|
|
164
163
|
return result;
|
|
165
164
|
}
|
|
166
165
|
export function resolveTaskDefs(taskNames, allTasks) {
|
|
167
|
-
return
|
|
166
|
+
return Object.fromEntries(taskNames
|
|
167
|
+
.map(name => {
|
|
168
168
|
const def = allTasks[name];
|
|
169
169
|
if (!def) {
|
|
170
170
|
const available = Object.keys(allTasks).join(', ');
|
|
171
171
|
throw new Exception(`Task "${name}" not found. Available: ${available}`, 1774742082083);
|
|
172
172
|
}
|
|
173
173
|
return [name, def];
|
|
174
|
-
});
|
|
174
|
+
}));
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
concurrent: false,
|
|
178
|
-
renderer: 'simple',
|
|
179
|
-
rendererOptions: {
|
|
180
|
-
clearOutput: true,
|
|
181
|
-
},
|
|
182
|
-
};
|
|
183
|
-
function buildServerListr(serverName, server, config, tasks) {
|
|
176
|
+
function buildServerListr(serverName, server, config, tasks, skipTasks = []) {
|
|
184
177
|
return new Listr([
|
|
185
178
|
{
|
|
186
179
|
task: async (ctx) => {
|
|
@@ -191,19 +184,22 @@ function buildServerListr(serverName, server, config, tasks) {
|
|
|
191
184
|
ctx.ph = buildPlaceholders(serverName, server);
|
|
192
185
|
},
|
|
193
186
|
},
|
|
194
|
-
...
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
187
|
+
...Object.entries(tasks)
|
|
188
|
+
.map(([key, taskDef]) => ({
|
|
189
|
+
title: taskDef.name,
|
|
190
|
+
skip: (ctx) => {
|
|
191
|
+
if (skipTasks.includes(key)) {
|
|
192
|
+
return 'Skipped via --skip';
|
|
200
193
|
}
|
|
201
|
-
: undefined,
|
|
202
|
-
task: async (ctx) => {
|
|
203
194
|
ctx.taskCtx.taskConfig = taskDef.config;
|
|
204
|
-
return taskDef.
|
|
195
|
+
return taskDef.skip
|
|
196
|
+
? taskDef.skip(ctx.taskCtx, ctx.ph)
|
|
197
|
+
: false;
|
|
198
|
+
},
|
|
199
|
+
task: async (ctx, task) => {
|
|
200
|
+
ctx.taskCtx.taskConfig = taskDef.config;
|
|
201
|
+
return taskDef.task(ctx.taskCtx, ctx.ph, task);
|
|
205
202
|
},
|
|
206
|
-
options: listrOptions,
|
|
207
203
|
})),
|
|
208
204
|
{
|
|
209
205
|
task: async (ctx) => {
|
|
@@ -212,9 +208,12 @@ function buildServerListr(serverName, server, config, tasks) {
|
|
|
212
208
|
}
|
|
213
209
|
},
|
|
214
210
|
},
|
|
215
|
-
],
|
|
211
|
+
], {
|
|
212
|
+
concurrent: false,
|
|
213
|
+
renderer: CustomRenderer,
|
|
214
|
+
});
|
|
216
215
|
}
|
|
217
|
-
export async function runScenario(config, scenarioName, serverNames) {
|
|
216
|
+
export async function runScenario(config, scenarioName, serverNames, options = {}) {
|
|
218
217
|
const scenarioDef = config.scenarios?.[scenarioName];
|
|
219
218
|
if (!scenarioDef) {
|
|
220
219
|
const available = Object.keys(config.scenarios ?? {}).join(', ') || 'none';
|
|
@@ -223,14 +222,18 @@ export async function runScenario(config, scenarioName, serverNames) {
|
|
|
223
222
|
const allTasks = config.tasks ?? {};
|
|
224
223
|
const tasks = resolveTaskDefs(scenarioDef.tasks, allTasks);
|
|
225
224
|
const servers = resolveServers(config, serverNames);
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
const skipTasks = options.skip ?? [];
|
|
226
|
+
const listr = new Listr(Object.entries(servers)
|
|
227
|
+
.map(([name, server]) => ({
|
|
228
|
+
title: `${scenarioDef.name} to ${name} (${server.host})`,
|
|
229
|
+
task: () => buildServerListr(name, server, config, tasks, skipTasks),
|
|
230
|
+
})), {
|
|
231
|
+
concurrent: false,
|
|
232
|
+
renderer: CustomRenderer,
|
|
233
|
+
});
|
|
231
234
|
await listr.run();
|
|
232
235
|
}
|
|
233
|
-
export async function runTask(config, taskName, serverNames) {
|
|
236
|
+
export async function runTask(config, taskName, serverNames, options = {}) {
|
|
234
237
|
const allTasks = config.tasks ?? {};
|
|
235
238
|
const taskDef = allTasks[taskName];
|
|
236
239
|
if (!taskDef) {
|
|
@@ -238,10 +241,14 @@ export async function runTask(config, taskName, serverNames) {
|
|
|
238
241
|
throw new Exception(`Task "${taskName}" not found. Available: ${available}`, 1774742100356);
|
|
239
242
|
}
|
|
240
243
|
const servers = resolveServers(config, serverNames);
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
244
|
+
const skipTasks = options.skip ?? [];
|
|
245
|
+
const listr = new Listr(Object.entries(servers)
|
|
246
|
+
.map(([name, server]) => ({
|
|
247
|
+
title: `${taskDef.name} to ${name} (${server.host})`,
|
|
248
|
+
task: () => buildServerListr(name, server, config, { [taskName]: taskDef }, skipTasks),
|
|
249
|
+
})), {
|
|
250
|
+
concurrent: false,
|
|
251
|
+
renderer: CustomRenderer,
|
|
252
|
+
});
|
|
246
253
|
await listr.run();
|
|
247
254
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { ListrEnquirerPromptAdapter } from '@listr2/prompt-adapter-enquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
export const clearTargetTask = async (ctx, ph, task) => {
|
|
4
|
+
const confirmed = await task.prompt(ListrEnquirerPromptAdapter).run({
|
|
5
|
+
type: 'Confirm',
|
|
6
|
+
message: chalk.red(`Remove entire deploy path ${ph.deployPath} on ${ctx.server.host}?`),
|
|
7
|
+
initial: false,
|
|
8
|
+
});
|
|
9
|
+
console.log();
|
|
10
|
+
if (!confirmed) {
|
|
11
|
+
task.skip('User cancelled');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await ctx.run(`rm -rf ${ph.deployPath}`);
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Exception } from '../utils/index.js';
|
|
4
|
+
import { buildRsyncCommand } from './upload.js';
|
|
5
|
+
export const downloadSkip = (ctx) => {
|
|
6
|
+
const files = ctx.taskConfig;
|
|
7
|
+
return !files
|
|
8
|
+
? 'No files configuration provided in task config'
|
|
9
|
+
: false;
|
|
10
|
+
};
|
|
11
|
+
export const downloadTask = async (ctx, ph) => {
|
|
12
|
+
const files = ctx.taskConfig;
|
|
13
|
+
if (!files) {
|
|
14
|
+
throw new Exception('No files configuration provided in task config', 1784523741234);
|
|
15
|
+
}
|
|
16
|
+
const localBase = files.basePath?.startsWith('/')
|
|
17
|
+
? files.basePath
|
|
18
|
+
: path.resolve(ctx.config.rootDir, files.basePath ?? '.');
|
|
19
|
+
const remotePath = ph.deployPath;
|
|
20
|
+
const source = `${ctx.server.username}@${ctx.server.host}:${remotePath}/`;
|
|
21
|
+
const dest = localBase.endsWith('/') ? localBase : localBase + '/';
|
|
22
|
+
const command = buildRsyncCommand(ctx.server, source, dest, files, {
|
|
23
|
+
delete: false,
|
|
24
|
+
});
|
|
25
|
+
console.log(chalk.grey(command));
|
|
26
|
+
await ctx.runLocal(command);
|
|
27
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ScenarioDef, TaskDef } from '../def.js';
|
|
2
|
+
export { buildRsyncCommand } from './upload.js';
|
|
3
|
+
export type { RsyncOptions } from './upload.js';
|
|
4
|
+
export { downloadSkip, downloadTask } from './download.js';
|
|
5
|
+
export declare const defaultTasks: Record<string, TaskDef>;
|
|
6
|
+
export declare const defaultScenarios: Record<string, ScenarioDef>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { clearTargetTask } from './clearTarget.js';
|
|
2
|
+
import { downloadSkip, downloadTask } from './download.js';
|
|
3
|
+
import { installPackagesSkip, installPackagesTask } from './installPackages.js';
|
|
4
|
+
import { printDeploymentTask } from './printDeployment.js';
|
|
5
|
+
import { printLogsDockerSkip, printLogsDockerTask, printLogsPm2Skip, printLogsPm2Task } from './printLogs.js';
|
|
6
|
+
import { setupDockerSkip, setupDockerTask } from './setupDocker.js';
|
|
7
|
+
import { setupPm2Skip, setupPm2Task } from './setupPm2.js';
|
|
8
|
+
import { symlinksSkip, symlinksTask } from './symlinks.js';
|
|
9
|
+
import { uploadSkip, uploadTask } from './upload.js';
|
|
10
|
+
export { buildRsyncCommand } from './upload.js';
|
|
11
|
+
export { downloadSkip, downloadTask } from './download.js';
|
|
12
|
+
export const defaultTasks = {
|
|
13
|
+
'clear:target': {
|
|
14
|
+
name: 'Clear target',
|
|
15
|
+
task: clearTargetTask,
|
|
16
|
+
},
|
|
17
|
+
upload: {
|
|
18
|
+
name: 'Upload files',
|
|
19
|
+
skip: uploadSkip,
|
|
20
|
+
task: uploadTask,
|
|
21
|
+
},
|
|
22
|
+
download: {
|
|
23
|
+
name: 'Download files',
|
|
24
|
+
skip: downloadSkip,
|
|
25
|
+
task: downloadTask,
|
|
26
|
+
},
|
|
27
|
+
symlinks: {
|
|
28
|
+
name: 'Create symlinks',
|
|
29
|
+
skip: symlinksSkip,
|
|
30
|
+
task: symlinksTask,
|
|
31
|
+
},
|
|
32
|
+
'install:packages': {
|
|
33
|
+
name: 'Install packages',
|
|
34
|
+
skip: installPackagesSkip,
|
|
35
|
+
task: installPackagesTask,
|
|
36
|
+
},
|
|
37
|
+
'setup:pm2': {
|
|
38
|
+
name: 'PM2 setup',
|
|
39
|
+
skip: setupPm2Skip,
|
|
40
|
+
task: setupPm2Task,
|
|
41
|
+
},
|
|
42
|
+
'setup:docker': {
|
|
43
|
+
name: 'Docker Compose setup',
|
|
44
|
+
skip: setupDockerSkip,
|
|
45
|
+
task: setupDockerTask,
|
|
46
|
+
},
|
|
47
|
+
'print:deployment': {
|
|
48
|
+
name: 'Print deployment info',
|
|
49
|
+
task: printDeploymentTask,
|
|
50
|
+
},
|
|
51
|
+
'print:logs:pm2': {
|
|
52
|
+
name: 'Print PM2 logs',
|
|
53
|
+
skip: printLogsPm2Skip,
|
|
54
|
+
task: printLogsPm2Task,
|
|
55
|
+
},
|
|
56
|
+
'print:logs:docker': {
|
|
57
|
+
name: 'Print docker logs',
|
|
58
|
+
skip: printLogsDockerSkip,
|
|
59
|
+
task: printLogsDockerTask,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
export const defaultScenarios = {
|
|
63
|
+
deploy: {
|
|
64
|
+
name: 'Deploy',
|
|
65
|
+
tasks: [
|
|
66
|
+
'upload',
|
|
67
|
+
'symlinks',
|
|
68
|
+
'install:packages',
|
|
69
|
+
'setup:pm2',
|
|
70
|
+
'setup:docker',
|
|
71
|
+
'print:deployment',
|
|
72
|
+
'print:logs:pm2',
|
|
73
|
+
'print:logs:docker',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Exception } from '../utils/index.js';
|
|
2
|
+
export const installPackagesSkip = (ctx) => {
|
|
3
|
+
if (ctx.server.packageManager !== undefined) {
|
|
4
|
+
return ctx.server.packageManager === false
|
|
5
|
+
? 'Package manager disabled for server'
|
|
6
|
+
: false;
|
|
7
|
+
}
|
|
8
|
+
if (ctx.config.packageManager !== undefined) {
|
|
9
|
+
return ctx.config.packageManager === false
|
|
10
|
+
? 'Package manager disabled in config'
|
|
11
|
+
: false;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
};
|
|
15
|
+
export const installPackagesTask = async (ctx) => {
|
|
16
|
+
const config = {
|
|
17
|
+
manager: 'npm',
|
|
18
|
+
productionOnly: true,
|
|
19
|
+
...ctx.config.packageManager,
|
|
20
|
+
...ctx.server.packageManager,
|
|
21
|
+
};
|
|
22
|
+
let cmd = `${config.manager} install`;
|
|
23
|
+
if (config.productionOnly) {
|
|
24
|
+
if (config.manager === 'npm') {
|
|
25
|
+
cmd += ' --omit=dev';
|
|
26
|
+
}
|
|
27
|
+
else if (config.manager === 'yarn') {
|
|
28
|
+
cmd += ' --production';
|
|
29
|
+
}
|
|
30
|
+
else if (config.manager === 'pnpm') {
|
|
31
|
+
cmd += ' --prod';
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
throw new Exception(`Unsupported package manager "${config.manager}"`, 1774823752134);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
await ctx.run(cmd);
|
|
38
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { buildDockerComposeTestCmd } from './setupDocker.js';
|
|
3
|
+
export const printLogsPm2Skip = async (ctx) => {
|
|
4
|
+
if (ctx.config.pm2 === false
|
|
5
|
+
|| ctx.config.pm2.logs === false) {
|
|
6
|
+
return 'Logs disabled';
|
|
7
|
+
}
|
|
8
|
+
const hasPm2 = await ctx.test('test -f pm2.config.*');
|
|
9
|
+
if (!hasPm2) {
|
|
10
|
+
return 'No PM2 detected';
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
export const printLogsDockerSkip = async (ctx) => {
|
|
15
|
+
if (ctx.config.dockerCompose === false
|
|
16
|
+
|| ctx.config.dockerCompose.logs === false) {
|
|
17
|
+
return 'Logs disabled';
|
|
18
|
+
}
|
|
19
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
20
|
+
const hasDocker = await ctx.test(testCmd);
|
|
21
|
+
if (!hasDocker) {
|
|
22
|
+
return 'No Docker Compose detected';
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
26
|
+
export const printLogsPm2Task = async (ctx) => {
|
|
27
|
+
if (ctx.config.pm2 === false
|
|
28
|
+
|| ctx.config.pm2.logs === false) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const logsConfig = {
|
|
32
|
+
time: 3,
|
|
33
|
+
lines: 25,
|
|
34
|
+
...ctx.config.pm2.logs,
|
|
35
|
+
};
|
|
36
|
+
const hasPm2 = await ctx.test('test -f pm2.config.*');
|
|
37
|
+
if (hasPm2) {
|
|
38
|
+
const pm2ConfigRaw = await ctx.run('cat pm2.config.*', { printOutput: false });
|
|
39
|
+
const nameMatch = pm2ConfigRaw.stdout.match(/name: ['"](?<name>.+?)['"]/);
|
|
40
|
+
const name = nameMatch.groups?.name ?? 'all';
|
|
41
|
+
console.log(chalk.cyan(`Streaming PM2 logs for ${logsConfig.time}s...`));
|
|
42
|
+
await ctx.run(`timeout ${logsConfig.time} pm2 logs --lines=${logsConfig.lines} "${name}" || true`, {
|
|
43
|
+
printOutput: true,
|
|
44
|
+
ignoreError: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export const printLogsDockerComposeTask = async (ctx) => {
|
|
49
|
+
if (ctx.config.dockerCompose === false
|
|
50
|
+
|| ctx.config.dockerCompose.logs === false) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const logsConfig = {
|
|
54
|
+
time: 3,
|
|
55
|
+
lines: 25,
|
|
56
|
+
...ctx.config.dockerCompose.logs,
|
|
57
|
+
};
|
|
58
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
59
|
+
const hasDocker = await ctx.test(testCmd);
|
|
60
|
+
if (hasDocker) {
|
|
61
|
+
const configFiles = ctx.config.dockerCompose.configFiles ?? [];
|
|
62
|
+
const options = configFiles.map(f => `-f ${f}`).join(' ');
|
|
63
|
+
console.log(chalk.cyan(`Streaming Docker Compose logs for ${logsConfig.time}s...`));
|
|
64
|
+
await ctx.run(`timeout ${logsConfig.time} docker compose ${options} logs --tail=${logsConfig.lines} -f || true`, {
|
|
65
|
+
printOutput: true,
|
|
66
|
+
ignoreError: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export const printDeploymentTask = async (ctx, ph) => {
|
|
3
|
+
await ctx.run('date');
|
|
4
|
+
console.log(chalk.cyan('Deployment directory'), ph.deployPath);
|
|
5
|
+
await ctx.run('ls -la .');
|
|
6
|
+
console.log(chalk.cyan('Directory size'));
|
|
7
|
+
await ctx.run('du -hd 1 .');
|
|
8
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { buildDockerComposeTestCmd } from './setupDocker.js';
|
|
3
|
+
export const printLogsPm2Skip = async (ctx) => {
|
|
4
|
+
if (ctx.config.pm2 === false
|
|
5
|
+
|| ctx.config.pm2.logs === false) {
|
|
6
|
+
return 'Logs disabled';
|
|
7
|
+
}
|
|
8
|
+
const hasPm2 = await ctx.test('test -f pm2.config.*');
|
|
9
|
+
if (!hasPm2) {
|
|
10
|
+
return 'No PM2 detected';
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
};
|
|
14
|
+
export const printLogsDockerSkip = async (ctx) => {
|
|
15
|
+
if (ctx.config.dockerCompose === false
|
|
16
|
+
|| ctx.config.dockerCompose.logs === false) {
|
|
17
|
+
return 'Logs disabled';
|
|
18
|
+
}
|
|
19
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
20
|
+
const hasDocker = await ctx.test(testCmd);
|
|
21
|
+
if (!hasDocker) {
|
|
22
|
+
return 'No Docker Compose detected';
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
26
|
+
export const printLogsPm2Task = async (ctx) => {
|
|
27
|
+
if (ctx.config.pm2 === false
|
|
28
|
+
|| ctx.config.pm2.logs === false) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const logsConfig = {
|
|
32
|
+
time: 3,
|
|
33
|
+
lines: 25,
|
|
34
|
+
...ctx.config.pm2.logs,
|
|
35
|
+
};
|
|
36
|
+
const hasPm2 = await ctx.test('test -f pm2.config.*');
|
|
37
|
+
if (hasPm2) {
|
|
38
|
+
const pm2ConfigRaw = await ctx.run('cat pm2.config.*', { printOutput: false });
|
|
39
|
+
const nameMatch = pm2ConfigRaw.stdout.match(/name: ['"](?<name>.+?)['"]/);
|
|
40
|
+
const name = nameMatch.groups?.name ?? 'all';
|
|
41
|
+
console.log(chalk.cyan(`Streaming PM2 logs for ${logsConfig.time}s...`));
|
|
42
|
+
await ctx.run(`timeout ${logsConfig.time} pm2 logs --lines=${logsConfig.lines} "${name}" || true`, {
|
|
43
|
+
printOutput: true,
|
|
44
|
+
ignoreError: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
export const printLogsDockerTask = async (ctx) => {
|
|
49
|
+
if (ctx.config.dockerCompose === false
|
|
50
|
+
|| ctx.config.dockerCompose.logs === false) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const logsConfig = {
|
|
54
|
+
time: 3,
|
|
55
|
+
lines: 25,
|
|
56
|
+
...ctx.config.dockerCompose.logs,
|
|
57
|
+
};
|
|
58
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
59
|
+
const hasDocker = await ctx.test(testCmd);
|
|
60
|
+
if (hasDocker) {
|
|
61
|
+
const configFiles = ctx.config.dockerCompose.configFiles ?? [];
|
|
62
|
+
const options = configFiles.map(f => `-f ${f}`).join(' ');
|
|
63
|
+
console.log(chalk.cyan(`Streaming Docker Compose logs for ${logsConfig.time}s...`));
|
|
64
|
+
await ctx.run(`timeout ${logsConfig.time} docker compose ${options} logs --tail=${logsConfig.lines} -f || true`, {
|
|
65
|
+
printOutput: true,
|
|
66
|
+
ignoreError: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DockerComposeConfig, TaskFn, TaskSkipFn } from '../def.js';
|
|
2
|
+
export declare function buildDockerComposeTestCmd(dockerComposeConfig: DockerComposeConfig | false): string;
|
|
3
|
+
export declare const setupDockerSkip: TaskSkipFn;
|
|
4
|
+
export declare const setupDockerTask: TaskFn;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function buildDockerComposeTestCmd(dockerComposeConfig) {
|
|
2
|
+
if (dockerComposeConfig === false) {
|
|
3
|
+
return 'false';
|
|
4
|
+
}
|
|
5
|
+
const configFiles = dockerComposeConfig.configFiles ?? [
|
|
6
|
+
'docker-compose.yml',
|
|
7
|
+
'docker-compose.yaml',
|
|
8
|
+
'compose.yml',
|
|
9
|
+
'compose.yaml',
|
|
10
|
+
];
|
|
11
|
+
const testCmdPart = configFiles.map(f => `-f ${f}`);
|
|
12
|
+
return `test ${testCmdPart.join(' -o ')}`;
|
|
13
|
+
}
|
|
14
|
+
export const setupDockerSkip = async (ctx) => {
|
|
15
|
+
if (ctx.config.dockerCompose === false) {
|
|
16
|
+
return 'Docker Compose disabled';
|
|
17
|
+
}
|
|
18
|
+
const testCmd = buildDockerComposeTestCmd(ctx.config.dockerCompose);
|
|
19
|
+
const composeExists = await ctx.test(testCmd);
|
|
20
|
+
if (!composeExists) {
|
|
21
|
+
return 'Docker Compose config not found';
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
export const setupDockerTask = async (ctx) => {
|
|
26
|
+
if (ctx.config.dockerCompose === false) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const configFiles = ctx.config.dockerCompose?.configFiles ?? [];
|
|
30
|
+
const options = configFiles.map(f => `-f ${f}`).join(' ');
|
|
31
|
+
await ctx.run(`docker compose ${options} down --remove-orphans`);
|
|
32
|
+
await ctx.run(`docker compose ${options} up -d --build`);
|
|
33
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const setupPm2Skip = async (ctx) => {
|
|
2
|
+
if (ctx.config.pm2 === false) {
|
|
3
|
+
return 'PM2 disabled';
|
|
4
|
+
}
|
|
5
|
+
const pm2ConfigExists = await ctx.test('test -f pm2.config.*');
|
|
6
|
+
if (!pm2ConfigExists) {
|
|
7
|
+
return 'PM2 config not found';
|
|
8
|
+
}
|
|
9
|
+
return false;
|
|
10
|
+
};
|
|
11
|
+
export const setupPm2Task = async (ctx) => {
|
|
12
|
+
await ctx.run('pm2 start pm2.config.* --update-env');
|
|
13
|
+
await ctx.run('pm2 save');
|
|
14
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const symlinksSkip = (ctx) => {
|
|
2
|
+
const symlinks = ctx.config.symlinks;
|
|
3
|
+
return !symlinks || symlinks.length === 0
|
|
4
|
+
? 'No symlinks defined in config'
|
|
5
|
+
: false;
|
|
6
|
+
};
|
|
7
|
+
export const symlinksTask = async (ctx, ph) => {
|
|
8
|
+
const symlinks = ctx.config.symlinks;
|
|
9
|
+
for (const link of symlinks) {
|
|
10
|
+
const target = link.target.startsWith('/')
|
|
11
|
+
? link.target
|
|
12
|
+
: `${ph.deployPath}/${link.target}`;
|
|
13
|
+
const path = link.path.startsWith('/')
|
|
14
|
+
? link.path
|
|
15
|
+
: `${ph.deployPath}/${link.path}`;
|
|
16
|
+
await ctx.run(`ln -sfn ${target} ${path}`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FilesConfig, ServerConfig, TaskFn, TaskSkipFn } from '../def.js';
|
|
2
|
+
export type RsyncOptions = {
|
|
3
|
+
delete?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare function buildRsyncCommand(server: ServerConfig, source: string, dest: string, files: FilesConfig, options?: RsyncOptions): string;
|
|
6
|
+
export declare const uploadSkip: TaskSkipFn;
|
|
7
|
+
export declare const uploadTask: TaskFn;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function buildRsyncCommand(server, source, dest, files, options = {}) {
|
|
4
|
+
const { delete: useDelete = true, } = options;
|
|
5
|
+
const args = ['rsync', '-avz', '--progress=info2'];
|
|
6
|
+
if (useDelete) {
|
|
7
|
+
args.push('--delete');
|
|
8
|
+
}
|
|
9
|
+
// ssh shell
|
|
10
|
+
const sshParts = ['ssh'];
|
|
11
|
+
if (server.port && server.port !== 22) {
|
|
12
|
+
sshParts.push(`-p ${server.port}`);
|
|
13
|
+
}
|
|
14
|
+
if (server.authMethod === 'key' && server.privateKey) {
|
|
15
|
+
sshParts.push(`-i ${server.privateKey}`);
|
|
16
|
+
}
|
|
17
|
+
sshParts.push('-o StrictHostKeyChecking=no');
|
|
18
|
+
args.push('-e', `"${sshParts.join(' ')}"`);
|
|
19
|
+
// include/exclude
|
|
20
|
+
if (files.exclude) {
|
|
21
|
+
for (const pattern of files.exclude) {
|
|
22
|
+
args.push(`--exclude="${pattern}"`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (files.include) {
|
|
26
|
+
for (const pattern of files.include) {
|
|
27
|
+
args.push(`--include="${pattern}"`);
|
|
28
|
+
}
|
|
29
|
+
args.push('--exclude="*"');
|
|
30
|
+
}
|
|
31
|
+
args.push(source, dest);
|
|
32
|
+
return args.join(' ');
|
|
33
|
+
}
|
|
34
|
+
export const uploadSkip = (ctx) => {
|
|
35
|
+
const files = ctx.config.files;
|
|
36
|
+
return !files
|
|
37
|
+
? 'No files configuration defined'
|
|
38
|
+
: false;
|
|
39
|
+
};
|
|
40
|
+
export const uploadTask = async (ctx, ph) => {
|
|
41
|
+
const files = ctx.config.files;
|
|
42
|
+
const localBase = files.basePath?.startsWith('/')
|
|
43
|
+
? files.basePath
|
|
44
|
+
: path.resolve(ctx.config.rootDir, files.basePath ?? '.');
|
|
45
|
+
const remotePath = ph.deployPath;
|
|
46
|
+
const dest = `${ctx.server.username}@${ctx.server.host}:${remotePath}`;
|
|
47
|
+
const source = localBase.endsWith('/') ? localBase : localBase + '/';
|
|
48
|
+
await ctx.run(`mkdir -p ${remotePath}`);
|
|
49
|
+
const command = buildRsyncCommand(ctx.server, source, dest, files, {
|
|
50
|
+
delete: true,
|
|
51
|
+
});
|
|
52
|
+
console.log(chalk.grey(command));
|
|
53
|
+
await ctx.runLocal(command);
|
|
54
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "d3ployer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,11 +25,12 @@
|
|
|
25
25
|
"prepare": "husky"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
28
|
+
"@listr2/prompt-adapter-enquirer": "^4.2.1",
|
|
29
29
|
"chalk": "^5.6.2",
|
|
30
30
|
"commander": "^14.0.3",
|
|
31
|
+
"enquirer": "^2.4.1",
|
|
31
32
|
"listr2": "^10.2.1",
|
|
32
|
-
"lodash-es": "^4.
|
|
33
|
+
"lodash-es": "^4.18.1",
|
|
33
34
|
"ssh2-promise": "^1.0.3"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
@@ -38,7 +39,6 @@
|
|
|
38
39
|
"@tsconfig/node24": "^24.0.4",
|
|
39
40
|
"@types/chai": "^5.2.3",
|
|
40
41
|
"@types/chai-as-promised": "^8.0.2",
|
|
41
|
-
"@types/lodash-es": "^4.17.12",
|
|
42
42
|
"@types/mocha": "^10.0.10",
|
|
43
43
|
"@types/node": "^24.10.13",
|
|
44
44
|
"c8": "^10.1.3",
|