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 +73 -1
- package/bin/pg-here.mjs +23 -8
- package/package.json +2 -1
- package/scripts/cli-error-help.mjs +261 -0
- package/scripts/pg-dev.mjs +23 -8
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
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
|
+
}
|
package/scripts/pg-dev.mjs
CHANGED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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);
|