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

@studiometa/trafic-cli

v0.1.41

Published

CLI to deploy projects to DDEV servers — preview environments from CI

Readme

@studiometa/trafic-cli

CLI to deploy projects to DDEV servers — preview environments from CI.

Part of Trafic, a tool for managing DDEV preview environments on Linux servers.

Installation

npm install -g @studiometa/trafic-cli
# or use directly with npx
npx @studiometa/trafic-cli deploy ...

Commands

trafic setup

Setup a new server over SSH. Run it from any machine that can reach the server with SSH — the command bootstraps Node.js, installs @studiometa/trafic-agent on the server, then runs trafic-agent setup there.

trafic setup \
  --host server.example.com \
  --tld previews.example.com \
  --email [email protected]

Options:

| Option | Description | Default | |--------|-------------|---------| | --host | SSH host (required) | - | | --tld | TLD for DDEV projects (required) | - | | --email | Email for Let's Encrypt certificates | - | | --user | SSH user | root | | --port | SSH port | 22 | | --agent-version | Agent version to install | latest | | --ssh-users | SSH users to allow after hardening, comma-separated. The --user value is always added | ddev | | --trusted-proxy-hops | Proxies in front of the agent — 2 behind a CDN. Fresh installs only | 1 | | --no-hardening | Skip server hardening | false | | --no-root-ssh | Disable root SSH login. Refused when connecting as root | false | | --no-docker | Skip Docker installation | false | | --no-ddev | Skip DDEV installation | false | | --dry-run | Print the remote commands without running them | false | | --ssh-options | Extra SSH options. Use the = form when the value starts with a dash: --ssh-options="-i key -o IdentitiesOnly=yes" | - |

Requirements:

  • Ubuntu 24.04 LTS on the target server (26.04 also verified)
  • SSH access as root, or as a user with passwordless sudo — SSH runs in batch mode, so a password prompt cannot be answered
  • With --no-root-ssh, connect as a sudo user: root is dropped from AllowUsers and PermitRootLogin becomes no, leaving the provider's rescue mode as the only recovery path
  • Wildcard DNS (*.previews.example.com → server IP)

Recommended: a firewall in front of the server, as an extra layer. setup closes DDEV's tool ports itself with DOCKER-USER rules — UFW cannot, because Docker's rules run before its chains — but a network-level firewall drops the packets before they reach the host at all. On OVH dedicated servers that is the Network Firewall in the control panel; it is stateless, so permit tcp established is required or outbound return traffic breaks, and it is IPv4-only. Note it does not filter traffic originating inside the same provider: measured on OVH, a host inside OVH still reached a denied port. setup does not configure it, since it can neither create nor verify it. See Network exposure.

The command is safe to run again: each step is skipped when the server is already in the target state.

trafic deploy

Deploy a project to a DDEV server.

trafic deploy \
  --host server.example.com \
  --name my-project \
  --sync "dist/" \
  --script "composer install --no-dev"

Options:

| Option | Description | Default | |--------|-------------|---------| | --host | SSH host (required) | - | | --name | Project name (required) | - | | --user | SSH user | ddev | | --port | SSH port | 22 | | --sync | Comma-separated local paths to sync. Directories and files both work | . | | --script | Post-sync script to run in DDEV | - | | --env | Environment for --script, repeatable. KEY=VALUE, or bare KEY to take the runner's value | - | | --before-script | Script to run before deploy (on server, outside the container) | - | | --after-script | Script to run after deploy (on server, outside the container) | - | | --create-script | Script to run only on the deploy that creates the project (on server) | - | | --timeout | Per-command timeout, e.g. 10m, 90s, 1h | 10m | | --branch | Git branch name | auto-detected from CI | | --preview | Preview environment ID (MR/PR number) | - | | --repo | Repository URL | auto-detected from CI | | --ssh-options | Extra SSH options. Use the = form when the value starts with a dash: --ssh-options="-i key -o IdentitiesOnly=yes" | - |

trafic destroy

Remove a DDEV project from the server.

trafic destroy \
  --host server.example.com \
  --name my-project \
  --preview 123

CI Examples

GitLab CI

SSH_PRIVATE_KEY is a file-type CI variable, so ssh can read the key directly:

deploy_preview:
  stage: deploy
  image: node:24
  before_script:
    - chmod 600 "$SSH_PRIVATE_KEY"
  script:
    - npx @studiometa/trafic-cli deploy
        --host $SSH_HOST
        --name $CI_PROJECT_PATH_SLUG
        --preview $CI_MERGE_REQUEST_IID
        --ssh-options="-i $SSH_PRIVATE_KEY -o IdentitiesOnly=yes"
        --sync "dist/"
  rules:
    - if: $CI_MERGE_REQUEST_ID

Two things worth copying exactly:

No ssh-agent. GitLab runs after_script in a separate shell from before_script, so SSH_AGENT_PID is not set there and ssh-agent -k kills nothing. The surviving agent holds the job's output file descriptors, and the runner waits on them — jobs sat running for up to an hour after their script had finished. Pointing ssh at the key avoids the daemon entirely. IdentitiesOnly=yes stops any other key on the runner from being offered first.

--ssh-options= with an equals sign. The value starts with a dash, and --ssh-options "-i …" makes the parser read it as another option: Option '--ssh-options' argument is ambiguous.

GitHub Actions

deploy_preview:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: webfactory/[email protected]
      with:
        ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
    - run: npx @studiometa/trafic-cli deploy
        --host ${{ vars.SSH_HOST }}
        --name ${{ github.event.repository.name }}
        --preview ${{ github.event.pull_request.number }}
        --sync "dist/"

How it works

The setup command executes 5 steps over SSH:

  1. Check the server — Read the OS, resolve root or sudo privileges
  2. Install Node.js — Install Node.js 24 from the NodeSource apt repository (skipped when already present)
  3. Install the agentnpm install -g @studiometa/trafic-agent
  4. Setup the server — Run trafic-agent setup (Docker, DDEV, Traefik, systemd, hardening)
  5. Verify — Check that the trafic-agent service is active

The deploy command executes 8 steps over SSH:

  1. Update source code — Clone the repository, or fetch and check out the branch when it is already there. A clone also writes .ddev/config.local.yaml with the project name and the server's router ports, read from ddev config global
  2. Start DDEV — Run ddev start unless the project is already running
  3. Before-script — Optional, on the server, outside the container
  4. Rsync files — Sync local paths to the server. A directory is mirrored with --delete, so a file the build stops producing is removed from the server too; a single file is copied as itself. A path that does not exist stops the deploy
  5. Create-script — Optional, on the server, and only on the deploy that created the project. For one-time seeding such as ddev pull, which overwrites the database and so must not repeat
  6. Run script — Optional, inside the container, with --env values available to it
  7. After-script — Optional, on the server, outside the container
  8. Verify — Run ddev describe and return the project URL

Seeding a database once

A preview environment starts with an empty database. --create-script runs on the deploy that created the project and never again, which is what a ddev pull provider needs — it overwrites the database it imports into, so repeating it on every deploy would discard the environment's content:

trafic deploy \
  --host server.example.com \
  --name my-app \
  --preview 42 \
  --sync "vendor,dist" \
  --create-script "ddev pull prod-db -y" \
  --timeout 30m

Raise --timeout when the pull is slow: it applies per remote command, and a timeout partway through an import leaves the environment half-built.

Zero dependencies

This package has no runtime dependencies. It uses native Node.js APIs (node:child_process) for SSH and rsync operations.

License

MIT — see LICENSE