pg-here 0.1.4 → 0.1.8

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/README.md CHANGED
@@ -31,7 +31,27 @@ If you have Bun installed, you can run the published package directly:
31
31
  bunx pg-here
32
32
  ```
33
33
 
34
- If this command fails with `could not determine executable to run for package`, install at least `pg-here@0.1.4` (this is the first release with a real package binary).
34
+ This is the single command you want from any directory.
35
+
36
+ If it still resolves an older release, the package `latest` dist-tag may be behind:
37
+
38
+ To force that version and bypass a stale cache:
39
+
40
+ ```bash
41
+ bunx pg-here@0.1.8
42
+ ```
43
+
44
+ If maintainers want plain `bunx pg-here` to work for everyone, run once:
45
+
46
+ ```bash
47
+ npm dist-tag add pg-here@0.1.8 latest
48
+ ```
49
+
50
+ After that, this should work anywhere:
51
+
52
+ ```bash
53
+ bunx pg-here
54
+ ```
35
55
 
36
56
  This starts a local PostgreSQL instance in your current project directory and prints the connection string, then keeps the process alive until you stop it.
37
57
 
@@ -49,6 +69,58 @@ Pass CLI flags just like the local script when you want to override defaults:
49
69
  bunx pg-here --username postgres --password postgres --database my_app --port 55432
50
70
  ```
51
71
 
72
+ #### Linux note
73
+
74
+ If `bunx pg-here` fails with `error while loading shared libraries`:
75
+ - install missing system packages on the host (not in npm), then retry
76
+ - restart the command
77
+
78
+ Most commonly:
79
+
80
+ ```bash
81
+ # Ubuntu/Debian
82
+ sudo apt-get update && sudo apt-get install -y libxml2
83
+
84
+ # Fedora/RHEL
85
+ sudo dnf install -y libxml2
86
+
87
+ # Alpine
88
+ sudo apk add libxml2
89
+ ```
90
+
91
+ If that machine is intentionally minimal (or containerized), use an image with PostgreSQL runtime dependencies.
92
+
93
+ If apt says `Package 'libxml2' has no installation candidate`, your apt sources are likely missing standard repos.
94
+
95
+ ```bash
96
+ cat /etc/os-release
97
+ apt-cache search '^libxml2$'
98
+ ```
99
+
100
+ and if that returns nothing, fix apt sources for your distro/arch first, or use a base image that includes PostgreSQL runtime packages.
101
+
102
+ Important ABI note:
103
+
104
+ - If your host exposes `libxml2.so.16` but not `libxml2.so.2`, older releases may fail to start before applying fallback.
105
+ - If a failure mentions `/pg_local/bin/bin/postgres`, clear and redownload:
106
+
107
+ ```bash
108
+ rm -rf pg_local
109
+ bunx pg-here@0.1.8
110
+ ```
111
+
112
+ For this release, `0.1.8` also attempts a one-time compatibility workaround when it detects
113
+ `libxml2.so.16`-only hosts:
114
+ - it creates a local runtime symlink in `./pg_local/runtime-libs/libxml2.so.2` pointing to the discovered `libxml2.so.16`
115
+ - sets `LD_LIBRARY_PATH` for the retry so `postgres`/`initdb` can launch
116
+
117
+ If your system is locked down and this still fails, create a global compatibility symlink (where permitted):
118
+
119
+ ```bash
120
+ sudo ln -sfn /usr/lib/x86_64-linux-gnu/libxml2.so.16 /usr/local/lib/libxml2.so.2
121
+ sudo ldconfig
122
+ ```
123
+
52
124
  ### Programmatic usage
53
125
 
54
126
  Use `pg-here` directly from your server startup code and auto-create the app database if missing.
package/bin/pg-here.mjs CHANGED
@@ -3,6 +3,10 @@
3
3
  import yargs from "yargs";
4
4
  import { hideBin } from "yargs/helpers";
5
5
  import { startPgHere } from "../dist/index.js";
6
+ import {
7
+ maybePrintLinuxRuntimeHelp,
8
+ startPgHereWithLibxml2Compat,
9
+ } from "../scripts/cli-error-help.mjs";
6
10
 
7
11
  const argv = await yargs(hideBin(process.argv))
8
12
  .version(false)
@@ -31,14 +35,25 @@ const argv = await yargs(hideBin(process.argv))
31
35
  })
32
36
  .parse();
33
37
 
