@hamedb89/localghost 0.1.8 → 0.1.10

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.
@@ -0,0 +1,293 @@
1
+ # Ghost Tunnel
2
+
3
+ Ghost Tunnel is the production-facing Localghost entrypoint for apps that want a stable wildcard route on top of their existing Vite product:
4
+
5
+ ```txt
6
+ <route>-<project>-<owner>.ghost.<your-domain>
7
+ ```
8
+
9
+ For Social Workouts, the default namespace is:
10
+
11
+ ```txt
12
+ <route>-<project>-<owner>.ghost.socialworkouts.app
13
+ ```
14
+
15
+ The feature is off by default. Opt in from `localghost.config.mjs`:
16
+
17
+ ```js
18
+ import { defineLocalghostConfig } from "@hamedb89/localghost";
19
+
20
+ export default defineLocalghostConfig({
21
+ ghostTunnel: {
22
+ domains: "socialworkouts.app",
23
+ mode: "manual"
24
+ }
25
+ });
26
+ ```
27
+
28
+ With `ghostTunnel: { domains }` in the default manual mode, route output and Vite startup output use local defaults for `route`, `project`, and `owner`, then fill the configured domain:
29
+
30
+ ```txt
31
+ localghost ghost tunnel
32
+ mode: manual
33
+ expected: https://app-decision-layer-hamed.ghost.socialworkouts.app/
34
+ ```
35
+
36
+ Without `domains`, the log keeps the production domain wildcarded with `*`:
37
+
38
+ ```txt
39
+ localghost ghost tunnel
40
+ mode: manual
41
+ expected: https://app-decision-layer-hamed.ghost.*/
42
+ ```
43
+
44
+ `manual` is the default activation mode. `ghostTunnel: "manual"` and `ghostTunnel: "public"` are shorthand modes. Use `enabled: false` to keep domains/config in the file without exposing the tunnel surface.
45
+
46
+ Public mode keeps the namespace flexible unless you configure a preview. This is useful for deployed wildcard endpoints that react to whichever route arrives:
47
+
48
+ ```txt
49
+ localghost ghost tunnel
50
+ mode: public
51
+ configured: https://<route>-<project>-<owner>.ghost.socialworkouts.app/
52
+ ```
53
+
54
+ Use object form to override defaults or provide a concrete preview URL:
55
+
56
+ ```js
57
+ export default defineLocalghostConfig({
58
+ ghostTunnel: {
59
+ domains: "socialworkouts.app",
60
+ preview: {
61
+ route: "plan",
62
+ project: "summer-base",
63
+ owner: "hamed"
64
+ }
65
+ }
66
+ });
67
+ ```
68
+
69
+ ## Flow
70
+
71
+ 1. Add `ghostTunnel: { domains: "your-domain.com" }`, `ghostTunnel: "manual"`, or `ghostTunnel.preview` to `localghost.config.mjs`.
72
+ 2. Point the wildcard DNS record for `*.ghost.<your-domain>` at the deployed app.
73
+ 3. Route `*.ghost.<your-domain>` to the same production app that serves the Vite build.
74
+ 4. In production request handling, read the Localghost project config without resolving local `.localghost` setup.
75
+ 5. Construct tunnel URLs from `route`, `project`, and `owner`.
76
+ 6. Validate the incoming request host, protocol, and auth before serving the tunnel surface.
77
+
78
+ ## Vercel DNS
79
+
80
+ For Vercel, add the Ghost Tunnel wildcard domain to the project first:
81
+
82
+ ```txt
83
+ *.ghost.decisionlayer.tech
84
+ ```
85
+
86
+ Then create the DNS record that points that wildcard at the same Vercel project. In Vercel-managed DNS, the record name is relative to the zone:
87
+
88
+ ```txt
89
+ Name: *.ghost
90
+ Type: ALIAS
91
+ Value: <the Vercel DNS target shown for the project>
92
+ TTL: 60
93
+ ```
94
+
95
+ For example, if Vercel shows `cname.vercel-dns-016.com` as the project target, use the fully-qualified value with the trailing dot when the DNS form requires it:
96
+
97
+ ```txt
98
+ Name: *.ghost
99
+ Type: ALIAS
100
+ Value: cname.vercel-dns-016.com.
101
+ TTL: 60
102
+ ```
103
+
104
+ When DNS is managed somewhere else, use the provider's wildcard subdomain shape:
105
+
106
+ ```txt
107
+ Name: *.ghost
108
+ Type: CNAME
109
+ Value: cname.vercel-dns-016.com.
110
+ ```
111
+
112
+ Do not use only `*` for Ghost Tunnel. A record named `*` covers `anything.decisionlayer.tech`, but Ghost Tunnel URLs look like `decisionlayer-decision-layer-hamed.ghost.decisionlayer.tech`, so the wildcard must live under `ghost`.
113
+
114
+ ```ts
115
+ import {
116
+ assertSecureGhostTunnelRequest,
117
+ constructGhostTunnelUrl,
118
+ readLocalghostProjectConfig
119
+ } from "@hamedb89/localghost";
120
+
121
+ const { config } = await readLocalghostProjectConfig();
122
+
123
+ const url = constructGhostTunnelUrl({
124
+ domain: "socialworkouts.app",
125
+ route: "plan",
126
+ project: "summer-base",
127
+ owner: "hamed",
128
+ ghostTunnel: config.ghostTunnel
129
+ });
130
+
131
+ const route = assertSecureGhostTunnelRequest({
132
+ host: request.headers.get("host") ?? "",
133
+ domain: "socialworkouts.app",
134
+ protocol: request.url.startsWith("https:") ? "https" : "http",
135
+ authenticated: Boolean(session),
136
+ ghostTunnel: config.ghostTunnel
137
+ });
138
+
139
+ // url is https://plan-summer-base-hamed.ghost.socialworkouts.app/
140
+ // route.namespace is { route: "plan", project: "summer-base", owner: "hamed" }.
141
+ ```
142
+
143
+ When `ghostTunnel.preview` is configured, Localghost logs the concrete preview URL in route output and Vite startup output:
144
+
145
+ ```txt
146
+ localghost ghost tunnel
147
+ mode: manual
148
+ expected: https://plan-summer-base-hamed.ghost.socialworkouts.app/
149
+ ```
150
+
151
+ In an interactive Vite terminal, press `g` to show the Ghost Tunnel configuration and open a numbered concrete URL. Wildcard `*` URLs are shown for observability, but the menu only opens configured concrete domains.
152
+
153
+ When the app is behind a trusted deployment proxy, derive `protocol` from the platform's trusted request metadata. Do not trust arbitrary forwarded headers unless the platform has already normalized them.
154
+
155
+ ## Namespace DSL
156
+
157
+ The default namespace tags are `route`, `project`, and `owner`, joined with `-`. The `project` tag is the default spread tag, so project slugs may contain hyphens:
158
+
159
+ ```js
160
+ export default defineLocalghostConfig({
161
+ ghostTunnel: {
162
+ domains: "socialworkouts.app"
163
+ }
164
+ });
165
+ ```
166
+
167
+ That is equivalent to:
168
+
169
+ ```js
170
+ export default defineLocalghostConfig({
171
+ ghostTunnel: {
172
+ namespace: {
173
+ tags: ["route", "project", "owner"],
174
+ spreadTag: "project"
175
+ }
176
+ }
177
+ });
178
+ ```
179
+
180
+ Apps can change the tag order, spread tag, or choose different tag names:
181
+
182
+ ```js
183
+ export default defineLocalghostConfig({
184
+ ghostTunnel: {
185
+ namespace: {
186
+ tags: ["owner", "project", "route"],
187
+ spreadTag: "project"
188
+ }
189
+ }
190
+ });
191
+ ```
192
+
193
+ For custom tags, pass extra values to the constructor:
194
+
195
+ ```ts
196
+ constructGhostTunnelUrl({
197
+ domain: "socialworkouts.app",
198
+ route: "plan",
199
+ project: "summer-base",
200
+ owner: "hamed",
201
+ values: { environment: "preview" },
202
+ ghostTunnel: {
203
+ namespace: ["environment", "route", "project", "owner"]
204
+ }
205
+ });
206
+ ```
207
+
208
+ ## Guardrails
209
+
210
+ - `ghostTunnel` is opt-in and resolves to disabled unless the project config enables it.
211
+ - The default production entry host is `ghost.<your-domain>`, with a wildcard of `*.ghost.<your-domain>`.
212
+ - The default wildcard label must be `route-project-owner`, such as `plan-summer-base-hamed.ghost.socialworkouts.app`.
213
+ - The configured spread tag may contain the namespace separator. By default, that is `project`.
214
+ - Other namespace values cannot include the namespace separator, because parsing must be reversible.
215
+ - Host labels must be DNS-safe lowercase ASCII labels after normalization.
216
+ - HTTPS is required by default. Set `ghostTunnel: { requireHttps: false }` only for controlled non-production testing.
217
+ - Auth is required by default. `assertSecureGhostTunnelRequest` rejects the request unless the app passes `authenticated: true`.
218
+ - Local Caddy and `/etc/hosts` setup do not manage Ghost Tunnel. They stay local-development-only.
219
+
220
+ ## Relay Security
221
+
222
+ Localghost relay is private by default. Public requests can select a Ghost Tunnel route, but they must never select the local target URL, hostname, IP, or port. There must be no `/proxy?url=...` style endpoint.
223
+
224
+ Route registration goes through an authenticated local agent:
225
+
226
+ ```ts
227
+ import {
228
+ createRelayRouteRegistration,
229
+ signRelayRouteClaim
230
+ } from "@hamedb89/localghost";
231
+
232
+ const claim = signRelayRouteClaim({
233
+ host: "plan-summer-base-hamed.ghost.socialworkouts.app",
234
+ scope: "socialworkouts:preview",
235
+ agentId: "local-agent-1",
236
+ expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString()
237
+ }, signingSecret);
238
+
239
+ const route = createRelayRouteRegistration({
240
+ authorizationHeader: request.headers.get("authorization"),
241
+ agentToken,
242
+ claimToken: claim.token,
243
+ signingSecret,
244
+ expectedScope: "socialworkouts:preview",
245
+ target: { host: "127.0.0.1", port: 5173 },
246
+ passwordProtected: true
247
+ });
248
+ ```
249
+
250
+ The relay helpers enforce these rules:
251
+
252
+ - Route registration requires a matching `Bearer <agentToken>`.
253
+ - Route claims are exact hostnames, signed, scoped, and expiring.
254
+ - Wildcard route claims are rejected.
255
+ - Targets must be explicit `{ host, port, protocol }` objects, never arbitrary URL strings.
256
+ - Default target hosts are only `localhost`, `127.0.0.1`, and `::1`.
257
+ - Blocked ports are `22`, `2375`, `2376`, `5432`, `6379`, `9200`, `9229`, and `27017`.
258
+ - LAN/private-network targets require explicit target-policy opt-in and explicit allowed hosts.
259
+ - Private previews require password or app auth unless `publicMode: true` is explicitly set.
260
+ - `isRelayRouteActive(route, { agentConnected })` expires routes when the local agent disconnects or the claim expires.
261
+ - Default limits cover request body size, response size, timeout, concurrency, per-route rate, and per-IP rate.
262
+ - `stripRelayForwardHeaders()` removes hop-by-hop and `x-localghost-*` internal headers before forwarding.
263
+ - `redactRelayHeaders()` and `redactRelayLogUrl()` redact `Authorization`, `Cookie`, `Set-Cookie`, and token-like query params from logs.
264
+ - `renderRelayOfflineResponse()` returns a safe offline page with no secrets or stack traces.
265
+ - Vite integration continues to generate explicit `allowedHosts`; it never sets `allowedHosts: true`.
266
+
267
+ Run the guardrail tests locally:
268
+
269
+ ```sh
270
+ npm test
271
+ npm run test:cli
272
+ npm run test:coverage
273
+ ```
274
+
275
+ `npm test` checks the built package surface. `npm run test:cli` runs the local CLI smoke checks. `npm run test:coverage` imports the source modules and enforces coverage thresholds for `src/relay.ts` and `src/tunnel.ts`.
276
+
277
+ ## Custom Subdomain
278
+
279
+ Use a custom entry label only when the production route truly needs it:
280
+
281
+ ```js
282
+ export default defineLocalghostConfig({
283
+ ghostTunnel: {
284
+ subdomain: "preview"
285
+ }
286
+ });
287
+ ```
288
+
289
+ That changes the wildcard to:
290
+
291
+ ```txt
292
+ <route>-<project>-<owner>.preview.example.app
293
+ ```
package/docs/github.md CHANGED
@@ -4,11 +4,11 @@ Use this copy for the GitHub repo About box, topics, and social cards. Keep it s
4
4
 
