npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

d3ployer

v0.0.17

Published

A TypeScript-based SSH deployment CLI tool. Run tasks and scenarios against remote servers over SSH, with rsync for file transfer.

Readme

d3ployer

A TypeScript-based SSH deployment CLI tool. Run tasks and scenarios against remote servers over SSH, with rsync for file transfer.

Install

npm install d3ployer

This exposes the dpl CLI command.

Quick start

Create a deployer.config.ts in your project root:

import { defineConfig } from 'd3ployer';

export default defineConfig({
  servers: {
    prod: {
      host: '192.168.1.10',
      deployPath: '/opt/myapp',
    },
  },
  files: {
    localPath: './dist',
    exclude: ['node_modules', '.git'],
  },
  symlinks: [
    { path: 'config.json', target: '/etc/myapp/config.json' },
  ],
  tasks: {
    restart: async (ctx) => {
      await ctx.run('systemctl restart myapp');
    },
  },
  scenarios: {
    deploy: ['upload', 'symlinks', 'install:packages', 'restart'],
  },
});

Then deploy:

dpl deploy                          # run "deploy" scenario on all servers
dpl deploy prod                     # run on specific server(s)
dpl upload                          # run a single task
dpl deploy --skip install:packages  # skip specific tasks (comma-separated)
dpl list                            # list available scenarios, tasks, and servers

CLI

dpl <name> [servers...]         Run a scenario or task
dpl list                        List scenarios, tasks, and servers

Options:
  -p, --project <path>          Path to deployer.config.ts
  --skip <tasks>                Comma-separated list of tasks to skip
  --config <task.key=value...>  Override task config values at runtime

If <name> matches a scenario, it runs all tasks in that scenario sequentially. Otherwise it runs the matching task directly.

Runtime config overrides

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.

dpl deploy --config download.dryRun=true
dpl upload --config upload.delete=false
dpl deploy --config myTask.someKey=hello --config myTask.retries=3

The format is taskName.path.to.key=value, where taskName is the task's key in the config.

Config

servers

Define target servers. Only host and deployPath are required.

| Field | Default | Description | | --------------- | -------------------- | ---------------------------------------- | | host | (required) | Server hostname or IP | | deployPath | (required) | Remote path to deploy to | | port | 22 | SSH port | | username | Current OS user | SSH username | | authMethod | 'agent' | 'agent', 'key', or 'password' | | privateKey | - | Path to private key (for 'key') | | password | - | SSH password (for 'password') | | agent | SSH_AUTH_SOCK | SSH agent socket path | | packageManager| - | Override package manager config per server (or false to disable) | | initCmd | - | Shell command to run before each remote command (e.g. source ~/.nvm/nvm.sh) |

files

Configure rsync file upload.

files: {
  localPath: './dist',        // local directory to sync (relative to rootDir or absolute, default: '.')
  remotePath: 'app',          // remote directory (relative to deployPath or absolute, default: deployPath)
  include: ['src/**'],        // rsync include patterns
  exclude: ['node_modules'],  // rsync exclude patterns
}

Or as an array of entries to sync multiple directories:

files: [
  { localPath: './dist', remotePath: 'app', exclude: ['*.map'] },
  { localPath: './config', remotePath: 'config' },
]

Each entry can also include per-entry rsync options:

files: {
  localPath: './dist',
  exclude: ['node_modules'],
  rsync: {
    delete: false,  // don't delete extra files on remote (default: true for upload, false for download)
    dryRun: true,   // preview changes without applying them
  },
}

symlinks

Create symlinks on the remote server.

symlinks: [
  { path: 'config.json', target: '/etc/myapp/config.json' },
]

Relative paths are resolved against deployPath.

packageManager

Configure dependency installation. Can be set globally and/or per server. Set to false to disable.

packageManager: {
  manager: 'npm',       // 'npm' | 'yarn' | 'pnpm' (default: 'npm')
  productionOnly: true, // install production deps only (default: true)
}

pm2

Configure PM2 integration. Set to false to disable entirely. The setup:pm2 task auto-detects pm2.config.* files. Post-deploy log streaming is configured via pm2.logs.

