turbo-editors/turbo-jspublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo JS 91999d1 · on v1.0.0 · k33g · 11h ago
run-node-commands.md · 236 lines · 11.9 KBmarkdown
Blame HistoryOpen raw

How to run Node commands from the editor

This guide shows how to install, format, lint, test and run your project without leaving Turbo JS. It assumes the editor is installed and you have a directory with a package.json in it.

Get a starter file

Start the editor from the directory holding package.json, then choose JavaScript ▸ Create tools file (Alt-J, then C).

That writes .turbo-js/tools.toml with the commands a Node project runs most, and opens it. The first three:

[[tool]]
name = "~I~nstall"
command = "npm install"
output = "popup"

[[tool]]
name = "~F~ormat"
command = "npx prettier --write ."
output = "popup"

[[tool]]
name = "~L~int"
command = "npx eslint ."
output = "popup"

Each [[tool]] becomes one line of the JavaScript menu, in the order they appear — unless it names a menu of its own, which a later section covers. The file is read every time the menu opens, so an edit takes effect immediately.

Run one

Alt-J, then the letter between the tildes — I to install, T to test.

Test opens a popup at once, which fills in as the command runs. Its title carries the command and, once it has ended, how it went:

┌──────────────── node --test — ok ─────────────────┐
│  ✔ greets by name (1.204ms)                        │
│  ℹ tests 1                                         │
│  ℹ suites 0                                        │
│  ℹ pass 1                                          │
│  ℹ fail 0                                          │
│                                                    │
│                     [ Close ]                      │
└────────────────────────────────────────────────────┘
Key Effect
PgUp PgDn Home End Read through the output
Escape Close it — and stop the command if it is still running
Enter Close it

A command that succeeded silently — npx prettier --write . on a project already formatted — shows (no output) rather than a blank box, so you can tell it from one that has not started.

Run asks first, because a Node project has no single entry point the editor could know:

┌──────────────── Run ────────────────┐
│  script, e.g. main.js               │
│  [                               ]  │
└─────────────────────────────────────┘

Type main.js and press Enter. A terminal window opens and the script runs in it; when it ends the window stays, showing what it printed. Press Ctrl-W to close it. Run it again and the box remembers the name for the rest of the session.

The rest of the menu

Item What it runs Where
Install npm install — the dependencies package.json names, into node_modules/ a popup
Format npx prettier --write . — Prettier over the whole project a popup; the open files are re-read afterwards
Lint npx eslint . — ESLint over the whole project a popup; ESLint 9 needs an eslint.config.js
Test node --test — Node's own runner over every *.test.js, *.spec.js and test/**/*.js a popup
Run node <script> — the script you name a terminal, because a script may read the keyboard
Start npm start — whatever the start script in package.json says a terminal, because it is usually a server

npx runs the project's own Prettier and ESLint when package.json depends on them, and downloads them into npm's cache the first time otherwise — which is why the first Format on a machine takes a few seconds and the second does not.

npm install can take a while. The popup is modal, so while it runs you cannot type anywhere else; Escape closes it and stops the install.

Choose where the output goes

Set output on a tool:

output What you get
popup A dialog that fills in as it runs. The default.
terminal A terminal window: colours, Ctrl-C, and the keyboard reaches the program
editor An editing window once it has finished, to search with Ctrl-F

Run and Start are terminal in the starter file, and they are the example of why the key exists: a popup cannot answer a script that reads process.stdin, and cannot be interrupted with Ctrl-C while a server is listening.

Reach for editor when the output is something to work through — the tree npm ls prints, or a long test report you want to search.

A long command holds the editor

A popup is modal: while npm install runs, you cannot type anywhere else. Escape closes it and stops the command.

If that gets in the way for a particular command, give it output = "terminal" — the window is an ordinary one and you can carry on working beside it. That is what making the key configurable is for.

What happens to your open files

npx prettier --write . rewrites the file you are looking at, npm install writes node_modules/ and package-lock.json into the directory, and a tool of your own may do anything. When a command finishes, the editor re-reads every open file that has no unsaved changes, so a file another command changed appears as it now is, and the project tree is refreshed so a new one shows up. The status bar says how many.