5
5
  ## Repository Description
6
6
 
7
- Friendly local hostnames for app repos. A tiny CLI for `.localghost` configs, `/etc/hosts` blocks, Caddy reverse proxies, and Vite `allowedHosts`.
7
+ Friendly local hostnames for app repos. Install the dev dependency, run `yarn dev`, and get clean `.localhost` URLs with Caddy and Vite-safe hosts.
8
8
 
9
9
  Shorter alternative:
10
10
 
11
- Friendly local hostnames for app repos, powered by `.localghost`, Caddy, `/etc/hosts`, and Vite.
11
+ Friendly local hostnames for app repos. `yarn add -D`, `yarn dev`, ready.
12
12
 
13
13
  ## Topics
14
14
 
@@ -62,7 +62,7 @@ After creating `hamedb89/localghost`, this sets the public repo metadata:
62
62
 
63
63
  ```sh
64
64
  gh repo edit hamedb89/localghost \
65
- --description "Friendly local hostnames for app repos. A tiny CLI for .localghost configs, /etc/hosts blocks, Caddy reverse proxies, and Vite allowedHosts." \
65
+ --description "Friendly local hostnames for app repos. Install the dev dependency, run yarn dev, and get clean .localhost URLs with Caddy and Vite-safe hosts." \
66
66
  --homepage "https://hamedb89.github.io/localghost/" \
67
67
  --add-topic localhost \
68
68
  --add-topic local-development \
@@ -78,13 +78,13 @@ gh repo edit hamedb89/localghost \
78
78
 
79
79
  ## README Opening Shape
80
80
 
81
- The first visible paragraph should say what it is, who it is for, and what tools it touches:
81
+ The first visible paragraph should make the entrypoint feel obvious before it gets into configuration:
82
82
 
83
83
  ```txt
