pgserve 2.2.1 → 2.2.2

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.
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="mdr" data-screen-label="autopg console">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>autopg · console</title>
6
+ <link rel="stylesheet" href="colors_and_type.css" />
7
+ <link rel="stylesheet" href="console.css?v=settings1" />
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="./app.js"></script>
12
+ </body>
13
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgserve",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
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",
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  "bin/",
13
13
  "src/",
14
- "console/",
14
+ "console/dist/",
15
15
  "README.md",
16
16
  "CHANGELOG.md",
17
17
  "LICENSE",
@@ -26,12 +26,14 @@
26
26
  "start": "bun bin/postgres-server.js",
27
27
  "build": "bun build --compile bin/postgres-server.js --outfile dist/pgserve",
28
28
  "build:all": "make build-all",
29
+ "console:build": "bun build console/src/main.jsx --target browser --minify --define 'process.env.NODE_ENV=\"production\"' --outfile console/dist/app.js && cp console/src/index.html console/dist/index.html && cp console/src/*.css console/dist/",
30
+ "console:dev": "bun build console/src/main.jsx --target browser --define 'process.env.NODE_ENV=\"development\"' --watch --outfile console/dist/app.js",
29
31
  "lint": "eslint src/ bin/",
30
32
  "lint:fix": "eslint src/ bin/ --fix",
31
33
  "deadcode": "knip",
32
34
  "test:npx": "scripts/test-npx.sh",
33
35
  "test:bun-self-heal": "scripts/test-bun-self-heal.sh",
34
- "prepublishOnly": "npm run lint && npm run deadcode && npm run test:npx && npm run test:bun-self-heal",
36
+ "prepublishOnly": "npm run console:build && npm run lint && npm run deadcode && npm run test:npx && npm run test:bun-self-heal",
35
37
  "prepare": "husky",
36
38
  "postinstall": "node scripts/postinstall.cjs"
37
39
  },
@@ -76,6 +78,8 @@
76
78
  ]
77
79
  },
78
80
  "dependencies": {
79
- "bun": "^1.3.4"
81
+ "bun": "^1.3.4",
82
+ "react": "^18.3.1",
83
+ "react-dom": "^18.3.1"
80
84
  }
81
85
  }
