Skip to content
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,51 @@ split, begin at v1.0.0 — see [VERSIONING.md](VERSIONING.md) for exactly what
that owns the file. Language-prefixed media links that already exist in the
wild start working too.

- **`dune ps` missed servers started through the `dune` CLI itself.** It only
matched the generated entrypoint (`main.ts dev|serve`), so a dev server run
via the global shim or a checkout (`.../cli.ts dev`), via
`jsr:@dune/core/cli serve`, or through a `deno task dev|serve` wrapper never
showed up — on a real machine it listed 19 hung processes and missed the one
server that was actually serving. It now matches those forms too. A bare
`cli.ts` or `deno task` match (which could be any project) counts only when a
`config/site.yaml` is found via `--root`, `PWD` or the live cwd.
- **`dune ps` reported "port unknown — is `lsof` installed?" for processes that
simply weren't listening.** It now says **not listening** when `lsof` ran and
found no open port (typically a server started on a port something else
already held, which never exits), and "port unknown (lsof not available)"
only when `lsof` can't be run.
- A server's wrapper, re-exec child and esbuild workers are now reported as one
instance, found from a single process-table snapshot (no more `pgrep`), and
`--root` on the command line is used to find the site.

### Added

- **`dune stop`** stops dune instances, with their whole process tree (including
any other tool a `deno task` wrapper started). A target is required, so
stopping every server on a machine always has to be asked for by name: `dune
stop <pid>` (any process of an instance), `dune stop --port <n>`, `dune stop
--idle` (running but not listening for at least 2 minutes — younger ones may
still be starting; an instance whose port state is unknown, with no `lsof`,
is never assumed idle) or `dune stop --all`. `--all` and `--idle` act only on
*confirmed* instances: one whose own command line points at a dune site (the
directory of the `main.ts` it runs, its `--root`, or the dune CLI's working
directory, each holding a `config/site.yaml`). Merely running from inside a
site directory confirms nothing, so another Deno project's `main.ts serve`
is listed by `dune ps`, marked unconfirmed, and must be named by pid. The
prompt shows each instance's full command line. Only your own processes are
considered (read from the process table, no `--allow-sys` needed); without
`--allow-run` it says what to re-run with instead of reporting no instances,
and a kill Deno's permissions refuse is reported as such. It sends SIGTERM and waits up to 10 seconds for a server to
drain; a process that ignores it is reported, and only `--force` escalates to
SIGKILL. Each pid's start time is re-checked immediately before it is
signalled, so a pid that exited and was reused while the prompt was open is
never hit, and a signal the OS refuses is reported as such. It asks first
(`--yes` skips the prompt; with no terminal it refuses unless `--yes` is
given), never signals itself or its parent processes, and exits 2 for a
usage error, 1 when a named pid/port matches nothing or something would not
stop, and 0 otherwise. `dune ps` stays read-only and now points at it.


## [0.34.14] — 2026-10-21

### Security
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ This snapshots `deno.json` at install time rather than reading it live, so it go
| `dune dev` | Dev server with hot-reload (watches content + themes) |
| `dune dev:link` | Reinstall the global shim against this checkout (local dev on Dune itself) |
| `dune ps` | List dune dev/serve instances running on this machine |
| `dune stop` | Stop instances: `<pid>`, `--port <n>`, `--idle` (only the hung/leftover ones) or `--all` (asks first; `--force` SIGKILLs what ignores SIGTERM) |
| `dune doctor` | Environment/runtime health checks — Deno version, dependency resolution, lockfile staleness |
| `dune doctor --boot` | Also boot the site for real and check it serves a request |
| `dune build` | Build content index, validate config |
Expand Down
13 changes: 13 additions & 0 deletions src/cli-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* dune dev — Start dev server with file watching
* dune dev:link — Reinstall the global shim against this checkout
* dune ps — List dune dev/serve instances running on this machine
* dune stop <pid>|--port|--idle|--all — Stop them (asks first)
* dune build — Build content index + validate config
* dune build --static — Generate a fully static site (SSG)
* dune serve — Start production server
Expand Down Expand Up @@ -49,6 +50,7 @@
import { devCommand } from "./cli/dev.ts";
import { devLinkCommand } from "./cli/dev-link.ts";
import { psCommand } from "./cli/ps.ts";
import { stopCommand } from "./cli/stop.ts";
import { computeLockPolicy, parseRootArg } from "./cli/lock-policy.ts";
import { loadEnvFile, parseEnvFileArg } from "./cli/env-file.ts";
import { serveCommand } from "./cli/serve.ts";
Expand Down Expand Up @@ -197,6 +199,17 @@ export async function main(args: string[] = Deno.args) {
await psCommand();
break;

case "stop":
await stopCommand({
pid: typeof options.positional === "string" ? options.positional : undefined,
port: typeof options.port === "string" ? options.port : undefined,
idle: options.idle === true,
all: options.all === true,
force: options.force === true,
yes: options.yes === true,
});
break;

case "validate":
await validateCommand(root, {
debug: options.debug === true,
Expand Down
4 changes: 4 additions & 0 deletions src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export function parseCliOptions(args: string[]): ParsedCliArgs {
options.integrity = args[++i];
} else if (args[i] === "--readonly") {
options.readonly = true;
} else if (args[i] === "--idle") {
options.idle = true;
} else if (args[i] === "--all") {
options.all = true;
} else if (args[i] === "--boot") {
options.boot = true;
} else if (args[i] === "--verify") {
Expand Down
11 changes: 10 additions & 1 deletion src/cli/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ Commands:
(for developing Dune itself — refreshes its frozen
import-map snapshot after deno.json's imports change)
ps List dune dev/serve instances currently running on
this machine (PID, site, port — macOS/Linux only)
this machine (PID, site, port or "not listening" —
macOS/Linux only)
stop <pid> Stop a dune instance (and its child processes) — any
PID shown by \`dune ps\`. SIGTERM; asks first
stop --port <n> Stop the instance listening on that port
stop --idle Stop only instances that are running but not listening
(hung or leftover)
stop --all Stop every instance \`dune ps\` lists
(--force SIGKILLs processes that ignore SIGTERM;
--yes skips the prompt)
build Build content index and validate config
build --static Generate a fully static site (SSG)
serve Start production server (--frozen enforces deno.lock;
Expand Down
Loading
Loading