84
- Localghost is a tiny Node.js CLI for 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.
84
+ 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.
85
85
  ```
86
86
 
87
- That phrasing helps GitHub search and npm search without making the README feel like SEO sludge.
87
+ Then the next docs layer can explain `.localghost`, Caddy, `/etc/hosts`, Vite `allowedHosts`, and configuration options.
88
88
 
89
89
  ## GitHub Pages
90
90
 
@@ -23,7 +23,7 @@ localghost print [--config file] [--config-pattern regex]
23
23
 
24
24
  ## Description
25
25
 
26
- Localghost reads `.localghost`, optionally reads `localghost.config.mjs`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local proxy. HTTP is the default; local HTTPS is explicit with `--https`, `--ssl`, or `https: true` in `localghost.config.mjs`. It is intentionally small and explicit: no hidden installs, no full hosts-file rewrites, no surprise browser tabs, and no broad Vite `allowedHosts: true` shortcut.
26
+ Localghost reads `.localghost`, optionally reads `localghost.config.mjs`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local proxy. The project name is derived from `package.json`, port `5173` is the fallback, HTTP is the default, dynamic ports are on by default, and local HTTPS is explicit with `--https`, `--ssl`, or `https: true` in `localghost.config.mjs`. It is intentionally small and explicit: no hidden installs, no full hosts-file rewrites, no surprise browser tabs, and no broad Vite `allowedHosts: true` shortcut.
27
27
 
28
28
  Localghost checks npm for newer releases after successful commands. The check is best-effort, cached for 24 hours, and can be disabled with `LOCALGHOST_NO_UPDATE_CHECK=1` or `--no-update-check`.
29
29
 
@@ -143,12 +143,18 @@ Resolves one Localghost context, ensures setup is ready, writes the runtime Cadd
143
143
  ```sh