package/src/cli-ui.cjs CHANGED
@@ -142,8 +142,25 @@ function listenWithFallback(server, host, preferredPort) {
142
142
  * via npm the `files` allowlist preserves the layout.
143
143
  */
144
144
  function resolveConsoleRoot() {
145
- // src/ repo root console/
146
- return path.resolve(__dirname, '..', 'console');
145
+ // After autopg-console-dist (v2.2.2): the SPA ships pre-bundled in
146
+ // console/dist/ instead of as flat .jsx files at console/. Prefer dist/;
147
+ // fall back to console/src/ for repo-checkout dev mode (where dist/ is
148
+ // gitignored and only built on demand).
149
+ const consoleParent = path.resolve(__dirname, '..', 'console');
150
+ const distRoot = path.join(consoleParent, 'dist');
151
+ if (fs.existsSync(distRoot)) return distRoot;
152
+
153
+ const srcRoot = path.join(consoleParent, 'src');
154
+ if (fs.existsSync(srcRoot)) {
155
+ process.stderr.write(
156
+ 'autopg ui: running unbuilt sources from console/src/ — run `bun run console:build` for production behavior\n',
157
+ );
158
+ return srcRoot;
159
+ }
160
+
161
+ throw new Error(
162
+ 'console assets not found: expected console/dist/ (run `bun run console:build`) or console/src/ (repo checkout)',
163
+ );
147
164
  }
148
165
 
149
166
  /**
package/console/README.md DELETED
@@ -1,131 +0,0 @@
1
- # `console/` — autopg console
2
-
3
- Local web console served by `autopg ui`. React + Babel via CDN, no build
4
- step. Single-user dev tool — binds 127.0.0.1 only, no auth, no TLS.
5
-
6
- ## Run
7
-
8
- ```bash
9
- autopg ui # walks 8433–8533 picking the first free port
10
- autopg ui --port 8500 # bind exactly 8500 or fail
11
- autopg ui --no-open # skip browser launch (CI / headless)
12
- ```
13
-
14
- `pgserve ui …` is a forever alias of the same command.
15
-
16
- The server boots in-process via `node:http` and serves this directory as
17
- its document root. Four helper endpoints are mounted alongside the static
18
- assets — every mutation shells out to the CLI rather than calling the
19
- daemon directly, so the console works with or without a running daemon.
20
-
21
- | Endpoint | Backed by |
22
- |----------|-----------|
23
- | `GET /api/settings` | `loadEffectiveConfig()` → `{ settings, sources, etag }` |
24
- | `PUT /api/settings` | `writeSettings(body, { ifMatch })` (409 on stale `If-Match`) |
25
- | `POST /api/restart` | `cli-restart.cjs` (pm2-aware) |
26
- | `GET /api/status` | shells out to `autopg status --json` |
27
-
28
- ## Layout
29
-
30
- ```
31
- console/
32
- ├── README.md # this file
33
- ├── index.html # entry — pinned React + Babel CDN scripts
34
- ├── app.jsx # shell + sidebar router (11 routes)
35
- ├── api.js # fetch wrapper, holds latest etag, surfaces ETAG_MISMATCH
36
- ├── components.jsx # shared widgets (Seg, Toggle, Field, …)
37
- ├── data.jsx # demo data fixtures (used by placeholder screens)
38
- ├── tweaks-panel.jsx # theme/phosphor/density/CRT toggles (persists to settings.ui)
39
- ├── colors_and_type.css # design tokens
40
- ├── console.css # layout + screen styles
41
- └── screens/
42
- ├── settings.jsx # ✅ functional — 6-section schema editor
43
- ├── databases.jsx # [ coming soon ]
44
- ├── tables.jsx # [ coming soon ]
45
- ├── sql.jsx # [ coming soon ]
46
- ├── optimizer.jsx # [ coming soon ]
47
- ├── security.jsx # [ coming soon ]
48
- ├── ingress.jsx # [ coming soon ]
49
- ├── health.jsx # [ coming soon ] — next wish
50
- ├── sync.jsx # [ coming soon ]
51
- ├── rlm-trace.jsx # [ coming soon ]
52
- └── rlm-sim.jsx # [ coming soon ]
53
- ```
54
-
55
- ## Screen rollout
56
-
57
- | Screen | Status | Notes |
58
- |--------|--------|-------|
59
- | Settings | ✅ functional | 6 sections, type-aware controls, raw GUC passthrough, etag concurrency, env-override chip |
60
- | Health | 🟡 next | Live cluster health metrics — next wish |
61
- | Databases | ⚪ placeholder | List + create + drop |
62
- | Tables | ⚪ placeholder | Per-DB table inspector |
63
- | SQL | ⚪ placeholder | Ad-hoc query runner |
64
- | Optimizer | ⚪ placeholder | Plan inspector / GUC tuner suggestions |
65
- | Security | ⚪ placeholder | Roles, RLS, audit log |
66
- | Ingress | ⚪ placeholder | Listener / TLS / token surface |
67
- | Sync | ⚪ placeholder | Replication-slot status |
68
- | RLM-trace | ⚪ placeholder | RLM agent trace viewer (depends on rlmx) |
69
- | RLM-sim | ⚪ placeholder | RLM scenario simulator (depends on rlmx) |
70
-
71
- ## Local dev loop
72
-
73
- The console is shipped as static files — no build, no bundler. Edit the
74
- `.jsx` files in place; refresh the browser tab to pick up the change
75
- (Babel transpiles in the browser at load time). The CDN scripts are
76
- pinned with SRI integrity hashes — bumping React or Babel requires
77
- re-pinning the matching `integrity="sha384-…"` attribute in
78
- [`index.html`](./index.html).
79
-
80
- ```bash
81
- autopg ui --no-open --port 8500 &
82
- open http://127.0.0.1:8500
83
- # … edit screens/settings.jsx, refresh browser …
84
- kill %1
85
- ```
86
-
87
- The Settings screen reads live state from `~/.autopg/settings.json`
88
- through the helper endpoints, so changes survive reload and round-trip
89
- through `autopg config get` from another shell.
90
-
91
- ### Concurrency model
92
-
93
- `api.js` stores the etag returned by every successful GET and sends it
94
- back as `If-Match` on the next PUT. If a parallel `autopg config set` (or
95
- another browser tab) drifts the file, the PUT comes back as
96
- `409 ETAG_MISMATCH` with a fresh `currentEtag`. The Settings screen
97
- catches this and shows a "settings changed, reload?" banner instead of
98
- overwriting the operator's other changes.
99
-
100
- ### Env-override chip
101
-
102
- `GET /api/settings` returns a `sources` map (one entry per leaf:
103
- `'default' | 'file' | 'env:<NAME>'`). Rows whose source starts with
104
- `env:` render a yellow `OVERRIDDEN BY ENV` chip — Save still writes the
105
- file, but `loadEffectiveConfig()` will keep returning the env value
106
- until the env var is unset or the daemon is restarted with a clean
107
- environment.
108
-
109
- ## Design system
110
-
111
- The console UI is derived from the `pgserve-console` design kit at
112
- `namastex-design-system/ui_kits/pgserve-console`. The CSS files
113
- (`colors_and_type.css`, `console.css`) and the shared widgets
114
- (`components.jsx`, `tweaks-panel.jsx`) are copied verbatim — the
115
- soft rename only touches the topbar identity (`pgserve` → `autopg`)
116
- and the Settings screen, which was rewritten to match the
117
- 6-section schema documented in
118
- [`docs/settings-schema.md`](../docs/settings-schema.md).
119
-
120
- ## What's deliberately not here
121
-
122
- - **No build step.** Pre-bundling is a future optimization. The CDN +
123
- Babel-in-browser path is intentional for v1 — zero infrastructure.
124
- - **No daemon HTTP API.** The CLI is the source of truth; every UI
125
- mutation shells out. This means the UI works ahead of `autopg
126
- install` (you can configure before the daemon ever runs) and
127
- cannot leak privileges through a long-lived listening socket.
128
- - **No multi-user / multi-machine access.** 127.0.0.1 only, by
129
- design.
130
- - **No telemetry, no analytics.** Static page + four endpoints, all
131
- local.
package/console/api.js DELETED
@@ -1,173 +0,0 @@
1
- /* autopg console · API client.
2
- *
3
- * Wraps the four helper endpoints exposed by `autopg ui`:
4
- * GET /api/settings → { settings, sources, etag, path }
5
- * PUT /api/settings → { ok, etag } | { error: { code, message, field? } }
6
- * POST /api/restart → { ok } | { error }
7
- * GET /api/status → whatever `pgserve status --json` returns
8
- *
9
- * The latest etag from a successful GET is cached on the module so PUTs can
10
- * send `If-Match` without the caller threading it through manually. PUT
11
- * replies update the cached etag too so successive saves chain cleanly.
12
- *
13
- * Errors from the server come back as `{ error: { code, message, field? } }`.
14
- * The wrapper raises a structured `ApiError` (with `.code`, `.field`,
15
- * `.message`, `.status`, `.currentEtag?`) so screens can branch on the code
16
- * without parsing strings. ETAG_MISMATCH is surfaced as a normal rejection
17
- * with `error.code === 'ETAG_MISMATCH'` plus `error.currentEtag` so the
18
- * Settings screen can show a "settings changed, reload?" banner.
19
- */
20
- (function (root) {
21
- 'use strict';
22
-
23
- const STATE = { etag: null };
24
-
25
- class ApiError extends Error {
26
- constructor({ code, message, field, status, currentEtag }) {
27
- super(message || code || 'api error');
28
- this.name = 'ApiError';
29
- this.code = code || 'UNKNOWN';
30
- if (field) this.field = field;
31
- if (typeof status === 'number') this.status = status;
32
- if (currentEtag) this.currentEtag = currentEtag;
33
- }
34
- }
35
-
36
- async function parseJson(res) {
37
- const text = await res.text();
38
- if (!text) return {};
39
- try {
40
- return JSON.parse(text);
41
- } catch {
42
- return { _raw: text };
43
- }
44
- }
45
-
46
- async function getSettings() {
47
- const res = await fetch('/api/settings', {
48
- method: 'GET',
49
- headers: { accept: 'application/json' },
50
- cache: 'no-store',
51
- });
52
- const body = await parseJson(res);
53
- if (!res.ok) {
54
- throw new ApiError({
55
- code: body?.error?.code,
56
- message: body?.error?.message,
57
- field: body?.error?.field,
58
- status: res.status,
59
- });
60
- }
61
- if (body && body.etag) STATE.etag = body.etag;
62
- return body;
63
- }
64
-
65
- async function putSettings(patch, { ifMatch } = {}) {
66
- const etag = ifMatch ?? STATE.etag;
67
- if (!etag) {
68
- throw new ApiError({
69
- code: 'PRECONDITION_REQUIRED',
70
- message: 'no etag cached — call getSettings() before putSettings()',
71
- status: 428,
72
- });
73
- }
74
- const res = await fetch('/api/settings', {
75
- method: 'PUT',
76
- headers: {
77
- 'content-type': 'application/json',
78
- 'if-match': etag,
79
- accept: 'application/json',
80
- },
81
- body: JSON.stringify(patch ?? {}),
82
- });
83
- const body = await parseJson(res);
84
- if (res.status === 409) {
85
- // Update the cached etag so the next reload has the latest.
86
- if (body && body.currentEtag) STATE.etag = body.currentEtag;
87
- throw new ApiError({
88
- code: body?.error?.code || 'ETAG_MISMATCH',
89
- message: body?.error?.message || 'settings changed on disk',
90
- status: 409,
91
- currentEtag: body?.currentEtag,
92
- });
93
- }
94
- if (!res.ok) {
95
- throw new ApiError({
96
- code: body?.error?.code,
97
- message: body?.error?.message,
98
- field: body?.error?.field,
99
- status: res.status,
100
- });
101
- }
102
- if (body && body.etag) STATE.etag = body.etag;
103
- return body;
104
- }
105
-
106
- async function restart() {
107
- const res = await fetch('/api/restart', {
108
- method: 'POST',
109
- headers: { accept: 'application/json' },
110
- });
111
- const body = await parseJson(res);
112
- if (!res.ok) {
113
- throw new ApiError({
114
- code: body?.error?.code,
115
- message: body?.error?.message,
116
- status: res.status,
117
- });
118
- }
119
- return body;
120
- }
121
-
122
- async function getStatus() {
123
- const res = await fetch('/api/status', {
124
- method: 'GET',
125
- headers: { accept: 'application/json' },
126
- cache: 'no-store',
127
- });
128
- const body = await parseJson(res);
129
- if (!res.ok) {
130
- throw new ApiError({
131
- code: body?.error?.code,
132
- message: body?.error?.message,
133
- status: res.status,
134
- });
135
- }
136
- return body;
137
- }
138
-
139
- // Live pgserve stats — connections + databases. Always returns a body
140
- // (status 200) even when the daemon is unreachable; check `body.ok`.
141
- // Safe to poll from the topbar without try/catch error handling.
142
- async function getStats() {
143
- try {
144
- const res = await fetch('/api/stats', {
145
- method: 'GET',
146
- headers: { accept: 'application/json' },
147
- cache: 'no-store',
148
- });
149
- return await parseJson(res);
150
- } catch (err) {
151
- return { ok: false, reason: 'fetch-failed', message: err.message };
152
- }
153
- }
154
-
155
- function getCachedEtag() {
156
- return STATE.etag;
157
- }
158
-
159
- function setCachedEtag(etag) {
160
- STATE.etag = etag || null;
161
- }
162
-
163
- root.AutopgApi = {
164
- getSettings,
165
- putSettings,
166
- restart,
167
- getStatus,
168
- getStats,
169
- getCachedEtag,
170
- setCachedEtag,
171
- ApiError,
172
- };
173
- })(typeof window !== 'undefined' ? window : globalThis);