34
- const pg = await startPgHere({
35
- projectDir: process.cwd(),
36
- port: argv.port,
37
- username: argv.username,
38
- password: argv.password,
39
- database: argv.database,
40
- postgresVersion: argv["pg-version"],
41
- });
38
+ let pg;
39
+ const startInstance = () =>
40
+ startPgHere({
41
+ projectDir: process.cwd(),
42
+ port: argv.port,
43
+ username: argv.username,
44
+ password: argv.password,
45
+ database: argv.database,
46
+ postgresVersion: argv["pg-version"],
47
+ });
48
+
49
+ try {
50
+ pg = await startPgHereWithLibxml2Compat(startInstance, process.cwd());
51
+ } catch (error) {
52
+ if (process.platform === "linux") {
53
+ maybePrintLinuxRuntimeHelp(error);
54
+ }
55
+ throw error;
56
+ }
42
57
 
43
58
  console.log(pg.databaseConnectionString);
44
59
  setInterval(() => {}, 1 << 30);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pg-here",
3
- "version": "0.1.4",
3
+ "version": "0.1.8",
4
4
  "description": "Per-project embedded PostgreSQL with programmatic startup and auto DB creation.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,6 +19,7 @@
19
19
  "index.ts",
20
20
  "bin/pg-here.mjs",
21
21
  "scripts/pg-dev.mjs",
22
+ "scripts/cli-error-help.mjs",
22
23
  "README.md"
23
24
  ],
24
25
  "private": false,