144
144
  localghost run -- vite
145
145
  localghost run --trust -- vite
146
- localghost run --dynamic-port -- turbo dev
146
+ localghost run --dynamic-port=no -- vite
147
147
  ```
148
148
 
149
- Pass `--dynamic-port` or `--dynamic-port=yes` to start at the configured port and walk upward until `127.0.0.1:<port>` is free. Pass `--setup` to explicitly allow setup when the hosts block is missing or stale.
149
+ By default, Localghost starts at the configured port and walks upward until `127.0.0.1:<port>` is free. Pass `--dynamic-port=no` when you want strict fixed-port behavior. Pass `--setup` to explicitly allow setup when the hosts block is missing or stale.
150
150
 
151
- When `localghost.config.mjs` exists, `run`, `dev`, `setup`, `status`, `routes`, and the Vite plugin use it as the shared context. This keeps `https`, `dynamicPort`, `project`, and `wwwAlias` decisions consistent.
151
+ When `localghost.config.mjs` exists, `run`, `dev`, `setup`, `status`, `routes`, and the Vite plugin use it as an override layer. Most repos can skip it; add it only for decisions like `https: true`, `dynamicPort: false`, `wwwAlias: false`, custom ports, explicit project names, or the production `ghostTunnel` opt-in.
152
+
153
+ `ghostTunnel` does not change local Caddy or `/etc/hosts` setup. It marks `<route>-<project>-<owner>.ghost.<domain>` as a production app entrypoint. Use `ghostTunnel: { domains: "example.com", mode: "manual" }` when the production base domain is known, or omit `domains` to keep logs wildcarded as `https://<route>-<project>-<owner>.ghost.*/`. Production code can call `readLocalghostProjectConfig()`, `constructGhostTunnelUrl()`, and `assertSecureGhostTunnelRequest()` to read the flag, construct default tunnel URLs, validate the wildcard host shape, require HTTPS by default, and require an app-authenticated request by default.
154
+
155
+ Relay helpers are private by default. Registration requires an authenticated local-agent bearer token plus an exact signed route claim. Targets must be explicit local host/port objects, dangerous ports are blocked, private/LAN targets require explicit opt-in, internal and hop-by-hop headers are stripped, sensitive logs are redacted, and offline agents get a safe 503 page.
156
+
157
+ When `ghostTunnel` is configured, route output and Vite startup logs print the production URL shape. Manual mode can fill local defaults for `route`, `project`, and `owner`; public mode leaves those slots as `<route>`, `<project>`, and `<owner>` unless `ghostTunnel.preview` pins a concrete URL. Add `ghostTunnel.domains` to fill one or more production base domains. When `ghostTunnel.preview` is configured with `route`, `project`, and `owner`, logs print the concrete URL, inheriting `ghostTunnel.domains` unless `preview.domain` is set. In an interactive Vite terminal, press `g` to show Ghost Tunnel configuration and open a numbered concrete URL.
152
158
 
