pgserve 2.1.3 → 2.2.0

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.
Files changed (228) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +56 -0
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-args.test.js +0 -86
  211. package/tests/daemon-control.test.js +0 -171
  212. package/tests/daemon-fingerprint-integration.test.js +0 -111
  213. package/tests/daemon-pr24-regression.test.js +0 -198
  214. package/tests/fingerprint.test.js +0 -263
  215. package/tests/fixtures/240-orphan-seed.sql +0 -30
  216. package/tests/multi-tenant.test.js +0 -374
  217. package/tests/orphan-cleanup.test.js +0 -390
  218. package/tests/pg-version-regex.test.js +0 -129
  219. package/tests/quick-bench.js +0 -135
  220. package/tests/router-handshake-retry.test.js +0 -119
  221. package/tests/router-handshake-watchdog.test.js +0 -110
  222. package/tests/sdk.test.js +0 -71
  223. package/tests/stale-postmaster-pid.test.js +0 -85
  224. package/tests/stress-test.js +0 -439
  225. package/tests/sync-perf-test.js +0 -150
  226. package/tests/tcp-listen.test.js +0 -368
  227. package/tests/tenancy.test.js +0 -403
  228. package/tests/wrapper-supervision.test.js +0 -107
@@ -1,104 +0,0 @@
1
- /**
2
- * Multi-Tenant Router Demo
3
- *
4
- * Shows how to use the new multi-tenant architecture
5
- */
6
-
7
- import { startMultiTenantServer } from '../src/index.js';
8
- import pg from 'pg';
9
-
10
- const { Client } = pg;
11
-
12
- async function demo() {
13
- console.log('🚀 Starting multi-tenant router demo...\n');
14
-
15
- // Start multi-tenant router
16
- const router = await startMultiTenantServer({
17
- port: 15432,
18
- baseDir: './demo-data',
19
- logLevel: 'info'
20
- });
21
-
22
- console.log('\n📊 Initial stats:');
23
- console.log(JSON.stringify(router.getStats(), null, 2));
24
-
25
- // Connect to database "user123" (auto-created)
26
- console.log('\n📥 Connecting to database: user123');
27
- const client1 = new Client({
28
- host: '127.0.0.1',
29
- port: 15432,
30
- database: 'user123'
31
- });
32
-
33
- await client1.connect();
34
- console.log('✅ Connected to user123');
35
-
36
- // Create table and insert data
37
- await client1.query('CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT)');
38
- await client1.query("INSERT INTO users (name) VALUES ('Alice'), ('Bob')");
39
-
40
- const result1 = await client1.query('SELECT * FROM users');
41
- console.log('📋 user123 data:', result1.rows);
42
-
43
- await client1.end();
44
- console.log('🔌 Disconnected from user123');
45
-
46
- // Connect to database "app456" (auto-created)
47
- console.log('\n📥 Connecting to database: app456');
48
- const client2 = new Client({
49
- host: '127.0.0.1',
50
- port: 15432,
51
- database: 'app456'
52
- });
53
-
54
- await client2.connect();
55
- console.log('✅ Connected to app456');
56
-
57
- // Different schema in different database
58
- await client2.query('CREATE TABLE posts (id SERIAL PRIMARY KEY, title TEXT)');
59
- await client2.query("INSERT INTO posts (title) VALUES ('Hello World'), ('Multi-tenant magic')");
60
-
61
- const result2 = await client2.query('SELECT * FROM posts');
62
- console.log('📋 app456 data:', result2.rows);
63
-
64
- await client2.end();
65
- console.log('🔌 Disconnected from app456');
66
-
67
- // Show final stats
68
- console.log('\n📊 Final stats:');
69
- console.log(JSON.stringify(router.getStats(), null, 2));
70
-
71
- console.log('\n📋 All databases:');
72
- console.log(JSON.stringify(router.listDatabases(), null, 2));
73
-
74
- // Reconnect to user123 to verify data persists
75
- console.log('\n🔄 Reconnecting to user123 to verify data persists...');
76
- const client1Again = new Client({
77
- host: '127.0.0.1',
78
- port: 15432,
79
- database: 'user123'
80
- });
81
-
82
- await client1Again.connect();
83
- const persistedData = await client1Again.query('SELECT * FROM users');
84
- console.log('✅ Persisted data in user123:', persistedData.rows);
85
-
86
- await client1Again.end();
87
-
88
- // Stop router
89
- console.log('\n🛑 Stopping router...');
90
- await router.stop();
91
-
92
- console.log('\n✅ Demo complete!');
93
- console.log('\n🎯 Key achievements:');
94
- console.log(' • Single port (15432) handled multiple databases');
95
- console.log(' • Auto-provisioned user123 and app456');
96
- console.log(' • Data isolated between databases');
97
- console.log(' • Data persisted across reconnections');
98
- console.log(' • Zero configuration required!');
99
- }
100
-
101
- demo().catch((error) => {
102
- console.error('❌ Demo failed:', error);
103
- process.exit(1);
104
- });
package/install.sh DELETED
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ============================================================================
3
- # pgserve — Canonical PostgreSQL backbone installer
4
- #
5
- # Bootstraps a single shared pgserve instance under pm2 supervision. Used as
6
- # a prerequisite by `omni/install.sh` and `genie/install.sh` so every
7
- # automagik service on a host points at the same Postgres.
8
- #
9
- # Usage:
10
- # curl -fsSL https://raw.githubusercontent.com/namastexlabs/pgserve/main/install.sh | bash
11
- #
12
- # With pinned version:
13
- # PGSERVE_VERSION=^2.1.1 curl -fsSL .../install.sh | bash
14
- #
15
- # Local checkout:
16
- # bash install.sh
17
- #
18
- # Idempotent — re-running is a no-op success when pgserve is already
19
- # registered under pm2 with a healthy entry.
20
- # ============================================================================
21
- set -euo pipefail
22
-
23
- PGSERVE_VERSION="${PGSERVE_VERSION:-^2.1.0}"
24
-
25
- # Colors (no-op when stdout isn't a tty)
26
- if [[ -t 1 ]]; then
27
- RED='\033[0;31m'
28
- GREEN='\033[0;32m'
29
- YELLOW='\033[1;33m'
30
- BLUE='\033[0;34m'
31
- CYAN='\033[0;36m'
32
- BOLD='\033[1m'
33
- NC='\033[0m'
34
- else
35
- RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC=''
36
- fi
37
-
38
- info() { printf "${BLUE}ℹ${NC} %s\n" "$*"; }
39
- ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
40
- warn() { printf "${YELLOW}⚠${NC} %s\n" "$*"; }
41
- fail() { printf "${RED}✗${NC} %s\n" "$*" >&2; exit 1; }
42
- step() { printf "\n${BOLD}${CYAN}▸ %s${NC}\n" "$*"; }
43
-
44
- has_cmd() { command -v "$1" >/dev/null 2>&1; }
45
-
46
- # ============================================================================
47
- # Prerequisites: bun + pm2
48
- # ============================================================================
49
-
50
- ensure_bun() {
51
- if has_cmd bun; then
52
- ok "bun $(bun --version 2>/dev/null || echo '?')"
53
- return 0
54
- fi
55
- info "Installing bun (https://bun.sh)..."
56
- curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1 || fail "bun install failed — see https://bun.sh"
57
- # Make bun available to the rest of this script without requiring a re-login.
58
- export PATH="$HOME/.bun/bin:$PATH"
59
- has_cmd bun || fail "bun installed but not on PATH — restart your shell and re-run."
60
- ok "bun $(bun --version)"
61
- }
62
-
63
- ensure_pm2() {
64
- if has_cmd pm2; then
65
- ok "pm2 $(pm2 --version 2>/dev/null || echo '?')"
66
- return 0
67
- fi
68
- info "Installing pm2 (process supervisor)..."
69
- bun add -g pm2 >/dev/null 2>&1 || fail "pm2 install failed — try: bun add -g pm2"
70
- has_cmd pm2 || fail "pm2 installed but not on PATH — restart your shell and re-run."
71
- ok "pm2 installed"
72
- }
73
-
74
- # ============================================================================
75
- # pgserve binary + pm2 registration
76
- # ============================================================================
77
-
78
- ensure_pgserve_binary() {
79
- # Probe via `pgserve port` (real subcommand). `pgserve --version` doesn't
80
- # exist in 2.1.x — using it would false-negative and trigger a redundant
81
- # reinstall every time install.sh runs.
82
- if has_cmd pgserve && pgserve port >/dev/null 2>&1; then
83
- ok "pgserve binary present (port $(pgserve port 2>/dev/null))"
84
- return 0
85
- fi
86
- info "Installing pgserve@${PGSERVE_VERSION} globally..."
87
- bun add -g "pgserve@${PGSERVE_VERSION}" >/dev/null 2>&1 \
88
- || fail "pgserve install failed — try: bun add -g pgserve@${PGSERVE_VERSION}"
89
- has_cmd pgserve || fail "pgserve installed but not on PATH — restart your shell and re-run."
90
- ok "pgserve $(pgserve port 2>/dev/null || echo '?')"
91
- }
92
-
93
- register_pgserve_pm2() {
94
- info "Registering pgserve under pm2 (idempotent)..."
95
- # `pgserve install` prints its own success/already-installed line and exits
96
- # 0 in both cases. We pipe stderr through so any pm2 errors surface to the
97
- # operator (the pm2-6.x --min-uptime breakage we hit on 2026-04-30 was
98
- # invisible because stderr was being captured).
99
- pgserve install || fail "pgserve install failed — see ~/.pgserve/logs/pgserve-error.log"
100
- }
101
-
102
- # ============================================================================
103
- # Main
104
- # ============================================================================
105
-
106
- main() {
107
- step "Installing canonical pgserve"
108
- ensure_bun
109
- ensure_pm2
110
- ensure_pgserve_binary
111
- register_pgserve_pm2
112
-
113
- echo ""
114
- ok "Canonical pgserve ready"
115
- info "URL: $(pgserve url 2>/dev/null || echo '<run: pgserve url>')"
116
- info "Port: $(pgserve port 2>/dev/null || echo '?')"
117
- info "Logs: ~/.pgserve/logs/"
118
- echo ""
119
- info "Other automagik services on this host (omni, genie, ...) will share this pgserve."
120
- echo ""
121
- }
122
-
123
- main "$@"
package/knip.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "$schema": "https://unpkg.com/knip@5/schema.json",
3
- "entry": ["src/index.js", "bin/postgres-server.js", "bin/pgserve-wrapper.cjs"],
4
- "project": ["src/**/*.js", "bin/**/*.js", "bin/**/*.cjs"],
5
- "ignore": ["tests/**", "helpers/**", "scripts/**"],
6
- "ignoreBinaries": ["scripts/test-npx.sh", "scripts/test-bun-self-heal.sh", "make"],
7
- "ignoreDependencies": ["bun"],
8
- "ignoreExportsUsedInFile": true
9
- }
@@ -1,163 +0,0 @@
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 postgres-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 postgres-server so we can detect a successful spawn without needing
41
- # postgres binaries in the fixture.
42
- cat > "$FIXTURE/node_modules/pgserve/bin/postgres-server.js" <<'EOF'
43
- console.log("postgres-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 "postgres-server-spawned"; then
101
- echo "✗ postgres-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 postgres-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 "postgres-server-spawned"; then
126
- echo "✗ postgres-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 postgres-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 ==="
@@ -1,60 +0,0 @@
1
- #!/bin/bash
2
- # Test that the package works with npx (simulates fresh user install)
3
- # This catches path resolution issues that static analysis can't detect
4
-
5
- set -e
6
-
7
- echo "=== Testing npx compatibility ==="
8
-
9
- # Create temp directory
10
- TEST_DIR=$(mktemp -d)
11
- trap "rm -rf $TEST_DIR" EXIT
12
-
13
- # Pack the current package
14
- echo "Packing package..."
15
- PACK_OUTPUT=$(npm pack --pack-destination "$TEST_DIR" 2>&1)
16
- PACK_FILE=$(echo "$PACK_OUTPUT" | grep -E '\.tgz$' | tail -1)
17
-
18
- # If npm pack fails, exit with an error
19
- if [ -z "$PACK_FILE" ] || [ ! -f "$TEST_DIR/$PACK_FILE" ]; then
20
- echo "✗ Failed to pack package with npm"
21
- echo "Pack output: $PACK_OUTPUT"
22
- exit 1
23
- fi
24
- echo "Packed: $PACK_FILE"
25
-
26
- # Install in isolated environment using npm
27
- echo "Installing in isolated environment..."
28
- cd "$TEST_DIR"
29
- echo '{"name":"test-npx-install","private":true}' > package.json
30
- npm install "./$PACK_FILE" > /dev/null 2>&1
31
-
32
- # Test that it starts (with timeout)
33
- echo "Testing server startup via npx..."
34
- timeout 30 npx pgserve --no-cluster --port 15432 > output.log 2>&1 &
35
- PID=$!
36
-
37
- # Wait for ready signal (Server started successfully!)
38
- for i in {1..60}; do
39
- if grep -q "Server started successfully" output.log 2>/dev/null; then
40
- echo "✓ Server started successfully via npx"
41
- kill $PID 2>/dev/null || true
42
- wait $PID 2>/dev/null || true
43
- echo "=== npx test PASSED ==="
44
- exit 0
45
- fi
46
- if ! kill -0 $PID 2>/dev/null; then
47
- echo "✗ Server exited unexpectedly"
48
- cat output.log
49
- echo "=== npx test FAILED ==="
50
- exit 1
51
- fi
52
- sleep 0.5
53
- done
54
-
55
- # Timeout
56
- kill $PID 2>/dev/null || true
57
- echo "✗ Server did not start within timeout"
58
- cat output.log
59
- echo "=== npx test FAILED ==="
60
- exit 1
@@ -1,189 +0,0 @@
1
- /**
2
- * Tests for src/audit.js — JSONL writer with rotation + syslog target.
3
- *
4
- * Tests use temp dirs under /tmp; nothing touches the user's real
5
- * `~/.pgserve/audit.log`. The syslog test stubs `logger` via PATH so we
6
- * don't depend on (or pollute) the host's syslog daemon.
7
- */
8
-
9
- import { test, expect, beforeEach, afterEach } from 'bun:test';
10
- import fs from 'fs';
11
- import os from 'os';
12
- import path from 'path';
13
- import {
14
- audit,
15
- configureAudit,
16
- readAuditTarget,
17
- AUDIT_EVENTS,
18
- _internals,
19
- } from '../src/audit.js';
20
-
21
- let scratchDir;
22
-
23
- beforeEach(() => {
24
- scratchDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgserve-audit-test-'));
25
- configureAudit({
26
- logFile: path.join(scratchDir, 'audit.log'),
27
- target: 'file',
28
- });
29
- });
30
-
31
- afterEach(() => {
32
- try {
33
- fs.rmSync(scratchDir, { recursive: true, force: true });
34
- } catch { /* noop */ }
35
- });
36
-
37
- test('audit() appends a JSON line per event', () => {
38
- audit(AUDIT_EVENTS.DB_CREATED, { fingerprint: 'abc123def456', db: 'app_demo_abc123def456' });
39
- audit(AUDIT_EVENTS.CONNECTION_ROUTED, { fingerprint: 'abc123def456', peer_pid: 1234 });
40
-
41
- const logFile = path.join(scratchDir, 'audit.log');
42
- const lines = fs.readFileSync(logFile, 'utf8').trim().split('\n');
43
- expect(lines.length).toBe(2);
44
- const r1 = JSON.parse(lines[0]);
45
- expect(r1.event).toBe('db_created');
46
- expect(r1.fingerprint).toBe('abc123def456');
47
- expect(typeof r1.ts).toBe('string');
48
- expect(new Date(r1.ts).toString()).not.toBe('Invalid Date');
49
-
50
- const r2 = JSON.parse(lines[1]);
51
- expect(r2.event).toBe('connection_routed');
52
- expect(r2.peer_pid).toBe(1234);
53
- });
54
-
55
- test('audit() refuses unknown events', () => {
56
- expect(() => audit('definitely_not_a_real_event', {})).toThrow(/unknown event/);
57
- });
58
-
59
- test('audit() creates the parent directory if missing', () => {
60
- const nested = path.join(scratchDir, 'nested', 'sub', 'audit.log');
61
- audit(AUDIT_EVENTS.DB_CREATED, { fingerprint: 'a'.repeat(12) }, { logFile: nested });
62
- expect(fs.existsSync(nested)).toBe(true);
63
- });
64
-
65
- test('all v2.0 event names are exported (incl. Group 6 tcp_*)', () => {
66
- expect(Object.values(AUDIT_EVENTS).sort()).toEqual([
67
- 'connection_denied_fingerprint_mismatch',
68
- 'connection_routed',
69
- 'db_created',
70
- 'db_persist_honored',
71
- 'db_reaped_liveness',
72
- 'db_reaped_ttl',
73
- 'enforcement_kill_switch_used',
74
- 'tcp_token_denied',
75
- 'tcp_token_issued',
76
- 'tcp_token_used',
77
- ]);
78
- });
79
-
80
- test('rotation kicks in once existing file crosses 50 MB', () => {
81
- const logFile = path.join(scratchDir, 'audit.log');
82
- // Use a sparse file to simulate a 50 MB log without writing 50 MB.
83
- const fd = fs.openSync(logFile, 'w');
84
- fs.ftruncateSync(fd, _internals.ROTATE_THRESHOLD_BYTES);
85
- fs.closeSync(fd);
86
-
87
- audit(AUDIT_EVENTS.DB_CREATED, { fingerprint: 'r'.repeat(12) });
88
-
89
- // Original file rotated to .1, fresh file holds the new line.
90
- expect(fs.existsSync(`${logFile}.1`)).toBe(true);
91
- const fresh = fs.readFileSync(logFile, 'utf8');
92
- expect(fresh.trim().split('\n').length).toBe(1);
93
- expect(JSON.parse(fresh.trim()).event).toBe('db_created');
94
-
95
- // The rotated file is the original 50 MB sparse file.
96
- expect(fs.statSync(`${logFile}.1`).size).toBe(_internals.ROTATE_THRESHOLD_BYTES);
97
- });
98
-
99
- test('rotation cascades up to KEEP files and drops the eldest', () => {
100
- const logFile = path.join(scratchDir, 'audit.log');
101
- // Pre-populate audit.log.1 ... audit.log.5 with distinct markers.
102
- for (let i = 1; i <= _internals.ROTATE_KEEP; i++) {
103
- fs.writeFileSync(`${logFile}.${i}`, `slot-${i}\n`);
104
- }
105
- // And the live audit.log just under threshold.
106
- const fd = fs.openSync(logFile, 'w');
107
- fs.ftruncateSync(fd, _internals.ROTATE_THRESHOLD_BYTES);
108
- fs.closeSync(fd);
109
-
110
- audit(AUDIT_EVENTS.DB_CREATED, { fingerprint: 'q'.repeat(12) });
111
-
112
- // .5 (was "slot-5") dropped; .4 → .5; .3 → .4; .2 → .3; .1 → .2; live → .1.
113
- expect(fs.readFileSync(`${logFile}.5`, 'utf8').trim()).toBe('slot-4');
114
- expect(fs.readFileSync(`${logFile}.4`, 'utf8').trim()).toBe('slot-3');
115
- expect(fs.readFileSync(`${logFile}.3`, 'utf8').trim()).toBe('slot-2');
116
- expect(fs.readFileSync(`${logFile}.2`, 'utf8').trim()).toBe('slot-1');
117
- expect(fs.statSync(`${logFile}.1`).size).toBe(_internals.ROTATE_THRESHOLD_BYTES);
118
- });
119
-
120
- test('audit({target:"syslog"}) spawns logger -t pgserve-audit', async () => {
121
- // Stub `logger` by prepending a temp shim to PATH.
122
- const shimDir = path.join(scratchDir, 'shim');
123
- fs.mkdirSync(shimDir, { recursive: true });
124
- const marker = path.join(scratchDir, 'logger-calls.txt');
125
- const shimPath = path.join(shimDir, 'logger');
126
- fs.writeFileSync(
127
- shimPath,
128
- `#!/usr/bin/env bash
129
- # Capture argv to a marker file so the test can verify the spawn.
130
- printf '%s\\n' "$*" >> "${marker}"
131
- `,
132
- { mode: 0o755 },
133
- );
134
-
135
- const oldPath = process.env.PATH;
136
- process.env.PATH = `${shimDir}:${oldPath}`;
137
- try {
138
- audit(
139
- AUDIT_EVENTS.CONNECTION_ROUTED,
140
- { fingerprint: 's'.repeat(12) },
141
- { target: 'syslog' },
142
- );
143
- // logger is spawned async; poll briefly for the marker.
144
- const deadline = Date.now() + 2000;
145
- while (!fs.existsSync(marker) && Date.now() < deadline) {
146
- await new Promise(r => setTimeout(r, 25));
147
- }
148
- expect(fs.existsSync(marker)).toBe(true);
149
- const contents = fs.readFileSync(marker, 'utf8');
150
- expect(contents).toContain('-t pgserve-audit');
151
- expect(contents).toContain('"event":"connection_routed"');
152
- } finally {
153
- process.env.PATH = oldPath;
154
- }
155
- });
156
-
157
- test('audit({target:"syslog"}) swallows missing logger binary', () => {
158
- // Point PATH at an empty dir → `logger` cannot be found → no throw.
159
- const empty = path.join(scratchDir, 'empty');
160
- fs.mkdirSync(empty);
161
- const oldPath = process.env.PATH;
162
- process.env.PATH = empty;
163
- try {
164
- expect(() =>
165
- audit(
166
- AUDIT_EVENTS.CONNECTION_ROUTED,
167
- { fingerprint: 'z'.repeat(12) },
168
- { target: 'syslog' },
169
- ),
170
- ).not.toThrow();
171
- } finally {
172
- process.env.PATH = oldPath;
173
- }
174
- });
175
-
176
- test('readAuditTarget reads pgserve.audit.target from package.json', () => {
177
- const pkgFile = path.join(scratchDir, 'package.json');
178
- fs.writeFileSync(
179
- pkgFile,
180
- JSON.stringify({ name: 'demo', pgserve: { audit: { target: 'syslog' } } }),
181
- );
182
- expect(readAuditTarget(pkgFile)).toBe('syslog');
183
-
184
- fs.writeFileSync(pkgFile, JSON.stringify({ name: 'demo' }));
185
- expect(readAuditTarget(pkgFile)).toBe('file');
186
-
187
- // Missing file → file (default).
188
- expect(readAuditTarget(path.join(scratchDir, 'missing.json'))).toBe('file');
189
- });