pgserve 1.1.9 → 1.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.
package/.genie/AGENTS.md CHANGED
@@ -5,6 +5,8 @@ description: Global agents (orchestration, QA, analysis, maintenance)
5
5
  github_url: https://github.com/namastexlabs/automagik-genie/tree/main/.genie
6
6
  ---
7
7
 
8
+ > **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
9
+
8
10
  # Base Genie Agents
9
11
 
10
12
  **Global agents available across all collectives.**
@@ -11,6 +11,8 @@ forge:
11
11
  OPENCODE: {}
12
12
  ---
13
13
 
14
+ > **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
15
+
14
16
  ## Framework Reference
15
17
 
16
18
  This agent uses the universal prompting framework documented in AGENTS.md §Prompting Standards Framework:
package/AGENTS.md CHANGED
@@ -1,3 +1,5 @@
1
+ > **Shared rules in `~/.claude/rules/agent-bible.md`. Read it.**
2
+
1
3
  # Genie Agent Framework
2
4
 
3
5
  ## Core Identity
@@ -53,8 +53,113 @@ if (!bunPath) {
53
53
  process.exit(1);
54
54
  }
55
55
 
56
+ // Pre-flight health check: verify bun can actually execute.
57
+ //
58
+ // When pgserve is installed via `bun install` (as a global or transitive dep),
59
+ // the nested `bun` npm package's postinstall can be skipped, leaving
60
+ // `@oven/bun-<platform>/bin/bun` empty. The bun stub at `node_modules/bun/bin/bun`
61
+ // then exits instantly with:
62
+ // Error: Bun's postinstall script was not run.
63
+ //
64
+ // pglite-server.js's TCP readiness poll can't distinguish this from a slow
65
+ // startup, so users see a confusing 30s timeout. Detect the specific error
66
+ // here, attempt the documented self-heal once (`node install.js`), and retry.
67
+ // If self-heal also fails, surface the real error instead of hanging later.
68
+ ensureBunHealthy(bunPath);
69
+
56
70
  const scriptPath = path.join(__dirname, 'pglite-server.js');
57
71
 