153
159
  `dev` and `run` register active sessions in a user-local activity file so `localghost ps` can show what is running across projects.
154
160
 
@@ -1,8 +1,10 @@
1
1
  # Localghost macOS Widget
2
2
 
3
- The Localghost widget is a tiny native macOS menu-bar helper. It does not start or stop apps. It only shows what Localghost already knows is running from `localghost ps --json`.
3
+ The Localghost widget is a tiny native macOS helper. It does not start or stop apps. It reads Localghost's shared activity file directly and shows the known setup/running instances.
4
4
 
5
- The menu-bar title is `LG n`, where `n` is the number of active Localghost-managed sessions. The menu lists each project, wrapper PID, working directory, route, target port, and whether the upstream port is listening.
5
+ The menu-bar title is `LG n`, where `n` is the number of known Localghost setups across the machine. The menu lists each project, working directory, route, target port, and whether the upstream port is listening. One widget tracks all setup instances; you do not run one widget per project. Running/listening routes use the green status dot, while configured but idle routes stay visible without the green state.
6
+
7
+ The app also opens a small floating desktop widget using the visual direction from `Resources/localghost-widget-ui-reference.png`: dark rounded panel, Localghost title, online count, route rows, ports, and an open-first-host footer. The black/white logo source at `Resources/localghost-logo-source.png` is bundled and processed at runtime into the app image, template menu-bar icon, and white panel logo.
6
8
 
