app
turnout app <add|list|show|edit|remove> [...]An app is a local project: its path, its commands (dev, build, …), its gateway port and the servers it is allowed to use. See Entities.
You are not asked for a port of either kind. Since v0.18.0 turnout assigns the gateway port when the app is added and the dev port on the first turnout dev; both flags are still there for a project that insists on a specific number.
turnout app add [NAME] [--path DIR] [--port PORT] [--env-var NAME] [--env-file FILE] [--dev-port PORT] [--dist DIR] [--command NAME=CMD]... [--server SERVER]...| Flag | Short | Description |
|---|---|---|
--path |
-p |
Project directory |
--port |
-P |
Pin the app’s gateway port; assigned from 7100-7199 when the app is added |
--env-var |
-e |
Variable that carries the gateway URL to the app’s commands (default TURNOUT_GATEWAY_URL) |
--env-file |
Dotenv file turnout keeps in step with the port, relative to the project (default .env.development.local) |
|
--dev-port |
Port the dev server listens on; assigned from 5100-5199 on the first dev when unset, 0 hands it back |
|
--dist |
-d |
Build artifact directory, relative to the project path |
--command |
-c |
Set a command as NAME=CMD (repeatable); overrides detected defaults |
--server |
-s |
Allow a server for this app (repeatable) |
With NAME or --path missing, the wizard walks every field of the app, so nothing here needs a flag.
Ports turnout hands out
Section titled “Ports turnout hands out”A new app is given a gateway port from 7100-7199 straight away - the first of the range no app holds and nothing on the machine is listening on. It is assigned at this point rather than on first use because the number goes into the app’s dotenv file immediately, which is what lets pnpm dev from an IDE reach the stand without turnout in the loop.
A gateway port belongs to exactly one app: add and edit refuse a port another app already holds and name that app. Two apps on one port would leave the gateway unable to bind the second listener.
--port PORT pins a specific one, and turnout app edit NAME --port 0 hands it back - an app with no gateway port has no listener of its own and no address to carry, which is what you want for a project that talks to its stand directly.
An app registered by an older turnout that has no gateway port is given one when the data directory is migrated. A port already written is kept as it stands, including one from outside the range: the number is already in a dotenv file, and renumbering it would break a working setup silently.
The wizard
Section titled “The wizard”turnout app add with nothing to go on, and turnout app edit NAME with no flags, open the same form. It asks for every field an app has except the gateway port, in order: the project directory, the commands, the variable that carries the gateway URL, the dotenv file, the dev server port, the build artifact directory and the allowed servers. edit starts from the stored app, with its current values as the defaults - pressing enter through the form changes nothing.
The gateway port is not among the questions: turnout assigns it, and --port changes it for the rare project that needs a particular number. It was the one question with no good answer - any free port does, and which ports are free is turnout’s business.
Empty means not set: an empty dev port hands the port back so the next dev assigns a fresh one. Questions that would name nothing are skipped - an app whose port was handed back with --port 0 has no address to carry, so the variable and the dotenv file are not asked for.
Commands
Section titled “Commands”The commands question shows what the app has and works on the list:
Commands of 'myshop': build pnpm build dev pnpm dev --port {port} storybook pnpm storybook? Commands > Done Add a command Change a command line Rename a command Remove a commandAdd offers turnout’s own roles first - dev, build, test, lint, the ones run by name - and takes any other name for a command reachable through turnout run. Change edits a command line with the current one already filled in.
Detection proposes; it does not rule. On add, and on an edit that moves the app to another directory, turnout reads the project and fills in the roles it recognises - without ever overwriting a command set by hand. What it proposes is simply there in the list, to be corrected, renamed or removed.
Flags do the same non-interactively, for scripts: --command NAME=CMD sets one and --command NAME= removes it.
How the app learns the gateway address
Section titled “How the app learns the gateway address”The port is set once, here. The app is handed the address (http://localhost:PORT) by two roads, so the project’s own .env never carries it:
- A variable on every command turnout runs.
turnout dev, and any custom command run throughturnout run, getVARIABLE=http://localhost:PORTin their environment.build,testandlintdo not: a variable in the process environment overrides every dotenv file whatever the mode, and a production bundle must not bake in localhost. The variable’s name is per app (--env-var), because the framework decides what the app can see - Vite exposes onlyVITE_*to the client, Create React App onlyREACT_APP_*. The wizard suggestsVITE_API_URLfor a Vite project andTURNOUT_GATEWAY_URLotherwise. - A dotenv file turnout keeps in step. For the times the app is started past turnout - from an IDE, or with
pnpm devby hand -app add,app editandgateway startwrite the same assignment into.env.development.local(or the file named with--env-file). Vite and CRA read.env.*.localon top of.envin development only, so the project’s.envstays untouched and committable. Only one block of the file is turnout’s: a header line and the assignment under it; everything else in the file survives every rewrite, and unsetting the port takes the block out again. The file name is appended to.gitignorewhen the project is a git repository.
Commands can also name the address inline: {gateway} in a command line is replaced with the URL and {gateway_port} with the number before the command runs - vite --api {gateway} works on every platform without $VAR or %VAR% syntax. A command that uses a placeholder on an app without a port is refused with the way to set one.
The app’s address
Section titled “The app’s address”Every app answers at http://NAME.localhost through the gateway’s front door, whatever port its dev server took. The port behind the name is the app’s dev port: assigned from 5100-5199 on the first turnout dev and kept from then on, or pinned with --dev-port (a project that insists on 3000, say). --dev-port 0 hands an assigned port back; the next dev picks a fresh one. Like a gateway port, a dev port belongs to one app.
dev hands the port to the server as the PORT variable and as {port} in the command line. Vite does not read PORT, so a Vite project’s detected dev command already ends in --port {port} (pnpm dev --port {port}, npm run dev -- --port {port}); a project registered before that gets the same with turnout app edit myapp --command "dev=pnpm dev --port {port}".
app list shows the front-door address of every app; app show prints it with the dev server port behind it; turnout open myapp opens it.
turnout app add myshop --path ~/dev/myshop --env-var VITE_API_URL# Wrote ~/dev/myshop/.env.development.local (VITE_API_URL=http://localhost:7100).With both given, add is fully non-interactive (useful for scripts): commands come from detection, adjustable via --command.
Where the commands come from
Section titled “Where the commands come from”When the project has a package.json, turnout reads its actual scripts instead of assuming names. Each of turnout’s roles takes the first script that fills it:
| Role | Script names tried, in order |
|---|---|
dev |
dev, serve, start, dev:server, watch |
build |
build, build:prod, compile, dist |
test |
test, test:unit, spec |
lint |
lint, lint:js, eslint |
So a Vue CLI project whose dev script is serve gets dev -> pnpm serve, and turnout dev just works. Scripts that fill no role are kept under their own name, reachable through turnout run:
turnout run storybook myappProjects without a package.json (or without scripts) fall back to the conventional command set for the detected manager.
turnout app add # wizard, from the current directoryturnout app add myapp --path ~/dev/myappturnout app add legacy --path ~/dev/legacy --port 7001 # pin a port the stand expectsturnout app add api --path ~/dev/api --command "dev=make run" --server stagingturnout app add api -p ~/dev/api -c "dev=make run" -s staging # same, short formlist / show
Section titled “list / show”turnout app list # one line per app: name, path, address, spare portturnout app show myapp # full card: commands, dist, allowed serversmyshop /home/me/dev/myshop http://myshop.localhost (spare :7100)api /home/me/dev/api http://api.localhost (spare :7101)The address is the one to use and to share; the spare is the app’s own gateway port, the way in when the front door cannot open (see gateway). A bound app ends its line with the server it points at.
Both take --json and print the same fields as JSON - list an array of what show prints for one. See JSON output.
show warns if the project directory no longer exists on disk. Omit the name on a terminal and turnout offers a picker; edit and remove do the same.
turnout app edit myapp # the wizard, over every fieldturnout app edit myapp --port 7200 # pin another port; the dotenv file followsturnout app edit myapp --env-var REACT_APP_API_URL # the name the framework can seeturnout app edit myapp --command "deploy=make ship" # add or override a commandturnout app edit myapp --command deploy= # remove a commandturnout app edit myapp --add-server prod --rm-server staging| Flag | Short | Description |
|---|---|---|
--path |
-p |
Project directory |
--port |
-P |
The app’s gateway port; 0 leaves the app without one |
--env-var |
-e |
Variable that carries the gateway URL to the app’s commands (default TURNOUT_GATEWAY_URL) |
--env-file |
Dotenv file turnout keeps in step with the port, relative to the project (default .env.development.local) |
|
--dev-port |
Port the dev server listens on; assigned from 5100-5199 on the first dev when unset, 0 hands it back |
|
--dist |
-d |
Build artifact directory, relative to the project path |
--command |
-c |
Set a command as NAME=CMD, or NAME= to remove it (repeatable) |
--add-server |
-a |
Allow a server (repeatable) |
--rm-server |
-r |
Disallow a server (repeatable) |
remove
Section titled “remove”turnout app remove myapp [--yes]Removes the app from the catalog only - the project on disk is never touched.