A file with unsaved changes is left alone, and the status bar says so too:

Reloaded 2 files; 1 file with unsaved changes left alone

That is deliberate: your edit and the command genuinely disagree, and the editor is not the one that should decide which wins. Save first (F2) and run the command again, or keep editing. With Format in particular: save, then format, or Prettier's work on that file is skipped.

Add your own commands

Edit .turbo-js/tools.toml. A command goes to sh -c, so one entry can be a whole sequence:

[[tool]]
name = "~C~heck"
command = "node --check {{script, e.g. main.js}}"
output = "popup"

[[tool]]
name = "Lint and ~t~est"
command = "npx eslint . && node --test"
output = "popup"

[[tool]]
name = "~W~atch"
command = "node --watch {{script, e.g. server.js}}"
output = "terminal"

[[tool]]
name = "~D~ependencies"
command = "npm ls --all"
output = "editor"

Give each a hot key with tildes, and keep them distinct — the menu answers the first match it finds.

Put a tool in a menu of its own

A tool that has nothing to do with JavaScript does not belong in the JavaScript menu. Give it a menu:

[[tool]]
name = "~E~cho"
command = "echo TADA"
output = "terminal"
menu = "Tools"

[[tool]]
name = "~U~p"
command = "docker compose up -d"
menu = "Docker"

[[tool]]
name = "~D~own"
command = "docker compose down"
menu = "Docker"

That gives you a Tools menu and a Docker menu on the bar, between JavaScript and Help, in the order the names first appear in the file. Docker holds both its tools. Nothing needs restarting: save the file and the bar follows.

The name is yours to choose — there is no list to pick from. Leave menu out and the tool stays in JavaScript, which is where six of the seven starter commands are.

The hot key is chosen for you

You cannot know, when writing the file, which letters the editor's own menus have taken. So it works it out: the first letter of the name that nothing else claims gets the tildes.

Tools gets Alt-T, because T is free. A menu called Format would get Alt-M, because F is File's, o is Options' and r is Run's. A menu called Jobs would get Alt-B, because J is JavaScript's and o is Options'.

Write the tildes yourself — menu = "Doc~k~er" — and a free letter is kept. A taken one is not: the bar answers the first menu matching a key, so honouring your choice would make one of the two menus unreachable. It picks another letter and says nothing.

Variants

  • Your project uses pnpm, Yarn or Bun. Replace npm install with pnpm install, npx eslint . with pnpm exec eslint ., and so on. The starter file is npm because npm ships with Node; nothing in the editor knows which package manager runs.
  • You have one script and never another. Replace node {{script, e.g. main.js}} with node main.js, and the box stops appearing. The placeholder is there because a starter file cannot know which file is the program.
  • Your tests use Vitest, Jest or Mocha. Replace node --test with npx vitest run, npx jest or npx mocha. npm test also works, if package.json has a test script that is not the echo "Error: no test specified" npm writes by default.
  • You started the editor from a subdirectory. Commands run there, and npm looks for package.json there and in the directories above — but npx prettier --write . formats only that subdirectory. Start from the directory package.json is in.
  • The file has a mistake in it. The menu shows a greyed-out Cannot read tools where the commands would be, and Create tools file is still there.
  • npx eslint . says it cannot find a configuration file. ESLint 9 reads eslint.config.js; npm init @eslint/config@latest writes one. Older projects have .eslintrc.json, which ESLint 9 no longer reads.
  • You want a menu named after one that exists. menu = "File" gives you a second File menu, further along the bar, with a different hot key. Nothing stops you; nothing recommends it either.
  • Your menu has no hot key. Every letter in its name was already taken. F10 and the arrow keys reach it, and so does the mouse. Rename it to something with a free letter.
  • You misspelt output's value. The whole file is refused and the menu says Cannot read tools, naming the tool and listing what it could have been. A silent fallback would have sent the output somewhere you did not ask for.

