d3ployer 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/README.md CHANGED
@@ -33,11 +33,11 @@ export default defineConfig({
33
33
  ],
34
34
  tasks: {
35
35
  restart: async (ctx) => {
36
- await ctx.runRemote('systemctl restart myapp');
36
+ await ctx.run('systemctl restart myapp');
37
37
  },
38
38
  },
39
39
  scenarios: {
40
- deploy: ['upload', 'symlinks', 'depInstall', 'restart'],
40
+ deploy: ['upload', 'symlinks', 'dep:install', 'restart'],
41
41
  },
42
42
  });
43
43
  ```
@@ -69,18 +69,18 @@ If `<name>` matches a scenario, it runs all tasks in that scenario sequentially.
69
69
 
70
70
  Define target servers. Only `host` and `deployPath` are required.
71
71
 
72
- | Field | Default | Description |
73
- | --------------- | -------------------- | ------------------------------------ |
74
- | `host` | (required) | Server hostname or IP |
75
- | `deployPath` | (required) | Remote path to deploy to |
76
- | `port` | `22` | SSH port |
77
- | `username` | Current OS user | SSH username |
78
- | `authMethod` | `'agent'` | `'agent'`, `'key'`, or `'password'` |
79
- | `privateKey` | - | Path to private key (for `'key'`) |
80
- | `password` | - | SSH password (for `'password'`) |
81
- | `agent` | `SSH_AUTH_SOCK` | SSH agent socket path |
82
- | `packageManager`| - | Override package manager per server |
83
- | `initCmd` | - | Command to run on connect |
72
+ | Field | Default | Description |
73
+ | --------------- | -------------------- | ---------------------------------------- |
74
+ | `host` | (required) | Server hostname or IP |
75
+ | `deployPath` | (required) | Remote path to deploy to |
76
+ | `port` | `22` | SSH port |
77
+ | `username` | Current OS user | SSH username |
78
+ | `authMethod` | `'agent'` | `'agent'`, `'key'`, or `'password'` |
79
+ | `privateKey` | - | Path to private key (for `'key'`) |
80
+ | `password` | - | SSH password (for `'password'`) |
81
+ | `agent` | `SSH_AUTH_SOCK` | SSH agent socket path |
82
+ | `packageManager`| - | Override package manager config per server (or `false` to disable) |
83
+ | `initCmd` | - | Shell command to run before each remote command (e.g. `source ~/.nvm/nvm.sh`) |
84
84
 
85
85
  ### `files`
86
86
 
@@ -106,6 +106,25 @@ symlinks: [
106
106
 
107
107
  Relative paths are resolved against `deployPath`.
108
108
 
109
+ ### `packageManager`
110
+
111
+ Configure dependency installation. Can be set globally and/or per server. Set to `false` to disable.
112
+
113
+ ```ts
114
+ packageManager: {
115
+ manager: 'npm', // 'npm' | 'yarn' | 'pnpm' (default: 'npm')
116
+ productionOnly: true, // install production deps only (default: true)
117
+ }
118
+ ```
119
+
120
+ ### `pm2`
121
+
122
+ Set to `false` to disable the built-in PM2 task. When enabled (default), the `pm2:setup` task auto-detects `pm2.config.*` files and runs `pm2 start`.
123
+
124
+ ### `dockerCompose`
125
+
126
+ Set to `false` to disable the built-in Docker Compose task. When enabled (default), the `docker:setup` task auto-detects compose files and runs `docker compose up -d --build`.
127
+
109
128
  ### `tasks`
110
129
 
111
130
  Custom task functions receive a `TaskContext` and `Placeholders`:
@@ -113,17 +132,39 @@ Custom task functions receive a `TaskContext` and `Placeholders`:
113
132
  ```ts
114
133
  tasks: {
115
134
  migrate: async (ctx, ph) => {
116
- await ctx.runRemote(`cd ${ph.deployPath} && npm run migrate`);
135
+ await ctx.run('npm run migrate');
117
136
  },
118
137
  }
