| 📦 Turbo JS 91999d1 k33g 12h ago | 1 | # How to run Node commands from the editor |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## Get a starter file |
| 6 | |
| 7 | Start the editor **from the directory holding `package.json`**, then choose **JavaScript ▸ Create tools file** (`Alt-J`, then `C`). |
| 8 | |
| 9 | That writes `.turbo-js/tools.toml` with the commands a Node project runs most, and opens it. The first three: |
| 10 | |
| 11 | ```toml |
| 12 | [[tool]] |
| 13 | name = "~I~nstall" |
| 14 | command = "npm install" |
| 15 | output = "popup" |
| 16 | |
| 17 | [[tool]] |
| 18 | name = "~F~ormat" |
| 19 | command = "npx prettier --write ." |
| 20 | output = "popup" |
| 21 | |
| 22 | [[tool]] |
| 23 | name = "~L~int" |
| 24 | command = "npx eslint ." |
| 25 | output = "popup" |
| 26 | ``` |
| 27 | |
| 28 | 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. |
| 29 | |
| 30 | ## Run one |
| 31 | |
| 32 | `Alt-J`, then the letter between the tildes — `I` to install, `T` to test. |
| 33 | |
| 34 | **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: |
| 35 | |
| 36 | ``` |
| 37 | ┌──────────────── node --test — ok ─────────────────┐ |
| 38 | │ ✔ greets by name (1.204ms) │ |
| 39 | │ ℹ tests 1 │ |
| 40 | │ ℹ suites 0 │ |
| 41 | │ ℹ pass 1 │ |
| 42 | │ ℹ fail 0 │ |
| 43 | │ │ |
| 44 | │ [ Close ] │ |
| 45 | └────────────────────────────────────────────────────┘ |
| 46 | ``` |
| 47 | |
| 48 | | Key | Effect | |
| 49 | | --- | --- | |
| 50 | | `↑` `↓` `PgUp` `PgDn` `Home` `End` | Read through the output | |
| 51 | | `Escape` | Close it — and **stop the command** if it is still running | |
| 52 | | `Enter` | Close it | |
| 53 | |
| 54 | 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. |
| 55 | |
| 56 | **Run** asks first, because a Node project has no single entry point the editor could know: |
| 57 | |
| 58 | ``` |
| 59 | ┌──────────────── Run ────────────────┐ |
| 60 | │ script, e.g. main.js │ |
| 61 | │ [ ] │ |
| 62 | └─────────────────────────────────────┘ |
| 63 | ``` |
| 64 | |
| 65 | 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. |
| 66 | |
| 67 | ## The rest of the menu |
| 68 | |
| 69 | | Item | What it runs | Where | |
| 70 | | --- | --- | --- | |
| 71 | | **Install** | `npm install` — the dependencies `package.json` names, into `node_modules/` | a popup | |
| 72 | | **Format** | `npx prettier --write .` — Prettier over the whole project | a popup; the open files are re-read afterwards | |
| 73 | | **Lint** | `npx eslint .` — ESLint over the whole project | a popup; ESLint 9 needs an `eslint.config.js` | |
| 74 | | **Test** | `node --test` — Node's own runner over every `*.test.js`, `*.spec.js` and `test/**/*.js` | a popup | |
| 75 | | **Run** | `node <script>` — the script you name | a terminal, because a script may read the keyboard | |
| 76 | | **Start** | `npm start` — whatever the `start` script in `package.json` says | a terminal, because it is usually a server | |
| 77 | |
| 78 | `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. |
| 79 | |
| 80 | `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. |
| 81 | |
| 82 | ## Choose where the output goes |
| 83 | |
| 84 | Set `output` on a tool: |
| 85 | |
| 86 | | `output` | What you get | |
| 87 | | --- | --- | |
| 88 | | `popup` | A dialog that fills in as it runs. The default. | |
| 89 | | `terminal` | A terminal window: colours, `Ctrl-C`, and the keyboard reaches the program | |
| 90 | | `editor` | An editing window once it has finished, to search with `Ctrl-F` | |
| 91 | |
| 92 | `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. |
| 93 | |
| 94 | 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. |
| 95 | |
| 96 | ## A long command holds the editor |
| 97 | |
| 98 | A popup is modal: while `npm install` runs, you cannot type anywhere else. `Escape` closes it and stops the command. |
| 99 | |
| 100 | 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. |
| 101 | |
| 102 | ## What happens to your open files |
| 103 | |
| 104 | `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. |
| 105 | |
| 106 | A file with unsaved changes is **left alone**, and the status bar says so too: |
| 107 | |
| 108 | ``` |
| 109 | Reloaded 2 files; 1 file with unsaved changes left alone |
| 110 | ``` |
| 111 | |
| 112 | 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. |
| 113 | |
| 114 | ## Add your own commands |
| 115 | |
| 116 | Edit `.turbo-js/tools.toml`. A command goes to `sh -c`, so one entry can be a whole sequence: |
| 117 | |
| 118 | ```toml |
| 119 | [[tool]] |
| 120 | name = "~C~heck" |
| 121 | command = "node --check {{script, e.g. main.js}}" |
| 122 | output = "popup" |
| 123 | |
| 124 | [[tool]] |
| 125 | name = "Lint and ~t~est" |
| 126 | command = "npx eslint . && node --test" |
| 127 | output = "popup" |
| 128 | |
| 129 | [[tool]] |
| 130 | name = "~W~atch" |
| 131 | command = "node --watch {{script, e.g. server.js}}" |
| 132 | output = "terminal" |
| 133 | |
| 134 | [[tool]] |
| 135 | name = "~D~ependencies" |
| 136 | command = "npm ls --all" |
| 137 | output = "editor" |
| 138 | ``` |
| 139 | |
| 140 | Give each a hot key with tildes, and keep them distinct — the menu answers the first match it finds. |
| 141 | |
| 142 | ## Put a tool in a menu of its own |
| 143 | |
| 144 | A tool that has nothing to do with JavaScript does not belong in the JavaScript menu. Give it a `menu`: |
| 145 | |
| 146 | ```toml |
| 147 | [[tool]] |
| 148 | name = "~E~cho" |
| 149 | command = "echo TADA" |
| 150 | output = "terminal" |
| 151 | menu = "Tools" |
| 152 | |
| 153 | [[tool]] |
| 154 | name = "~U~p" |
| 155 | command = "docker compose up -d" |
| 156 | menu = "Docker" |
| 157 | |
| 158 | [[tool]] |
| 159 | name = "~D~own" |
| 160 | command = "docker compose down" |
| 161 | menu = "Docker" |
| 162 | ``` |
| 163 | |
| 164 | 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. |
| 165 | |
| 166 | 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. |
| 167 | |
| 168 | ### The hot key is chosen for you |
| 169 | |
| 170 | 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. |
| 171 | |
| 172 | `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'. |
| 173 | |
| 174 | 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. |
| 175 | |
| 176 | ## Variants |
| 177 | |
| 178 | - **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. |
| 179 | - **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. |
| 180 | - **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. |
| 181 | - **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. |
| 182 | - **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. |
| 183 | - **`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. |
| 184 | - **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. |
| 185 | - **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. |
| 186 | - **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. |
| 187 | |
| 188 | ## Ask for a value when the command runs |
| 189 | |
| 190 | `Run` already does. The pattern is a `{{label}}` where the value goes: |
| 191 | |
| 192 | ```toml |
| 193 | [[tool]] |
| 194 | name = "~A~dd a package" |
| 195 | command = "npm install {{package name}}" |
| 196 | output = "popup" |
| 197 | ``` |
| 198 | |
| 199 | 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. |
| 200 | |
| 201 | The value is quoted, so a path with a space in it stays one argument. |
| 202 | |
| 203 | ### One field standing for several arguments |
| 204 | |
| 205 | Quoting is wrong when you mean "put these on the end". Add `...` inside the braces and the value goes in verbatim: |
| 206 | |
| 207 | ```toml |
| 208 | [[tool]] |
| 209 | name = "Run with ~a~rguments" |
| 210 | command = "node main.js {{arguments...}}" |
| 211 | output = "terminal" |
| 212 | ``` |
| 213 | |
| 214 | Type `--verbose input.txt` and both reach the script as separate arguments — `process.argv.slice(2)` holds them. |
| 215 | |
| 216 | ### The same value twice |
| 217 | |
| 218 | Write the label twice; you are asked once: |
| 219 | |
| 220 | ```toml |
| 221 | [[tool]] |
| 222 | name = "Test ~o~ne" |
| 223 | command = "node --test {{file}} && node --check {{file}}" |
| 224 | ``` |
| 225 | |
| 226 | ### Variants |
| 227 | |
| 228 | - **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. |
| 229 | - **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. |
| 230 | - **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. |
| 231 | |
| 232 | ## See also |
| 233 | |
| 234 | - Every key of the file and every rule: [JavaScript tools reference](../reference/javascript-tools.md) |
| 235 | - Why Install comes first, and why an unmodified file reloads: [JavaScript tools](../explanation/javascript-tools.md) |
| 236 | - The windows the commands run in: [Terminal windows](../reference/terminal.md) |