Ask for a value when the command runs

Run already does. The pattern is a {{label}} where the value goes:

[[tool]]
name = "~A~dd a package"
command = "npm install {{package name}}"
output = "popup"

Choosing it opens a box titled Add a package with one field, labelled package name. Enter runs the command. Escape, and nothing runs. Several placeholders give several fields, in the order they appear; Tab moves between them.

The value is quoted, so a path with a space in it stays one argument.

One field standing for several arguments

Quoting is wrong when you mean "put these on the end". Add ... inside the braces and the value goes in verbatim:

[[tool]]
name = "Run with ~a~rguments"
command = "node main.js {{arguments...}}"
output = "terminal"

Type --verbose input.txt and both reach the script as separate arguments — process.argv.slice(2) holds them.

The same value twice

Write the label twice; you are asked once:

[[tool]]
name = "Test ~o~ne"
command = "node --test {{file}} && node --check {{file}}"

Variants

  • The value is the same most times. Run it once and the box remembers what you typed, for the rest of the session. It is not written to disk.
  • Your command has braces in it already. awk '{print $1}' and find . -exec rm {} + are left alone: only double braces ask for anything. So is a JavaScript ${x} inside a node -e one-liner — one brace, not two.
  • The command asks for more values than fit on screen. The editor says so rather than opening a box whose OK button is below the bottom of the terminal. Make the terminal taller, or split the command into two tools.

See also

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# How to run Node commands from the editor

This guide shows how to install, format, lint, test and run your project without leaving Turbo JS. It assumes the editor is installed and you have a directory with a `package.json` in it.

## Get a starter file

Start the editor **from the directory holding `package.json`**, then choose **JavaScript ▸ Create tools file** (`Alt-J`, then `C`).

That writes `.turbo-js/tools.toml` with the commands a Node project runs most, and opens it. The first three:

```toml
[[tool]]
name = "~I~nstall"
command = "npm install"
output = "popup"

[[tool]]
name = "~F~ormat"
command = "npx prettier --write ."
output = "popup"

[[tool]]
name = "~L~int"
command = "npx eslint ."
output = "popup"
```

Each `[[tool]]` becomes one line of the **JavaScript** menu, in the order they appear — unless it names a `menu` of its own, which a later section covers. The file is read every time the menu opens, so an edit takes effect immediately.

## Run one

`Alt-J`, then the letter between the tildes — `I` to install, `T` to test.

**Test** opens a **popup** at once, which fills in as the command runs. Its title carries the command and, once it has ended, how it went:

```
┌──────────────── node --test — ok ─────────────────┐
│  ✔ greets by name (1.204ms)                        │
│  ℹ tests 1                                         │
│  ℹ suites 0                                        │
│  ℹ pass 1                                          │
│  ℹ fail 0                                          │
│                                                    │
│                     [ Close ]                      │
└────────────────────────────────────────────────────┘
```

| Key | Effect |
| --- | --- |
| `↑` `↓` `PgUp` `PgDn` `Home` `End` | Read through the output |
| `Escape` | Close it — and **stop the command** if it is still running |
| `Enter` | Close it |

A command that succeeded silently — `npx prettier --write .` on a project already formatted — shows `(no output)` rather than a blank box, so you can tell it from one that has not started.

**Run** asks first, because a Node project has no single entry point the editor could know:

```
┌──────────────── Run ────────────────┐
│  script, e.g. main.js               │
│  [                               ]  │
└─────────────────────────────────────┘
```

Type `main.js` and press `Enter`. A terminal window opens and the script runs in it; when it ends the window stays, showing what it printed. Press `Ctrl-W` to close it. Run it again and the box remembers the name for the rest of the session.

## The rest of the menu