119
138
  ```
120
139
 
140
+ Tasks can also be defined as objects with skip logic and config:
141
+
142
+ ```ts
143
+ tasks: {
144
+ myTask: {
145
+ name: 'My Task',
146
+ skip: async (ctx) => !someCondition ? 'Reason to skip' : false,
147
+ task: async (ctx, ph) => { /* ... */ },
148
+ config: { /* passed as ctx.taskConfig */ },
149
+ },
150
+ }
151
+ ```
152
+
153
+ Task keys are auto-converted from camelCase to colon:case (e.g. `depInstall` becomes `dep:install`).
154
+
121
155
  **TaskContext** provides:
122
- - `runRemote(cmd)` - execute a command on the remote server
123
- - `runLocal(cmd)` - execute a command locally
124
- - `server` - current server config
156
+ - `run(cmd, options?)` - execute a command on the remote server (auto cd's to `deployPath`)
157
+ - `test(cmd)` - execute a command on the remote server, returns `boolean`
158
+ - `runLocal(cmd, options?)` - execute a command locally
159
+ - `testLocal(cmd)` - execute a command locally, returns `boolean`
160
+ - `server` - current server config (includes `name`)
125
161
  - `ssh` - SSH2Promise connection
126
162
  - `config` - full deployer config
163
+ - `taskConfig` - per-task config from task definition
164
+
165
+ **RunOptions** (for `run` and `runLocal`):
166
+ - `printOutput` - print stdout/stderr (default: `true`)
167
+ - `ignoreError` - don't throw on non-zero exit (default: `false`)
127
168
 
128
169
  **Placeholders** provide:
129
170
  - `serverName` - name of the current server
@@ -136,22 +177,44 @@ Named sequences of tasks:
136
177
 
137
178
  ```ts
138
179
  scenarios: {
139
- deploy: ['upload', 'symlinks', 'depInstall', 'restart'],
180
+ deploy: ['upload', 'symlinks', 'dep:install', 'restart'],
181
+ }
182
+ ```
183
+
184
+ Or as objects with a custom name:
185
+
186
+ ```ts
187
+ scenarios: {
188
+ deploy: {
189
+ name: 'Deploy',
190
+ tasks: ['upload', 'symlinks', 'dep:install', 'restart'],
191
+ },
140
192
  }
