@hamedb89/localghost 0.1.6 → 0.1.9

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.
package/README.md CHANGED
@@ -9,9 +9,9 @@ Buh. Friendly local hostnames for app repos.
9
9
  [![CI](https://github.com/hamedb89/localghost/actions/workflows/ci.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/ci.yml)
10
10
  [![GitHub Pages](https://github.com/hamedb89/localghost/actions/workflows/pages.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/pages.yml)
11
11
  [![Publish npm](https://github.com/hamedb89/localghost/actions/workflows/publish-npm.yml/badge.svg)](https://github.com/hamedb89/localghost/actions/workflows/publish-npm.yml)
12
- [![npm version](https://img.shields.io/badge/npm-v0.1.6-CB3837?logo=npm)](https://www.npmjs.com/package/@hamedb89/localghost)
12
+ [![npm version](https://img.shields.io/badge/npm-v0.1.9-CB3837?logo=npm)](https://www.npmjs.com/package/@hamedb89/localghost)
13
13
 
14
- Localghost is a tiny Node.js CLI for friendly local domains in app repos. It gives each project one small contract for `.localhost` hostnames, Caddy reverse proxies, Vite `allowedHosts`, and the system hosts file, so developers can open `http://app.localhost/` instead of remembering which localhost port belongs to which process.
14
+ Localghost is a tiny Node.js CLI for friendly local domains in app repos. Add it as a dev dependency, run `yarn dev`, and use `http://app.localhost/` instead of remembering which localhost port belongs to which process.
15
15
 
16
16
  [Website](https://hamedb89.github.io/localghost/) · [npm](https://www.npmjs.com/package/@hamedb89/localghost) · [GitHub](https://github.com/hamedb89/localghost)
17
17
 
@@ -31,6 +31,7 @@ Localghost is a tiny Node.js CLI for friendly local domains in app repos. It giv
31
31
 
32
32
  - CI runs typecheck, build, site build, and npm package dry-run on Node 20 and 22.
33
33
  - GitHub Pages is deployed by Actions from the checked-in `site/` and `assets/` sources.
34
+ - Preview the exact Pages artifact locally with `npm run site:serve`, then open `http://127.0.0.1:4173/`.
34
35
  - npm publish is guarded by `prepublishOnly` and the release workflow publishes with npm provenance.
35
36
  - Runtime dependencies are intentionally small: `commander` for the CLI and `execa` for process execution. Vite is an optional peer dependency for the Vite plugin.
36
37
  - No postinstall scripts, hidden Homebrew installs, or broad hosts-file rewrites.
@@ -40,21 +41,62 @@ Localghost is a tiny Node.js CLI for friendly local domains in app repos. It giv
40
41
  <img src="./assets/localghost-app-icon.png" alt="Localghost app icon" width="180">
41
42
  </p>
42
43
 
43
- ## Install
44
+ ## Start Here
44
45
 
45
46
  ```sh
46
47
  yarn add -D @hamedb89/localghost
47
48
  ```
48
49
 
49
- ## Quick Start
50
+ That is the entrypoint you are optimizing for: install the dev dependency, keep using the dev command your team already knows, and let Localghost handle the local-domain setup around it.
50
51
 
51
- Create the project config and optional package scripts:
52
+ For Vite apps, add the plugin once:
53
+
54
+ ```ts
55
+ import { defineConfig } from "vite";
56
+ import { localGhostPlugin } from "@hamedb89/localghost/vite";
57
+
58
+ export default defineConfig({
59
+ plugins: [localGhostPlugin()]
60
+ });
61
+ ```
62
+
63
+ Now your daily command is just:
64
+
65
+ ```sh
66
+ yarn dev
67
+ ```
68
+
69
+ And you are ready.
70
+
71
+ On the first interactive `yarn dev`, Localghost can create `.localghost`, explain the `/etc/hosts` change, write the local Caddyfile, and then print the browser-facing URL:
72
+
73
+ ```txt
74
+ localghost
75
+ local: http://app.localhost/
76
+ also: http://www.app.localhost/
77
+ target: http://127.0.0.1:5173/
78
+ ```
79
+
80
+ If your project does not use Vite, or you want one command that starts Caddy and then your app process, wrap your raw dev script:
81
+
82
+ ```json
83
+ {
84
+ "scripts": {
85
+ "dev": "localghost run -- yarn dev:raw",
86
+ "dev:raw": "next dev"
87
+ }
88
+ }
89
+ ```
90
+
91
+ Then the daily command stays the same:
52
92
 
53
93
  ```sh
54
- yarn localghost init --write-scripts
94
+ yarn dev
55
95
  ```
56
96
 
57
- This creates `.localghost`:
97
+ ## Configuration
98
+
99
+ Most apps only need a `.localghost` file when they want explicit hostnames or multiple services:
58
100
 
59
101
  ```txt
60
102
  # Buh. Friendly names for local services.
@@ -64,6 +106,12 @@ www.app.localhost 5173
64
106
  api.app.localhost 8787
65
107
  ```
66
108
 
109
+ If `.localghost` is missing, the Vite plugin can prompt to create it during `yarn dev`. You can also create it directly:
110
+
111
+ ```sh
112
+ yarn localghost init
113
+ ```
114
+
67
115
  Check the machine:
68
116
 
69
117
  ```sh
@@ -78,35 +126,41 @@ Run: brew install caddy
78
126
  Localghost will not install it for you. No surprise spells.
79
127
  ```
80
128
 
81
- First time on a machine:
129
+ Prepare or repair the machine setup directly:
82
130
 
83
131
  ```sh
84
- yarn localghost:setup
132
+ yarn localghost setup
85
133
  ```
86
134
 
87
135
  Check that the hosts block and Caddyfile are ready:
88
136
 
89
137
  ```sh
90
- yarn localghost:ready
138
+ yarn localghost status --ready
91
139
  ```
92
140
 
93
- Daily proxy:
141
+ Run only the proxy when your app server is started separately:
94
142
 
95
143
  ```sh
96
- yarn localghost:proxy
144
+ yarn localghost dev
97
145
  ```
98
146
 
99
147
  Use HTTPS only when you explicitly want Caddy local certificates:
100
148
 
101
149
  ```sh
102
- yarn localghost:proxy:https
150
+ yarn localghost dev --https
151
+ ```
152
+
153
+ Trust Caddy's local HTTPS CA when you want browsers to stop showing local certificate warnings:
154
+
155
+ ```sh
156
+ yarn localghost trust
103
157
  ```
104
158
 
105
159
  Reset generated setup without deleting `.localghost`:
106
160
 
107
161
  ```sh
108
- yarn localghost:reset
109
- yarn localghost:setup
162
+ yarn localghost reset
163
+ yarn localghost setup
110
164
  ```
111
165
 
112
166
  Prefer `.localhost` names. `.local` is supported, but Localghost warns because `.local` can collide with mDNS/Bonjour.
@@ -146,6 +200,7 @@ The Vite plugin accepts the same shape through `fileName`, `configFiles`, or `co
146
200
  "localghost:proxy:https": "localghost dev --https",
147
201
  "localghost:run": "localghost run --",
148
202
  "localghost:ready": "localghost status --ready",
203
+ "localghost:trust": "localghost trust",
149
204
  "localghost:ps": "localghost ps",
150
205
  "localghost:print": "localghost print",
151
206
  "localghost:routes": "localghost routes",
@@ -165,8 +220,7 @@ A full app might compose them with its own servers:
165
220
  ```json
166
221
  {
167
222
  "scripts": {
168
- "dev": "localghost run -- vite",
169
- "dev:dynamic": "localghost run --dynamic-port -- vite",
223
+ "dev": "localghost run -- yarn dev:raw",
170
224
  "dev:raw": "vite"
171
225
  }
172
226
  }
@@ -177,7 +231,7 @@ In Turborepo, let Localghost wrap the dev runner and keep dev uncached:
177
231
  ```json
178
232
  {
179
233
  "scripts": {
180
- "dev": "localghost run --dynamic-port -- turbo dev",
234
+ "dev": "localghost run -- yarn dev:raw",
181
235
  "dev:raw": "turbo dev"
182
236
  }
183
237
  }
@@ -193,7 +247,117 @@ Then keep persistent dev tasks uncached:
193
247
  }
194
248
  ```
195
249
 
196
- `localghost run` resolves one shared Localghost context, starts Caddy, passes the selected port to the child command through `LOCALGHOST_PORT` and `VITE_PORT`, and stops Caddy when the child exits. With `--dynamic-port`, Localghost starts at the configured port, checks `127.0.0.1:<port>`, and walks upward until it finds a free port.
250
+ `localghost run` resolves one shared Localghost context, starts Caddy, handles the optional HTTPS trust prompt, then starts the child command. That keeps Localghost setup/proxy output before Vite's ready log. It passes the selected port to the child command through `LOCALGHOST_PORT` and `VITE_PORT`, and stops Caddy when the child exits. Dynamic ports are on by default: Localghost starts at the configured port, checks `127.0.0.1:<port>`, and walks upward until it finds a free port. Use `--dynamic-port=no` when you want strict fixed-port behavior.
251
+
252
+ Most repos do not need `localghost.config.mjs`. Localghost derives `project` from `package.json`, defaults to port `5173`, keeps HTTPS off by default, enables dynamic ports by default, and adds `www.` aliases by default. Add `localghost.config.mjs` only when you want to override those defaults:
253
+
254
+ ```js
255
+ import { defineLocalghostConfig } from "@hamedb89/localghost";
256
+
257
+ export default defineLocalghostConfig({
258
+ https: true
259
+ });
260
+ ```
261
+
262
+ Then the daily script can stay small:
263
+
264
+ ```json
265
+ {
266
+ "scripts": {
267
+ "dev": "localghost run -- yarn dev:raw",
268
+ "dev:raw": "vite"
269
+ }
270
+ }
271
+ ```
272
+
273
+ `www.` aliases are enabled by default. A `.localghost` entry like `app.localhost 5173` also sets up `www.app.localhost` unless `wwwAlias: false` is set in `localghost.config.mjs`.
274
+
275
+ `ghostTunnel` is the production opt-in for a wildcard product entrypoint on top of your deployed Vite app. The default namespace is `<route>-<project>-<owner>.ghost.<domain>`, and omitted domains are shown as `*` in logs:
276
+
277
+ ```js
278
+ import { defineLocalghostConfig } from "@hamedb89/localghost";
279
+
280
+ export default defineLocalghostConfig({
281
+ ghostTunnel: {
282
+ domains: "socialworkouts.app",
283
+ mode: "manual"
284
+ }
285
+ });
286
+ ```
287
+
288
+ With `ghostTunnel: { domains }`, local route and Vite startup logs use local defaults for `route`, `project`, and `owner`, then fill the configured domain:
289
+
290
+ ```txt
291
+ localghost ghost tunnel
292
+ mode: manual
293
+ expected: https://app-decision-layer-hamed.ghost.socialworkouts.app/
294
+ ```
295
+
296
+ Without `domains`, the expected URL stays wildcarded:
297
+
298
+ ```txt
299
+ localghost ghost tunnel
300
+ mode: manual
301
+ expected: https://app-decision-layer-hamed.ghost.*/
302
+ ```
303
+
304
+ `ghostTunnel: "manual"` and `ghostTunnel: "public"` are shorthand modes. `manual` is the default; use `enabled: false` to keep domains/config in the file without exposing the tunnel surface.
305
+
306
+ Use object form to override defaults or provide a concrete preview URL:
307
+
308
+ ```js
309
+ export default defineLocalghostConfig({
310
+ ghostTunnel: {
311
+ domains: "socialworkouts.app",
312
+ preview: {
313
+ route: "plan",
314
+ project: "summer-base",
315
+ owner: "hamed"
316
+ }
317
+ }
318
+ });
319
+ ```
320
+
321
+ Production apps can read that flag without requiring the local `.localghost` file, then construct and validate tunnel URLs:
322
+
323
+ ```ts
324
+ import {
325
+ assertSecureGhostTunnelRequest,
326
+ constructGhostTunnelUrl,
327
+ readLocalghostProjectConfig
328
+ } from "@hamedb89/localghost";
329
+
330
+ const { config } = await readLocalghostProjectConfig();
331
+ const url = constructGhostTunnelUrl({
332
+ domain: "socialworkouts.app",
333
+ route: "plan",
334
+ project: "summer-base",
335
+ owner: "hamed",
336
+ ghostTunnel: config.ghostTunnel
337
+ });
338
+
339
+ const route = assertSecureGhostTunnelRequest({
340
+ host: request.headers.get("host") ?? "",
341
+ domain: "socialworkouts.app",
342
+ protocol: "https",
343
+ authenticated: Boolean(session),
344
+ ghostTunnel: config.ghostTunnel
345
+ });
346
+ ```
347
+
348
+ That constructs `https://plan-summer-base-hamed.ghost.socialworkouts.app/`, validates the same host shape, requires HTTPS by default, and requires the app to confirm auth by default. See [Ghost Tunnel](./docs/ghost-tunnel.md) for the production DNS and routing flow.
349
+
350
+ When `ghostTunnel.preview` is configured, local route and Vite startup logs include the concrete URL as `expected: https://plan-summer-base-hamed.ghost.socialworkouts.app/`. In an interactive Vite terminal, press `g` to show the Ghost Tunnel configuration and open a numbered concrete URL. Wildcard `*` URLs are shown for clarity, but the menu only opens configured concrete domains.
351
+
352
+ Relay guardrails are private-by-default: public requests never choose the local target, route registration requires a matching local-agent bearer token, signed route claims are exact/scoped/expiring, and default targets are limited to `localhost`, `127.0.0.1`, and `::1` with dangerous ports blocked. The package exports relay helpers for registration, target validation, header stripping, log redaction, limits, and safe offline responses.
353
+
354
+ Local security checks:
355
+
356
+ ```sh
357
+ npm test
358
+ npm run test:cli
359
+ npm run test:coverage
360
+ ```
197
361
 
198
362
  `localghost dev` and `localghost run` also register their active sessions in a user-local activity file. Use `localghost ps` to see the Localghost apps currently running on the machine:
199
363
 
@@ -209,6 +373,19 @@ app run: vite
209
373
 
210
374
  Pass `--json` when another helper, such as a menu bar app, needs to poll the same state.
211
375
 
376
+ ## macOS Widget
377
+
378
+ Localghost includes a tiny native macOS widget in `apps/macos-widget`. One widget tracks all active Localghost sessions across the machine. It shows `LG n` in the top bar and opens a small floating desktop panel with the Localghost route list, target ports, and listening state.
379
+
380
+ Build it from source:
381
+
382
+ ```sh
383
+ npm run build
384
+ npm run macos:widget:build
385
+ ```
386
+
387
+ The app bundle is written to `dist/LocalghostWidget.app`. See [docs/macos-widget.md](./docs/macos-widget.md) for local development notes.
388
+
212
389
  ## Vite
213
390
 
214
391
  ```ts
@@ -219,14 +396,13 @@ export default defineConfig({
219
396
  plugins: [
220
397
  localGhostPlugin({
221
398
  port: 5173,
222
- dynamicPort: true,
223
399
  configFiles: [".localghost.private", ".localghost"]
224
400
  })
225
401
  ]
226
402
  });
227
403
  ```
228
404
 
229
- The plugin binds Vite to `127.0.0.1` by default, prints the selected Localghost domain, generates an explicit `server.allowedHosts` list from the selected config file, and does not set `allowedHosts: true`. It runs only during local `vite serve`; production/build mode no-ops. When `dynamicPort` is enabled, the plugin uses the configured port when available and otherwise moves to the next free port before Vite starts.
405
+ The plugin binds Vite to `127.0.0.1` by default, prints the selected Localghost domain, generates an explicit `server.allowedHosts` list from the selected config file, and does not set `allowedHosts: true`. It runs only during local `vite serve`; production/build mode no-ops. Dynamic ports are enabled by default, so the plugin uses the configured port when available and otherwise moves to the next free port before Vite starts. Set `dynamicPort: false` when strict fixed-port behavior matters.
230
406
 
231
407
  If `.localghost` is missing and Vite is running in an interactive terminal, the plugin asks whether to create one, prompts for the primary domain and optional extra domains, and then asks whether to run setup. Before touching `/etc/hosts`, it explains why macOS may ask for your password and confirms that only Localghost's managed block is changed.
232
408
 
@@ -253,6 +429,7 @@ localghost setup
253
429
  localghost setup --project app
254
430
  localghost setup --config .localghost.preview
255
431
  localghost setup --https
432
+ localghost trust
256
433
  localghost status
257
434
  localghost status --ready
258
435
  localghost ps
@@ -263,7 +440,8 @@ localghost teardown --remove-caddyfile
263
440
  localghost update
264
441
  localghost --no-update-check doctor
265
442
  localghost run -- vite
266
- localghost run --dynamic-port -- turbo dev
443
+ localghost run --trust -- vite
444
+ localghost run --dynamic-port=no -- vite
267
445
  localghost dev --config-pattern '^\.localghost\.'
268
446
  localghost dev --https
269
447
  localghost print
@@ -273,6 +451,8 @@ Localghost checks npm for newer releases after successful commands. The check ha
273
451
 
274
452
  `setup`, `dev`, and `teardown` refuse to run in production-like environments such as `NODE_ENV=production`, `VERCEL_ENV=production`, or `LOCALGHOST_ENV=production`.
275
453
 
454
+ When HTTPS is enabled, `localghost dev` and `localghost run` ask once whether to trust Caddy's local HTTPS CA. If you accept, macOS may ask for your password so Caddy can add its local CA to Keychain. Localghost records the result in `ops/local/localghost-state.json`; use `localghost trust` or `localghost run --trust -- ...` when you want to rerun the trust step intentionally.
455
+
276
456
  `setup` writes only a managed block in the system hosts file:
277
457
 
278
458
  ```txt
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.app-sandbox</key>
6
+ <true/>
7
+ <key>com.apple.security.application-groups</key>
8
+ <array>
9
+ <string>group.app.localghost</string>
10
+ </array>
11
+ </dict>
12
+ </plist>