| Item | What it runs | Where |
| --- | --- | --- |
| **Install** | `npm install` — the dependencies `package.json` names, into `node_modules/` | a popup |
| **Format** | `npx prettier --write .` — Prettier over the whole project | a popup; the open files are re-read afterwards |
| **Lint** | `npx eslint .` — ESLint over the whole project | a popup; ESLint 9 needs an `eslint.config.js` |
| **Test** | `node --test` — Node's own runner over every `*.test.js`, `*.spec.js` and `test/**/*.js` | a popup |
| **Run** | `node <script>` — the script you name | a terminal, because a script may read the keyboard |
| **Start** | `npm start` — whatever the `start` script in `package.json` says | a terminal, because it is usually a server |

`npx` runs the project's own Prettier and ESLint when `package.json` depends on them, and downloads them into npm's cache the first time otherwise — which is why the first **Format** on a machine takes a few seconds and the second does not.

`npm install` can take a while. The popup is modal, so while it runs you cannot type anywhere else; `Escape` closes it and stops the install.

## Choose where the output goes

Set `output` on a tool:

| `output` | What you get |
| --- | --- |
| `popup` | A dialog that fills in as it runs. The default. |
| `terminal` | A terminal window: colours, `Ctrl-C`, and the keyboard reaches the program |
| `editor` | An editing window once it has finished, to search with `Ctrl-F` |

`Run` and `Start` are `terminal` in the starter file, and they are the example of why the key exists: a popup cannot answer a script that reads `process.stdin`, and cannot be interrupted with `Ctrl-C` while a server is listening.

Reach for `editor` when the output is something to work through — the tree `npm ls` prints, or a long test report you want to search.

## A long command holds the editor

A popup is modal: while `npm install` runs, you cannot type anywhere else. `Escape` closes it and stops the command.

If that gets in the way for a particular command, give it `output = "terminal"` — the window is an ordinary one and you can carry on working beside it. That is what making the key configurable is for.

## What happens to your open files

`npx prettier --write .` rewrites the file you are looking at, `npm install` writes `node_modules/` and `package-lock.json` into the directory, and a tool of your own may do anything. When a command finishes, the editor **re-reads every open file that has no unsaved changes**, so a file another command changed appears as it now is, and the project tree is refreshed so a new one shows up. The status bar says how many.

A file with unsaved changes is **left alone**, and the status bar says so too:

```
Reloaded 2 files; 1 file with unsaved changes left alone
```

That is deliberate: your edit and the command genuinely disagree, and the editor is not the one that should decide which wins. Save first (`F2`) and run the command again, or keep editing. With **Format** in particular: save, then format, or Prettier's work on that file is skipped.

## Add your own commands

Edit `.turbo-js/tools.toml`. A command goes to `sh -c`, so one entry can be a whole sequence:

```toml
[[tool]]
name = "~C~heck"
command = "node --check {{script, e.g. main.js}}"
output = "popup"

[[tool]]
name = "Lint and ~t~est"
command = "npx eslint . && node --test"
output = "popup"

[[tool]]
name = "~W~atch"
command = "node --watch {{script, e.g. server.js}}"
output = "terminal"

[[tool]]
name = "~D~ependencies"
command = "npm ls --all"
output = "editor"
```

Give each a hot key with tildes, and keep them distinct — the menu answers the first match it finds.

## Put a tool in a menu of its own

A tool that has nothing to do with JavaScript does not belong in the JavaScript menu. Give it a `menu`:

```toml
[[tool]]
name = "~E~cho"
command = "echo TADA"
output = "terminal"
menu = "Tools"

[[tool]]
name = "~U~p"
command = "docker compose up -d"
menu = "Docker"

[[tool]]
name = "~D~own"
command = "docker compose down"
menu = "Docker"
```

That gives you a **Tools** menu and a **Docker** menu on the bar, between JavaScript and Help, in the order the names first appear in the file. Docker holds both its tools. Nothing needs restarting: save the file and the bar follows.

The name is yours to choose — there is no list to pick from. Leave `menu` out and the tool stays in JavaScript, which is where six of the seven starter commands are.

### The hot key is chosen for you

You cannot know, when writing the file, which letters the editor's own menus have taken. So it works it out: the first letter of the name that nothing else claims gets the tildes.

