Skip to content

Services ​

A service is a long-running dev process: an API, a frontend, a worker. To stackctl it is a directory with a dev entry point, plus a key in stackctl.config.ts.

The dev script ​

stackctl looks in the service's directory:

  • With a package.json, it runs the dev script.
  • With a Makefile and no package.json, it runs make dev.

That is how a Node service and a Go service sit side by side:

jsonc
// apps/api/package.json
{
  "name": "api",
  "scripts": {
    "dev": "node --watch src/index.ts"
  }
}
makefile
# apps/worker/Makefile
.PHONY: dev
dev:
	@go run .

A service's other scripts (build, test, seed, …) are not part of its lifecycle. They show up in the terminal UI's commands menu, and sctl run runs one from a shell.

To run a script with another name, set command:

ts
worker: { command: 'start' }, // runs the `start` script or target

After a code edit ​

stackctl does not watch your source. Whether a service picks up an edit depends on its own dev script, and command says which kind it is:

commandThe dev script…After you edit its code
'dev' (the default)watches files and reloadsnothing to do
['start', { watches: false }]runs once, from current sourcerestart it

Restart a service with r in the terminal UI, sctl service restart -s <name>, or POST /command/service/restart/<name> on the control API. watches changes nothing about how the service runs: it tells you, and a coding agent, whether a restart is needed.

An edit to a service's .env files is different: stackctl watches those and restarts the service itself.

Dependencies ​

deps lists the services that run alongside this one. Add wait: true to hold it until that service's healthcheck passes:

ts
services: {
  api: { port: 3001, healthcheck: '/health' },
  web: { port: 3000, deps: [{ service: 'api', wait: true }, 'worker'] },
  worker: {},
}

Selecting web runs api and worker too, and web starts once api is healthy. See deps.

How a script is run ​

stackctl runs a package.json script itself: the script's text goes to /bin/sh in the service's directory, with no package manager in between. A Makefile target runs as make <name>.

  • PATH starts with the service's node_modules/.bin, then each parent directory's, so a script can name a binary installed in the service or at the workspace root.
  • The environment is the service's resolved .env files, its PORT, and the usual npm_package_name, npm_lifecycle_event and INIT_CWD.
  • pre<name> and post<name> scripts are not run. Put the step in the script: "dev": "node codegen.ts && vite dev".
  • Package manager script settings have no effect (pnpm's scriptShell, shellEmulator, …).
  • Exit codes are reported as they are. A script killed by a signal reports 128 plus the signal number (137 for SIGKILL).

Shared code ​

A library several services import is not a service, and is not in the stackctl config: stackctl never builds or runs it. How services import shared code is up to the project. An edit to a library reaches a service the way an edit to its own code does: its dev script picks it up, or the service is restarted.

Released under the MIT License.