7
9
  ## Build
8
10
 
@@ -19,6 +21,31 @@ The app is written to:
19
21
  dist/LocalghostWidget.app
20
22
  ```
21
23
 
24
+ The bundle includes:
25
+
26
+ ```txt
27
+ Contents/Resources/localghost-logo-source.png
28
+ Contents/Resources/localghost-widget-ui-reference.png
29
+ ```
30
+
31
+ ## Targets
32
+
33
+ The macOS widget code is split into three slices:
34
+
35
+ ```txt
36
+ apps/macos-widget/LocalghostWidget.swift
37
+ apps/macos-widget/Shared/LocalghostWidgetSnapshot.swift
38
+ apps/macos-widget/WidgetExtension/LocalghostDesktopWidget.swift
39
+ apps/macos-widget/project.yml
40
+ ```
41
+
42
+ - `LocalghostWidget.swift`: menu-bar helper and floating glass desktop panel.
43
+ - `Shared/LocalghostWidgetSnapshot.swift`: Codable snapshot contract shared by the helper app and WidgetKit extension.
44
+ - `WidgetExtension/LocalghostDesktopWidget.swift`: WidgetKit extension source for a real macOS desktop widget.
45
+ - `project.yml`: XcodeGen target definition for the containing app and the WidgetKit extension.
46
+
47
+ The helper app reads Localghost's live activity file and writes a smaller snapshot to the shared widget store. The WidgetKit target reads that snapshot because system widgets run in an extension context and should not depend on shell commands or arbitrary home-directory paths.
48
+
22
49
  ## Run
23
50
 
24
51
  If `localghost` is installed on your shell path, launch the app bundle normally.
@@ -29,18 +56,53 @@ For source development, point the widget at the repo build:
29
56
  LOCALGHOST_CLI="$PWD/dist/cli.js" dist/LocalghostWidget.app/Contents/MacOS/LocalghostWidget
30
57
  ```
31
58
 
32
- ## Data Source
59
+ ## Desktop Widget Model
60
+
61
+ This helper behaves like a desktop widget: it is a small glass panel that can sit on the desktop and is shown or hidden from the menu-bar icon. Install or run it like a normal macOS app, then use the Localghost icon in the top bar to show or hide the panel.
62
+
63
+ The separate WidgetKit target is the source needed for a system desktop widget. To make it addable from macOS "Edit Widgets":
33
64
 
34
- The widget polls:
65
+ 1. Create or open an Xcode macOS app project for `LocalghostWidget`.
66
+ 2. Add `LocalghostWidget.swift`, `Shared/LocalghostWidgetSnapshot.swift`, and the resources to the app target.
67
+ 3. Add a Widget Extension target named `LocalghostDesktopWidgetExtension`.
68
+ 4. Add `WidgetExtension/LocalghostDesktopWidget.swift` and `Shared/LocalghostWidgetSnapshot.swift` to the extension target.
69
+ 5. Enable the same App Group on both targets: `group.app.localghost`.
70
+ 6. Sign and run/open the containing app once.
71
+ 7. Control-click the desktop, choose `Edit Widgets`, search for `Localghost`, and add the widget.
72
+
73
+ The raw `npm run macos:widget:build` script builds only the standalone menu-bar helper. WidgetKit discovery requires the Xcode app + extension bundle/signing flow above.
74
+
75
+ If XcodeGen is installed, generate the Xcode project with:
35
76
 
36
77
  ```sh
37
- localghost --no-update-check ps --json
78
+ cd apps/macos-widget
79
+ xcodegen generate
80
+ open LocalghostWidget.xcodeproj
38
81
  ```
39
82
 
40
- That command reads the user-local activity file, prunes stale records, and probes each upstream port. The activity file defaults to:
83
+ Then set your development team and App Group identifier before building/running the app from Xcode.
84
+
85
+ ## Data Source
86
+
87
+ The helper reads:
41
88
 
42
89
  ```txt
43
90
  ~/.local/state/localghost/activity.json
44
91
  ```
45
92
 
