UNPKG

4.97 kBPlain TextView Raw
1import * as fs from 'fs-extra-plus';
2import { spawn } from 'p-spawn';
3
4
5export type PsqlOptions = {
6 user?: string,
7 password?: string,
8 database?: string,
9 host?: string,
10 port?: string,
11 toConsole?: boolean;
12}
13
14const defaultPsqlOptions = {
15 toConsole: true
16}
17
18// --------- psql public API --------- //
19export type PsqlImportItem = { file: string, stdout?: string, stderr?: string };
20
21// NOTE: for now, ignore pgOpts.pwd
22export async function psqlImport(pgOpts: PsqlOptions, filePaths: string[]): Promise<PsqlImportItem[]> {
23 pgOpts = { ...defaultPsqlOptions, ...pgOpts };
24 const items: PsqlImportItem[] = [];
25 if (typeof filePaths === "string") {
26 filePaths = [filePaths];
27 }
28
29 for (let file of filePaths) {
30
31 const item: PsqlImportItem = { file };
32
33 const { stdout, stderr } = await execPsql(pgOpts, ["-f", file])
34
35 item.stdout = stdout;
36 item.stderr = stderr;
37 items.push(item);
38
39 if (stderr) {
40 const err: Error & { items?: PsqlImportItem[] } = new Error(`psqlImport ERROR for file ${file}:\n${item.stderr}`);
41 err.items = items;
42 throw err;
43 }
44 }
45
46 return items;
47
48}
49
50// pgdump with no-owner, no-acl
51export async function psqlExport(pgOpts: PsqlOptions, filepath: string) {
52 var defaultArgs = ["--no-owner", "--no-acl"];
53
54 var cmd = "pg_dump";
55 const { args, env } = buildPgArgs(pgOpts);
56 args.push.apply(args, defaultArgs);
57
58 var fStream = fs.createWriteStream(filepath, { flags: 'w' });
59 console.log("will execute >> " + cmd + " " + args.join(" ") + "\n\t into " + filepath);
60
61 await spawn(cmd, args, {
62 env,
63 onStdout: (data) => {
64 fStream.write(data);
65 }
66 });
67
68}
69
70export async function psqlCommand(pgOpts: PsqlOptions, command: string): Promise<string> {
71
72 const { stdout, stderr } = await execPsql({ ...pgOpts, toConsole: true }, [`--command=${command}`]);
73
74 if (stderr) {
75 throw Error(`ERROR - cannot psqlCommand "${command}" because ${stderr}`);
76 }
77
78 return stdout;
79
80}
81
82// --------- /psql public API --------- //
83
84// --------- Utils public API --------- //
85export type PgTestResult = { success: boolean, message?: string, err?: string };
86
87export async function pgTest(pgOpts: PsqlOptions): Promise<PgTestResult> {
88 const status = await pgStatus(pgOpts);
89
90 if (!status.accepting) {
91 return { success: false, message: status.message };
92 }
93 // --command="SELECT version();
94
95 // var args = buildPgArgs(pgOpts);
96 const { args, env } = buildPgArgs(pgOpts);
97 args.push("--command=SELECT version()");
98 const p = await spawn('psql', args, { env, capture: ['stdout', 'stderr'], ignoreFail: true });
99 if (p.code === 0) {
100 return { success: true, message: p.stdout.trim() };
101 } else {
102 const r: PgTestResult = { success: false, message: p.stdout };
103 if (p.stderr) {
104 r.err = p.stderr.trim();
105 }
106 return r;
107 }
108}
109
110export type PgStatusResult = { accepting: boolean, message: string, code: number };
111/** Return the status of a pg database process (without the database) */
112export async function pgStatus(pgOpts: PsqlOptions): Promise<PgStatusResult> {
113 const { args, env } = buildPgArgs(pgOpts);
114 //args.push('-q'); // for the quiet mode, we just need to result code
115 const p = await spawn('pg_isready', args, { env, ignoreFail: true, capture: ['stdout', 'stderr'] });
116 const code = p.code;
117 const message = p.stdout?.trim();
118 const accepting = (0 === p.code) ? true : false;
119 return { accepting, message, code };
120}
121
122// --------- /Utils public API --------- //
123
124
125// --------- Private Utils --------- //
126async function execPsql(pgOpts: PsqlOptions, args: string[]): Promise<{ stdout: string, stderr: string }> {
127
128 const { args: baseArgs, env } = buildPgArgs(pgOpts);
129
130 // add the args at the end
131 args = [...baseArgs, ...args];
132
133 const spawnOptions = buildSpawnOptions(pgOpts);
134
135 const spawnResult = await spawn('psql', args, { env, ...spawnOptions, ignoreFail: true });
136
137 let { stdout, stderr } = spawnResult;
138
139 if (stderr) {
140 const err = stderr.trim();
141 let itemErr = null;
142 for (const line of err.split('\n')) {
143 if (!line.includes("NOTICE:")) {
144 itemErr = (itemErr == null) ? line : `${itemErr}\n${line}`;
145 }
146 }
147 stderr = itemErr ?? undefined;
148 }
149
150 return { stdout, stderr };
151}
152
153function buildSpawnOptions(pgOpts: PsqlOptions) {
154 let spawnOptions: any = { capture: ['stdout', 'stderr'] };
155 if (pgOpts.toConsole) {
156 spawnOptions.toConsole = true;
157 }
158 return spawnOptions;
159}
160// private: Build a cmd line argument list from a pgOpts {user, db[, pwd][, host][, port: 5432]} and make it an command line arguments
161function buildPgArgs(pgOpts: PsqlOptions): { args: string[], env: { [name: string]: string | undefined } } {
162 var args = [];
163
164 if (pgOpts.user) {
165 args.push("-U", pgOpts.user);
166 }
167 if (pgOpts.database) {
168 args.push("-d", pgOpts.database);
169 }
170 if (pgOpts.host) {
171 args.push("-h", pgOpts.host);
172 }
173 if (pgOpts.port) {
174 args.push("-p", pgOpts.port);
175 }
176
177 const env = (pgOpts.password == null) ? process.env : { ...process.env, PGPASSWORD: pgOpts.password };
178
179 return { args, env };
180}
181// --------- /Private Utils --------- //