pgserve 2.1.3 → 2.2.1
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/CHANGELOG.md +96 -0
- package/README.md +105 -1
- package/bin/autopg-wrapper.cjs +16 -0
- package/bin/pgserve-wrapper.cjs +32 -6
- package/bin/postgres-server.js +56 -0
- package/console/README.md +131 -0
- package/console/api.js +173 -0
- package/console/app.jsx +483 -0
- package/console/colors_and_type.css +227 -0
- package/console/components.jsx +167 -0
- package/console/console.css +1666 -0
- package/console/data.jsx +350 -0
- package/console/index.html +31 -0
- package/console/screens/databases.jsx +5 -0
- package/console/screens/health.jsx +5 -0
- package/console/screens/ingress.jsx +5 -0
- package/console/screens/optimizer.jsx +5 -0
- package/console/screens/rlm-sim.jsx +5 -0
- package/console/screens/rlm-trace.jsx +5 -0
- package/console/screens/security.jsx +5 -0
- package/console/screens/settings.jsx +611 -0
- package/console/screens/sql.jsx +5 -0
- package/console/screens/sync.jsx +5 -0
- package/console/screens/tables.jsx +5 -0
- package/console/tweaks-panel.jsx +425 -0
- package/package.json +14 -2
- package/scripts/postinstall.cjs +60 -0
- package/src/cli-config.cjs +310 -0
- package/src/cli-install.cjs +112 -11
- package/src/cli-restart.cjs +228 -0
- package/src/cli-ui.cjs +580 -0
- package/src/cluster.js +43 -38
- package/src/postgres.js +141 -19
- package/src/settings-loader.cjs +235 -0
- package/src/settings-migrate.cjs +212 -0
- package/src/settings-pg-args.cjs +146 -0
- package/src/settings-schema.cjs +422 -0
- package/src/settings-validator.cjs +416 -0
- package/src/settings-writer.cjs +288 -0
- package/src/upgrade/index.js +65 -0
- package/src/upgrade/runner.js +23 -0
- package/src/upgrade/steps/binary-cache-flush.js +67 -0
- package/src/upgrade/steps/consumer-signal.js +40 -0
- package/src/upgrade/steps/env-refresh.js +89 -0
- package/src/upgrade/steps/health-validate.js +53 -0
- package/src/upgrade/steps/plpgsql-resolve.js +66 -0
- package/src/upgrade/steps/port-reconcile.js +52 -0
- package/.claude/context/windows-debug.md +0 -119
- package/.genie/AGENTS.md +0 -15
- package/.genie/agents/README.md +0 -110
- package/.genie/agents/analyze.md +0 -176
- package/.genie/agents/forge.md +0 -290
- package/.genie/agents/garbage-cleaner.md +0 -324
- package/.genie/agents/garbage-collector.md +0 -596
- package/.genie/agents/github-issue-gc.md +0 -618
- package/.genie/agents/review.md +0 -380
- package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
- package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
- package/.genie/agents/semantic-analyzer.md +0 -101
- package/.genie/agents/update.md +0 -182
- package/.genie/agents/wish.md +0 -357
- package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
- package/.genie/code/AGENTS.md +0 -694
- package/.genie/code/agents/audit/risk.md +0 -173
- package/.genie/code/agents/audit/security.md +0 -189
- package/.genie/code/agents/audit.md +0 -145
- package/.genie/code/agents/challenge.md +0 -230
- package/.genie/code/agents/change-reviewer.md +0 -295
- package/.genie/code/agents/code-garbage-collector.md +0 -425
- package/.genie/code/agents/code-quality.md +0 -410
- package/.genie/code/agents/commit-suggester.md +0 -255
- package/.genie/code/agents/commit.md +0 -124
- package/.genie/code/agents/consensus.md +0 -204
- package/.genie/code/agents/daily-standup.md +0 -722
- package/.genie/code/agents/docgen.md +0 -48
- package/.genie/code/agents/explore.md +0 -79
- package/.genie/code/agents/fix.md +0 -100
- package/.genie/code/agents/git/commit-advisory.md +0 -219
- package/.genie/code/agents/git/workflows/issue.md +0 -244
- package/.genie/code/agents/git/workflows/pr.md +0 -179
- package/.genie/code/agents/git/workflows/release.md +0 -460
- package/.genie/code/agents/git/workflows/report.md +0 -342
- package/.genie/code/agents/git.md +0 -432
- package/.genie/code/agents/implementor.md +0 -161
- package/.genie/code/agents/install.md +0 -515
- package/.genie/code/agents/issue-creator.md +0 -344
- package/.genie/code/agents/polish.md +0 -116
- package/.genie/code/agents/qa.md +0 -653
- package/.genie/code/agents/refactor.md +0 -294
- package/.genie/code/agents/release.md +0 -1129
- package/.genie/code/agents/roadmap.md +0 -885
- package/.genie/code/agents/tests.md +0 -557
- package/.genie/code/agents/tracer.md +0 -50
- package/.genie/code/agents/update/upstream-update.md +0 -85
- package/.genie/code/agents/update/versions/generic-update.md +0 -305
- package/.genie/code/agents/vibe.md +0 -1317
- package/.genie/code/spells/agent-configuration.md +0 -58
- package/.genie/code/spells/automated-rc-publishing.md +0 -106
- package/.genie/code/spells/branch-tracker-guidance.md +0 -28
- package/.genie/code/spells/debug.md +0 -320
- package/.genie/code/spells/emoji-naming-convention.md +0 -303
- package/.genie/code/spells/evidence-storage.md +0 -26
- package/.genie/code/spells/file-naming-rules.md +0 -35
- package/.genie/code/spells/forge-code-blueprints.md +0 -195
- package/.genie/code/spells/genie-integration.md +0 -153
- package/.genie/code/spells/publishing-protocol.md +0 -61
- package/.genie/code/spells/team-consultation-protocol.md +0 -284
- package/.genie/code/spells/tool-requirements.md +0 -20
- package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
- package/.genie/code/teams/tech-council/council.md +0 -328
- package/.genie/code/teams/tech-council/jt.md +0 -352
- package/.genie/code/teams/tech-council/nayr.md +0 -305
- package/.genie/code/teams/tech-council/oettam.md +0 -375
- package/.genie/neurons/README.md +0 -193
- package/.genie/neurons/forge.md +0 -106
- package/.genie/neurons/genie.md +0 -63
- package/.genie/neurons/review.md +0 -106
- package/.genie/neurons/wish.md +0 -104
- package/.genie/product/README.md +0 -20
- package/.genie/product/cli-automation.md +0 -359
- package/.genie/product/environment.md +0 -60
- package/.genie/product/mission.md +0 -60
- package/.genie/product/roadmap.md +0 -44
- package/.genie/product/tech-stack.md +0 -34
- package/.genie/product/templates/context-template.md +0 -218
- package/.genie/product/templates/qa-done-report-template.md +0 -68
- package/.genie/product/templates/review-report-template.md +0 -89
- package/.genie/product/templates/wish-template.md +0 -120
- package/.genie/scripts/helpers/analyze-commit.js +0 -195
- package/.genie/scripts/helpers/bullet-counter.js +0 -194
- package/.genie/scripts/helpers/bullet-find.js +0 -289
- package/.genie/scripts/helpers/bullet-id.js +0 -244
- package/.genie/scripts/helpers/check-secrets.js +0 -237
- package/.genie/scripts/helpers/count-tokens.js +0 -200
- package/.genie/scripts/helpers/create-frontmatter.js +0 -456
- package/.genie/scripts/helpers/detect-markers.js +0 -293
- package/.genie/scripts/helpers/detect-todos.js +0 -267
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
- package/.genie/scripts/helpers/embeddings.js +0 -344
- package/.genie/scripts/helpers/find-empty-sections.js +0 -158
- package/.genie/scripts/helpers/index.js +0 -319
- package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
- package/.genie/scripts/helpers/validate-links.js +0 -207
- package/.genie/scripts/helpers/validate-paths.js +0 -373
- package/.genie/spells/README.md +0 -9
- package/.genie/spells/ace-protocol.md +0 -118
- package/.genie/spells/ask-one-at-a-time.md +0 -175
- package/.genie/spells/backup-analyzer.md +0 -542
- package/.genie/spells/blocker.md +0 -12
- package/.genie/spells/break-things-move-fast.md +0 -56
- package/.genie/spells/context-candidates.md +0 -72
- package/.genie/spells/context-critic.md +0 -51
- package/.genie/spells/defer-to-expertise.md +0 -278
- package/.genie/spells/delegate-dont-do.md +0 -292
- package/.genie/spells/error-investigation-protocol.md +0 -328
- package/.genie/spells/evidence-based-completion.md +0 -273
- package/.genie/spells/experiment.md +0 -65
- package/.genie/spells/file-creation-protocol.md +0 -229
- package/.genie/spells/forge-integration.md +0 -281
- package/.genie/spells/forge-orchestration.md +0 -514
- package/.genie/spells/gather-context.md +0 -18
- package/.genie/spells/global-health-check.md +0 -34
- package/.genie/spells/global-noop-roundtrip.md +0 -25
- package/.genie/spells/install-genie.md +0 -1232
- package/.genie/spells/install.md +0 -82
- package/.genie/spells/investigate-before-commit.md +0 -112
- package/.genie/spells/know-yourself.md +0 -288
- package/.genie/spells/learn.md +0 -828
- package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
- package/.genie/spells/mcp-first.md +0 -124
- package/.genie/spells/multi-step-execution.md +0 -67
- package/.genie/spells/orchestration-boundary-protocol.md +0 -256
- package/.genie/spells/orchestrator-not-implementor.md +0 -189
- package/.genie/spells/prompt.md +0 -746
- package/.genie/spells/reflect.md +0 -404
- package/.genie/spells/routing-decision-matrix.md +0 -368
- package/.genie/spells/run-in-parallel.md +0 -12
- package/.genie/spells/session-state-updater-example.md +0 -196
- package/.genie/spells/session-state-updater.md +0 -220
- package/.genie/spells/track-long-running-tasks.md +0 -133
- package/.genie/spells/troubleshoot-infrastructure.md +0 -176
- package/.genie/spells/upgrade-genie.md +0 -415
- package/.genie/spells/url-presentation-protocol.md +0 -301
- package/.genie/spells/wish-initiation.md +0 -158
- package/.genie/spells/wish-issue-linkage.md +0 -410
- package/.genie/spells/wish-lifecycle.md +0 -100
- package/.genie/state/provider-status.json +0 -3
- package/.genie/state/version.json +0 -16
- package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
- package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
- package/.genie/wishes/pgserve-v2/WISH.md +0 -442
- package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
- package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
- package/.gitguardian.yaml +0 -29
- package/.gitguardianignore +0 -16
- package/.github/workflows/ci.yml +0 -122
- package/.github/workflows/release.yml +0 -289
- package/.github/workflows/version.yml +0 -228
- package/.husky/pre-commit +0 -2
- package/AGENTS.md +0 -433
- package/CLAUDE.md +0 -1
- package/Makefile +0 -285
- package/assets/icon.ico +0 -0
- package/bun.lock +0 -435
- package/bunfig.toml +0 -28
- package/ecosystem.config.cjs +0 -23
- package/eslint.config.js +0 -63
- package/examples/multi-tenant-demo.js +0 -104
- package/install.sh +0 -123
- package/knip.json +0 -9
- package/tests/audit.test.js +0 -189
- package/tests/backpressure.test.js +0 -167
- package/tests/benchmarks/runner.js +0 -1197
- package/tests/benchmarks/vector-generator.js +0 -368
- package/tests/cli-install.test.js +0 -322
- package/tests/control-db.test.js +0 -285
- package/tests/daemon-args.test.js +0 -86
- package/tests/daemon-control.test.js +0 -171
- package/tests/daemon-fingerprint-integration.test.js +0 -111
- package/tests/daemon-pr24-regression.test.js +0 -198
- package/tests/fingerprint.test.js +0 -263
- package/tests/fixtures/240-orphan-seed.sql +0 -30
- package/tests/multi-tenant.test.js +0 -374
- package/tests/orphan-cleanup.test.js +0 -390
- package/tests/pg-version-regex.test.js +0 -129
- package/tests/quick-bench.js +0 -135
- package/tests/router-handshake-retry.test.js +0 -119
- package/tests/router-handshake-watchdog.test.js +0 -110
- package/tests/sdk.test.js +0 -71
- package/tests/stale-postmaster-pid.test.js +0 -85
- package/tests/stress-test.js +0 -439
- package/tests/sync-perf-test.js +0 -150
- package/tests/tcp-listen.test.js +0 -368
- package/tests/tenancy.test.js +0 -403
- package/tests/wrapper-supervision.test.js +0 -107
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `autopg config` subcommand router (also reachable via `pgserve config`).
|
|
3
|
+
*
|
|
4
|
+
* Surface:
|
|
5
|
+
* autopg config list - print every leaf as key|value|source
|
|
6
|
+
* autopg config get <key> - print the resolved value (machine-friendly)
|
|
7
|
+
* autopg config set <key> <value> - validate + atomic write, round-trips through get
|
|
8
|
+
* autopg config edit - open $EDITOR on settings.json
|
|
9
|
+
* autopg config path - print absolute path to settings.json
|
|
10
|
+
* autopg config init [--force] - write schema defaults; refuses to clobber
|
|
11
|
+
*
|
|
12
|
+
* Exit codes:
|
|
13
|
+
* 0 - success
|
|
14
|
+
* 1 - unknown subcommand / IO error / EDITOR not set / settings file unreadable
|
|
15
|
+
* 2 - validation error (stable shape: `error: <field> — <CODE>: <detail>`)
|
|
16
|
+
*
|
|
17
|
+
* The CLI is single-process and skips the etag round-trip — each `set` is its
|
|
18
|
+
* own transaction. Concurrency control is the UI helper's responsibility.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const { spawnSync } = require('node:child_process');
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
|
|
26
|
+
const { loadEffectiveConfig, getSettingsPath } = require('./settings-loader.cjs');
|
|
27
|
+
const {
|
|
28
|
+
setLeaf,
|
|
29
|
+
initSettings,
|
|
30
|
+
ensureConfigDir,
|
|
31
|
+
} = require('./settings-writer.cjs');
|
|
32
|
+
const {
|
|
33
|
+
ValidationError,
|
|
34
|
+
validateSetting,
|
|
35
|
+
resolveKey,
|
|
36
|
+
} = require('./settings-validator.cjs');
|
|
37
|
+
const { SCHEMA, flattenSchema } = require('./settings-schema.cjs');
|
|
38
|
+
|
|
39
|
+
const EXIT_OK = 0;
|
|
40
|
+
const EXIT_UNKNOWN = 1;
|
|
41
|
+
const EXIT_VALIDATION = 2;
|
|
42
|
+
|
|
43
|
+
function emitError(field, code, detail) {
|
|
44
|
+
process.stderr.write(`error: ${field} — ${code}: ${detail}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function emitErrorFromValidation(err) {
|
|
48
|
+
emitError(err.field ?? '_root', err.code ?? 'INVALID', err.detail ?? err.message);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the current value of `key` from the merged effective config tree.
|
|
53
|
+
* Supports curated leaves (`section.field`) and `_extra` entries
|
|
54
|
+
* (`postgres._extra.<gucName>`). Returns `{ value }` or `null` when missing.
|
|
55
|
+
*/
|
|
56
|
+
function readValue(tree, key) {
|
|
57
|
+
if (key.startsWith('postgres._extra.')) {
|
|
58
|
+
const guc = key.slice('postgres._extra.'.length);
|
|
59
|
+
const map = tree?.postgres?._extra;
|
|
60
|
+
if (map && Object.prototype.hasOwnProperty.call(map, guc)) {
|
|
61
|
+
return { value: map[guc] };
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const [section, field] = key.split('.');
|
|
66
|
+
if (!section || !field) return null;
|
|
67
|
+
const node = tree?.[section];
|
|
68
|
+
if (!node || !Object.prototype.hasOwnProperty.call(node, field)) return null;
|
|
69
|
+
return { value: node[field] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Serialize a leaf value for human consumption. Objects (the `_extra` map
|
|
74
|
+
* descriptor) round-trip through JSON; primitives stringify directly.
|
|
75
|
+
* `null` / `undefined` render as the empty string so `autopg config get`
|
|
76
|
+
* stays scriptable.
|
|
77
|
+
*/
|
|
78
|
+
function formatValue(value) {
|
|
79
|
+
if (value === null || value === undefined) return '';
|
|
80
|
+
if (typeof value === 'object') return JSON.stringify(value);
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Assemble the full set of keys to display in `config list`. Curated
|
|
86
|
+
* leaves come from the schema; `_extra` entries are expanded from the
|
|
87
|
+
* effective tree so user-added GUCs surface.
|
|
88
|
+
*/
|
|
89
|
+
function enumerateKeys(tree) {
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const [section, fields] of Object.entries(SCHEMA)) {
|
|
92
|
+
for (const field of Object.keys(fields)) {
|
|
93
|
+
out.push(`${section}.${field}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const extras = tree?.postgres?._extra;
|
|
97
|
+
if (extras && typeof extras === 'object') {
|
|
98
|
+
for (const guc of Object.keys(extras)) {
|
|
99
|
+
out.push(`postgres._extra.${guc}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function pad(s, n) {
|
|
106
|
+
s = String(s);
|
|
107
|
+
if (s.length >= n) return s;
|
|
108
|
+
return s + ' '.repeat(n - s.length);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function cmdList() {
|
|
112
|
+
const { settings, sources } = loadEffectiveConfig();
|
|
113
|
+
const keys = enumerateKeys(settings);
|
|
114
|
+
|
|
115
|
+
// Source for `_extra` entries inherits the parent map's source. The
|
|
116
|
+
// loader doesn't break the map per-entry because env precedence
|
|
117
|
+
// applies wholesale, so we surface each row's source as the parent.
|
|
118
|
+
const rows = keys.map((key) => {
|
|
119
|
+
const valueResolved = readValue(settings, key);
|
|
120
|
+
const value = valueResolved ? formatValue(valueResolved.value) : '';
|
|
121
|
+
let source;
|
|
122
|
+
if (key.startsWith('postgres._extra.')) {
|
|
123
|
+
source = sources['postgres._extra'] || 'default';
|
|
124
|
+
} else {
|
|
125
|
+
source = sources[key] || 'default';
|
|
126
|
+
}
|
|
127
|
+
return { key, value, source };
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const widths = {
|
|
131
|
+
key: Math.max(3, ...rows.map((r) => r.key.length)),
|
|
132
|
+
value: Math.max(5, ...rows.map((r) => r.value.length)),
|
|
133
|
+
source: Math.max(6, ...rows.map((r) => r.source.length)),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
process.stdout.write(
|
|
137
|
+
`${pad('KEY', widths.key)} ${pad('VALUE', widths.value)} ${pad('SOURCE', widths.source)}\n`,
|
|
138
|
+
);
|
|
139
|
+
for (const row of rows) {
|
|
140
|
+
process.stdout.write(
|
|
141
|
+
`${pad(row.key, widths.key)} ${pad(row.value, widths.value)} ${pad(row.source, widths.source)}\n`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return EXIT_OK;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function cmdGet(args) {
|
|
148
|
+
const key = args[0];
|
|
149
|
+
if (!key) {
|
|
150
|
+
emitError('_args', 'INVALID_KEY', 'config get requires a key');
|
|
151
|
+
return EXIT_VALIDATION;
|
|
152
|
+
}
|
|
153
|
+
// Validate key shape early so typos surface as INVALID_KEY rather than
|
|
154
|
+
// an empty value print.
|
|
155
|
+
try {
|
|
156
|
+
resolveKey(key);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err instanceof ValidationError) {
|
|
159
|
+
emitErrorFromValidation(err);
|
|
160
|
+
return EXIT_VALIDATION;
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { settings } = loadEffectiveConfig();
|
|
166
|
+
const resolved = readValue(settings, key);
|
|
167
|
+
if (!resolved) {
|
|
168
|
+
process.stdout.write('\n');
|
|
169
|
+
return EXIT_OK;
|
|
170
|
+
}
|
|
171
|
+
process.stdout.write(`${formatValue(resolved.value)}\n`);
|
|
172
|
+
return EXIT_OK;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function cmdSet(args) {
|
|
176
|
+
if (args.length < 2) {
|
|
177
|
+
emitError('_args', 'INVALID_KEY', 'config set requires <key> <value>');
|
|
178
|
+
return EXIT_VALIDATION;
|
|
179
|
+
}
|
|
180
|
+
const [key, ...rest] = args;
|
|
181
|
+
// Allow values that contain spaces by joining the remainder. Operators
|
|
182
|
+
// can still quote the value as a single argv slot; this is the safe
|
|
183
|
+
// fallback.
|
|
184
|
+
const value = rest.join(' ');
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
setLeaf(key, value);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (err instanceof ValidationError) {
|
|
190
|
+
emitErrorFromValidation(err);
|
|
191
|
+
return EXIT_VALIDATION;
|
|
192
|
+
}
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
return EXIT_OK;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function cmdPath() {
|
|
199
|
+
process.stdout.write(`${getSettingsPath()}\n`);
|
|
200
|
+
return EXIT_OK;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cmdInit(args) {
|
|
204
|
+
const force = args.includes('--force');
|
|
205
|
+
ensureConfigDir();
|
|
206
|
+
try {
|
|
207
|
+
initSettings({ force });
|
|
208
|
+
} catch (err) {
|
|
209
|
+
if (err.code === 'EEXIST') {
|
|
210
|
+
emitError(
|
|
211
|
+
getSettingsPath(),
|
|
212
|
+
'EEXIST',
|
|
213
|
+
'settings.json already exists; pass --force to overwrite',
|
|
214
|
+
);
|
|
215
|
+
return EXIT_VALIDATION;
|
|
216
|
+
}
|
|
217
|
+
if (err instanceof ValidationError) {
|
|
218
|
+
emitErrorFromValidation(err);
|
|
219
|
+
return EXIT_VALIDATION;
|
|
220
|
+
}
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
process.stdout.write(`autopg: wrote defaults to ${getSettingsPath()}\n`);
|
|
224
|
+
return EXIT_OK;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* `autopg config edit` — open the configured editor on `settings.json`,
|
|
229
|
+
* creating the file with defaults if it doesn't exist yet (so the
|
|
230
|
+
* operator gets a useful template instead of an empty buffer).
|
|
231
|
+
*
|
|
232
|
+
* Editor resolution: $VISUAL, $EDITOR, then `vi` (POSIX) / `notepad` (Windows).
|
|
233
|
+
*/
|
|
234
|
+
function cmdEdit() {
|
|
235
|
+
const settingsPath = getSettingsPath();
|
|
236
|
+
if (!fs.existsSync(settingsPath)) {
|
|
237
|
+
ensureConfigDir();
|
|
238
|
+
initSettings({});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const editor =
|
|
242
|
+
process.env.VISUAL ||
|
|
243
|
+
process.env.EDITOR ||
|
|
244
|
+
(process.platform === 'win32' ? 'notepad' : 'vi');
|
|
245
|
+
|
|
246
|
+
// Editors are interactive — inherit stdio so the operator gets the TUI.
|
|
247
|
+
const result = spawnSync(editor, [settingsPath], { stdio: 'inherit' });
|
|
248
|
+
if (result.error) {
|
|
249
|
+
emitError(
|
|
250
|
+
'editor',
|
|
251
|
+
'EEDITOR',
|
|
252
|
+
`failed to launch editor "${editor}": ${result.error.message}`,
|
|
253
|
+
);
|
|
254
|
+
return EXIT_UNKNOWN;
|
|
255
|
+
}
|
|
256
|
+
return result.status ?? EXIT_OK;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Subcommand dispatch. Returns the exit code; the parent dispatcher
|
|
261
|
+
* uses the return value as `process.exit(code)` directly.
|
|
262
|
+
*/
|
|
263
|
+
function dispatch(subcommand, args = []) {
|
|
264
|
+
switch (subcommand) {
|
|
265
|
+
case 'list':
|
|
266
|
+
return cmdList();
|
|
267
|
+
case 'get':
|
|
268
|
+
return cmdGet(args);
|
|
269
|
+
case 'set':
|
|
270
|
+
return cmdSet(args);
|
|
271
|
+
case 'path':
|
|
272
|
+
return cmdPath();
|
|
273
|
+
case 'init':
|
|
274
|
+
return cmdInit(args);
|
|
275
|
+
case 'edit':
|
|
276
|
+
return cmdEdit();
|
|
277
|
+
case undefined:
|
|
278
|
+
case '': {
|
|
279
|
+
// Bare `autopg config` → list (mirrors `git config --list` ergonomics).
|
|
280
|
+
return cmdList();
|
|
281
|
+
}
|
|
282
|
+
default:
|
|
283
|
+
emitError(subcommand, 'INVALID_KEY', `unknown config subcommand "${subcommand}"`);
|
|
284
|
+
process.stderr.write(
|
|
285
|
+
'usage: autopg config <list|get|set|edit|path|init> [args]\n',
|
|
286
|
+
);
|
|
287
|
+
return EXIT_UNKNOWN;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = {
|
|
292
|
+
dispatch,
|
|
293
|
+
EXIT_OK,
|
|
294
|
+
EXIT_UNKNOWN,
|
|
295
|
+
EXIT_VALIDATION,
|
|
296
|
+
// Test surface
|
|
297
|
+
_internals: {
|
|
298
|
+
cmdList,
|
|
299
|
+
cmdGet,
|
|
300
|
+
cmdSet,
|
|
301
|
+
cmdPath,
|
|
302
|
+
cmdInit,
|
|
303
|
+
cmdEdit,
|
|
304
|
+
enumerateKeys,
|
|
305
|
+
formatValue,
|
|
306
|
+
readValue,
|
|
307
|
+
flattenSchema,
|
|
308
|
+
validateSetting,
|
|
309
|
+
},
|
|
310
|
+
};
|
package/src/cli-install.cjs
CHANGED
|
@@ -77,8 +77,22 @@ const HARDENED_DEFAULTS = {
|
|
|
77
77
|
logDateFormat: 'YYYY-MM-DD HH:mm:ss.SSS',
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the config directory. AUTOPG_CONFIG_DIR (the new var) wins,
|
|
82
|
+
* PGSERVE_CONFIG_DIR (the legacy var) is honored as a fall-through, and
|
|
83
|
+
* `~/.autopg/` is the new default. The legacy default `~/.pgserve/` is
|
|
84
|
+
* NOT consulted here — `settings-migrate.js` handles the one-shot copy.
|
|
85
|
+
*
|
|
86
|
+
* Soft-rename rule: AUTOPG_<X> beats PGSERVE_<X>. When only the legacy
|
|
87
|
+
* env is set we still honor it but the loader emits a one-time
|
|
88
|
+
* deprecation log via logger.warn (see settings-loader.js).
|
|
89
|
+
*/
|
|
80
90
|
function getConfigDir() {
|
|
81
|
-
return
|
|
91
|
+
return (
|
|
92
|
+
process.env.AUTOPG_CONFIG_DIR ||
|
|
93
|
+
process.env.PGSERVE_CONFIG_DIR ||
|
|
94
|
+
path.join(os.homedir(), '.autopg')
|
|
95
|
+
);
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
function getConfigPath() {
|
|
@@ -139,11 +153,40 @@ function pm2IsAvailable() {
|
|
|
139
153
|
}
|
|
140
154
|
}
|
|
141
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Resolve the effective supervision config — start from HARDENED_DEFAULTS,
|
|
158
|
+
* overlay any values found in `~/.autopg/settings.json` `supervision`
|
|
159
|
+
* section. Failures fall through to defaults silently so `pgserve install`
|
|
160
|
+
* still works on a fresh machine before `autopg config init` has run.
|
|
161
|
+
*
|
|
162
|
+
* Precedence: defaults < settings.json < env (env wins via loadEffectiveConfig).
|
|
163
|
+
*/
|
|
164
|
+
function getEffectiveSupervision() {
|
|
165
|
+
try {
|
|
166
|
+
const { loadEffectiveConfig } = require('./settings-loader.cjs');
|
|
167
|
+
const { settings } = loadEffectiveConfig();
|
|
168
|
+
const sup = settings?.supervision || {};
|
|
169
|
+
return {
|
|
170
|
+
maxRestarts: sup.maxRestarts ?? HARDENED_DEFAULTS.maxRestarts,
|
|
171
|
+
minUptimeMs: sup.minUptimeMs ?? HARDENED_DEFAULTS.minUptimeMs,
|
|
172
|
+
restartDelayMs: sup.restartDelayMs ?? HARDENED_DEFAULTS.restartDelayMs,
|
|
173
|
+
expBackoffRestartDelayMs: sup.expBackoffRestartDelayMs ?? HARDENED_DEFAULTS.expBackoffRestartDelayMs,
|
|
174
|
+
expBackoffMaxMs: sup.expBackoffMaxMs ?? HARDENED_DEFAULTS.expBackoffMaxMs,
|
|
175
|
+
maxMemory: sup.maxMemory ?? HARDENED_DEFAULTS.maxMemory,
|
|
176
|
+
killTimeoutMs: sup.killTimeoutMs ?? HARDENED_DEFAULTS.killTimeoutMs,
|
|
177
|
+
logDateFormat: sup.logDateFormat ?? HARDENED_DEFAULTS.logDateFormat,
|
|
178
|
+
};
|
|
179
|
+
} catch {
|
|
180
|
+
return { ...HARDENED_DEFAULTS };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
142
184
|
function buildPm2StartArgs({ scriptPath, port, dataDir }) {
|
|
143
185
|
const logs = {
|
|
144
186
|
out: path.join(getLogsDir(), `${PM2_PROCESS_NAME}-out.log`),
|
|
145
187
|
error: path.join(getLogsDir(), `${PM2_PROCESS_NAME}-error.log`),
|
|
146
188
|
};
|
|
189
|
+
const supervision = getEffectiveSupervision();
|
|
147
190
|
return [
|
|
148
191
|
'start',
|
|
149
192
|
scriptPath,
|
|
@@ -152,7 +195,7 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
|
|
|
152
195
|
'--interpreter',
|
|
153
196
|
'none',
|
|
154
197
|
'--max-restarts',
|
|
155
|
-
String(
|
|
198
|
+
String(supervision.maxRestarts),
|
|
156
199
|
// NOTE: pm2 ≥ 6.0 dropped `--min-uptime` from the CLI surface — passing
|
|
157
200
|
// it produces `error: unknown option --min-uptime` and aborts the
|
|
158
201
|
// install. The flag still works inside an ecosystem file, but per the
|
|
@@ -162,18 +205,18 @@ function buildPm2StartArgs({ scriptPath, port, dataDir }) {
|
|
|
162
205
|
// than only sub-`min_uptime` ones; the budget of 50 above is sized
|
|
163
206
|
// accordingly.
|
|
164
207
|
'--restart-delay',
|
|
165
|
-
String(
|
|
208
|
+
String(supervision.restartDelayMs),
|
|
166
209
|
// Exponential backoff between successive failures: starts at 100ms,
|
|
167
210
|
// doubles each crash, ramps to ~60s. Avoids hammering pm2 + the host
|
|
168
211
|
// when the underlying issue is persistent.
|
|
169
212
|
'--exp-backoff-restart-delay',
|
|
170
|
-
String(
|
|
213
|
+
String(supervision.expBackoffRestartDelayMs),
|
|
171
214
|
'--max-memory-restart',
|
|
172
|
-
|
|
215
|
+
supervision.maxMemory,
|
|
173
216
|
'--kill-timeout',
|
|
174
|
-
String(
|
|
217
|
+
String(supervision.killTimeoutMs),
|
|
175
218
|
'--log-date-format',
|
|
176
|
-
|
|
219
|
+
supervision.logDateFormat,
|
|
177
220
|
'--output',
|
|
178
221
|
logs.out,
|
|
179
222
|
'--error',
|
|
@@ -393,12 +436,42 @@ function parseDataDir(args) {
|
|
|
393
436
|
}
|
|
394
437
|
|
|
395
438
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
*
|
|
439
|
+
* One-shot migration check from `~/.pgserve/` → `~/.autopg/`. Runs once
|
|
440
|
+
* per process at the top of dispatch() so every CLI entry point gets
|
|
441
|
+
* the cutover. Fully best-effort: any failure is swallowed (we never
|
|
442
|
+
* want migration to block an `autopg status` invocation).
|
|
443
|
+
*/
|
|
444
|
+
let _migrationChecked = false;
|
|
445
|
+
function ensureMigrationOnce() {
|
|
446
|
+
if (_migrationChecked) return;
|
|
447
|
+
_migrationChecked = true;
|
|
448
|
+
try {
|
|
449
|
+
const { migrateIfNeeded } = require('./settings-migrate.cjs');
|
|
450
|
+
const result = migrateIfNeeded();
|
|
451
|
+
if (result.migrated) {
|
|
452
|
+
process.stderr.write(
|
|
453
|
+
`autopg: migrated ${result.legacy} → ${result.fresh} (one-time)\n`,
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
} catch {
|
|
457
|
+
// Swallow — operator can re-run migration manually if needed.
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Entry point invoked by the wrapper. Returns the exit code (or a Promise
|
|
463
|
+
* for async subcommands such as `ui`). Throws on unknown subcommand so
|
|
464
|
+
* the wrapper's normal flow can take over (the router treats any
|
|
465
|
+
* non-recognized subcommand as "pass through to the postgres-server.js
|
|
466
|
+
* dispatcher").
|
|
467
|
+
*
|
|
468
|
+
* `ctx.scriptPath` is the path to `bin/postgres-server.js` (used by
|
|
469
|
+
* install for the pm2 entry point). For `restart` and `ui` we need the
|
|
470
|
+
* wrapper script path instead — `ctx.wrapperPath`. The wrapper provides
|
|
471
|
+
* both before calling dispatch.
|
|
400
472
|
*/
|
|
401
473
|
function dispatch(subcommand, args, ctx) {
|
|
474
|
+
ensureMigrationOnce();
|
|
402
475
|
switch (subcommand) {
|
|
403
476
|
case 'install':
|
|
404
477
|
return cmdInstall(args, ctx);
|
|
@@ -410,6 +483,33 @@ function dispatch(subcommand, args, ctx) {
|
|
|
410
483
|
return cmdUrl();
|
|
411
484
|
case 'port':
|
|
412
485
|
return cmdPort();
|
|
486
|
+
case 'upgrade': {
|
|
487
|
+
const opts = {
|
|
488
|
+
quiet: args.includes('--quiet'),
|
|
489
|
+
dryRun: args.includes('--dry-run'),
|
|
490
|
+
skipSteps: (() => {
|
|
491
|
+
const idx = args.indexOf('--skip-steps');
|
|
492
|
+
if (idx === -1) return [];
|
|
493
|
+
return (args[idx + 1] || '').split(',').filter(Boolean);
|
|
494
|
+
})(),
|
|
495
|
+
};
|
|
496
|
+
return import(require('node:path').join(__dirname, 'upgrade', 'index.js'))
|
|
497
|
+
.then((mod) => mod.upgrade(opts))
|
|
498
|
+
.then((r) => process.exit(r.ok ? 0 : 1));
|
|
499
|
+
}
|
|
500
|
+
case 'config': {
|
|
501
|
+
const cfg = require('./cli-config.cjs');
|
|
502
|
+
const [sub, ...rest] = args;
|
|
503
|
+
return cfg.dispatch(sub, rest);
|
|
504
|
+
}
|
|
505
|
+
case 'restart': {
|
|
506
|
+
const restart = require('./cli-restart.cjs');
|
|
507
|
+
return restart.dispatch(args, { scriptPath: ctx.wrapperPath });
|
|
508
|
+
}
|
|
509
|
+
case 'ui': {
|
|
510
|
+
const ui = require('./cli-ui.cjs');
|
|
511
|
+
return ui.dispatch(args, { scriptPath: ctx.wrapperPath });
|
|
512
|
+
}
|
|
413
513
|
default:
|
|
414
514
|
throw new Error(`pgserve: dispatch called with unknown subcommand "${subcommand}"`);
|
|
415
515
|
}
|
|
@@ -430,6 +530,7 @@ module.exports = {
|
|
|
430
530
|
readConfig,
|
|
431
531
|
writeConfig,
|
|
432
532
|
buildPm2StartArgs,
|
|
533
|
+
getEffectiveSupervision,
|
|
433
534
|
parsePort,
|
|
434
535
|
parseDataDir,
|
|
435
536
|
},
|