d3ployer 0.0.12 → 0.0.13

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
@@ -55,16 +55,29 @@ dpl list # list available scenarios, tasks, and serve
55
55
  ## CLI
56
56
 
57
57
  ```
58
- dpl <name> [servers...] Run a scenario or task
59
- dpl list List scenarios, tasks, and servers
58
+ dpl <name> [servers...] Run a scenario or task
59
+ dpl list List scenarios, tasks, and servers
60
60
 
61
61
  Options:
62
- -c, --config <path> Path to deployer.config.ts
63
- --skip <tasks> Comma-separated list of tasks to skip
62
+ -p, --project <path> Path to deployer.config.ts
63
+ --skip <tasks> Comma-separated list of tasks to skip
64
+ --config <task.key=value...> Override task config values at runtime
64
65
  ```
65
66
 
66
67
  If `<name>` matches a scenario, it runs all tasks in that scenario sequentially. Otherwise it runs the matching task directly.
67
68
 
69
+ ### Runtime config overrides
70
+
71
+ Use `--config` to override individual task config values without editing `deployer.config.ts`. Values are automatically coerced to `boolean` (`true`/`false`) or `number` where applicable.
72
+
73
+ ```bash
74
+ dpl deploy --config download.dryRun=true
75
+ dpl upload --config upload.delete=false
76
+ dpl deploy --config myTask.someKey=hello --config myTask.retries=3
77
+ ```
78
+
79
+ The format is `taskName.path.to.key=value`, where `taskName` is the task's key in the config.
80
+
68
81
  ## Config
69
82
 
70
83
  ### `servers`
package/dist/bin.js CHANGED
File without changes
package/dist/cli.js CHANGED
@@ -6,15 +6,16 @@ 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')
10
- .option('--skip <tasks>', 'comma-separated list of tasks to skip');
9
+ .option('-p, --project <path>', 'path to deployer.config.ts')
10
+ .option('--skip <tasks>', 'comma-separated list of tasks to skip')
11
+ .option('-c, --config <taskConfig...>', 'option to override task config (format: path.to.entity=value)');
11
12
  program
12
13
  .argument('<name>', 'scenario or task name')
13
14
  .argument('[servers...]', 'target server(s)')