`Tools` gets `Alt-T`, because `T` is free. A menu called `Format` would get `Alt-M`, because `F` is File's, `o` is Options' and `r` is Run's. A menu called `Jobs` would get `Alt-B`, because `J` is JavaScript's and `o` is Options'.

Write the tildes yourself — `menu = "Doc~k~er"` — and a free letter is kept. A taken one is not: the bar answers the *first* menu matching a key, so honouring your choice would make one of the two menus unreachable. It picks another letter and says nothing.

## Variants

- **Your project uses pnpm, Yarn or Bun.** Replace `npm install` with `pnpm install`, `npx eslint .` with `pnpm exec eslint .`, and so on. The starter file is npm because npm ships with Node; nothing in the editor knows which package manager runs.
- **You have one script and never another.** Replace `node {{script, e.g. main.js}}` with `node main.js`, and the box stops appearing. The placeholder is there because a starter file cannot know which file is the program.
- **Your tests use Vitest, Jest or Mocha.** Replace `node --test` with `npx vitest run`, `npx jest` or `npx mocha`. `npm test` also works, if `package.json` has a `test` script that is not the `echo "Error: no test specified"` npm writes by default.
- **You started the editor from a subdirectory.** Commands run there, and `npm` looks for `package.json` there and in the directories above — but `npx prettier --write .` formats only that subdirectory. Start from the directory `package.json` is in.
- **The file has a mistake in it.** The menu shows a greyed-out `Cannot read tools` where the commands would be, and **Create tools file** is still there.
- **`npx eslint .` says it cannot find a configuration file.** ESLint 9 reads `eslint.config.js`; `npm init @eslint/config@latest` writes one. Older projects have `.eslintrc.json`, which ESLint 9 no longer reads.
- **You want a menu named after one that exists.** `menu = "File"` gives you a second File menu, further along the bar, with a different hot key. Nothing stops you; nothing recommends it either.
- **Your menu has no hot key.** Every letter in its name was already taken. `F10` and the arrow keys reach it, and so does the mouse. Rename it to something with a free letter.
- **You misspelt `output`'s value.** The whole file is refused and the menu says `Cannot read tools`, naming the tool and listing what it could have been. A silent fallback would have sent the output somewhere you did not ask for.

## Ask for a value when the command runs

`Run` already does. The pattern is a `{{label}}` where the value goes:

```toml
[[tool]]
name = "~A~dd a package"
command = "npm install {{package name}}"
output = "popup"
```

Choosing it opens a box titled **Add a package** with one field, labelled `package name`. **Enter** runs the command. Escape, and nothing runs. Several placeholders give several fields, in the order they appear; **Tab** moves between them.

The value is quoted, so a path with a space in it stays one argument.

### One field standing for several arguments

Quoting is wrong when you mean "put these on the end". Add `...` inside the braces and the value goes in verbatim:

```toml
[[tool]]
name = "Run with ~a~rguments"
command = "node main.js {{arguments...}}"
output = "terminal"
```

Type `--verbose input.txt` and both reach the script as separate arguments — `process.argv.slice(2)` holds them.

### The same value twice

Write the label twice; you are asked once:

```toml
[[tool]]
name = "Test ~o~ne"
command = "node --test {{file}} && node --check {{file}}"
```

### Variants

- **The value is the same most times.** Run it once and the box remembers what you typed, for the rest of the session. It is not written to disk.
- **Your command has braces in it already.** `awk '{print $1}'` and `find . -exec rm {} +` are left alone: only double braces ask for anything. So is a JavaScript `${x}` inside a `node -e` one-liner — one brace, not two.
- **The command asks for more values than fit on screen.** The editor says so rather than opening a box whose OK button is below the bottom of the terminal. Make the terminal taller, or split the command into two tools.

## See also

- Every key of the file and every rule: [JavaScript tools reference](../reference/javascript-tools.md)
- Why Install comes first, and why an unmodified file reloads: [JavaScript tools](../explanation/javascript-tools.md)
- The windows the commands run in: [Terminal windows](../reference/terminal.md)