@openparachute/app 0.2.0-rc.4 → 0.2.0-rc.6

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.
@@ -3,6 +3,7 @@
3
3
  "manifestName": "parachute-app",
4
4
  "displayName": "App",
5
5
  "tagline": "Host module for custom Parachute UIs — drop a built bundle in and serve it under one origin.",
6
+ "kind": "api",
6
7
  "port": 1946,
7
8
  "paths": ["/app", "/.parachute"],
8
9
  "stripPrefix": false,
package/CHANGELOG.md CHANGED
@@ -9,6 +9,47 @@ side-by-side:
9
9
  The admin SPA at `web/admin/` ships inside the host package as
10
10
  `dist/admin/`; its version mirrors the host's version.
11
11
 
12
+ ## [app 0.2.0-rc.6] - 2026-05-22
13
+
14
+ fix(app): correct `kind` to `"api"` — app is a backend that proxies,
15
+ not a static-served frontend (folds the in-flight rc.6 per
16
+ [app#14](https://github.com/ParachuteComputer/parachute-app/issues/14)).
17
+
18
+ The initial rc.6 in-flight version carried `"kind": "frontend"` to
19
+ unblock the hub validator (which at rc.13 still required the field).
20
+ That was the wrong value semantically. App is a **backend** that
21
+ serves UI bundles via its own HTTP server — hub's `/app/*` proxy
22
+ forwards to app on `:1946`, then app's HTTP layer serves the admin
23
+ SPA + `notes-ui` + any installed sub-units. Hub does NOT static-serve
24
+ from app's `dist/`; the `"frontend"` framing was inaccurate and
25
+ risked future tooling that branches on `kind === "frontend"` (already
26
+ in `parachute-hub/src/commands/upgrade.ts:376` — which runs
27
+ `bun run build` for kind-frontend modules) treating app as a
28
+ static-bundle module and breaking the runtime HTTP layer.
29
+
30
+ `"api"` is the accurate value: app's role is the backend-proxy lane,
31
+ same as vault / scribe / runner. With hub#327 landing alongside this
32
+ PR — the validator no longer inspects `kind` at all — future app
33
+ releases can drop the field entirely. For now keeping it explicit
34
+ works under both the old validator (rc.13 strict-require) and the
35
+ new (rc.14+ no-validate); safest immediate fix that doesn't gate on
36
+ hub-rc.14 propagation.
37
+
38
+ ## [app 0.2.0-rc.5] - 2026-05-22
39
+
40
+ fix(app): self-register uses `manifestName` as services.json row key
41
+ (matches hub install path; closes duplicate-port bug).
42
+
43
+ Hub installs modules under `manifest.manifestName` (`"parachute-app"`),
44
+ but the boot-time self-registration was writing under the short name
45
+ `"app"`. The two writes left services.json with two rows on the same
46
+ port, which trips hub's duplicate-port detector on re-read
47
+ (`duplicate port 1946 — claimed by both "parachute-app" and "app"`).
48
+
49
+ The row key is now sourced from `.parachute/module.json#manifestName`,
50
+ so the install path and the runtime path converge to one row. Mirrors
51
+ the fix landed in parachute-runner.
52
+
12
53
  ## [app 0.2.0-rc.1] + [app-client 0.1.0-rc.1] - 2026-05-21
13
54
 
14
55
  feat(app): Phase 2.0 — extract `@openparachute/app-client` shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openparachute/app",
3
- "version": "0.2.0-rc.4",
3
+ "version": "0.2.0-rc.6",
4
4
  "description": "Host module for custom Parachute UIs — drop a built bundle in and serve it under one origin.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -31,11 +31,40 @@
31
31
  * When per-UI `uis` map lands (design doc section 12), the caller assembles
32
32
  * the `uis` field and passes it through via `extraFields`.
33
33
  */
34
+ import { readFileSync } from "node:fs";
34
35
  import * as path from "node:path";
35
36
 
36
37
  import pkg from "../package.json" with { type: "json" };
37
38
  import { type ServiceEntry, readServiceEntry, upsertService } from "./services-manifest.ts";
38
39
 
40
+ /**
41
+ * The canonical services.json row key for the app module — sourced from
42
+ * `.parachute/module.json#manifestName`. Hub looks up modules in
43
+ * services.json by `manifestName` (vault + scribe use this convention), so
44
+ * self-registering under the short name "app" would create a duplicate row
45
+ * alongside the hub-installed `parachute-app` row and trip hub's
46
+ * duplicate-port detector on re-read. See parachute-patterns or the
47
+ * `manifestName` field on .parachute/module.json files. */
48
+ const ROW_NAME = resolveManifestName();
49
+
50
+ function resolveManifestName(): string {
51
+ // Read once at module load — `.parachute/module.json` ships in the
52
+ // package and doesn't change at runtime.
53
+ try {
54
+ const manifestPath = path.resolve(import.meta.dir, "..", ".parachute", "module.json");
55
+ const raw = JSON.parse(readFileSync(manifestPath, "utf8")) as { manifestName?: unknown };
56
+ if (typeof raw.manifestName === "string" && raw.manifestName.length > 0) {
57
+ return raw.manifestName;
58
+ }
59
+ } catch {
60
+ // Fall through to the conservative fallback. The module.json is part
61
+ // of the published package, so this branch is effectively unreachable
62
+ // in production — it exists so tests that mount a stub package layout
63
+ // don't hard-crash on import.
64
+ }
65
+ return "parachute-app";
66
+ }
67
+
39
68
  export type SelfRegisterOpts = {
40
69
  /**
41
70
  * The port app just bound. Used only as the first-run fallback — if
@@ -68,7 +97,7 @@ export type SelfRegisterResult = {
68
97
  ok: boolean;
69
98
  /** The path we wrote to (or attempted to write to). */
70
99
  manifestPath: string;
71
- /** True when services.json already had a row for `app` before we wrote. */
100
+ /** True when services.json already had a row for `parachute-app` before we wrote. */
72
101
  hadExistingEntry: boolean;
73
102
  /** The port we ended up stamping (existing-entry port or boundPort). */
74
103
  portWritten: number;
@@ -92,7 +121,7 @@ export function selfRegister(opts: SelfRegisterOpts): SelfRegisterResult {
92
121
 
93
122
  let existing: ServiceEntry | undefined;
94
123
  try {
95
- existing = readServiceEntry("app", manifestPath);
124
+ existing = readServiceEntry(ROW_NAME, manifestPath);
96
125
  } catch (e) {
97
126
  const err = e as Error;
98
127
  logger.warn(`[app] skipped self-register: ${err.message}`);
@@ -107,7 +136,7 @@ export function selfRegister(opts: SelfRegisterOpts): SelfRegisterResult {
107
136
 
108
137
  const portToWrite = existing?.port ?? opts.boundPort;
109
138
  const entry: ServiceEntry = {
110
- name: "app",
139
+ name: ROW_NAME,
111
140
  port: portToWrite,
112
141
  paths: ["/app", "/.parachute"],
113
142
  health: "/app/healthz",