72
+ /**
73
+ * Verify the selected bun binary can execute. If it fails with the known
74
+ * "postinstall script was not run" signature, attempt a one-shot repair via
75
+ * the bun npm package's install.js. Throws (with a useful message) rather
76
+ * than letting pglite-server.js hang on the TCP readiness poll for 30s.
77
+ */
78
+ function ensureBunHealthy(bunExe) {
79
+ const probe = probeBun(bunExe);
80
+ if (probe.ok) return;
81
+
82
+ // Only attempt self-heal for the specific postinstall-not-run failure.
83
+ // Any other failure (corrupt binary, unsupported glibc, etc.) is surfaced
84
+ // as-is rather than silently papered over.
85
+ if (!isPostinstallMissingError(probe.output)) {
86
+ console.error('Error: bun runtime at', bunExe, 'failed to execute:');
87
+ console.error(probe.output || '(no output)');
88
+ process.exit(1);
89
+ }
90
+
91
+ const installJs = findBunInstallJs(bunExe);
92
+ if (!installJs) {
93
+ console.error('Error: bun runtime at', bunExe, 'is missing its platform binary,');
94
+ console.error('and the recovery script (node_modules/bun/install.js) could not be located.');
95
+ console.error('');
96
+ console.error('Try reinstalling pgserve, or run the fix manually:');
97
+ console.error(' cd <node_modules>/bun && node install.js');
98
+ process.exit(1);
99
+ }
100
+
101
+ console.error('[pgserve] bun runtime missing platform binary; attempting self-heal...');
102
+ try {
103
+ execSync(`node ${JSON.stringify(installJs)}`, { stdio: 'inherit' });
104
+ } catch {
105
+ // fall through to second probe
106
+ }
107
+
108
+ const second = probeBun(bunExe);
109
+ if (second.ok) {
110
+ console.error('[pgserve] bun runtime recovered.');
111
+ return;
112
+ }
113
+
114
+ console.error('Error: bun runtime still broken after self-heal attempt.');
115
+ console.error(second.output || '(no output)');
116
+ console.error('');
117
+ console.error('Manual fix:');
118
+ console.error(` cd ${path.dirname(path.dirname(installJs))}/bun && node install.js`);
119
+ console.error('');
120
+ console.error('Upstream bug: https://github.com/namastexlabs/pgserve/issues/22');
121
+ process.exit(1);
122
+ }
123
+
124
+ function probeBun(bunExe) {
125
+ try {
126
+ const out = execSync(`${JSON.stringify(bunExe)} --version`, {
127
+ stdio: ['ignore', 'pipe', 'pipe'],
128
+ timeout: 10000,
129
+ encoding: 'utf8'
130
+ });
131
+ return { ok: true, output: out };
132
+ } catch (err) {
133
+ const output = [err.stderr, err.stdout, err.message]
134
+ .filter(Boolean).map(String).join('\n');
135
+ return { ok: false, output };
136
+ }
137
+ }
138
+
139
+ function isPostinstallMissingError(output) {
140
+ return typeof output === 'string' &&
141
+ /Bun's postinstall script was not run/i.test(output);
142
+ }
143
+
144
+ function findBunInstallJs(bunExe) {
145
+ // Walk up from the bun binary toward a `bun` package dir containing install.js.
146
+ // Matches the wrapper's own location list - bun is always nested under a
147
+ // `bun` package directory (or its `bin/` subdir).
148
+ let cursor = path.dirname(path.resolve(bunExe));
149
+ for (let i = 0; i < 6; i++) {
150
+ const candidate = path.join(cursor, 'install.js');
151
+ if (fs.existsSync(candidate) && fs.existsSync(path.join(cursor, 'package.json'))) {
152
+ return candidate;
153
+ }
154
+ const nested = path.join(cursor, 'bun', 'install.js');
155
+ if (fs.existsSync(nested)) return nested;
156
+ const parent = path.dirname(cursor);
157
+ if (parent === cursor) break;
158
+ cursor = parent;
159
+ }
160
+ return null;
161
+ }
162
+
58
163
  // Platform-specific spawning strategy:
59
164
  // - Windows: Use pipes for explicit handle control (prevents EBUSY errors)
60
165
  // - Unix: Use inherit for simplicity (works fine)
package/knip.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "entry": ["src/index.js", "bin/pglite-server.js", "bin/pgserve-wrapper.cjs"],
4
4
  "project": ["src/**/*.js", "bin/**/*.js", "bin/**/*.cjs"],
5
5
  "ignore": ["tests/**", "helpers/**", "scripts/**"],
6
- "ignoreBinaries": ["scripts/test-npx.sh", "make"],
6
+ "ignoreBinaries": ["scripts/test-npx.sh", "scripts/test-bun-self-heal.sh", "make"],
7
7
  "ignoreDependencies": ["bun"],
8
8
  "ignoreExportsUsedInFile": true
9
9
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
4
4
  "description": "Embedded PostgreSQL server with true concurrent connections - zero config, auto-provision databases",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -19,7 +19,8 @@
19
19
  "lint:fix": "eslint src/ bin/ --fix",
20
20
  "deadcode": "knip",
21
21
  "test:npx": "scripts/test-npx.sh",
22
- "prepublishOnly": "npm run lint && npm run deadcode && npm run test:npx",
22
+ "test:bun-self-heal": "scripts/test-bun-self-heal.sh",
23
+ "prepublishOnly": "npm run lint && npm run deadcode && npm run test:npx && npm run test:bun-self-heal",
23
24
  "prepare": "husky"
24
25
  },