@@ -0,0 +1,261 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { accessSync, existsSync, lstatSync, mkdirSync, readlinkSync, rmSync, symlinkSync, F_OK } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ const LIBXML2_SONAME = "libxml2.so.2";
6
+ const LIBXML2_ALTERNATE_SONAME = "libxml2.so.16";
7
+ const LIBXML2_COMPAT_DIR = "pg_local/runtime-libs";
8
+
9
+ const LIB_PATHS = [
10
+ "/usr/lib/x86_64-linux-gnu",
11
+ "/usr/lib/i386-linux-gnu",
12
+ "/usr/lib",
13
+ "/lib/x86_64-linux-gnu",
14
+ "/lib/i386-linux-gnu",
15
+ "/lib",
16
+ "/usr/local/lib",
17
+ ];
18
+
19
+ export function maybePrintLinuxRuntimeHelp(error) {
20
+ const message = String(error?.message ?? error);
21
+ const missingLibsFromError = extractMissingLibrariesFromMessage(message);
22
+ const missingLibsFromBinary = extractMissingLibrariesFromBinaryPath(message);
23
+ const binPath = extractBinaryPathFromError(message);
24
+
25
+ const missingLibs = [...new Set([...missingLibsFromError, ...missingLibsFromBinary])];
26
+
27
+ if (missingLibs.length === 0) {
28
+ return;
29
+ }
30
+
31
+ console.error();
32
+ console.error("PostgreSQL startup failed due to missing Linux runtime dependencies.");
33
+ console.error(`Missing libraries: ${missingLibs.join(", ")}`);
34
+ console.error();
35
+ console.error("Install system packages for your distro and retry:");
36
+ console.error(
37
+ " Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y libxml2 libxml2-dev libxml2-utils"
38
+ );
39
+ console.error(
40
+ " Fedora/RHEL: sudo dnf install -y libxml2"
41
+ );
42
+ console.error(
43
+ " Alpine: sudo apk add libxml2"
44
+ );
45
+ console.error();
46
+ console.error(" Quick checks:");
47
+ console.error(
48
+ " find /usr/lib /usr/local/lib /lib -name 'libxml2.so.2*' 2>/dev/null | head"
49
+ );
50
+ if (binPath) {
51
+ console.error(` ldd ${binPath} | grep libxml2`);
52
+ }
53
+ console.error(" sudo ldconfig && sudo ldconfig -p | grep libxml2.so.2");
54
+ if (binPath.includes("/bin/bin/postgres")) {
55
+ console.error(" Your local pg_local cache looks partially provisioned (bin/bin/postgres path).");
56
+ console.error(" Try removing it and retrying:");
57
+ console.error(" rm -rf pg_local && bunx pg-here@0.1.8");
58
+ }
59
+ console.error(
60
+ `If your distro requires different package names, install packages that provide: ${missingLibs.join(", ")}`
61
+ );
62
+ printPotentialSymlinkHint();
63
+
64
+ if (hasLibxml2CompatibilityNeed(error)) {
65
+ const fallback = findLibraryPath(LIBXML2_ALTERNATE_SONAME);
66
+ console.error();
67
+ console.error("Compatibility note:");
68
+ if (fallback) {
69
+ console.error(
70
+ `This host only provides ${LIBXML2_ALTERNATE_SONAME} at ${fallback}, not ${LIBXML2_SONAME}.`
71
+ );
72
+ console.error(
73
+ "This release now retries startup with a project-local symlink fallback automatically."
74
+ );
75
+ console.error(
76
+ `If that does not work, you can retry with a global symlink (requires sudo):\n sudo ln -sfn ${fallback} /usr/local/lib/${LIBXML2_SONAME} && sudo ldconfig`
77
+ );
78
+ } else {
79
+ console.error(
80
+ `Expected ${LIBXML2_SONAME} was not found and no ${LIBXML2_ALTERNATE_SONAME} fallback was discovered.`
81
+ );
82
+ console.error(
83
+ `Your host may be too minimal or custom; install a PostgreSQL-ready runtime stack for your distro.`
84
+ );
85
+ }
86
+ }
87
+ }
88
+
89
+ export function hasLibxml2CompatibilityNeed(error) {
90
+ const message = String(error?.message ?? error);
91
+ const missingFromMessage = extractMissingLibrariesFromMessage(message);
92
+ const missingFromBinary = extractMissingLibrariesFromBinaryPath(message);
93
+ const missing = [...missingFromMessage, ...missingFromBinary];
94
+ return missing.includes(LIBXML2_SONAME) || message.includes("libxml2.so.2");
95
+ }
96
+
97
+ export async function startPgHereWithLibxml2Compat(start, workingDir) {
98
+ try {
99
+ return await start();
100
+ } catch (error) {
101
+ if (!hasLibxml2CompatibilityNeed(error)) {
102
+ throw error;
103
+ }
104
+
105
+ const patched = ensureLibxml2Compatibility(workingDir);
106
+ if (!patched) {
107
+ throw error;
108
+ }
109
+
110
+ return await start();
111
+ }
112
+ }
113
+
114
+ export function ensureLibxml2Compatibility(workingDir) {
115
+ if (process.platform !== "linux") {
116
+ return false;
117
+ }
118
+
119
+ const projectDir = typeof workingDir === "string" && workingDir ? workingDir : process.cwd();
120
+ const compatDir = join(projectDir, LIBXML2_COMPAT_DIR);
121
+ const compatLib = join(compatDir, LIBXML2_SONAME);
122
+
123
+ if (findLibraryPath(LIBXML2_SONAME)) {
124
+ return false;
125
+ }
126
+
127
+ const fallback = findLibraryPath(LIBXML2_ALTERNATE_SONAME);
128
+ if (!fallback) {
129
+ return false;
130
+ }
131
+
132
+ try {
133
+ mkdirSync(compatDir, { recursive: true });
134
+ if (existsSync(compatLib)) {
135
+ const existing = readCompatLink(compatLib);
136
+ if (existing === fallback) {
137
+ ensureLdLibraryPath(compatDir);
138
+ return true;
139
+ }
140
+
141
+ rmSync(compatLib, { force: true });
142
+ }
143
+
144
+ symlinkSync(fallback, compatLib);
145
+ ensureLdLibraryPath(compatDir);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ function readCompatLink(path) {
153
+ try {
154
+ const info = lstatSync(path);
155
+ if (!info.isSymbolicLink()) {
156
+ return "";
157
+ }
158
+ return readlinkSync(path);
159
+ } catch {
160
+ return "";
161
+ }
162
+ }
163
+
164
+ function ensureLdLibraryPath(path) {
165
+ const currentPath = process.env.LD_LIBRARY_PATH ?? "";
166
+ const paths = currentPath.split(":").filter(Boolean);
167
+ if (paths.includes(path)) {
168
+ return;
169
+ }
170
+
171
+ process.env.LD_LIBRARY_PATH = path + (currentPath ? `:${currentPath}` : "");
172
+ }
173
+
174
+ function extractMissingLibrariesFromMessage(message) {
175
+ const regex = /([A-Za-z0-9._-]+\.so(?:\.\d+)*)\b/g;
176
+ const matches = [...message.matchAll(regex)].map((match) => match[1]);
177
+ return matches.filter((value) => value.toLowerCase().includes("so"));
178
+ }
179
+
180
+ function extractMissingLibrariesFromBinaryPath(message) {
181
+ const pathMatch = message.match(/(\/[^\s"]*\/bin\/postgres)/);
182
+ if (!pathMatch) {
183
+ return [];
184
+ }
185
+
186
+ const binaryPath = pathMatch[1];
187
+ const result = spawnSync("ldd", [binaryPath], { encoding: "utf8" });
188
+ const output = `${result.stdout ?? ""} ${result.stderr ?? ""}`;
189
+ if (!output) {
190
+ return [];
191
+ }
192
+
193
+ return [...output.matchAll(/([^\s]+)\s+=>\s+not found/g)].map(
194
+ (match) => match[1]
195
+ );
196
+ }
197
+
198
+ function extractBinaryPathFromError(message) {
199
+ const pathMatch = message.match(/(\/[^\s"]*\/bin\/postgres)/);
200
+ return pathMatch ? pathMatch[1] : "";
201
+ }
202
+
203
+ function hasLibxmlVersion16Only() {
204
+ const candidates = ["/usr/lib/x86_64-linux-gnu", "/usr/lib", "/lib/x86_64-linux-gnu", "/lib"];
205
+ for (const dir of candidates) {
206
+ try {
207
+ accessSync(`${dir}/libxml2.so.2`, F_OK);
208
+ return false;
209
+ } catch {
210
+ // continue
211
+ }
212
+
213
+ try {
214
+ accessSync(`${dir}/libxml2.so.16`, F_OK);
215
+ if (hasLibxmlVersion16AtPath(`${dir}/libxml2.so.16`)) {
216
+ return true;
217
+ }
218
+ } catch {}
219
+ }
220
+
221
+ return false;
222
+ }
223
+
224
+ function hasLibxmlVersion16AtPath(path) {
225
+ return typeof path === "string" && path.endsWith("/libxml2.so.16");
226
+ }
227
+
228
+ function printPotentialSymlinkHint() {
229
+ const result = spawnSync("sh", [
230
+ "-c",
231
+ "find /usr/lib /usr/local/lib /lib -name 'libxml2.so.2*' 2>/dev/null",
232
+ ], { encoding: "utf8" });
233
+
234
+ if (result.status !== 0) {
235
+ return;
236
+ }
237
+
238
+ const matches = (result.stdout ?? "").split("\n").filter(Boolean);
239
+ const exact = matches.find((item) => item.endsWith("/libxml2.so.2"));
240
+ if (!exact) {
241
+ console.error();
242
+ console.error("Hint:");
243
+ if (hasLibxmlVersion16Only()) {
244
+ console.error("This host appears to provide only libxml2.so.16. Compatibility is not guaranteed.");
245
+ } else {
246
+ console.error("No libxml2.so.2 file was found in the standard library paths.");
247
+ }
248
+ }
249
+ }
250
+
251
+ function findLibraryPath(name) {
252
+ const args = [...LIB_PATHS, "-name", `${name}*`, "-print"];
253
+ const result = spawnSync("find", args, { encoding: "utf8" });
254
+ const matches = (result.stdout ?? "").split("\n").filter(Boolean);
255
+ if (matches.length === 0) {
256
+ return "";
257
+ }
258
+
259
+ const exact = matches.find((item) => item.endsWith(`/${name}`));
260
+ return exact ?? matches[0];
261
+ }
@@ -1,6 +1,10 @@
1
1
  import yargs from "yargs";
2
2
  import { hideBin } from "yargs/helpers";
3
3
  import { startPgHere } from "../index.ts";
4
+ import {
5
+ maybePrintLinuxRuntimeHelp,
6
+ startPgHereWithLibxml2Compat,
7
+ } from "./cli-error-help.mjs";
4
8
 
5
9
  const argv = await yargs(hideBin(process.argv))
6
10
  .version(false)
@@ -29,14 +33,25 @@ const argv = await yargs(hideBin(process.argv))
29
33
  })
30
34
  .parse();
31
35
 
32
- const pg = await startPgHere({
33
- projectDir: process.cwd(),
34
- port: argv.port,
35
- username: argv.username,
36
- password: argv.password,
37
- database: argv.database,
38
- postgresVersion: argv["pg-version"],
39
- });
36
+ let pg;
37
+ const startInstance = () =>
38
+ startPgHere({
39
+ projectDir: process.cwd(),
40
+ port: argv.port,
41
+ username: argv.username,
42
+ password: argv.password,
43
+ database: argv.database,
44
+ postgresVersion: argv["pg-version"],
45
+ });
46
+
47
+ try {
48
+ pg = await startPgHereWithLibxml2Compat(startInstance, process.cwd());
49
+ } catch (error) {
50
+ if (process.platform === "linux") {
51
+ maybePrintLinuxRuntimeHelp(error);
52
+ }
53
+ throw error;
54
+ }
40
55
 
41
56
  // print connection string for tooling
42
57
  console.log(pg.databaseConnectionString);