14
15
  .action(async (name, servers) => {
15
16
  try {
16
17
  const opts = program.opts();
17
- const config = await loadConfig(opts.config);
18
+ const config = await loadConfig(opts.project, opts.config);
18
19
  const serverList = servers.length > 0 ? servers : undefined;
19
20
  const skipTasks = opts.skip
20
21
  ? opts.skip.split(',').map((s) => s.trim()).filter(Boolean)
@@ -36,7 +37,8 @@ program
36
37
  .description('list available scenarios, tasks and servers')
37
38
  .action(async () => {
38
39
  try {
39
- const config = await loadConfig(program.opts().config);
40
+ const opts = program.opts();
41
+ const config = await loadConfig(opts.project, opts.config);
40
42
  console.log(chalk.bold('\nScenarios:'));
41
43
  const scenarios = config.scenarios ?? {};
42
44
  const scenarioKeys = Object.keys(scenarios);
@@ -1,3 +1,3 @@
1
1
  import type { DeployerConfig } from './def.js';
2
2
  export declare function findConfigFile(startDir?: string): string;
3
- export declare function loadConfig(configPath?: string): Promise<DeployerConfig>;
3
+ export declare function loadConfig(configPath?: string, configOverrides?: string[]): Promise<DeployerConfig>;
@@ -1,3 +1,4 @@
1
+ import { set } from 'lodash-es';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { Exception } from './utils/Exception.js';
@@ -15,7 +16,7 @@ export function findConfigFile(startDir = process.cwd()) {
15
16
  } while (parent !== dir);
16
17
  throw new Exception(`Could not find ${CONFIG_FILENAME} in ${startDir} or any parent directory`, 1774741892462);
17
18
  }
18
- export async function loadConfig(configPath) {
19
+ export async function loadConfig(configPath, configOverrides) {
19
20
  const resolvedPath = configPath ?? findConfigFile();
20
21
  const absolutePath = path.resolve(resolvedPath);
21
22
  if (!fs.existsSync(absolutePath)) {
@@ -27,5 +28,39 @@ export async function loadConfig(configPath) {
27
28
  if (!config.servers || Object.keys(config.servers).length === 0) {
28
29
  throw new Exception('Config must define at least one server', 1774741913430);
29
30
  }
31
+ // override task config if provided via CLI
32
+ if (configOverrides) {
33
+ for (const configOverride of configOverrides) {
34
+ const eqIdx = configOverride.indexOf('=');
35
+ if (eqIdx === -1) {
36
+ console.warn(`Invalid task config override (missing '='): ${configOverride}`);
37
+ continue;
38
+ }
39
+ const overrideKey = configOverride.slice(0, eqIdx);
40
+ const rawValue = configOverride.slice(eqIdx + 1);
41
+ const keyParts = overrideKey.split('.');
42
+ if (keyParts.length < 2) {
43
+ console.warn(`Invalid task config override key: ${overrideKey}`);
44
+ continue;
45
+ }
46
+ const [taskName, ...configPathParts] = keyParts;
47
+ const taskDef = config.tasks?.[taskName];
48
+ if (!taskDef) {
49
+ console.warn(`Task not found for config override: ${taskName}`);
50
+ continue;
51
+ }
52
+ if (!taskDef.config) {
53
+ taskDef.config = {};
54
+ }
55
+ const value = rawValue === 'true'
56
+ ? true
57
+ : rawValue === 'false'
58
+ ? false
59
+ : rawValue !== '' && !isNaN(Number(rawValue))
60
+ ? Number(rawValue)
61
+ : rawValue;
62
+ set(taskDef.config, configPathParts, value);
63
+ }
64
+ }
30
65
  return config;
31
66
  }
package/dist/def.d.ts CHANGED
@@ -40,6 +40,12 @@ export type DockerComposeConfig = {
40
40
  configFiles: string[];
41
41
  logs: ConfigOrDisable<LogsConfig>;
42
42
  };
43
+ export interface TaskDef {
44
+ name: string;
45
+ task: TaskFn;
46
+ skip?: TaskSkipFn;
47
+ config?: any;
48
+ }
43
49
  export interface DeployerConfig {
44
50
  rootDir: string;
45
51
  servers: Record<string, ServerConfig>;
@@ -85,12 +91,6 @@ export interface TaskContext {
85
91
  }
86
92
  export type TaskFn = (ctx: TaskContext, ph: Placeholders, task: ListrTaskWrapper<any, any, any>) => Promise<void>;
87
93
  export type TaskSkipFn = (ctx: TaskContext, ph: Placeholders) => Promise<boolean | string> | boolean | string;
88
- export interface TaskDef {
89
- name: string;
90
- task: TaskFn;
91
- skip?: TaskSkipFn;
92
- config?: any;
93
- }
94
94
  export type TaskInput = TaskFn | {
95
95
  name?: string;
96
96
  task: TaskFn;
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export { defineConfig } from './config.js';
3
3
  export { runScenario, runTask } from './runner.js';
4
4
  export { loadConfig, findConfigFile } from './configLoader.js';
5
5
  export { buildRsyncCommand, downloadSkip, downloadTask } from './tasks/index.js';
6
- export type { RsyncOptions } from './tasks/upload.js';
6
+ export type { RsyncOptions } from './tasks/index.js';
package/dist/runner.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import type { DeployerConfig, ServerConfig, TaskDef } from './def.js';
2
2
  export declare function resolveServers(config: DeployerConfig, serverNames?: string[]): Record<string, ServerConfig>;
3
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?: {
4
+ export type RunTaskOrScenarioOptions = {
5
5
  skip?: string[];
6
- }): Promise<void>;
7
- export declare function runTask(config: DeployerConfig, taskName: string, serverNames?: string[], options?: {
8
- skip?: string[];
9
- }): Promise<void>;
6
+ };
7
+ export declare function runScenario(config: DeployerConfig, scenarioName: string, serverNames?: string[], options?: RunTaskOrScenarioOptions): Promise<void>;
8
+ export declare function runTask(config: DeployerConfig, taskName: string, serverNames?: string[], options?: RunTaskOrScenarioOptions): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import path from 'node:path';
3
3
  import { Exception } from '../utils/index.js';
4
- import { buildRsyncCommand } from './upload.js';
4
+ import { buildRsyncCommand } from './helpers/rsync.js';
5
5
  export const downloadSkip = (ctx) => {
6
6
  const files = ctx.taskConfig;
7
7
  return !files
@@ -21,6 +21,7 @@ export const downloadTask = async (ctx, ph) => {
21
21
  const dest = localBase.endsWith('/') ? localBase : localBase + '/';
22
22
  const command = buildRsyncCommand(ctx.server, source, dest, files, {
23
23
  delete: false,
24
+ ...ctx.taskConfig,
24
25
  });
25
26
  console.log(chalk.grey(command));
26
27
  await ctx.runLocal(command);
@@ -0,0 +1,2 @@
1
+ import type { DockerComposeConfig } from '../../def.js';
2
+ export declare function buildDockerComposeTestCmd(dockerComposeConfig: DockerComposeConfig | false): string;
@@ -0,0 +1,13 @@
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
+ }
@@ -0,0 +1,6 @@
1
+ import type { FilesConfig, ServerConfig } from '../../def.js';
2
+ export type RsyncOptions = {
3
+ delete?: boolean;
4
+ dryRun?: boolean;
5
+ };
6
+ export declare function buildRsyncCommand(server: ServerConfig, source: string, dest: string, files: FilesConfig, options?: RsyncOptions): string;
@@ -0,0 +1,38 @@
1
+ export function buildRsyncCommand(server, source, dest, files, options = {}) {
2
+ options = {
3
+ delete: true,
4
+ dryRun: false,
5
+ ...options,
6
+ };
7
+ const args = ['rsync', '-avz', '--progress=info2'];
8
+ if (options.delete) {
9
+ args.push('--delete');
10
+ }
11
+ if (options.dryRun) {
12
+ args.push('--dry-run');
13
+ }
14
+ // ssh shell
15
+ const sshParts = ['ssh'];
16
+ if (server.port && server.port !== 22) {
17
+ sshParts.push(`-p ${server.port}`);
18
+ }
19
+ if (server.authMethod === 'key' && server.privateKey) {
20
+ sshParts.push(`-i ${server.privateKey}`);
21
+ }
22
+ sshParts.push('-o StrictHostKeyChecking=no');
23
+ args.push('-e', `"${sshParts.join(' ')}"`);
24
+ // include/exclude
25
+ if (files.exclude) {
26
+ for (const pattern of files.exclude) {
27
+ args.push(`--exclude="${pattern}"`);
28
+ }
29
+ }
30
+ if (files.include) {
31
+ for (const pattern of files.include) {
32
+ args.push(`--include="${pattern}"`);
33
+ }
34
+ args.push('--exclude="*"');
35
+ }
36
+ args.push(source, dest);
37
+ return args.join(' ');
38
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ScenarioDef, TaskDef } from '../def.js';
2
- export { buildRsyncCommand } from './upload.js';
3
- export type { RsyncOptions } from './upload.js';
2
+ export { buildRsyncCommand } from './helpers/rsync.js';
3
+ export type { RsyncOptions } from './helpers/rsync.js';
4
4
  export { downloadSkip, downloadTask } from './download.js';
5
5
  export declare const defaultTasks: Record<string, TaskDef>;
6
6
  export declare const defaultScenarios: Record<string, ScenarioDef>;
@@ -7,7 +7,7 @@ import { setupDockerSkip, setupDockerTask } from './setupDocker.js';
7
7
  import { setupPm2Skip, setupPm2Task } from './setupPm2.js';
8
8
  import { symlinksSkip, symlinksTask } from './symlinks.js';
9
9
  import { uploadSkip, uploadTask } from './upload.js';
10
- export { buildRsyncCommand } from './upload.js';
10
+ export { buildRsyncCommand } from './helpers/rsync.js';
11
11
  export { downloadSkip, downloadTask } from './download.js';
12
12
  export const defaultTasks = {
13
13
  'clear:target': {
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { buildDockerComposeTestCmd } from './setupDocker.js';
2
+ import { buildDockerComposeTestCmd } from './helpers/docker.js';
3
3
  export const printLogsPm2Skip = async (ctx) => {
4
4
  if (ctx.config.pm2 === false
5
5
  || ctx.config.pm2.logs === false) {
@@ -1,4 +1,3 @@
1
- import type { DockerComposeConfig, TaskFn, TaskSkipFn } from '../def.js';
2
- export declare function buildDockerComposeTestCmd(dockerComposeConfig: DockerComposeConfig | false): string;
1
+ import type { TaskFn, TaskSkipFn } from '../def.js';
3
2
  export declare const setupDockerSkip: TaskSkipFn;
4
3
  export declare const setupDockerTask: TaskFn;
@@ -1,16 +1,4 @@
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
- }
1
+ import { buildDockerComposeTestCmd } from './helpers/docker.js';
14
2
  export const setupDockerSkip = async (ctx) => {
15
3
  if (ctx.config.dockerCompose === false) {
16
4
  return 'Docker Compose disabled';
@@ -1,7 +1,3 @@
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;
1
+ import type { TaskFn, TaskSkipFn } from '../def.js';
6
2
  export declare const uploadSkip: TaskSkipFn;
7
3
  export declare const uploadTask: TaskFn;
@@ -1,36 +1,6 @@
1
1
  import chalk from 'chalk';
2
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
- }
3
+ import { buildRsyncCommand } from './helpers/rsync.js';
34
4
  export const uploadSkip = (ctx) => {
35
5
  const files = ctx.config.files;
36
6
  return !files
@@ -48,6 +18,7 @@ export const uploadTask = async (ctx, ph) => {
48
18
  await ctx.run(`mkdir -p ${remotePath}`);
49
19
  const command = buildRsyncCommand(ctx.server, source, dest, files, {
50
20
  delete: true,
21
+ ...ctx.taskConfig,
51
22
  });
52
23
  console.log(chalk.grey(command));
53
24
  await ctx.runLocal(command);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d3ployer",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "@tsconfig/node24": "^24.0.4",
40
40
  "@types/chai": "^5.2.3",
41
41
  "@types/chai-as-promised": "^8.0.2",
42
+ "@types/lodash-es": "^4.17.12",
42
43
  "@types/mocha": "^10.0.10",
43
44
  "@types/node": "^24.10.13",
44
45
  "c8": "^10.1.3",