25
26
  "keywords": [
@@ -0,0 +1,163 @@
1
+ #!/bin/bash
2
+ # Regression test for https://github.com/namastexlabs/pgserve/issues/22
3
+ #
4
+ # When pgserve is installed via `bun install`, the nested `bun` npm package's
5
+ # postinstall can be skipped, leaving @oven/bun-<platform>/bin/bun empty.
6
+ # The bun stub then refuses to run with "Bun's postinstall script was not run".
7
+ # pgserve-wrapper.cjs must detect this and self-heal via `node install.js`.
8
+ #
9
+ # This test stages a synthetic broken install tree, runs the wrapper, and
10
+ # asserts that it recovers and spawns pglite-server.
11
+
12
+ set -e
13
+
14
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
15
+ WRAPPER="$REPO_ROOT/bin/pgserve-wrapper.cjs"
16
+
17
+ if [ ! -f "$WRAPPER" ]; then
18
+ echo "✗ wrapper not found: $WRAPPER"
19
+ exit 1
20
+ fi
21
+
22
+ # Use a real bun binary as the "recovered" payload so the healthy-path
23
+ # assertion is meaningful. Falls back to any bun on PATH.
24
+ REAL_BUN="${BUN_BIN:-$(command -v bun || true)}"
25
+ if [ -z "$REAL_BUN" ] || [ ! -x "$REAL_BUN" ]; then
26
+ echo "✗ bun runtime not found on PATH (set BUN_BIN to override)"
27
+ exit 1
28
+ fi
29
+
30
+ FIXTURE=$(mktemp -d)
31
+ trap "rm -rf $FIXTURE" EXIT
32
+
33
+ mkdir -p "$FIXTURE/node_modules/bun/bin"
34
+ mkdir -p "$FIXTURE/node_modules/@oven/bun-linux-x64/bin" # empty, simulating the bug
35
+ mkdir -p "$FIXTURE/node_modules/.bin"
36
+ mkdir -p "$FIXTURE/node_modules/pgserve/bin"
37
+
38
+ cp "$WRAPPER" "$FIXTURE/node_modules/pgserve/bin/pgserve-wrapper.cjs"
39
+
40
+ # Stub pglite-server so we can detect a successful spawn without needing
41
+ # postgres binaries in the fixture.
42
+ cat > "$FIXTURE/node_modules/pgserve/bin/pglite-server.js" <<'EOF'
43
+ console.log("pglite-server-spawned");
44
+ process.exit(0);
45
+ EOF
46
+
47
+ # Fake bun install.js: copies the real bun into the expected @oven location,
48
+ # mirroring what the real postinstall does.
49
+ cat > "$FIXTURE/node_modules/bun/install.js" <<EOF
50
+ const fs = require('fs');
51
+ const path = require('path');
52
+ const dst = path.resolve(__dirname, '..', '@oven', 'bun-linux-x64', 'bin', 'bun');
53
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
54
+ fs.copyFileSync('$REAL_BUN', dst);
55
+ fs.chmodSync(dst, 0o755);
56
+ console.log('[test] install.js populated', dst);
57
+ EOF
58
+ echo '{"name":"bun","version":"1.3.12"}' > "$FIXTURE/node_modules/bun/package.json"
59
+
60
+ # Broken bun stub: prints the postinstall error unless the @oven binary exists.
61
+ cat > "$FIXTURE/node_modules/bun/bin/bun" <<'EOF'
62
+ #!/bin/sh
63
+ SELF=$(readlink -f "$0")
64
+ TARGET="$(dirname "$SELF")/../../@oven/bun-linux-x64/bin/bun"
65
+ if [ ! -x "$TARGET" ]; then
66
+ echo "Error: Bun's postinstall script was not run." >&2
67
+ echo "" >&2
68
+ echo "To fix this, run the postinstall script manually:" >&2
69
+ echo " cd node_modules/bun && node install.js" >&2
70
+ exit 1
71
+ fi
72
+ exec "$TARGET" "$@"
73
+ EOF
74
+ chmod +x "$FIXTURE/node_modules/bun/bin/bun"
75
+
76
+ ln -s ../bun/bin/bun "$FIXTURE/node_modules/.bin/bun"
77
+
78
+ echo "=== Testing self-heal on broken install ==="
79
+ OUTPUT=$(node "$FIXTURE/node_modules/pgserve/bin/pgserve-wrapper.cjs" 2>&1)
80
+ EXIT=$?
81
+
82
+ if [ $EXIT -ne 0 ]; then
83
+ echo "✗ wrapper exited non-zero: $EXIT"
84
+ echo "$OUTPUT"
85
+ exit 1
86
+ fi
87
+
88
+ if ! echo "$OUTPUT" | grep -q "attempting self-heal"; then
89
+ echo "✗ wrapper did not attempt self-heal"
90
+ echo "$OUTPUT"
91
+ exit 1
92
+ fi
93
+
94
+ if ! echo "$OUTPUT" | grep -q "bun runtime recovered"; then
95
+ echo "✗ wrapper did not report recovery"
96
+ echo "$OUTPUT"
97
+ exit 1
98
+ fi
99
+
100
+ if ! echo "$OUTPUT" | grep -q "pglite-server-spawned"; then
101
+ echo "✗ pglite-server was not spawned after self-heal"
102
+ echo "$OUTPUT"
103
+ exit 1
104
+ fi
105
+
106
+ echo "✓ self-heal path: wrapper detected, repaired, and spawned pglite-server"
107
+
108
+ echo ""
109
+ echo "=== Testing healthy path is unaffected ==="
110
+ OUTPUT=$(node "$FIXTURE/node_modules/pgserve/bin/pgserve-wrapper.cjs" 2>&1)
111
+ EXIT=$?
112
+
113
+ if [ $EXIT -ne 0 ]; then
114
+ echo "✗ wrapper exited non-zero on healthy path: $EXIT"
115
+ echo "$OUTPUT"
116
+ exit 1
117
+ fi
118
+
119
+ if echo "$OUTPUT" | grep -q "self-heal\|recovered"; then
120
+ echo "✗ wrapper logged self-heal messages on a healthy install"
121
+ echo "$OUTPUT"
122
+ exit 1
123
+ fi
124
+
125
+ if ! echo "$OUTPUT" | grep -q "pglite-server-spawned"; then
126
+ echo "✗ pglite-server was not spawned on healthy path"
127
+ echo "$OUTPUT"
128
+ exit 1
129
+ fi
130
+
131
+ echo "✓ healthy path: wrapper was silent and spawned pglite-server directly"
132
+
133
+ echo ""
134
+ echo "=== Testing non-postinstall errors surface raw ==="
135
+ # Replace stub with one that emits an unrelated error.
136
+ cat > "$FIXTURE/node_modules/bun/bin/bun" <<'EOF'
137
+ #!/bin/sh
138
+ echo "Error: GLIBC_2.99 not found (libc mismatch)" >&2
139
+ exit 127
140
+ EOF
141
+ chmod +x "$FIXTURE/node_modules/bun/bin/bun"
142
+
143
+ # Clear the @oven healed binary so the stub is what runs.
144
+ rm -f "$FIXTURE/node_modules/@oven/bun-linux-x64/bin/bun"
145
+
146
+ OUTPUT=$(node "$FIXTURE/node_modules/pgserve/bin/pgserve-wrapper.cjs" 2>&1 || true)
147
+
148
+ if echo "$OUTPUT" | grep -q "self-heal"; then
149
+ echo "✗ wrapper tried self-heal for a non-postinstall error"
150
+ echo "$OUTPUT"
151
+ exit 1
152
+ fi
153
+
154
+ if ! echo "$OUTPUT" | grep -q "GLIBC_2.99"; then
155
+ echo "✗ wrapper did not surface the real error message"
156
+ echo "$OUTPUT"
157
+ exit 1
158
+ fi
159
+
160
+ echo "✓ unrelated-error path: wrapper surfaced the raw error without self-heal"
161
+
162
+ echo ""
163
+ echo "=== bun self-heal test PASSED ==="