@openparachute/hub 0.6.5-rc.3 → 0.6.5-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/package.json +4 -1
- package/src/__tests__/hub.test.ts +27 -1
- package/src/__tests__/notes-serve.test.ts +10 -0
- package/src/commands/serve.ts +11 -2
- package/src/notes-serve.ts +24 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openparachute/hub",
|
|
3
|
-
"version": "0.6.5-rc.
|
|
3
|
+
"version": "0.6.5-rc.4",
|
|
4
4
|
"description": "parachute — the local hub for the Parachute ecosystem (discovery, ports, lifecycle, soon OAuth).",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"start": "bun src/cli.ts",
|
|
29
|
+
"build:depcheck": "[ ! -f packages/depcheck/package.json ] || [ -f packages/depcheck/dist/index.js ] || bun run --cwd packages/depcheck build",
|
|
30
|
+
"prepare": "bun run build:depcheck",
|
|
31
|
+
"pretest": "bun run build:depcheck",
|
|
29
32
|
"test": "bun test ./src",
|
|
30
33
|
"lint": "biome check .",
|
|
31
34
|
"lint:fix": "biome check --write .",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { renderHub, writeHubFile } from "../hub.ts";
|
|
@@ -301,4 +301,30 @@ describe("writeHubFile", () => {
|
|
|
301
301
|
rmSync(dir, { recursive: true, force: true });
|
|
302
302
|
}
|
|
303
303
|
});
|
|
304
|
+
|
|
305
|
+
// hub#171: serve regenerates hub.html on EVERY start (the `!existsSync`
|
|
306
|
+
// guard in commands/serve.ts was dropped), so writeHubFile must be safe to
|
|
307
|
+
// call when the file already exists and must overwrite stale content with
|
|
308
|
+
// a fresh render from current code — otherwise an upgrade serves the old
|
|
309
|
+
// page until an unrelated `parachute expose` re-runs.
|
|
310
|
+
test("overwrites a pre-existing stale hub.html with a fresh render (hub#171)", () => {
|
|
311
|
+
const dir = mkdtempSync(join(tmpdir(), "pcli-hub-"));
|
|
312
|
+
try {
|
|
313
|
+
const path = join(dir, "well-known", "hub.html");
|
|
314
|
+
// First write creates the well-known dir + a real file; then plant
|
|
315
|
+
// stale content to simulate an old hub.html left over from a prior code
|
|
316
|
+
// version after `git pull` + `parachute restart hub`.
|
|
317
|
+
writeHubFile(path);
|
|
318
|
+
writeFileSync(path, "<!-- stale pre-upgrade hub.html -->");
|
|
319
|
+
expect(readFileSync(path, "utf8")).toContain("stale");
|
|
320
|
+
|
|
321
|
+
const written = writeHubFile(path);
|
|
322
|
+
expect(written).toBe(path);
|
|
323
|
+
const content = readFileSync(path, "utf8");
|
|
324
|
+
expect(content).not.toContain("stale");
|
|
325
|
+
expect(content).toBe(renderHub());
|
|
326
|
+
} finally {
|
|
327
|
+
rmSync(dir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
304
330
|
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
normalizeMount,
|
|
7
7
|
notesDistCandidates,
|
|
8
8
|
notesFetch,
|
|
9
|
+
notesServeOptions,
|
|
9
10
|
resolveNotesDistFrom,
|
|
10
11
|
} from "../notes-serve.ts";
|
|
11
12
|
|
|
@@ -26,6 +27,15 @@ function req(path: string): Request {
|
|
|
26
27
|
return new Request(`http://127.0.0.1${path}`);
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
describe("notesServeOptions (hub#399 residual)", () => {
|
|
31
|
+
test("sets idleTimeout: 255 to outlast edge keep-alive pools, matching hub-server.ts", () => {
|
|
32
|
+
const opts = notesServeOptions(5173, "/tmp/dist", "/notes");
|
|
33
|
+
expect(opts.idleTimeout).toBe(255);
|
|
34
|
+
expect(opts.port).toBe(5173);
|
|
35
|
+
expect(typeof opts.fetch).toBe("function");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
29
39
|
describe("normalizeMount", () => {
|
|
30
40
|
test("strips trailing slashes", () => {
|
|
31
41
|
expect(normalizeMount("/notes/")).toBe("/notes");
|
package/src/commands/serve.ts
CHANGED
|
@@ -337,13 +337,22 @@ export async function serve(opts: ServeOpts = {}): Promise<{
|
|
|
337
337
|
// hatch for setups that want loopback-only inside a sidecar.
|
|
338
338
|
const hostname = env.PARACHUTE_BIND_HOST || "0.0.0.0";
|
|
339
339
|
|
|
340
|
-
// Ensure the well-known dir exists, and
|
|
340
|
+
// Ensure the well-known dir exists, and (re)write the static hub.html so `/`
|
|
341
341
|
// serves something coherent on a fresh disk (the dynamic path through
|
|
342
342
|
// `hubFetch` takes over once a DB row exists; the disk file is the
|
|
343
343
|
// signed-out fallback).
|
|
344
|
+
//
|
|
345
|
+
// Regenerate on EVERY serve start, not just when the file is absent (#171):
|
|
346
|
+
// hub.html is a served artifact built from current code, and code ships via
|
|
347
|
+
// `git pull` + `parachute restart hub`. Guarding on `!existsSync` left the
|
|
348
|
+
// stale post-upgrade file on disk until an unrelated `parachute expose`
|
|
349
|
+
// re-ran — so operators saw old hub.html after an upgrade. The write is a
|
|
350
|
+
// cheap, deterministic, atomic (tmp+rename) render of static signed-out
|
|
351
|
+
// HTML with no expose-state or DB dependency, so it's safe to call every
|
|
352
|
+
// start.
|
|
344
353
|
if (!existsSync(WELL_KNOWN_DIR)) mkdirSync(WELL_KNOWN_DIR, { recursive: true });
|
|
345
354
|
const hubHtmlPath = join(WELL_KNOWN_DIR, "hub.html");
|
|
346
|
-
|
|
355
|
+
writeHubFile(hubHtmlPath);
|
|
347
356
|
|
|
348
357
|
const dbPath = hubDbPath();
|
|
349
358
|
// Self-heal-or-die DB holder (#594). The handle lives behind a mutable
|
package/src/notes-serve.ts
CHANGED
|
@@ -176,6 +176,29 @@ export function notesFetch(dist: string, mount: string): (req: Request) => Respo
|
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Build the `Bun.serve` config for the notes static server.
|
|
181
|
+
*
|
|
182
|
+
* `idleTimeout: 255` matches hub-server.ts. When this static-serve sits behind
|
|
183
|
+
* an edge proxy that pools keep-alive connections (Render, Cloudflare, fly
|
|
184
|
+
* proxy), the edge's idle timeout outlasts Bun's default — the proxy reuses a
|
|
185
|
+
* connection we just closed and returns a "random" 502. 255s comfortably
|
|
186
|
+
* exceeds Render's community-observed ~120s edge pool TTL. Closes the hub#399
|
|
187
|
+
* residual on the second serve entrypoint (the Notes PWA path). Exported so a
|
|
188
|
+
* test can assert the option is set without booting a server.
|
|
189
|
+
*/
|
|
190
|
+
export function notesServeOptions(
|
|
191
|
+
port: number,
|
|
192
|
+
dist: string,
|
|
193
|
+
mount: string,
|
|
194
|
+
): { port: number; idleTimeout: number; fetch: (req: Request) => Response } {
|
|
195
|
+
return {
|
|
196
|
+
port,
|
|
197
|
+
idleTimeout: 255,
|
|
198
|
+
fetch: notesFetch(dist, mount),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
179
202
|
if (import.meta.main) {
|
|
180
203
|
const { port, dist: distArg, mount } = parseArgs(process.argv.slice(2));
|
|
181
204
|
|
|
@@ -187,10 +210,7 @@ if (import.meta.main) {
|
|
|
187
210
|
process.exit(1);
|
|
188
211
|
}
|
|
189
212
|
|
|
190
|
-
Bun.serve(
|
|
191
|
-
port,
|
|
192
|
-
fetch: notesFetch(dist, mount),
|
|
193
|
-
});
|
|
213
|
+
Bun.serve(notesServeOptions(port, dist, mount));
|
|
194
214
|
|
|
195
215
|
console.log(`notes static-serve listening on :${port} (dist=${dist}, mount=${mount || "/"})`);
|
|
196
216
|
}
|