pm2: {
  logs: {
    time: 5,   // seconds to stream (default: 3)
    lines: 50, // log lines to show (default: 25)
  },
  // logs: false  — disable log streaming only
}
// pm2: false    — disable PM2 entirely

dockerCompose

Configure Docker Compose integration. Set to false to disable entirely. The setup:docker task auto-detects compose files. Specify custom compose files via configFiles.

dockerCompose: {
  configFiles: ['docker-compose.prod.yml'], // optional, defaults to auto-detect
  logs: {
    time: 5,   // seconds to stream (default: 3)
    lines: 50, // log lines to show (default: 25)
  },
  // logs: false  — disable log streaming only
}
// dockerCompose: false  — disable Docker Compose entirely

tasks

Custom task functions receive a TaskContext and Placeholders:

tasks: {
  migrate: async (ctx, ph, task) => {
    await ctx.run('npm run migrate');
  },
}

Tasks can also be defined as objects with skip logic and config:

tasks: {
  myTask: {
    name: 'My Task',
    skip: async (ctx) => !someCondition ? 'Reason to skip' : false,
    task: async (ctx, ph, task) => { /* ... */ },
    config: { /* passed as ctx.taskConfig */ },
  },
}

Use defineTask for type-safe task definitions with a typed config:

import { defineConfig, defineTask } from 'd3ployer';

export default defineConfig({
  tasks: {
    myTask: defineTask<{ retries: number }>({
      task: async (ctx) => {
        const retries = ctx.taskConfig!.retries; // typed as number
      },
      config: { retries: 3 },
    }),
  },
});

Task keys are auto-converted from camelCase to colon:case (e.g. depInstall becomes dep:install).

TaskContext<C> provides:

  • run(cmd, options?) - execute a command on the remote server (auto cd's to deployPath)
  • test(cmd) - execute a command on the remote server, returns boolean
  • runLocal(cmd, options?) - execute a command locally
  • testLocal(cmd) - execute a command locally, returns boolean
  • server - current server config (includes name)
  • ssh - SSH2Promise connection
  • config - full deployer config
  • taskConfig - per-task config from task definition (typed as C when using defineTask<C>)

RunOptions (for run and runLocal):

  • printOutput - print stdout/stderr (default: true)
  • ignoreError - don't throw on non-zero exit (default: false)

Placeholders provide:

  • serverName - name of the current server
  • deployPath - remote deploy path
  • timestamp - ISO timestamp (safe for filenames)

scenarios

Named sequences of tasks:

scenarios: {
  deploy: ['upload', 'symlinks', 'install:packages', 'restart'],
}

Or as objects with a custom name:

scenarios: {
  deploy: {
    name: 'Deploy',
    tasks: ['upload', 'symlinks', 'install:packages', 'restart'],
  },
}

Built-in tasks

| Task | Description | | ------------------- | -------------------------------------------------------- | | upload | Rsync files to the remote server (supports multiple file entries) | | download | Rsync files from the remote server (uses task config, supports multiple file entries) | | symlinks | Create configured symlinks on the remote server | | install:packages | Install dependencies via npm/yarn/pnpm | | setup:pm2 | Start/restart PM2 processes (auto-detects pm2.config.*) | | setup:docker | Run docker compose up (auto-detects compose files) | | clear:target | Remove the entire deploy path (with confirmation prompt) | | print:deployment | Print deployment info (date, files, disk usage) | | print:logs:pm2 | Stream PM2 logs for a configured duration | | print:logs:docker | Stream Docker Compose logs for a configured duration |

Default deploy scenario

The built-in deploy scenario runs: uploadsymlinksinstall:packagessetup:pm2setup:dockerprint:deploymentprint:logs:pm2print:logs:docker

Tasks with skip conditions will be automatically skipped when not applicable (e.g. setup:pm2 skips if no PM2 config file exists).

Requirements

  • Node.js (ESM)
  • rsync installed locally (for the upload/download tasks)
  • SSH access to target servers

License

MIT