93
+ The helper writes the WidgetKit snapshot to the App Group container when available:
94
+
95
+ ```txt
96
+ group.app.localghost/LocalghostWidgetSnapshot.json
97
+ ```
98
+
99
+ The CLI can inspect the same state with:
100
+
101
+ ```sh
102
+ localghost ps
103
+ localghost ps --json
104
+ ```
105
+
106
+ The activity file stores setup records plus active run records. `localghost setup` registers configured projects in that shared file. `localghost dev`, `localghost run`, and the Vite plugin overlay active process data on top. `localghost reset` and `localghost teardown` remove the setup from the shared file.
107
+
46
108
  Set `LOCALGHOST_ACTIVITY_PATH` when you want the CLI and widget to share a custom activity file during tests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamedb89/localghost",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Friendly local hostnames for app repos with .localghost, Caddy, /etc/hosts, and Vite.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,12 +44,16 @@
44
44
  "build": "tsup src/index.ts src/vite.ts src/cli.ts --format esm --dts",
45
45
  "macos:widget:build": "bash apps/macos-widget/build.sh",
46
46
  "dev": "tsx src/cli.ts",
47
+ "test": "npm run build && node --test tests/*.test.mjs",
48
+ "test:coverage": "npm run build && LOCALGHOST_TEST_SOURCE=1 node --import tsx --test --experimental-test-coverage --test-coverage-include=src/relay.ts --test-coverage-include=src/tunnel.ts --test-coverage-lines=90 --test-coverage-branches=75 --test-coverage-functions=100 tests/*.test.mjs",
49
+ "test:cli": "npm run build && node --test tests/cli.test.mjs",
47
50
  "typecheck": "tsc --noEmit",
48
51
  "prepack": "npm run build",
49
52
  "prepublishOnly": "npm run release:check",
50
53
  "pack:dry": "npm pack --dry-run",
51
- "release:check": "npm run version:check && npm run typecheck && npm run build && npm run site:build && npm pack --dry-run",
54
+ "release:check": "npm run version:check && npm run typecheck && npm test && npm run site:build && npm pack --dry-run",
52
55
  "site:build": "node scripts/build-site.mjs",
56
+ "site:serve": "node scripts/serve-site.mjs",
53
57
  "sync:version": "node scripts/sync-readme-version.mjs",
54
58
  "version:check": "node scripts/sync-readme-version.mjs --check",
55
59
  "version": "npm run sync:version && git add README.md src/update-check.ts",
@@ -1,31 +0,0 @@
1
- type DevHostEntry = {
2
- host: string;
3
- port: number;
4
- target: string;
5
- };
6
- declare function parseDevHosts(input: string, fileName?: string): DevHostEntry[];
7
- declare function findLocalMdnsHosts(entries: DevHostEntry[]): string[];
8
-
9
- declare const LOCALGHOST_CONFIG_FILE = ".localghost";
10
- type ConfigPattern = string | RegExp;
11
- type ReadDevHostsOptions = {
12
- cwd?: string;
13
- fileName?: string;
14
- configFiles?: string[];
15
- configPattern?: ConfigPattern;
16
- };
17
- type ResolvedDevHostsPath = {
18
- path: string;
19
- fileName: string;
20
- exists: boolean;
21
- searchedFiles: string[];
22
- configPattern?: ConfigPattern;
23
- };
24
- declare function getConfigFileCandidates(options?: ReadDevHostsOptions): string[];
25
- declare function resolveDevHostsPath(options?: ReadDevHostsOptions): ResolvedDevHostsPath;
26
- declare function getDevHostsPath(options?: ReadDevHostsOptions): string;
27
- declare function readDevHosts(options?: ReadDevHostsOptions | string): DevHostEntry[];
28
- declare function getProjectName(cwd?: string): string;
29
- declare function sanitizeProjectName(value: string): string;
30
-
31
- export { type ConfigPattern as C, type DevHostEntry as D, LOCALGHOST_CONFIG_FILE as L, type ReadDevHostsOptions as R, type ResolvedDevHostsPath as a, getDevHostsPath as b, getProjectName as c, resolveDevHostsPath as d, findLocalMdnsHosts as f, getConfigFileCandidates as g, parseDevHosts as p, readDevHosts as r, sanitizeProjectName as s };