@openparachute/app 0.2.0-rc.4
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/.parachute/config/schema +62 -0
- package/.parachute/info +14 -0
- package/.parachute/module.json +14 -0
- package/CHANGELOG.md +405 -0
- package/LICENSE +661 -0
- package/bin/parachute-app.ts +525 -0
- package/dist/admin/assets/index-BXlRNPxk.js +60 -0
- package/dist/admin/assets/index-DaGP1hmw.css +1 -0
- package/dist/admin/index.html +14 -0
- package/package.json +51 -0
- package/src/admin-routes.ts +884 -0
- package/src/auth.ts +212 -0
- package/src/bootstrap.ts +153 -0
- package/src/cache-headers.ts +106 -0
- package/src/config.ts +289 -0
- package/src/dcr.ts +334 -0
- package/src/dev-injection.ts +166 -0
- package/src/dev-mode.ts +205 -0
- package/src/dev-routes.ts +380 -0
- package/src/dev-watcher.ts +479 -0
- package/src/http-server.ts +533 -0
- package/src/index.ts +394 -0
- package/src/meta-schema.ts +662 -0
- package/src/npm-fetch.ts +320 -0
- package/src/operator-token.ts +95 -0
- package/src/provision-schema.ts +180 -0
- package/src/self-register.ts +155 -0
- package/src/services-manifest.ts +104 -0
- package/src/ui-registry.ts +202 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://parachute.computer/schemas/app-config.json",
|
|
4
|
+
"title": "parachute-app config",
|
|
5
|
+
"description": "Runtime configuration for parachute-app. Persisted at $PARACHUTE_HOME/app/config.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"hub_url": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"format": "uri",
|
|
12
|
+
"default": "http://127.0.0.1:1939",
|
|
13
|
+
"description": "Hub origin for DCR registration."
|
|
14
|
+
},
|
|
15
|
+
"auto_register_oauth_clients": {
|
|
16
|
+
"type": "boolean",
|
|
17
|
+
"default": true,
|
|
18
|
+
"description": "Auto-register each added UI as an OAuth client with hub."
|
|
19
|
+
},
|
|
20
|
+
"disabled": {
|
|
21
|
+
"type": "boolean",
|
|
22
|
+
"default": false,
|
|
23
|
+
"description": "Stop hosting all UIs. Useful for maintenance."
|
|
24
|
+
},
|
|
25
|
+
"default_scope_required": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": { "type": "string" },
|
|
28
|
+
"default": ["vault:*:read"],
|
|
29
|
+
"description": "Fallback OAuth scopes when a UI's meta.json doesn't declare any."
|
|
30
|
+
},
|
|
31
|
+
"dev_mode_allowed": {
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"default": true,
|
|
34
|
+
"description": "Whether `parachute-app dev <name>` is permitted."
|
|
35
|
+
},
|
|
36
|
+
"bootstrap_default_apps": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": false,
|
|
39
|
+
"default": { "enabled": true, "apps": ["@openparachute/notes-ui"] },
|
|
40
|
+
"description": "On first boot (when uis/ is empty), apps auto-installs these npm packages so operators get something working out of the box. Set enabled=false to skip; set apps=[] to disable without flipping the toggle.",
|
|
41
|
+
"properties": {
|
|
42
|
+
"enabled": {
|
|
43
|
+
"type": "boolean",
|
|
44
|
+
"default": true,
|
|
45
|
+
"description": "If true, bootstrap default apps on first boot when uis/ is empty."
|
|
46
|
+
},
|
|
47
|
+
"apps": {
|
|
48
|
+
"type": "array",
|
|
49
|
+
"items": { "type": "string" },
|
|
50
|
+
"default": ["@openparachute/notes-ui"],
|
|
51
|
+
"description": "npm package specifiers to install as default apps on first boot."
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"auto_provision_required_schema": {
|
|
56
|
+
"type": "boolean",
|
|
57
|
+
"default": true,
|
|
58
|
+
"description": "When a UI's meta.json declares required_schema, auto-provision its tags in vault on add. Best-effort: errors log + warn but don't fail the install. See patterns#57."
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"x-scopes": ["app:read", "app:admin"]
|
|
62
|
+
}
|
package/.parachute/info
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "parachute-app",
|
|
3
|
+
"displayName": "App",
|
|
4
|
+
"tagline": "Host module for custom Parachute UIs — drop a built bundle in and serve it under one origin.",
|
|
5
|
+
"version": "0.2.0-rc.2",
|
|
6
|
+
"capabilities": [
|
|
7
|
+
"ui-host",
|
|
8
|
+
"oauth-client-registration",
|
|
9
|
+
"admin-spa",
|
|
10
|
+
"dev-mode-sse",
|
|
11
|
+
"bootstrap-default-apps",
|
|
12
|
+
"auto-provision-schema"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app",
|
|
3
|
+
"manifestName": "parachute-app",
|
|
4
|
+
"displayName": "App",
|
|
5
|
+
"tagline": "Host module for custom Parachute UIs — drop a built bundle in and serve it under one origin.",
|
|
6
|
+
"port": 1946,
|
|
7
|
+
"paths": ["/app", "/.parachute"],
|
|
8
|
+
"stripPrefix": false,
|
|
9
|
+
"health": "/app/healthz",
|
|
10
|
+
"uiUrl": "/app/admin/",
|
|
11
|
+
"managementUrl": "/app/admin/",
|
|
12
|
+
"startCmd": ["parachute-app", "serve"],
|
|
13
|
+
"scopes": { "defines": ["app:read", "app:admin"] }
|
|
14
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file tracks the workspace's two npm-publishable packages
|
|
4
|
+
side-by-side:
|
|
5
|
+
|
|
6
|
+
- `@openparachute/app` (host module, lives in `packages/app-host/`)
|
|
7
|
+
- `@openparachute/app-client` (shared client library, lives in `packages/app-client/`)
|
|
8
|
+
|
|
9
|
+
The admin SPA at `web/admin/` ships inside the host package as
|
|
10
|
+
`dist/admin/`; its version mirrors the host's version.
|
|
11
|
+
|
|
12
|
+
## [app 0.2.0-rc.1] + [app-client 0.1.0-rc.1] - 2026-05-21
|
|
13
|
+
|
|
14
|
+
feat(app): Phase 2.0 — extract `@openparachute/app-client` shared
|
|
15
|
+
library as a sub-package + add `required_schema` to meta.json
|
|
16
|
+
(folds [patterns#57](https://github.com/ParachuteComputer/parachute-patterns/issues/57)).
|
|
17
|
+
|
|
18
|
+
This is the monorepo-restructure release. The repo grows a workspace
|
|
19
|
+
shape with two publishable packages and a workspace-only admin SPA.
|
|
20
|
+
Each hosted app today re-implements OAuth + vault REST + token storage
|
|
21
|
+
from scratch (Notes did this; the Gitcoin Brain UI has its own); the
|
|
22
|
+
new `@openparachute/app-client` package extracts the canonical pattern.
|
|
23
|
+
|
|
24
|
+
Reference: [design doc 2026-05-21-parachute-apps-design.md](https://github.com/ParachuteComputer/parachute.computer/blob/main/design/2026-05-21-parachute-apps-design.md).
|
|
25
|
+
|
|
26
|
+
### Monorepo restructure
|
|
27
|
+
|
|
28
|
+
- `packages/app-host/` — the host module (formerly the entire repo).
|
|
29
|
+
Bumped to `0.2.0-rc.1` (minor for the restructure).
|
|
30
|
+
- `packages/app-client/` — NEW shared library at `0.1.0-rc.1`.
|
|
31
|
+
- Root `package.json` becomes the workspace root (private
|
|
32
|
+
`@openparachute/app-monorepo`). Workspaces: `packages/*` + `web/admin`.
|
|
33
|
+
- `web/admin/` (admin SPA) unchanged in shape; build output redirected
|
|
34
|
+
to `packages/app-host/dist/admin/` so the daemon's `defaultAdminDir`
|
|
35
|
+
still resolves correctly. Bumped to `0.2.0-rc.1` to mirror the host.
|
|
36
|
+
|
|
37
|
+
### `@openparachute/app-client` 0.1.0-rc.1 — public surface
|
|
38
|
+
|
|
39
|
+
Tree-shake-friendly subpath exports + a barrel:
|
|
40
|
+
|
|
41
|
+
| Subpath | Surface |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `oauth` | `ParachuteOAuth` driver class (PKCE + same-hub auto-trust); `PendingApprovalError`, `RefreshHttpError`, `InsecureContextError` |
|
|
44
|
+
| `vault-client` | `VaultClient` REST client with auto-refresh on 401/403; `VaultAuthError` (carries `errorType` per notes#150), `VaultNotFoundError`, `VaultUnreachableError`, `VaultConflictError`, `VaultTargetExistsError`, `VaultUploadError` |
|
|
45
|
+
| `token-storage` | `loadToken` / `saveToken` / `clearToken` / `clearAllTokensForApp`; key format `parachute_token:<app-name>:<vault-scope>`; auto-prunes expired tokens that have no refresh_token |
|
|
46
|
+
| `sw-reload` | `reloadAfterServiceWorkerUpdate` (lifted from notes#148) |
|
|
47
|
+
| `vault-id` | `vaultIdFromUrl` + `normalizeVaultUrl` (notes#149 URL-drift fix) |
|
|
48
|
+
|
|
49
|
+
Notes-canonical implementation extracted with the following deltas:
|
|
50
|
+
- All paths handle the no-`window` / SSR case (token-storage falls back
|
|
51
|
+
to a `NULL_STORAGE` shim; sessionStorage in `ParachuteOAuth` likewise).
|
|
52
|
+
- Cursor pagination (`queryNotesCursor`) reads `X-Next-Cursor` and
|
|
53
|
+
preserves the cursor through the auth-retry path.
|
|
54
|
+
- `ParachuteOAuth.beginFlow` accepts a `vaultName` opt that adds the
|
|
55
|
+
`vault=<name>` hint to `/oauth/authorize` for the multi-vault
|
|
56
|
+
narrow-on-pick pattern Notes uses today.
|
|
57
|
+
|
|
58
|
+
### `@openparachute/app` 0.2.0-rc.1 — meta.json `required_schema`
|
|
59
|
+
|
|
60
|
+
Per patterns#57 ("Surfaces declare required vault schema"), `meta.json`
|
|
61
|
+
gains an optional `required_schema` field:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"required_schema": {
|
|
66
|
+
"tags": [
|
|
67
|
+
{
|
|
68
|
+
"name": "capture",
|
|
69
|
+
"description": "Quick captures",
|
|
70
|
+
"fields": {
|
|
71
|
+
"source": { "type": "string", "required": true },
|
|
72
|
+
"createdAt": { "type": "date" }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Phase 2.0 scope: **validate + surface in admin SPA**. Phase 2.1+ will
|
|
81
|
+
auto-provision missing tag-identity rows in vault via
|
|
82
|
+
`VaultClient.updateTag` at install time; that's tracked separately.
|
|
83
|
+
|
|
84
|
+
The admin SPA's modules table grows a per-row "Schema requirements"
|
|
85
|
+
expandable summary; the per-UI info page renders the full declaration.
|
|
86
|
+
|
|
87
|
+
### Verified
|
|
88
|
+
|
|
89
|
+
| Suite | Before | After |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `bun test packages/app-host/src/` | 270 / 0 | 281 / 0 |
|
|
92
|
+
| `bun test packages/app-client/src/` | n/a | 80 / 0 |
|
|
93
|
+
| `cd web/admin && bun run test` | 31 / 0 | 40 / 0 |
|
|
94
|
+
|
|
95
|
+
Typecheck clean (`tsc --noEmit` across all three). Build clean
|
|
96
|
+
(`bun run build` from root builds app-client then app-host).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## [0.1.0-rc.4] - 2026-05-22
|
|
101
|
+
|
|
102
|
+
feat(app): Phase 1.3 — dev mode with SSE live-reload (closes Phase 1).
|
|
103
|
+
|
|
104
|
+
Phase 1.3 closes Phase 1 of parachute-app and resolves the recurring
|
|
105
|
+
"edit code, build, browser shows old" frustration tracked in
|
|
106
|
+
[parachute-notes#151](https://github.com/ParachuteComputer/parachute-notes/issues/151)
|
|
107
|
+
at the platform level. Adds operator-triggered dev mode: `parachute-app
|
|
108
|
+
dev <name>` flips a UI into a no-cache mode + injects an EventSource
|
|
109
|
+
shim into `index.html` that reloads the tab when the operator runs
|
|
110
|
+
`parachute-app dev <name> --trigger` after a rebuild. Reference:
|
|
111
|
+
[design doc section 18](https://github.com/ParachuteComputer/parachute.computer/blob/main/design/2026-05-21-parachute-apps-design.md#18-caching--reload-strategy).
|
|
112
|
+
|
|
113
|
+
### Added
|
|
114
|
+
|
|
115
|
+
- `src/dev-mode.ts` — process-local, in-memory dev-mode state. One Map
|
|
116
|
+
for `name → { enabled, enabledAt, watchDir?, buildCmd? }`, one Map
|
|
117
|
+
for `name → Set<DevReloadSubscriber>`. Exports `enableDevMode`,
|
|
118
|
+
`disableDevMode`, `isDevMode`, `listDevMode`, `getDevMode`,
|
|
119
|
+
`addSubscriber`, `removeSubscriber`, `broadcastReload`,
|
|
120
|
+
`subscriberCount`, `closeAllSubscribers`, `resetDevMode`. Idempotent
|
|
121
|
+
enable preserves `enabledAt`; disable closes every connected SSE
|
|
122
|
+
stream so the next request resumes production cache headers cleanly.
|
|
123
|
+
- `src/dev-injection.ts` — HTML script-injection (string scan, no
|
|
124
|
+
cheerio dep). Inserts `<script id="parachute-app-dev-reload">` just
|
|
125
|
+
before `</head>`, with fallbacks (`before-script` → `after-body` →
|
|
126
|
+
`append`) for unusual document structures. Idempotent via the marker
|
|
127
|
+
id — re-rendering the same document doesn't duplicate the tag. The
|
|
128
|
+
script body opens an EventSource against `/app/<name>/_dev/reload`
|
|
129
|
+
and `window.location.reload()`s on `reload` events (200ms debounce).
|
|
130
|
+
- `src/dev-routes.ts` — Phase 1.3 HTTP endpoints:
|
|
131
|
+
- `GET /app/<name>/_dev/reload` (UNAUTHENTICATED) — SSE stream;
|
|
132
|
+
404 when the UI isn't in dev mode. Emits a `: connected` keepalive
|
|
133
|
+
on accept; broadcasts `event: reload\ndata: {"timestamp": ...}` on
|
|
134
|
+
trigger. Disconnects clean up via the stream's `cancel` hook.
|
|
135
|
+
- `POST /app/<name>/dev/enable` (`app:admin`) — flip on. Honors
|
|
136
|
+
`config.dev_mode_allowed: false` with 409.
|
|
137
|
+
- `POST /app/<name>/dev/disable` (`app:admin`) — flip off + close
|
|
138
|
+
every subscriber.
|
|
139
|
+
- `POST /app/<name>/dev/trigger` (`app:admin`) — broadcast `reload`;
|
|
140
|
+
409 when dev mode is off. Returns `{ notified: <count> }`.
|
|
141
|
+
- `GET /app/<name>/dev` (`app:read`) — per-UI status.
|
|
142
|
+
- `GET /app/dev/list` (`app:read`) — UIs currently in dev mode.
|
|
143
|
+
- `src/cache-headers.ts` — `cacheHeadersFor` takes a `devMode` boolean.
|
|
144
|
+
When true, every response is `no-cache, no-store, must-revalidate` —
|
|
145
|
+
overrides immutable on hashed assets AND `no-cache` on the PWA SW.
|
|
146
|
+
- `src/http-server.ts` — wires dev-routes ahead of admin routes; per-
|
|
147
|
+
request `isDevMode(meta.name)` check feeds both the cache headers
|
|
148
|
+
and the index.html injection. `serveFileWithHeaders` accepts a
|
|
149
|
+
`devMode` parameter; when true + filename is `index.html`, it parses
|
|
150
|
+
the body via `injectDevReloadScript` before responding. HEAD reports
|
|
151
|
+
the injected byte length.
|
|
152
|
+
- `src/index.ts` — re-exports the dev-mode + dev-injection surface,
|
|
153
|
+
exposes `routeDev` + `DevRoutesOpts`, replaces the Phase 1.3 stub
|
|
154
|
+
`setDevMode` with a real wrapper.
|
|
155
|
+
- `bin/parachute-app.ts` — replaces the Phase 1.3 stub with four
|
|
156
|
+
sub-verbs:
|
|
157
|
+
- `parachute-app dev <name>` — enable (idempotent)
|
|
158
|
+
- `parachute-app dev <name> --off` — disable
|
|
159
|
+
- `parachute-app dev <name> --trigger` — broadcast reload
|
|
160
|
+
- `parachute-app dev list` — show UIs currently in dev mode
|
|
161
|
+
Help text reflects the full Phase 1.3 verb set.
|
|
162
|
+
- `web/admin/src/lib/api.ts` — typed helpers: `enableDevMode`,
|
|
163
|
+
`disableDevMode`, `triggerReload`, `getDevModeStatus`, `listDevMode`.
|
|
164
|
+
- `web/admin/src/routes/Modules.tsx` — per-row "Dev" badge + "Enable
|
|
165
|
+
dev" / "Disable dev" / "Trigger reload" buttons. Refreshes the
|
|
166
|
+
dev-status map alongside the UI list.
|
|
167
|
+
- Tests:
|
|
168
|
+
- `src/__tests__/dev-mode.test.ts` (15 tests) — state, subscribers,
|
|
169
|
+
broadcast reaping.
|
|
170
|
+
- `src/__tests__/dev-injection.test.ts` (10 tests) — happy path +
|
|
171
|
+
idempotence + all three fallback branches + escape defense.
|
|
172
|
+
- `src/__tests__/dev-routes.test.ts` (14 tests) — every endpoint +
|
|
173
|
+
auth gates + SSE subscribe / broadcast / cancel.
|
|
174
|
+
- `src/__tests__/dev-integration.test.ts` (10 tests) — full
|
|
175
|
+
end-to-end via Bun.serve including script injection, no-cache
|
|
176
|
+
override, SSE broadcast, dev-list, HEAD content-length.
|
|
177
|
+
- `src/__tests__/cache-headers.test.ts` — extra coverage for the
|
|
178
|
+
`devMode` parameter.
|
|
179
|
+
- `src/__tests__/cli.test.ts` — refreshed for the new `dev` verbs.
|
|
180
|
+
- `web/admin/src/routes/Modules.test.tsx` — updated to mock the
|
|
181
|
+
`/app/dev/list` companion fetch + assert the new dev controls.
|
|
182
|
+
|
|
183
|
+
### Changed
|
|
184
|
+
|
|
185
|
+
- Bumped to `0.1.0-rc.4`. `.parachute/info` capabilities now include
|
|
186
|
+
`dev-mode-sse`.
|
|
187
|
+
- HTTP server routing: dev-routes dispatcher fires ahead of admin-routes
|
|
188
|
+
so the per-UI `_dev/reload` path doesn't race with the admin matcher.
|
|
189
|
+
- `cacheHeadersFor` signature gains a third `devMode = false` parameter
|
|
190
|
+
(backwards-compatible — existing meta-less callers continue to work).
|
|
191
|
+
- Admin SPA's Modules table grew a "Dev" column; existing layout
|
|
192
|
+
preserved.
|
|
193
|
+
|
|
194
|
+
### Verified
|
|
195
|
+
|
|
196
|
+
- `bun test src/` → 270 pass / 0 fail (was 213).
|
|
197
|
+
- `cd web/admin && bun run test` → 31 pass / 0 fail (was 21).
|
|
198
|
+
- `bun run typecheck` → clean (root + web/admin).
|
|
199
|
+
- `bunx biome check .` → clean.
|
|
200
|
+
- `bun run build` → `dist/admin/` populated.
|
|
201
|
+
- `bin/parachute-app.ts --version` → 0.1.0-rc.4.
|
|
202
|
+
- `bin/parachute-app.ts --help` → shows the four `dev` sub-verbs.
|
|
203
|
+
|
|
204
|
+
## [0.1.0-rc.3] - 2026-05-21
|
|
205
|
+
|
|
206
|
+
feat(app): Phase 1.2 — admin endpoints + DCR + npm-fetch + Vite+React admin SPA.
|
|
207
|
+
|
|
208
|
+
Phase 1.2 takes the bundled-UI-host daemon from "operator manually drops
|
|
209
|
+
dist/ into uis/" to "operator runs `parachute-app add <source>` and the
|
|
210
|
+
daemon handles copy + DCR + re-scan." Adds the admin HTTP surface, the
|
|
211
|
+
Dynamic Client Registration call to hub, an npm-fetch shorthand for
|
|
212
|
+
sourcing UIs by package specifier, and a Vite + React admin SPA mounted
|
|
213
|
+
at `/app/admin/`.
|
|
214
|
+
|
|
215
|
+
### Added
|
|
216
|
+
|
|
217
|
+
- `src/auth.ts` — hub-JWT validation via `@openparachute/scope-guard@^0.3.0`.
|
|
218
|
+
Audience `app`; scopes `app:read` (list/info) and `app:admin` (add/remove/
|
|
219
|
+
reload). `enforceScope` mirrors runner's pattern; `hasReadAccess` lets
|
|
220
|
+
admin imply read.
|
|
221
|
+
- `src/operator-token.ts` — operator bearer sourcing for outbound DCR
|
|
222
|
+
calls. Priority: `PARACHUTE_HUB_TOKEN` env > `~/.parachute/operator.token`
|
|
223
|
+
file (chmod 0o600 required on Unix). Missing token returns undefined; the
|
|
224
|
+
caller decides whether that's fatal.
|
|
225
|
+
- `src/dcr.ts` — RFC 7591 Dynamic Client Registration with hub. Sends
|
|
226
|
+
`client_name`, `redirect_uris` (`/app/<name>/` + `/app/<name>/oauth-callback`),
|
|
227
|
+
`scope` (joined), `token_endpoint_auth_method: "none"`, `grant_types:
|
|
228
|
+
["authorization_code"]`, `response_types: ["code"]`. Persists the returned
|
|
229
|
+
`client_id` to `~/.parachute/app/uis/<name>/.oauth-client.json` (chmod 0o600).
|
|
230
|
+
Surfaces hub errors as a typed `DcrError` (status: hub_unreachable /
|
|
231
|
+
hub_rejected / invalid_response). Best-effort `DELETE /oauth/clients/<id>`
|
|
232
|
+
on remove; tolerates 404/405 (RFC 7592 not universally implemented yet).
|
|
233
|
+
- `src/npm-fetch.ts` — `bun add <spec>` into a `/tmp/parachute-app-staging-*`
|
|
234
|
+
dir, then copies `node_modules/<pkg>/dist/` into the UI's home. Distinguishes
|
|
235
|
+
404 / network / generic errors by sniffing stderr. Cleanup always runs.
|
|
236
|
+
Supports plain names, scoped names, and `@version` tails.
|
|
237
|
+
- `src/admin-routes.ts` — the Phase 1.2 admin endpoints:
|
|
238
|
+
- `GET /app/list` (`app:read`) — serialized UI summaries + skipped list
|
|
239
|
+
- `POST /app/add` (`app:admin`) — accepts local path OR npm spec; copies
|
|
240
|
+
bundle + writes meta.json + (optionally) fires DCR + re-scans
|
|
241
|
+
- `DELETE /app/<name>` (`app:admin`) — revokes OAuth + removes dir +
|
|
242
|
+
re-scans
|
|
243
|
+
- `POST /app/<name>/reload` (`app:admin`) — re-scans without daemon restart
|
|
244
|
+
- `GET /app/<name>/info` (`app:read`) — full info: meta + oauth + paths
|
|
245
|
+
- `GET /app/<name>/oauth-client` — UNAUTHENTICATED — returns
|
|
246
|
+
`{client_id, hub_url, scope, redirect_uris}` for the UI to use at boot
|
|
247
|
+
- Auto-rejects `/app/admin` as a reserved mount path.
|
|
248
|
+
- Validates name + path patterns; rejects collisions with 409.
|
|
249
|
+
- After every mutation: re-runs `scanUis()` + refreshes `services.json`
|
|
250
|
+
with the per-UI `uis` map (design doc section 12 shape).
|
|
251
|
+
- `src/http-server.ts` — wires the admin routes into the existing Bun.serve
|
|
252
|
+
handler. POST/DELETE now flow through the admin matcher; non-admin POST/
|
|
253
|
+
DELETE returns 404 (was 405). Unknown methods still return 405. New
|
|
254
|
+
`/app/admin/[*]` static mount serves the built SPA from `dist/admin/`;
|
|
255
|
+
falls back to a dev-time placeholder when the bundle is absent.
|
|
256
|
+
- `web/admin/` — Vite + React + TypeScript admin SPA. React 19, react-router
|
|
257
|
+
7. Routes: `/` (Modules), `/add` (Add UI form), `/info/:name`. Auth via
|
|
258
|
+
`localStorage["parachute_operator_token"]` (Phase 1.3 wires hub-session
|
|
259
|
+
auth). Builds to root `dist/admin/`. Per-UI Reload + Remove buttons hit
|
|
260
|
+
the live admin endpoints. Skipped UIs surface inline with their failure
|
|
261
|
+
reason.
|
|
262
|
+
- `bin/parachute-app.ts` — `add`, `remove`, `list`, `reload` verbs are no
|
|
263
|
+
longer stubs. Each calls the local daemon's admin endpoints over HTTP
|
|
264
|
+
(`PARACHUTE_APP_URL` env overrides). Sources the operator bearer via the
|
|
265
|
+
same `readOperatorToken` the daemon uses.
|
|
266
|
+
- Tests:
|
|
267
|
+
- `src/__tests__/auth.test.ts` — bearer extraction, scope checks,
|
|
268
|
+
`validateBearer` 401 paths, `getHubOrigin` resolution
|
|
269
|
+
- `src/__tests__/operator-token.test.ts` — env vs file priority, mode
|
|
270
|
+
0o600 defense
|
|
271
|
+
- `src/__tests__/dcr.test.ts` — DCR request shape, operator-bearer
|
|
272
|
+
forwarding, hub-error surfacing, file persistence + revocation
|
|
273
|
+
- `src/__tests__/npm-fetch.test.ts` — spec parsing, fake-bun-add
|
|
274
|
+
integration, error-code mapping
|
|
275
|
+
- `src/__tests__/admin-routes.test.ts` — auth gates + full happy paths
|
|
276
|
+
with the `enforceScopeFn` test seam
|
|
277
|
+
- `src/__tests__/admin-integration.test.ts` — end-to-end add/delete/
|
|
278
|
+
reload through Bun.serve
|
|
279
|
+
- `web/admin/src/lib/api.test.ts` — api.ts wrapper coverage
|
|
280
|
+
- `web/admin/src/routes/Modules.test.tsx` — list view, error banner,
|
|
281
|
+
Reload + Remove button flows
|
|
282
|
+
- `web/admin/src/routes/Add.test.tsx` — form submission shape + success
|
|
283
|
+
rendering
|
|
284
|
+
- `web/admin/src/App.test.tsx` — shell + token banner
|
|
285
|
+
|
|
286
|
+
### Changed
|
|
287
|
+
|
|
288
|
+
- Bumped to `0.1.0-rc.3`. `.parachute/info` capabilities now include
|
|
289
|
+
`admin-spa`.
|
|
290
|
+
- `bin/parachute-app.ts` help text reflects the live `add`/`remove`/`list`/
|
|
291
|
+
`reload` verbs.
|
|
292
|
+
- `src/http-server.ts` 405 policy: POST/DELETE no longer return 405 globally;
|
|
293
|
+
they flow to admin routes and fall through to 404 when no admin route
|
|
294
|
+
matches. PATCH and other unhandled methods still return 405.
|
|
295
|
+
- `package.json#files` now includes `dist/admin/**` so the npm-published
|
|
296
|
+
bundle ships the admin SPA. Added `build` / `test:admin` / `typecheck:all`
|
|
297
|
+
scripts coordinating root + web/admin.
|
|
298
|
+
- `package.json` now depends on `@openparachute/scope-guard@^0.3.0` for
|
|
299
|
+
hub-JWT validation.
|
|
300
|
+
|
|
301
|
+
### Verified
|
|
302
|
+
|
|
303
|
+
- `bun test ./src` → 213 pass / 0 fail (was 117).
|
|
304
|
+
- `cd web/admin && bun run test` → 21 pass / 0 fail.
|
|
305
|
+
- `bun run typecheck` → clean (root + web/admin).
|
|
306
|
+
- `bunx biome check .` → clean.
|
|
307
|
+
- `cd web/admin && bun run build` → `dist/admin/` populated.
|
|
308
|
+
- `bin/parachute-app.ts --version` → 0.1.0-rc.3.
|
|
309
|
+
- `bin/parachute-app.ts --help` → shows full Phase 1.2 verb list.
|
|
310
|
+
|
|
311
|
+
## [0.1.0-rc.2] - 2026-05-22
|
|
312
|
+
|
|
313
|
+
feat(app): Phase 1.1 — core UI hosting with smart cache headers + PWA opt-in.
|
|
314
|
+
|
|
315
|
+
Replaces the Phase 1.0 stub with a real `serve` daemon. App now scans
|
|
316
|
+
`$PARACHUTE_HOME/app/uis/` for declared UIs, validates each meta.json,
|
|
317
|
+
mounts each bundle at its declared path under `/app/`, and serves the
|
|
318
|
+
dist/ contents with smart cache headers + SPA-routing fallback.
|
|
319
|
+
|
|
320
|
+
### Added
|
|
321
|
+
|
|
322
|
+
- `src/config.ts` — load + validate `$PARACHUTE_HOME/app/config.json`,
|
|
323
|
+
with sensible defaults. Missing file is OK; malformed file fails fast.
|
|
324
|
+
Honors `PARACHUTE_HOME` env var.
|
|
325
|
+
- `src/meta-schema.ts` — hand-rolled validator for per-UI meta.json.
|
|
326
|
+
Required fields: `name` (pattern `^[a-z][a-z0-9-]*$`), `displayName`,
|
|
327
|
+
`path` (pattern `^/app/[a-z0-9-]+$`). Optional: `tagline`, `version`,
|
|
328
|
+
`iconUrl`, `scopes_required` (defaults to `["vault:*:read"]`),
|
|
329
|
+
`vault_default`, `pwa` (default false), `pwa_service_worker` (required
|
|
330
|
+
when `pwa: true`), `public` (default false). Exposes
|
|
331
|
+
`InvalidMetaError` with a flat `details` list.
|
|
332
|
+
- `src/ui-registry.ts` — `scanUis()` scans the uis-dir, validates each
|
|
333
|
+
meta.json + dist/index.html, resolves mount-path collisions
|
|
334
|
+
deterministically (alphabetical-by-name wins, losers demoted to
|
|
335
|
+
`status: "collision"`). Returns `{registered, skipped}`. The reserved
|
|
336
|
+
path `/app/admin` is rejected for hosted UIs (admin SPA lands in
|
|
337
|
+
Phase 1.2).
|
|
338
|
+
- `src/cache-headers.ts` — `cacheHeadersFor(filename, meta?)` returns
|
|
339
|
+
smart `Cache-Control` headers per design doc section 18:
|
|
340
|
+
index.html → `no-cache, no-store, must-revalidate`; content-hashed
|
|
341
|
+
assets (matching `[a-f0-9]{8,}`) → `public, max-age=31536000,
|
|
342
|
+
immutable`; non-hashed assets → `public, max-age=3600`; PWA service
|
|
343
|
+
worker (when meta opts in) → `no-cache`.
|
|
344
|
+
- `src/http-server.ts` — Bun.serve loopback HTTP server on port 1946.
|
|
345
|
+
Routes: `GET /healthz` + `GET /app/healthz` (open, returns UI counts),
|
|
346
|
+
`GET /.parachute/info` + `/.parachute/config/schema` + `/.parachute/config`
|
|
347
|
+
(open; no secrets in app config), and per-UI bundle serving with SPA
|
|
348
|
+
fallback. Path-traversal-safe. HEAD requests supported. 405 on
|
|
349
|
+
non-GET methods (Phase 1.2 opens up POST/PUT/DELETE for admin
|
|
350
|
+
endpoints).
|
|
351
|
+
- `src/services-manifest.ts` + `src/self-register.ts` — mirrors runner's
|
|
352
|
+
pattern exactly. Self-registers app's row into
|
|
353
|
+
`~/.parachute/services.json` on `serve` boot. Best-effort: write
|
|
354
|
+
failures are logged + swallowed. Existing operator-set ports are
|
|
355
|
+
preserved across restarts. `extraFields` hook lets Phase 1.2 stamp the
|
|
356
|
+
per-UI `uis` map without changing the signature.
|
|
357
|
+
- `src/__tests__/{config,meta-schema,ui-registry,cache-headers,http-server,self-register,serve,cli}.test.ts` —
|
|
358
|
+
unit + integration coverage. 114 tests total (was 1).
|
|
359
|
+
|
|
360
|
+
### Changed
|
|
361
|
+
|
|
362
|
+
- `bin/parachute-app.ts` — `serve` verb now boots the real daemon (no
|
|
363
|
+
more stub). Wires SIGINT/SIGTERM to graceful shutdown. Help text
|
|
364
|
+
updated to reflect Phase 1.1 capabilities.
|
|
365
|
+
- `src/index.ts` — `serve()` and `runOnce()` implemented; the per-verb
|
|
366
|
+
stubs for Phase 1.2 (`addUi`, `removeUi`, `listUis`, `reloadUi`) and
|
|
367
|
+
Phase 1.3 (`setDevMode`) remain as documented placeholders. Re-exports
|
|
368
|
+
the new modules.
|
|
369
|
+
- `.parachute/info` — version bumped to 0.1.0-rc.2.
|
|
370
|
+
|
|
371
|
+
### Verified
|
|
372
|
+
|
|
373
|
+
- `bun test` → 114 pass / 0 fail.
|
|
374
|
+
- `bun run typecheck` → clean.
|
|
375
|
+
- `bunx biome check .` → clean.
|
|
376
|
+
- Live smoke against `~/.parachute/app/uis/test-ui/`:
|
|
377
|
+
- `/app/healthz` returns `{status:"ok",uis:1,skipped:0}`.
|
|
378
|
+
- `/app/test-ui/` returns the index.html with
|
|
379
|
+
`Cache-Control: no-cache, no-store, must-revalidate`.
|
|
380
|
+
- `/app/test-ui/app.abc12345.js` returns `Cache-Control: public,
|
|
381
|
+
max-age=31536000, immutable`.
|
|
382
|
+
- `/app/test-ui/style.css` returns `Cache-Control: public,
|
|
383
|
+
max-age=3600`.
|
|
384
|
+
- `/app/test-ui/some/spa/route` falls through to index.html.
|
|
385
|
+
- `~/.parachute/services.json` has the parachute-app row with
|
|
386
|
+
`port: 19460`, `paths: ["/app","/.parachute"]`, `installDir`.
|
|
387
|
+
|
|
388
|
+
## [0.1.0-rc.1] - 2026-05-21
|
|
389
|
+
|
|
390
|
+
Initial scaffold per design doc. Module-protocol-compliant skeleton with stub bin and library entry — no UI hosting, no admin endpoints, no OAuth DCR yet. Those land in Phase 1.1+.
|
|
391
|
+
|
|
392
|
+
### Added
|
|
393
|
+
|
|
394
|
+
- `.parachute/module.json` — manifest declaring `port: 1946`, paths `["/app", "/.parachute"]`, health `/app/healthz`, scopes `app:read` + `app:admin`. No `kind` field (per hub#301 migration — `kind` is being dropped from the manifest validator).
|
|
395
|
+
- `.parachute/info` — module identity (name, displayName, tagline, version, capabilities).
|
|
396
|
+
- `.parachute/config/schema` — Draft-07 JSON Schema for `$PARACHUTE_HOME/app/config.json`: `hub_url`, `auto_register_oauth_clients`, `disabled`, `default_scope_required`, `dev_mode_allowed`.
|
|
397
|
+
- `bin/parachute-app.ts` — CLI with `--help` listing planned verbs by phase, `--version` printing from package.json, every subcommand stubbed to a phase-tagged not-yet-implemented message.
|
|
398
|
+
- `src/index.ts` — library surface: `VERSION`, `DEFAULT_PORT`, `DEFAULT_MOUNT`, plus stub functions (`serve`, `runOnce`, `addUi`, `removeUi`, `listUis`, `reloadUi`, `setDevMode`) each throwing a phase-tagged Error.
|
|
399
|
+
- `src/__tests__/scaffold.test.ts` — sanity test asserting `VERSION` matches `package.json#version`.
|
|
400
|
+
- `package.json` — `@openparachute/app@0.1.0-rc.1`, `bin: parachute-app → ./bin/parachute-app.ts`, scripts for `start` / `test` / `typecheck` / `lint`.
|
|
401
|
+
- `tsconfig.json`, `biome.json`, `.gitignore`, `LICENSE` (AGPL-3.0), `README.md` — standard repo scaffolding mirroring parachute-runner.
|
|
402
|
+
|
|
403
|
+
### Design
|
|
404
|
+
|
|
405
|
+
- [`2026-05-21-parachute-apps-design.md`](https://github.com/ParachuteComputer/parachute.computer/blob/main/design/2026-05-21-parachute-apps-design.md)
|