turnout-cli 0.27.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +27 -100
  2. package/download.js +1 -0
  3. package/package.json +2 -2
  4. package/run.js +24 -1
package/README.md CHANGED
@@ -13,11 +13,11 @@
13
13
 
14
14
  Working against several backend stands scatters the day: you `cd` into a folder to start a project, edit `.env` files across repositories to switch a stand, dig through notes for a password, and keep deploy paths in your head.
15
15
 
16
- turnout keeps all of it in one place and works from any directory.
16
+ turnout keeps all of it in one place and works from any directory. What it is, on one screen: [lacodda.github.io/turnout/what-is-turnout](https://lacodda.github.io/turnout/what-is-turnout/).
17
17
 
18
18
  ## A day in the life
19
19
 
20
- Point an app at a stand:
20
+ Point an app at a stand - the app keeps talking to `localhost`, and the [gateway](https://lacodda.github.io/turnout/concepts/gateway/) routes it there, with a cookie jar per app **and** stand so the switch does not log you out:
21
21
 
22
22
  ```console
23
23
  $ turnout use web staging
@@ -26,9 +26,7 @@ The running gateway picks this up automatically.
26
26
  'staging' is up: https://staging.example.com/health answered 200 in 84 ms.
27
27
  ```
28
28
 
29
- Nothing in the project changed - the app still talks to `localhost`, and the [gateway](https://lacodda.github.io/turnout/concepts/gateway/) routes it to the stand you picked. Your session survives the switch, because cookies are kept per app **and** stand.
30
-
31
- Start working, from wherever you happen to be:
29
+ Start working, from wherever you happen to be. The console stays quiet once the server is up - until something breaks:
32
30
 
33
31
  ```console
34
32
  $ cd ~/dev/web/src/components
@@ -38,136 +36,72 @@ $ turnout dev --open
38
36
  ◇ web ready in 1.4s - http://web.localhost
39
37
  ```
40
38
 
41
- The server is up, the page is open, and the console stays quiet from here -
42
- until something breaks, which is when you want it loud.
43
-
44
- Move the whole contour at once when the frontend and the API must agree:
45
-
46
- ```console
47
- $ turnout use contour prod-eu
48
- Group 'contour' now uses 'prod-eu':
49
- web -> prod-eu
50
- api -> prod-eu
51
- ```
52
-
53
- Forgot a name? Leave it out and pick from a list that shows where things point:
54
-
55
- ```console
56
- $ turnout use
57
- ? Switch ›
58
- ❯ contour group: web, api
59
- api -> staging
60
- web -> staging
61
- ```
62
-
63
- Ship it:
39
+ Ship it - build, back up, upload as one archive, restart, and ask the stand whether it works:
64
40
 
65
41
  ```console
66
42
  $ turnout deploy web-prod-eu -b
67
43
  [web] pnpm build
68
44
  ✓ Connected to deploy@prod-eu.example.com:22
69
45
  ✓ Backup 20260809-011500.tar.gz created in /var/www/web.backups
70
- ================> 2.02 MiB/3.11 MiB · 1.81 MiB/s · eta 1s assets/index-b3f0a1.js
71
46
  Uploaded 142 files (3.11 MiB) to prod-eu:/var/www/web
72
47
  ✓ Restarted: systemctl restart web
73
48
  ✓ Healthy: GET https://prod-eu.example.com/health - 200 in 38 ms
74
49
  Deploy of 'web' to 'prod-eu' finished.
75
50
  ```
76
51
 
77
- Every long flag has a short form, and nothing runs silently: the upload reports throughput and an ETA, and the steps that talk to the server say so while they wait.
78
-
79
52
  And see what has been going on:
80
53
 
81
54
  ```console
82
55
  $ turnout status
83
- turnout 0.27.0 ~/.local/share/lacodda/turnout
56
+ turnout 1.0.0 ~/.local/share/lacodda/turnout
84
57
 
85
58
  Apps
86
59
  api -> staging http://api.localhost spare :7101 (TURNOUT_GATEWAY_URL)
87
60
  web -> prod-eu http://web.localhost spare :7100 (VITE_API_URL)
88
61
 
89
62
  Gateway running · pid 24180 · up 2h 05m
90
- front door http://localhost
91
63
  ! 'staging' answers 502 - the stand is down, not the app (3m)
92
64
 
93
65
  Jobs 2 running - `turnout ps` shows the rest
94
- api dev ready 9m http://api.localhost
95
- web dev ready 14m http://web.localhost
96
66
 
97
67
  Recent
98
68
  3m ago deploy web -> prod-eu (142 files)
99
- 6m ago use web -> prod-eu
100
-
101
- $ turnout status --short
102
- prod-eu · gw ok · 2 dev
103
69
  ```
104
70
 
105
71
  ## What you get
106
72
 
107
- - **A dev gateway.** Apps always talk to `localhost`; turnout forwards to the selected stand over HTTP or HTTPS (self-signed certificates allowed per server), rewrites redirects, proxies WebSockets, and keeps a cookie jar per app+stand pair so switching does not log you out.
108
- - **One address per app.** `http://myapp.localhost` reaches the dev server turnout started for `myapp`, whichever port it took - the gateway's front door routes by name, and `*.localhost` needs no hosts file. The port is assigned once and handed to the server as `PORT` and `{port}`; `turnout open myapp` opens the address.
109
- - **You never type a port.** turnout assigns an app's gateway port when you register it and its dev port on the first `dev`; both live in turnout only, and the app is handed the address - `turnout dev` sets it as an environment variable the framework can see (`VITE_API_URL`, `REACT_APP_API_URL`, your pick), and a `.env.development.local` that turnout keeps in step covers a dev server started from an IDE. `build` never gets it, so a production bundle cannot bake in localhost.
110
- - **Servers, logins and paths kept apart.** A machine, the credential that logs into it and the directory files land in are three named entities. Define a deploy account once and point every stand at it; declare a web root once and reuse it across servers.
111
- - **Named deploy targets.** The four of them under one name: `turnout deploy web-prod-eu` from any directory, no flags to re-type. A first deploy that has no target yet offers to save the one it just used.
112
- - **Key access set up, not just used.** `turnout key setup prod` generates an ed25519 key, authorizes it on the server, proves it signs in and only then switches the credential over - so a server-side misconfiguration never costs you the password that still works. Windows servers included: an administrator account keeps its keys in a different file that sshd reads instead, and turnout writes to the right one.
113
- - **The SSH agent signs for you.** A credential can authenticate with a key the agent already holds unlocked, so a passphrase is typed once per login session instead of once per command - turnout never reads the key file at all. `SSH_AUTH_SOCK` on Linux and macOS; on Windows, Pageant or the OpenSSH agent service, whichever is running. An agent holding several keys is the normal case: they are offered in turn and the first the server accepts wins.
114
- - **Secrets in the OS keyring** - Windows Credential Manager, macOS Keychain, Linux Secret Service. A secret belongs to a credential, so one `pass set` covers every stand that credential reaches. Copy it to the clipboard with one command; nothing lands in a config file, and `status` only ever reports *that* a credential exists.
115
- - **Commands from any directory.** `dev`, `build`, `test`, `lint` and any custom command run in the right project folder. Commands are taken from your actual `package.json` scripts, so a project whose dev script is `serve` still answers to `turnout dev`.
116
- - **A quiet console.** A build shows a loader and its elapsed time, not two minutes of scrollback; a dev server goes quiet once it reports itself and speaks again only when something breaks. A failure prints its output on the spot - no re-run to see what went wrong - and every job's output is kept in a log file whatever the terminal showed. `-v` gives you all of it, and so does any pipe, so scripts and CI logs are unchanged.
117
- - **Jobs in the background.** `turnout dev --detach` gives the terminal back and the server keeps running; so do `build`, `run` and `deploy`. When one is done, a desktop notification says so and opens the page, the stand or the log of the failure. `turnout ps` shows everything turnout runs, `logs -f` follows any of it, and `stop` takes a job down with every process it started.
118
- - **A hand on the server.** `turnout ssh web-prod` opens a session with host, port, user and key from the catalogs, in the deploy directory, the password already on the clipboard; `turnout exec web-prod -- docker compose ps` runs one command there and hands back its exit code.
119
- - **Deploy over SSH/SFTP** - build, upload, restart, with remote backup and restore. Artifacts travel as a single archive instead of thousands of round trips. Linux and Windows servers alike: turnout detects which shell answers SSH and phrases every remote command in it.
120
- - **A deploy that checks itself.** Give a target a health check and a deploy asks the stand after the restart - and rolls it back when it does not answer. `--diff` shows what would change before anything is written; `deploy web-stage web-prod` ships one build to both; a long restart survives a dropped connection.
121
- - **A history of deploys, and the way back.** Every deploy is recorded with its version, commit and how it ended - files uploaded but the restart failed included, with what the restart said. `turnout history` lists them, `history --diff 12 14` shows the commits between two, and `turnout rollback` sends the build before back to the stand. Server backups are pruned to what each stand keeps.
122
- - **Portable settings.** `export` writes your apps, servers, credentials, paths, targets and groups to one file and `import` merges it on another machine; secrets come along only when you ask, sealed with a passphrase.
123
- - **Stays current.** A once-a-day check mentions a new release without ever delaying a command, and `self-update` installs it - leaving package-manager installs to their package manager.
124
- - **One window onto it all.** `status` shows where every app points, the gateway, running jobs and the latest actions, with clickable addresses; `status --short` puts `prod · gw ok · 2 dev` into a starship or tmux line; `use -` goes back to the stand before. Every command that reports takes `--json`.
125
- - **A gateway that speaks up.** A stand answering 502 or a session the stand refused shows in `status` and the prompt line, and once on the desktop - not as a mystery in your app. Stands behind single sign-on get their browser session handed over with `turnout cookie set`.
126
- - **A doctor.** `turnout doctor` answers "why does it not work": a dev server left on the app's port, a dotenv file naming an old port, a stand that is down, a session that lapsed overnight, a tool missing from PATH - each with its fix, and `--fix` repairs what is turnout's to repair. `doctor --server prod` says where a deploy would stop, before it starts.
127
- - **Groups.** Bind a whole contour to one stand with a single `use`.
128
- - **Nothing to memorize.** Leave a name out and pick it from a list; in bash, Tab completes app, server, credential, path and group names from your own catalogs. The short alias `tn` is installed alongside.
129
- - **An action journal.** Every state change appends one JSON line - what happened and to which entities, never secrets or output. `tail`, `grep` and `jq` work on it directly.
130
-
131
- ## Examples
132
-
133
- Three small apps under `examples/` exist to see turnout at work rather than read about it:
134
-
135
- - [`vue-demo`](https://github.com/lacodda/turnout/tree/main/examples/vue-demo) and [`react-demo`](https://github.com/lacodda/turnout/tree/main/examples/react-demo) - the gateway end to end: the app's own address through the front door, the gateway URL handed over by `turnout dev`, requests reaching the stand, and the cookie jar keeping what the browser never sees. Each README is the setup, three commands long.
136
- - [`deploy-demo`](https://github.com/lacodda/turnout/tree/main/examples/deploy-demo) - a page that shows when its bundle was built, so a deploy is verifiable at a glance.
137
-
138
- ## Install
139
-
140
- **One-line installers.** Windows (PowerShell):
73
+ - **A dev gateway.** One address per app (`http://web.localhost`), ports assigned for you, redirects and WebSockets proxied, a session per app and stand. [Gateway](https://lacodda.github.io/turnout/concepts/gateway/)
74
+ - **Servers, logins and paths, apart** - joined into named targets, so `turnout deploy web-prod` works from any directory. [Entities](https://lacodda.github.io/turnout/concepts/entities/)
75
+ - **A deploy that checks itself.** `--diff` before, a health check after and a rollback when it fails, a history of every deploy; Linux and Windows servers alike. [deploy](https://lacodda.github.io/turnout/reference/deploy/)
76
+ - **Access set up, not just used.** Secrets in the OS keyring, `key setup` that proves a key before switching to it, the SSH agent, and every server's host key checked. [SSH access](https://lacodda.github.io/turnout/guides/ssh-access/)
77
+ - **A quiet console and jobs in the background.** A loader instead of scrollback, the output on the spot when something fails, `--detach` with a desktop notification at the end. [Quiet console](https://lacodda.github.io/turnout/concepts/quiet-console/)
78
+ - **A doctor.** `turnout doctor` says why it does not work, and `doctor --server prod` where a deploy would stop - each with its fix. [doctor](https://lacodda.github.io/turnout/reference/doctor/)
79
+ - **One window onto it all.** `status`, `status --short` for a prompt line, `--json` on every command that reports, an action journal of every change.
80
+ - **Portable settings.** `export` and `import` between machines, secrets sealed with a passphrase; a file from any release since 0.11 still imports.
141
81
 
142
- ```powershell
143
- irm https://raw.githubusercontent.com/lacodda/turnout/main/tools/install.ps1 | iex
144
- ```
82
+ The whole tour is on [the documentation site](https://lacodda.github.io/turnout/).
145
83
 
146
- macOS / Linux:
84
+ ## Install
147
85
 
148
86
  ```bash
149
- curl -fsSL https://raw.githubusercontent.com/lacodda/turnout/main/tools/install.sh | sh
87
+ brew install lacodda/tap/turnout # macOS, Linux
88
+ scoop bucket add lacodda https://github.com/lacodda/scoop-bucket # Windows
89
+ scoop install turnout
90
+ npm i -g turnout-cli # anywhere with Node
91
+ cargo install turnout # from source
150
92
  ```
151
93
 
152
- On Windows use the PowerShell line above: `install.sh` carries the macOS and Linux builds only, and run from Git Bash it stops with a pointer back here.
153
-
154
- **With npm:**
94
+ Or the one-line installers - PowerShell on Windows, `sh` on macOS and Linux:
155
95
 
156
- ```bash
157
- npm i -g turnout-cli
96
+ ```powershell
97
+ irm https://raw.githubusercontent.com/lacodda/turnout/main/tools/install.ps1 | iex
158
98
  ```
159
99
 
160
- **With cargo:**
161
-
162
100
  ```bash
163
- cargo install turnout
101
+ curl -fsSL https://raw.githubusercontent.com/lacodda/turnout/main/tools/install.sh | sh
164
102
  ```
165
103
 
166
- **Binary releases** - grab the archive for your platform from [Releases](https://github.com/lacodda/turnout/releases/latest) (Windows x86_64, Linux x86_64, macOS arm64), unpack and put `turnout` on your `PATH`.
167
-
168
- The installers and the npm package also register the short alias `tn` (skipped if the name is already taken; `TURNOUT_NO_ALIAS=1` opts out). `cargo install` gives you `turnout` only. The alias is a link to the binary rather than a copy of it, so an install costs one binary and both names always answer with the same version.
169
-
170
- Both installers take the newest release by default; set `TURNOUT_VERSION` to a tag to pin one, and `TURNOUT_INSTALL_DIR` to choose where the binary lands.
104
+ Every way but `cargo install` also sets up the short alias `tn`. Archives for Windows x86_64, Linux x86_64 and arm64, and macOS arm64 are on [the releases page](https://github.com/lacodda/turnout/releases/latest); what each installer reads is in [Getting started](https://lacodda.github.io/turnout/getting-started/).
171
105
 
172
106
  ## Quick start
173
107
 
@@ -180,20 +114,13 @@ turnout gateway start # route traffic through the gateway
180
114
  turnout deploy-setup # to deploy: walks the credential, path and target
181
115
  ```
182
116
 
183
- Data lives in the platform user data directory (e.g. `%LOCALAPPDATA%\lacodda\turnout` on Windows); set `TURNOUT_DATA_DIR` to override.
184
-
185
- Full command reference and concepts: **[lacodda.github.io/turnout](https://lacodda.github.io/turnout/)**.
186
-
187
117
  ## Status
188
118
 
189
- v0.27.0, in daily use and being stabilised for 1.0: everything above works
190
- today, deploys are proven against real Linux and Windows servers, and a
191
- settings export from any version since v0.11 still imports. What landed in each
192
- version: [the changelog](https://github.com/lacodda/turnout/blob/main/CHANGELOG.md).
119
+ v1.0.0, the first stable release. The settings are a contract: an export written by any release since v0.11 imports into this one, and nothing in 1.x breaks it. Deploys are proven against real Linux and Windows servers on every push; what landed in each version is in [the changelog](https://github.com/lacodda/turnout/blob/main/CHANGELOG.md).
193
120
 
194
121
  ## Documentation
195
122
 
196
- The documentation site (Astro Starlight) lives in [`docs/`](https://github.com/lacodda/turnout/tree/main/docs); architecture decision records are in [`docs/adr/`](https://github.com/lacodda/turnout/tree/main/docs/adr).
123
+ Commands, concepts and guides: **[lacodda.github.io/turnout](https://lacodda.github.io/turnout/)**. The site's source is in [`docs/`](https://github.com/lacodda/turnout/tree/main/docs), and architecture decision records are in [`docs/adr/`](https://github.com/lacodda/turnout/tree/main/docs/adr).
197
124
 
198
125
  ## License
199
126
 
package/download.js CHANGED
@@ -15,6 +15,7 @@ const TAG = (pkg.turnout && pkg.turnout.binary) || `v${pkg.version}`;
15
15
  const TARGETS = {
16
16
  "win32-x64": ["x86_64-pc-windows-msvc", "zip"],
17
17
  "linux-x64": ["x86_64-unknown-linux-gnu", "tar.gz"],
18
+ "linux-arm64": ["aarch64-unknown-linux-gnu", "tar.gz"],
18
19
  "darwin-arm64": ["aarch64-apple-darwin", "tar.gz"],
19
20
  };
20
21
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "turnout-cli",
3
- "version": "0.27.0",
3
+ "version": "1.0.0",
4
4
  "turnout": {
5
- "binary": "v0.27.0"
5
+ "binary": "v1.0.0"
6
6
  },
7
7
  "description": "A developer's switchyard: point local apps at any backend stand, keep servers and secrets at hand, build and deploy from any directory",
8
8
  "license": "MIT",
package/run.js CHANGED
@@ -2,12 +2,35 @@
2
2
  // Thin launcher: forwards everything to the turnout binary, downloading it on
3
3
  // first run when the postinstall script was skipped (npm v12 default).
4
4
  const fs = require("fs");
5
+ const os = require("os");
5
6
  const { spawnSync } = require("child_process");
6
7
  const { install, exePath } = require("./download");
7
8
 
9
+ // The keystrokes a terminal sends to every process on it, this launcher
10
+ // included. Left to the default, the launcher would die at once and hand the
11
+ // prompt back while turnout - and the dev server `turnout dev` started - is
12
+ // still shutting down on the same keystroke, its exit code lost. Listening
13
+ // keeps the launcher alive until the binary is done; the binary alone decides.
14
+ const KEYSTROKES =
15
+ process.platform === "win32" ? ["SIGINT", "SIGBREAK"] : ["SIGINT", "SIGQUIT"];
16
+
8
17
  function exec() {
18
+ const stayPut = () => {};
19
+ for (const signal of KEYSTROKES) process.on(signal, stayPut);
20
+
9
21
  const result = spawnSync(exePath, process.argv.slice(2), { stdio: "inherit" });
10
- process.exit(result.status === null ? 1 : result.status);
22
+ if (result.error) {
23
+ console.error(`turnout-cli: cannot start ${exePath}: ${result.error.message}`);
24
+ process.exit(1);
25
+ }
26
+ if (result.signal) {
27
+ // Ended by a signal: end the same way, so the shell reports it as it
28
+ // would for the binary run directly (130 after Ctrl+C), not as a plain 1.
29
+ for (const signal of KEYSTROKES) process.removeListener(signal, stayPut);
30
+ process.kill(process.pid, result.signal);
31
+ process.exit(128 + (os.constants.signals[result.signal] || 0));
32
+ }
33
+ process.exit(result.status);
11
34
  }
12
35
 
13
36
  if (fs.existsSync(exePath)) {