141
193
  ```
142
194
 
143
195
  ## Built-in tasks
144
196
 
145
- | Task | Description |
146
- | ------------ | ---------------------------------------------- |
147
- | `upload` | Rsync files to the remote server |
148
- | `symlinks` | Create configured symlinks on the remote server |
149
- | `depInstall` | Run package manager install on the remote server|
197
+ | Task | Description |
198
+ | ------------------ | -------------------------------------------------------- |
199
+ | `upload` | Rsync files to the remote server |
200
+ | `download` | Rsync files from the remote server (uses task config) |
201
+ | `symlinks` | Create configured symlinks on the remote server |
202
+ | `dep:install` | Install dependencies via npm/yarn/pnpm |
203
+ | `pm2:setup` | Start/restart PM2 processes (auto-detects pm2.config.*) |
204
+ | `docker:setup` | Run docker compose up (auto-detects compose files) |
205
+ | `clear:target` | Remove the entire deploy path (with confirmation prompt) |
206
+ | `print:deployment` | Print deployment info (date, files, disk usage) |
207
+
208
+ ### Default `deploy` scenario
209
+
210
+ The built-in `deploy` scenario runs: `upload` → `symlinks` → `dep:install` → `pm2:setup` → `docker:setup` → `print:deployment`
211
+
212
+ Tasks with skip conditions will be automatically skipped when not applicable (e.g. `pm2:setup` skips if no PM2 config file exists).
150
213
 
151
214
  ## Requirements
152
215
 
153
216
  - Node.js (ESM)
154
- - `rsync` installed locally (for the `upload` task)
217
+ - `rsync` installed locally (for the `upload`/`download` tasks)
155
218
  - SSH access to target servers
156
219
 
157
220
  ## License
package/dist/config.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import type { DeployerConfig, DeployerConfigInput } from './def.js';
2
+ export declare function camelToColonCase(str: string): string;
2
3
  export declare function defineConfig(input: DeployerConfigInput): DeployerConfig;
package/dist/config.js CHANGED
@@ -11,31 +11,61 @@ function resolveServer(input) {
11
11
  }
12
12
  function normalizeTask(key, input) {
13
13
  if (typeof input === 'function') {
14
- return { name: key, fn: input };
14
+ return {
15
+ name: key,
16
+ task: input,
17
+ };
15
18
  }
16
- return { name: input.name, fn: input.task };
19
+ return {
20
+ name: input.name ?? key,
21
+ task: input.task,
22
+ skip: input.skip,
23
+ config: input.config,
24
+ };
17
25
  }
18
26
  function normalizeScenario(key, input) {
19
27
  if (Array.isArray(input)) {
20
- return { name: key, tasks: input };
28
+ return {
29
+ name: key,
30
+ tasks: input,
31
+ };
21
32
  }
22
- return { name: input.name, tasks: input.tasks };
33
+ return {
34
+ name: input.name,
35
+ tasks: input.tasks,
36
+ };
37
+ }
38
+ export function camelToColonCase(str) {
39
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1:$2').toLowerCase();
23
40
  }
24
41
  export function defineConfig(input) {
25
42
  const servers = {};
26
43
  for (const [name, serverInput] of Object.entries(input.servers)) {
27
44
  servers[name] = resolveServer(serverInput);
28
45
  }
29
- const tasks = { ...defaultTasks };
46
+ const tasks = {};
47
+ for (const [key, taskDef] of Object.entries(defaultTasks)) {
48
+ tasks[camelToColonCase(key)] = taskDef;
49
+ }
30
50
  if (input.tasks) {
31
51
  for (const [key, taskInput] of Object.entries(input.tasks)) {
32
- tasks[key] = normalizeTask(key, taskInput);
52
+ tasks[camelToColonCase(key)] = normalizeTask(key, taskInput);
33
53
  }
34
54
  }
35
- const scenarios = { ...defaultScenarios };
55
+ const scenarios = {};
56
+ for (const [key, scenarioDef] of Object.entries(defaultScenarios)) {
57
+ scenarios[camelToColonCase(key)] = {
58
+ ...scenarioDef,
59
+ tasks: scenarioDef.tasks.map(camelToColonCase),
60
+ };
61
+ }
36
62
  if (input.scenarios) {
37
63
  for (const [key, scenarioInput] of Object.entries(input.scenarios)) {
38
- scenarios[key] = normalizeScenario(key, scenarioInput);
64
+ const normalized = normalizeScenario(key, scenarioInput);
65
+ scenarios[camelToColonCase(key)] = {
66
+ ...normalized,
67
+ tasks: normalized.tasks.map(camelToColonCase),
68
+ };
39
69
  }
40
70
  }
41
71
  return {
package/dist/def.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface ServerConfig {
14
14
  password?: string;
15
15
  agent?: string;
16
16
  deployPath: string;
17
- packageManager?: PackageManagerConfig;
17
+ packageManager?: PackageManagerConfig | false;
18
18
  initCmd?: string;
19
19
  }
20
20
  export type ServerConfigInput = Partial<ServerConfig> & Pick<ServerConfig, 'host' | 'deployPath'>;
@@ -52,15 +52,21 @@ export interface TaskContext {
52
52
  testLocal: (cmd: string) => Promise<boolean>;
53
53
  run: (cmd: string, options?: RunOptions) => Promise<ExecResult>;
54
54
  test: (cmd: string) => Promise<boolean>;
55
+ taskConfig?: any;
55
56
  }
56
57
  export type TaskFn = (ctx: TaskContext, ph: Placeholders) => Promise<void>;
58
+ export type TaskSkipFn = (ctx: TaskContext, ph: Placeholders) => Promise<boolean | string> | boolean | string;
57
59
  export interface TaskDef {
58
60
  name: string;
59
- fn: TaskFn;
61
+ task: TaskFn;
62
+ skip?: TaskSkipFn;
63
+ config?: any;
60
64
  }
61
65
  export type TaskInput = TaskFn | {
62
- name: string;
66
+ name?: string;
63
67
  task: TaskFn;
68
+ skip?: TaskSkipFn;
69
+ config?: any;
64
70
  };
65
71
  export interface ScenarioDef {
66
72
  name: string;
@@ -73,9 +79,11 @@ export type ScenarioInput = string[] | {
73
79
  export interface DeployerConfig {
74
80
  rootDir: string;
75
81
  servers: Record<string, ServerConfig>;
76
- packageManager?: PackageManagerConfig;
77
82
  files?: FilesConfig;
78
83
  symlinks?: SymlinkConfig[];
84
+ packageManager?: PackageManagerConfig | false;
85
+ pm2?: boolean;
86
+ dockerCompose?: boolean;
79
87
  tasks?: Record<string, TaskDef>;
80
88
  scenarios?: Record<string, ScenarioDef>;
81
89
  }
@@ -1,3 +1,9 @@
1
- import type { ScenarioDef, TaskDef } from './def.js';
1
+ import type { FilesConfig, ScenarioDef, ServerConfig, TaskDef, 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 downloadSkip: TaskSkipFn;
7
+ export declare const downloadTask: TaskFn;
2
8
  export declare const defaultTasks: Record<string, TaskDef>;
3
9
  export declare const defaultScenarios: Record<string, ScenarioDef>;
@@ -1,8 +1,13 @@
1
+ import confirm from '@inquirer/confirm';
1
2
  import chalk from 'chalk';
2
3
  import path from 'node:path';
3
4
  import { Exception } from './utils/index.js';
4
- function buildRsyncCommand(server, source, dest, files) {
5
- const args = ['rsync', '-avz', '--delete', '--progress=info2'];
5
+ export function buildRsyncCommand(server, source, dest, files, options = {}) {
6
+ const { delete: useDelete = true, } = options;
7
+ const args = ['rsync', '-avz', '--progress=info2'];
8
+ if (useDelete) {
9
+ args.push('--delete');
10
+ }
6
11
  // ssh shell
7
12
  const sshParts = ['ssh'];
8
13
  if (server.port && server.port !== 22) {
@@ -14,25 +19,28 @@ function buildRsyncCommand(server, source, dest, files) {
14
19
  sshParts.push('-o StrictHostKeyChecking=no');
15
20
  args.push('-e', `"${sshParts.join(' ')}"`);
16
21
  // include/exclude
22
+ if (files.exclude) {
23
+ for (const pattern of files.exclude) {
24
+ args.push(`--exclude=${pattern}`);
25
+ }
26
+ }
17
27
  if (files.include) {
18
28
  for (const pattern of files.include) {
19
29
  args.push(`--include=${pattern}`);
20
30
  }
21
31
  args.push('--exclude=*');
22
32
  }
23
- if (files.exclude) {
24
- for (const pattern of files.exclude) {
25
- args.push(`--exclude=${pattern}`);
26
- }
27
- }
28
33
  args.push(source, dest);
29
34
  return args.join(' ');
30
35
  }
36
+ const uploadSkip = (ctx) => {
37
+ const files = ctx.config.files;
38
+ return !files
39
+ ? 'No files configuration defined'
40
+ : false;
41
+ };
31
42
  const uploadTask = async (ctx, ph) => {
32
43
  const files = ctx.config.files;
33
- if (!files) {
34
- return;
35
- }
36
44
  const localBase = files.basePath?.startsWith('/')
37
45
  ? files.basePath
38
46
  : path.resolve(ctx.config.rootDir, files.basePath ?? '.');
@@ -40,14 +48,40 @@ const uploadTask = async (ctx, ph) => {
40
48
  const dest = `${ctx.server.username}@${ctx.server.host}:${remotePath}`;
41
49
  const source = localBase.endsWith('/') ? localBase : localBase + '/';
42
50
  await ctx.run(`mkdir -p ${remotePath}`);
43
- const command = buildRsyncCommand(ctx.server, source, dest, files);
51
+ const command = buildRsyncCommand(ctx.server, source, dest, files, {
52
+ delete: true,
53
+ });
54
+ console.log(chalk.grey(command));
44
55
  await ctx.runLocal(command);
45
56
  };
57
+ export const downloadSkip = (ctx) => {
58
+ const files = ctx.taskConfig;
59
+ return !files
60
+ ? 'No files configuration provided in task config'
61
+ : false;
62
+ };
63
+ export const downloadTask = async (ctx, ph) => {
64
+ const files = ctx.taskConfig;
65
+ const localBase = files.basePath?.startsWith('/')
66
+ ? files.basePath
67
+ : path.resolve(ctx.config.rootDir, files.basePath ?? '.');
68
+ const remotePath = ph.deployPath;
69
+ const source = `${ctx.server.username}@${ctx.server.host}:${remotePath}/`;
70
+ const dest = localBase.endsWith('/') ? localBase : localBase + '/';
71
+ const command = buildRsyncCommand(ctx.server, source, dest, files, {
72
+ delete: false,
73
+ });
74
+ console.log(chalk.grey(command));
75
+ await ctx.runLocal(command);
76
+ };
77
+ const symlinksSkip = (ctx) => {
78
+ const symlinks = ctx.config.symlinks;
79
+ return !symlinks || symlinks.length === 0
80
+ ? 'No symlinks defined in config'
81
+ : false;
82
+ };
46
83
  const symlinksTask = async (ctx, ph) => {
47
84
  const symlinks = ctx.config.symlinks;
48
- if (!symlinks || symlinks.length === 0) {
49
- return;
50
- }
51
85
  for (const link of symlinks) {
52
86
  const target = link.target.startsWith('/')
53
87
  ? link.target
@@ -58,6 +92,19 @@ const symlinksTask = async (ctx, ph) => {
58
92
  await ctx.run(`ln -sfn ${target} ${path}`);
59
93
  }
60
94
  };
95
+ const depInstallSkip = (ctx) => {
96
+ if (ctx.server.packageManager !== undefined) {
97
+ return ctx.server.packageManager === false
98
+ ? 'Package manager disabled for server'
99
+ : false;
100
+ }
101
+ if (ctx.config.packageManager !== undefined) {
102
+ return ctx.config.packageManager === false
103
+ ? 'Package manager disabled in config'
104
+ : false;
105
+ }
106
+ return false;
107
+ };
61
108
  const depInstallTask = async (ctx) => {
62
109
  const config = {
63
110
  manager: 'npm',
@@ -82,6 +129,44 @@ const depInstallTask = async (ctx) => {
82
129
  }
83
130
  await ctx.run(cmd);
84
131
  };
132
+ const pm2SetupSkip = async (ctx) => {
133
+ if (ctx.config.pm2 === false) {
134
+ return 'PM2 disabled';
135
+ }
136
+ const pm2ConfigExists = await ctx.test('test -f pm2.config.*');
137
+ if (!pm2ConfigExists) {
138
+ return 'PM2 config not found';
139
+ }
140
+ return false;
141
+ };
142
+ const pm2SetupTask = async (ctx) => {
143
+ await ctx.run('pm2 start pm2.config.* --update-env');
144
+ await ctx.run('pm2 save');
145
+ };
146
+ const dockerSetupSkip = async (ctx) => {
147
+ if (ctx.config.dockerCompose === false) {
148
+ return 'Docker Compose disabled';
149
+ }
150
+ const composeExists = await ctx.test('test -f docker-compose.yml -o -f docker-compose.yaml -o -f compose.yml -o -f compose.yaml');
151
+ if (!composeExists) {
152
+ return 'Docker Compose config not found';
153
+ }
154
+ return false;
155
+ };
156
+ const dockerSetupTask = async (ctx) => {
157
+ await ctx.run('docker compose up -d --build --remove-orphans');
158
+ };
159
+ const clearTargetTask = async (ctx, ph) => {
160
+ const confirmed = await confirm({
161
+ message: chalk.red(`Remove entire deploy path ${ph.deployPath} on ${ctx.server.host}?`),
162
+ default: false,
163
+ });
164
+ if (!confirmed) {
165
+ console.log(chalk.yellow('Skipped clearing target'));
166
+ return;
167
+ }
168
+ await ctx.run(`rm -rf ${ph.deployPath}`);
169
+ };
85
170
  const printDeploymentTask = async (ctx, ph) => {
86
171
  await ctx.run('date');
87
172
  console.log(chalk.cyan('Deployment directory'), ph.deployPath);
@@ -89,35 +174,44 @@ const printDeploymentTask = async (ctx, ph) => {
89
174
  console.log(chalk.cyan('Directory size'));
90
175
  await ctx.run('du -hd 1 .');
91
176
  };
92
- const pm2SetupTask = async (ctx) => {
93
- const pm2ConfigExists = await ctx.test('test -f pm2.config.*');
94
- if (!pm2ConfigExists) {
95
- console.log(chalk.yellow('PM2 config not found, skipping setup'));
96
- return;
97
- }
98
- await ctx.run('pm2 start pm2.config.* --update-env');
99
- await ctx.run('pm2 save');
100
- };
101
177
  export const defaultTasks = {
178
+ clearTarget: {
179
+ name: 'Clear target',
180
+ task: clearTargetTask,
181
+ },
102
182
  upload: {
103
183
  name: 'Upload files',
104
- fn: uploadTask,
184
+ skip: uploadSkip,
185
+ task: uploadTask,
186
+ },
187
+ download: {
188
+ name: 'Download files',
189
+ skip: downloadSkip,
190
+ task: downloadTask,
105
191
  },
106
192
  symlinks: {
107
193
  name: 'Create symlinks',
108
- fn: symlinksTask,
194
+ skip: symlinksSkip,
195
+ task: symlinksTask,
109
196
  },
110
197
  depInstall: {
111
198
  name: 'Install dependencies',
112
- fn: depInstallTask,
113
- },
114
- printDeployment: {
115
- name: 'Print deployment info',
116
- fn: printDeploymentTask,
199
+ skip: depInstallSkip,
200
+ task: depInstallTask,
117
201
  },
118
202
  pm2Setup: {
119
203
  name: 'PM2 setup',
120
- fn: pm2SetupTask,
204
+ skip: pm2SetupSkip,
205
+ task: pm2SetupTask,
206
+ },
207
+ dockerSetup: {
208
+ name: 'Docker Compose setup',
209
+ skip: dockerSetupSkip,
210
+ task: dockerSetupTask,
211
+ },
212
+ printDeployment: {
213
+ name: 'Print deployment info',
214
+ task: printDeploymentTask,
121
215
  },
122
216
  };
123
217
  export const defaultScenarios = {
@@ -126,9 +220,10 @@ export const defaultScenarios = {
126
220
  tasks: [
127
221
  'upload',
128
222
  'symlinks',
129
- 'depInstall',
130
- 'pm2Setup',
131
- 'printDeployment',
223
+ 'dep:install',
224
+ 'pm2:setup',
225
+ 'docker:setup',
226
+ 'print:deployment',
132
227
  ],
133
228
  },
134
229
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export type { AuthMethod, DeployerConfig, DeployerConfigInput, FilesConfig, Placeholders, ScenarioDef, ScenarioInput, ServerConfig, ServerConfigInput, SymlinkConfig, TaskContext, TaskDef, TaskFn, TaskInput, } from './def.js';
1
+ export type { AuthMethod, DeployerConfig, DeployerConfigInput, FilesConfig, Placeholders, ScenarioDef, ScenarioInput, ServerConfig, ServerConfigInput, SymlinkConfig, TaskContext, TaskDef, TaskFn, TaskInput, TaskSkipFn, } 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';
5
+ export { buildRsyncCommand, downloadSkip, downloadTask } from './defaultTasks.js';
6
+ export type { RsyncOptions } from './defaultTasks.js';
package/dist/index.js CHANGED
@@ -1,3 +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 './defaultTasks.js';
package/dist/runner.js CHANGED
@@ -193,7 +193,16 @@ function buildServerListr(serverName, server, config, tasks) {
193
193
  },
194
194
  ...tasks.map(([_key, taskDef]) => ({
195
195
  title: chalk.bgCyan.black(` ${taskDef.name} `),
196
- task: async (ctx, task) => taskDef.fn(ctx.taskCtx, ctx.ph),
196
+ skip: taskDef.skip
197
+ ? (ctx) => {
198
+ ctx.taskCtx.taskConfig = taskDef.config;
199
+ return taskDef.skip(ctx.taskCtx, ctx.ph);
200
+ }
201
+ : undefined,
202
+ task: async (ctx) => {
203
+ ctx.taskCtx.taskConfig = taskDef.config;
204
+ return taskDef.task(ctx.taskCtx, ctx.ph);
205
+ },
197
206
  options: listrOptions,
198
207
  })),
199
208
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d3ployer",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,6 +25,7 @@
25
25
  "prepare": "husky"
26
26
  },
27
27
  "dependencies": {
28
+ "@inquirer/confirm": "^6.0.10",
28
29
  "chalk": "^5.6.2",
29
30
  "commander": "^14.0.3",
30
31
  "listr2": "^10.2.1",