@hesed/psql 0.4.1 → 0.5.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/README.md CHANGED
@@ -26,7 +26,7 @@ $ npm install -g @hesed/psql
26
26
  $ pg COMMAND
27
27
  running command...
28
28
  $ pg (--version)
29
- @hesed/psql/0.4.1 linux-x64 node-v24.16.0
29
+ @hesed/psql/0.5.1 linux-x64 node-v24.17.0
30
30
  $ pg --help [COMMAND]
31
31
  USAGE
32
32
  $ pg COMMAND
@@ -80,7 +80,7 @@ EXAMPLES
80
80
  $ pg psql auth add -p prod
81
81
  ```
82
82
 
83
- _See code: [src/commands/psql/auth/add.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/add.ts)_
83
+ _See code: [src/commands/psql/auth/add.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/add.ts)_
84
84
 
85
85
  ## `pg psql auth delete`
86
86
 
@@ -105,7 +105,7 @@ EXAMPLES
105
105
  $ pg psql auth delete -p prod
106
106
  ```
107
107
 
108
- _See code: [src/commands/psql/auth/delete.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/delete.ts)_
108
+ _See code: [src/commands/psql/auth/delete.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/delete.ts)_
109
109
 
110
110
  ## `pg psql auth list`
111
111
 
@@ -125,7 +125,7 @@ EXAMPLES
125
125
  $ pg psql auth list
126
126
  ```
127
127
 
128
- _See code: [src/commands/psql/auth/list.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/list.ts)_
128
+ _See code: [src/commands/psql/auth/list.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/list.ts)_
129
129
 
130
130
  ## `pg psql auth profile`
131
131
 
@@ -150,7 +150,7 @@ EXAMPLES
150
150
  $ pg psql auth profile --default test
151
151
  ```
152
152
 
153
- _See code: [src/commands/psql/auth/profile.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/profile.ts)_
153
+ _See code: [src/commands/psql/auth/profile.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/profile.ts)_
154
154
 
155
155
  ## `pg psql auth test`
156
156
 
@@ -175,7 +175,7 @@ EXAMPLES
175
175
  $ pg psql auth test -p prod
176
176
  ```
177
177
 
178
- _See code: [src/commands/psql/auth/test.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/test.ts)_
178
+ _See code: [src/commands/psql/auth/test.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/test.ts)_
179
179
 
180
180
  ## `pg psql auth update`
181
181
 
@@ -207,7 +207,7 @@ EXAMPLES
207
207
  $ pg psql auth update -p test
208
208
  ```
209
209
 
210
- _See code: [src/commands/psql/auth/update.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/auth/update.ts)_
210
+ _See code: [src/commands/psql/auth/update.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/auth/update.ts)_
211
211
 
212
212
  ## `pg psql databases`
213
213
 
@@ -215,10 +215,13 @@ List all databases accessible on the PostgreSQL server
215
215
 
216
216
  ```
217
217
  USAGE
218
- $ pg psql databases [--profile <value>]
218
+ $ pg psql databases [--json] [-p <value>]
219
219
 
220
220
  FLAGS
221
- --profile=<value> Database profile name from config
221
+ -p, --profile=<value> Database profile name from config
222
+
223
+ GLOBAL FLAGS
224
+ --json Format output as json.
222
225
 
223
226
  DESCRIPTION
224
227
  List all databases accessible on the PostgreSQL server
@@ -226,10 +229,10 @@ DESCRIPTION
226
229
  EXAMPLES
227
230
  $ pg psql databases
228
231
 
229
- $ pg psql databases --profile staging
232
+ $ pg psql databases -p staging
230
233
  ```
231
234
 
232
- _See code: [src/commands/psql/databases.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/databases.ts)_
235
+ _See code: [src/commands/psql/databases.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/databases.ts)_
233
236
 
234
237
  ## `pg psql describe-table TABLE`
235
238
 
@@ -237,15 +240,18 @@ Describe the structure of a PostgreSQL table
237
240
 
238
241
  ```
239
242
  USAGE
240
- $ pg psql describe-table TABLE [--format table|json|toon] [--profile <value>]
243
+ $ pg psql describe-table TABLE [--json] [--format table|json|toon] [-p <value>]
241
244
 
242
245
  ARGUMENTS
243
246
  TABLE Table name to describe
244
247
 
245
248
  FLAGS
246
- --format=<option> [default: table] Output format
247
- <options: table|json|toon>
248
- --profile=<value> Database profile name from config
249
+ -p, --profile=<value> Database profile name from config
250
+ --format=<option> [default: table] Output format
251
+ <options: table|json|toon>
252
+
253
+ GLOBAL FLAGS
254
+ --json Format output as json.
249
255
 
250
256
  DESCRIPTION
251
257
  Describe the structure of a PostgreSQL table
@@ -253,10 +259,10 @@ DESCRIPTION
253
259
  EXAMPLES
254
260
  $ pg psql describe-table users
255
261
 
256
- $ pg psql describe-table orders --format json --profile prod
262
+ $ pg psql describe-table orders --format json -p prod
257
263
  ```
258
264
 
259
- _See code: [src/commands/psql/describe-table.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/describe-table.ts)_
265
+ _See code: [src/commands/psql/describe-table.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/describe-table.ts)_
260
266
 
261
267
  ## `pg psql explain-query QUERY`
262
268
 
@@ -264,15 +270,18 @@ Show the execution plan for a PostgreSQL query
264
270
 
265
271
  ```
266
272
  USAGE
267
- $ pg psql explain-query QUERY [--format table|json|toon] [--profile <value>]
273
+ $ pg psql explain-query QUERY [--json] [--format table|json|toon] [-p <value>]
268
274
 
269
275
  ARGUMENTS
270
276
  QUERY SQL query to explain
271
277
 
272
278
  FLAGS
273
- --format=<option> [default: table] Output format
274
- <options: table|json|toon>
275
- --profile=<value> Database profile name from config
279
+ -p, --profile=<value> Database profile name from config
280
+ --format=<option> [default: table] Output format
281
+ <options: table|json|toon>
282
+
283
+ GLOBAL FLAGS
284
+ --json Format output as json.
276
285
 
277
286
  DESCRIPTION
278
287
  Show the execution plan for a PostgreSQL query
@@ -283,7 +292,7 @@ EXAMPLES
283
292
  $ pg psql explain-query "SELECT * FROM orders JOIN users ON orders.user_id = users.id" --format json
284
293
  ```
285
294
 
286
- _See code: [src/commands/psql/explain-query.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/explain-query.ts)_
295
+ _See code: [src/commands/psql/explain-query.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/explain-query.ts)_
287
296
 
288
297
  ## `pg psql indexes TABLE`
289
298
 
@@ -291,15 +300,18 @@ Show indexes for a PostgreSQL table
291
300
 
292
301
  ```
293
302
  USAGE
294
- $ pg psql indexes TABLE [--format table|json|toon] [--profile <value>]
303
+ $ pg psql indexes TABLE [--json] [--format table|json|toon] [-p <value>]
295
304
 
296
305
  ARGUMENTS
297
306
  TABLE Table name to show indexes for
298
307
 
299
308
  FLAGS
300
- --format=<option> [default: table] Output format
301
- <options: table|json|toon>
302
- --profile=<value> Database profile name from config
309
+ -p, --profile=<value> Database profile name from config
310
+ --format=<option> [default: table] Output format
311
+ <options: table|json|toon>
312
+
313
+ GLOBAL FLAGS
314
+ --json Format output as json.
303
315
 
304
316
  DESCRIPTION
305
317
  Show indexes for a PostgreSQL table
@@ -307,10 +319,10 @@ DESCRIPTION
307
319
  EXAMPLES
308
320
  $ pg psql indexes users
309
321
 
310
- $ pg psql indexes orders --format json --profile prod
322
+ $ pg psql indexes orders --format json -p prod
311
323
  ```
312
324
 
313
- _See code: [src/commands/psql/indexes.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/indexes.ts)_
325
+ _See code: [src/commands/psql/indexes.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/indexes.ts)_
314
326
 
315
327
  ## `pg psql query QUERY`
316
328
 
@@ -318,16 +330,19 @@ Execute a SQL query against a PostgreSQL database
318
330
 
319
331
  ```
320
332
  USAGE
321
- $ pg psql query QUERY [--format table|json|csv|toon] [--profile <value>] [--skip-confirmation]
333
+ $ pg psql query QUERY [--json] [--format table|json|csv|toon] [-p <value>] [--skip-confirmation]
322
334
 
323
335
  ARGUMENTS
324
336
  QUERY SQL query to execute
325
337
 
326
338
  FLAGS
327
- --format=<option> [default: table] Output format
328
- <options: table|json|csv|toon>
329
- --profile=<value> Database profile name from config
330
- --skip-confirmation Skip confirmation prompt for destructive operations
339
+ -p, --profile=<value> Database profile name from config
340
+ --format=<option> [default: table] Output format
341
+ <options: table|json|csv|toon>
342
+ --skip-confirmation Skip confirmation prompt for destructive operations
343
+
344
+ GLOBAL FLAGS
345
+ --json Format output as json.
331
346
 
332
347
  DESCRIPTION
333
348
  Execute a SQL query against a PostgreSQL database
@@ -337,10 +352,10 @@ EXAMPLES
337
352
 
338
353
  $ pg psql query "UPDATE users SET email = 'user@email.com' WHERE id = 999" --format json
339
354
 
340
- $ pg psql query "DELETE FROM sessions" --profile prod --skip-confirmation
355
+ $ pg psql query "DELETE FROM sessions" -p prod --skip-confirmation
341
356
  ```
342
357
 
343
- _See code: [src/commands/psql/query.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/query.ts)_
358
+ _See code: [src/commands/psql/query.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/query.ts)_
344
359
 
345
360
  ## `pg psql tables`
346
361
 
@@ -348,10 +363,13 @@ List all tables in the current PostgreSQL database
348
363
 
349
364
  ```
350
365
  USAGE
351
- $ pg psql tables [--profile <value>]
366
+ $ pg psql tables [--json] [-p <value>]
352
367
 
353
368
  FLAGS
354
- --profile=<value> Database profile name from config
369
+ -p, --profile=<value> Database profile name from config
370
+
371
+ GLOBAL FLAGS
372
+ --json Format output as json.
355
373
 
356
374
  DESCRIPTION
357
375
  List all tables in the current PostgreSQL database
@@ -359,8 +377,8 @@ DESCRIPTION
359
377
  EXAMPLES
360
378
  $ pg psql tables
361
379
 
362
- $ pg psql tables --profile local
380
+ $ pg psql tables -p local
363
381
  ```
364
382
 
365
- _See code: [src/commands/psql/tables.ts](https://github.com/hesedcasa/psql/blob/v0.4.1/src/commands/psql/tables.ts)_
383
+ _See code: [src/commands/psql/tables.ts](https://github.com/hesedcasa/psql/blob/v0.5.1/src/commands/psql/tables.ts)_
366
384
  <!-- commandsstop -->
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export declare abstract class BaseCommand extends Command {
3
+ static enableJsonFlag: boolean;
4
+ jsonEnabled(): boolean;
5
+ protected parse(options?: any, argv?: string[]): Promise<any>;
6
+ protected parseJsonOutput(output?: string): unknown;
7
+ protected toErrorJson(err: unknown): {
8
+ error: string;
9
+ };
10
+ private formatFlagValue;
11
+ private hasFormatFlag;
12
+ }
@@ -0,0 +1,50 @@
1
+ import { Command } from '@oclif/core';
2
+ export class BaseCommand extends Command {
3
+ static enableJsonFlag = true;
4
+ jsonEnabled() {
5
+ const separatorIndex = this.argv.indexOf('--');
6
+ const flagArgs = separatorIndex === -1 ? this.argv : this.argv.slice(0, separatorIndex);
7
+ if (this.hasFormatFlag(flagArgs))
8
+ return this.formatFlagValue(flagArgs) === 'json';
9
+ return flagArgs.includes('--json');
10
+ }
11
+ // oclif sets this.parsed=true only after Parser.parse() returns successfully.
12
+ // When parse() throws (e.g. missing required arg), this.parsed stays false and
13
+ // _run() emits an UnparsedCommand warning. The finally block prevents that.
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ async parse(options, argv) {
16
+ try {
17
+ return await super.parse(options, argv);
18
+ }
19
+ finally {
20
+ this.parsed = true;
21
+ }
22
+ }
23
+ parseJsonOutput(output) {
24
+ if (!output)
25
+ return null;
26
+ try {
27
+ return JSON.parse(output);
28
+ }
29
+ catch {
30
+ return output;
31
+ }
32
+ }
33
+ // oclif's default toErrorJson returns the raw error object which for
34
+ // CLIParseError includes context:this (the full config). Strip it down.
35
+ toErrorJson(err) {
36
+ const message = err instanceof Error ? err.message : String(err);
37
+ return { error: message };
38
+ }
39
+ formatFlagValue(flagArgs) {
40
+ for (const [index, arg] of flagArgs.entries()) {
41
+ if (arg === '--format')
42
+ return flagArgs[index + 1];
43
+ if (arg.startsWith('--format='))
44
+ return arg.slice('--format='.length);
45
+ }
46
+ }
47
+ hasFormatFlag(flagArgs) {
48
+ return flagArgs.some((arg) => arg === '--format' || arg.startsWith('--format='));
49
+ }
50
+ }
@@ -1,9 +1,11 @@
1
1
  import { Command } from '@oclif/core';
2
2
  export default class PostgresDatabases extends Command {
3
3
  static description: string;
4
+ static enableJsonFlag: boolean;
4
5
  static examples: string[];
5
6
  static flags: {
6
7
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  };
8
- run(): Promise<void>;
9
+ jsonEnabled(): boolean;
10
+ run(): Promise<string[]>;
9
11
  }
@@ -2,22 +2,21 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { closeConnections, listDatabases } from '../../psql/index.js';
3
3
  export default class PostgresDatabases extends Command {
4
4
  static description = 'List all databases accessible on the PostgreSQL server';
5
- static examples = [
6
- '<%= config.bin %> <%= command.id %>',
7
- '<%= config.bin %> <%= command.id %> --profile staging',
8
- ];
5
+ static enableJsonFlag = true;
6
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> -p staging'];
9
7
  static flags = {
10
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
8
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
11
9
  };
10
+ jsonEnabled() {
11
+ return true;
12
+ }
12
13
  async run() {
13
14
  const { flags } = await this.parse(PostgresDatabases);
14
15
  const result = await listDatabases(this.config, flags.profile);
15
16
  await closeConnections();
16
17
  if (result.success) {
17
- this.logJson(result.databases);
18
- }
19
- else {
20
- this.error(result.error ?? 'Failed to list databases');
18
+ return result.databases ?? [];
21
19
  }
20
+ this.error(result.error ?? 'Failed to list databases');
22
21
  }
23
22
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class PostgresDescribeTable extends Command {
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class PostgresDescribeTable extends BaseCommand {
3
3
  static args: {
4
4
  table: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -9,5 +9,5 @@ export default class PostgresDescribeTable extends Command {
9
9
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  };
12
- run(): Promise<void>;
12
+ run(): Promise<unknown>;
13
13
  }
@@ -1,13 +1,14 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
2
3
  import { closeConnections, describeTable } from '../../psql/index.js';
3
- export default class PostgresDescribeTable extends Command {
4
+ export default class PostgresDescribeTable extends BaseCommand {
4
5
  static args = {
5
6
  table: Args.string({ description: 'Table name to describe', required: true }),
6
7
  };
7
8
  static description = 'Describe the structure of a PostgreSQL table';
8
9
  static examples = [
9
10
  '<%= config.bin %> <%= command.id %> users',
10
- '<%= config.bin %> <%= command.id %> orders --format json --profile prod',
11
+ '<%= config.bin %> <%= command.id %> orders --format json -p prod',
11
12
  ];
12
13
  static flags = {
13
14
  format: Flags.string({
@@ -15,7 +16,7 @@ export default class PostgresDescribeTable extends Command {
15
16
  description: 'Output format',
16
17
  options: ['table', 'json', 'toon'],
17
18
  }),
18
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
19
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
19
20
  };
20
21
  async run() {
21
22
  const { args, flags } = await this.parse(PostgresDescribeTable);
@@ -23,9 +24,8 @@ export default class PostgresDescribeTable extends Command {
23
24
  await closeConnections();
24
25
  if (result.success) {
25
26
  this.log(result.result ?? '');
27
+ return result;
26
28
  }
27
- else {
28
- this.error(result.error ?? 'Failed to describe table');
29
- }
29
+ this.error(result.error ?? 'Failed to describe table');
30
30
  }
31
31
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class PostgresExplain extends Command {
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class PostgresExplain extends BaseCommand {
3
3
  static args: {
4
4
  query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -9,5 +9,5 @@ export default class PostgresExplain extends Command {
9
9
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  };
12
- run(): Promise<void>;
12
+ run(): Promise<unknown>;
13
13
  }
@@ -1,6 +1,7 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
2
3
  import { closeConnections, explainQuery } from '../../psql/index.js';
3
- export default class PostgresExplain extends Command {
4
+ export default class PostgresExplain extends BaseCommand {
4
5
  static args = {
5
6
  query: Args.string({ description: 'SQL query to explain', required: true }),
6
7
  };
@@ -15,7 +16,7 @@ export default class PostgresExplain extends Command {
15
16
  description: 'Output format',
16
17
  options: ['table', 'json', 'toon'],
17
18
  }),
18
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
19
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
19
20
  };
20
21
  async run() {
21
22
  const { args, flags } = await this.parse(PostgresExplain);
@@ -23,9 +24,8 @@ export default class PostgresExplain extends Command {
23
24
  await closeConnections();
24
25
  if (result.success) {
25
26
  this.log(result.result ?? '');
27
+ return result;
26
28
  }
27
- else {
28
- this.error(result.error ?? 'Failed to explain query');
29
- }
29
+ this.error(result.error ?? 'Failed to explain query');
30
30
  }
31
31
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class PostgresIndexes extends Command {
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class PostgresIndexes extends BaseCommand {
3
3
  static args: {
4
4
  table: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -9,5 +9,5 @@ export default class PostgresIndexes extends Command {
9
9
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  };
12
- run(): Promise<void>;
12
+ run(): Promise<unknown>;
13
13
  }
@@ -1,13 +1,14 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
2
3
  import { closeConnections, showIndexes } from '../../psql/index.js';
3
- export default class PostgresIndexes extends Command {
4
+ export default class PostgresIndexes extends BaseCommand {
4
5
  static args = {
5
6
  table: Args.string({ description: 'Table name to show indexes for', required: true }),
6
7
  };
7
8
  static description = 'Show indexes for a PostgreSQL table';
8
9
  static examples = [
9
10
  '<%= config.bin %> <%= command.id %> users',
10
- '<%= config.bin %> <%= command.id %> orders --format json --profile prod',
11
+ '<%= config.bin %> <%= command.id %> orders --format json -p prod',
11
12
  ];
12
13
  static flags = {
13
14
  format: Flags.string({
@@ -15,7 +16,7 @@ export default class PostgresIndexes extends Command {
15
16
  description: 'Output format',
16
17
  options: ['table', 'json', 'toon'],
17
18
  }),
18
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
19
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
19
20
  };
20
21
  async run() {
21
22
  const { args, flags } = await this.parse(PostgresIndexes);
@@ -23,9 +24,8 @@ export default class PostgresIndexes extends Command {
23
24
  await closeConnections();
24
25
  if (result.success) {
25
26
  this.log(result.result ?? '');
27
+ return result;
26
28
  }
27
- else {
28
- this.error(result.error ?? 'Failed to show indexes');
29
- }
29
+ this.error(result.error ?? 'Failed to show indexes');
30
30
  }
31
31
  }
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class PostgresQuery extends Command {
1
+ import { BaseCommand } from '../../base-command.js';
2
+ export default class PostgresQuery extends BaseCommand {
3
3
  static args: {
4
4
  query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
5
  };
@@ -10,5 +10,5 @@ export default class PostgresQuery extends Command {
10
10
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  'skip-confirmation': import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
12
  };
13
- run(): Promise<void>;
13
+ run(): Promise<unknown>;
14
14
  }
@@ -1,6 +1,7 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../../base-command.js';
2
3
  import { closeConnections, executeQuery } from '../../psql/index.js';
3
- export default class PostgresQuery extends Command {
4
+ export default class PostgresQuery extends BaseCommand {
4
5
  static args = {
5
6
  query: Args.string({ description: 'SQL query to execute', required: true }),
6
7
  };
@@ -8,7 +9,7 @@ export default class PostgresQuery extends Command {
8
9
  static examples = [
9
10
  '<%= config.bin %> <%= command.id %> "SELECT * FROM users LIMIT 10"',
10
11
  '<%= config.bin %> <%= command.id %> "UPDATE users SET email = \'user@email.com\' WHERE id = 999" --format json',
11
- '<%= config.bin %> <%= command.id %> "DELETE FROM sessions" --profile prod --skip-confirmation',
12
+ '<%= config.bin %> <%= command.id %> "DELETE FROM sessions" -p prod --skip-confirmation',
12
13
  ];
13
14
  static flags = {
14
15
  format: Flags.string({
@@ -16,7 +17,7 @@ export default class PostgresQuery extends Command {
16
17
  description: 'Output format',
17
18
  options: ['table', 'json', 'csv', 'toon'],
18
19
  }),
19
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
20
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
20
21
  'skip-confirmation': Flags.boolean({
21
22
  default: false,
22
23
  description: 'Skip confirmation prompt for destructive operations',
@@ -27,13 +28,19 @@ export default class PostgresQuery extends Command {
27
28
  const result = await executeQuery(this.config, args.query, flags.profile, flags.format, flags['skip-confirmation']);
28
29
  await closeConnections();
29
30
  if (result.success) {
31
+ // Notices (warnings, row counts) go to stderr so machine-readable formats
32
+ // leave stdout as clean, parseable data.
33
+ if (result.notices)
34
+ this.logToStderr(result.notices);
35
+ if (this.jsonEnabled())
36
+ return this.parseJsonOutput(result.result);
30
37
  this.log(result.result ?? '');
38
+ return result;
31
39
  }
32
- else if (result.requiresConfirmation) {
40
+ if (result.requiresConfirmation) {
33
41
  this.log(`${result.message ?? 'Destructive operation requires confirmation.'}\nRe-run with --skip-confirmation to proceed.`);
42
+ return result;
34
43
  }
35
- else {
36
- this.error(result.error ?? 'Query failed');
37
- }
44
+ this.error(result.error ?? 'Query failed');
38
45
  }
39
46
  }
@@ -1,9 +1,11 @@
1
1
  import { Command } from '@oclif/core';
2
2
  export default class PostgresTables extends Command {
3
3
  static description: string;
4
+ static enableJsonFlag: boolean;
4
5
  static examples: string[];
5
6
  static flags: {
6
7
  profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
8
  };
8
- run(): Promise<void>;
9
+ jsonEnabled(): boolean;
10
+ run(): Promise<string[]>;
9
11
  }
@@ -2,22 +2,21 @@ import { Command, Flags } from '@oclif/core';
2
2
  import { closeConnections, listTables } from '../../psql/index.js';
3
3
  export default class PostgresTables extends Command {
4
4
  static description = 'List all tables in the current PostgreSQL database';
5
- static examples = [
6
- '<%= config.bin %> <%= command.id %>',
7
- '<%= config.bin %> <%= command.id %> --profile local',
8
- ];
5
+ static enableJsonFlag = true;
6
+ static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> -p local'];
9
7
  static flags = {
10
- profile: Flags.string({ description: 'Database profile name from config', required: false }),
8
+ profile: Flags.string({ char: 'p', description: 'Database profile name from config', required: false }),
11
9
  };
10
+ jsonEnabled() {
11
+ return true;
12
+ }
12
13
  async run() {
13
14
  const { flags } = await this.parse(PostgresTables);
14
15
  const result = await listTables(this.config, flags.profile);
15
16
  await closeConnections();
16
17
  if (result.success) {
17
- this.logJson(result.tables);
18
- }
19
- else {
20
- this.error(result.error ?? 'Failed to list tables');
18
+ return result.tables ?? [];
21
19
  }
20
+ this.error(result.error ?? 'Failed to list tables');
22
21
  }
23
22
  }
@@ -8,6 +8,7 @@
8
8
  export interface QueryResult {
9
9
  error?: string;
10
10
  message?: string;
11
+ notices?: string;
11
12
  requiresConfirmation?: boolean;
12
13
  result?: string;
13
14
  success: boolean;
@@ -1,2 +1 @@
1
- export type { ConnectionTestResult } from './database.js';
2
1
  export { closeConnections, describeTable, executeQuery, explainQuery, listDatabases, listTables, showIndexes, testDirectConnection, } from './postgres-client.js';
@@ -12,7 +12,8 @@ export declare class PostgreSQLUtil implements DatabaseUtil {
12
12
  listTables(profileName: string): Promise<TableListResult>;
13
13
  showIndexes(profileName: string, table: string, format?: 'json' | 'table' | 'toon'): Promise<IndexResult>;
14
14
  testConnection(profileName: string): Promise<ConnectionTestResult>;
15
+ private formatReadResult;
15
16
  private formatRows;
16
- private formatSelectResult;
17
+ private formatWriteResult;
17
18
  private getConnection;
18
19
  }
@@ -50,36 +50,36 @@ export class PostgreSQLUtil {
50
50
  };
51
51
  }
52
52
  }
53
+ // Machine-readable formats must emit only the data payload on stdout, so
54
+ // analysis warnings and status lines are collected as notices instead.
55
+ const machineFormat = format === 'json' || format === 'csv' || format === 'toon';
56
+ const notices = [];
53
57
  const warnings = analyzeQuery(query);
54
- let warningText = '';
55
58
  if (warnings.length > 0) {
56
- warningText =
57
- 'Query Analysis:\n' +
58
- warnings.map((w) => ` [${w.level.toUpperCase()}] ${w.message}\n → ${w.suggestion}`).join('\n') +
59
- '\n\n';
59
+ notices.push('Query Analysis:\n' +
60
+ warnings.map((w) => ` [${w.level.toUpperCase()}] ${w.message}\n → ${w.suggestion}`).join('\n'));
60
61
  }
61
62
  let finalQuery = query;
62
63
  const queryType = getQueryType(query);
63
64
  if (queryType === 'SELECT') {
64
65
  finalQuery = applyDefaultLimit(query, this.config.safety.defaultLimit);
65
66
  if (finalQuery !== query) {
66
- warningText += `Applied default LIMIT ${this.config.safety.defaultLimit}\n\n`;
67
+ notices.push(`Applied default LIMIT ${this.config.safety.defaultLimit}`);
67
68
  }
68
69
  }
69
70
  try {
70
71
  const client = await this.getConnection(profileName);
71
72
  const result = await client.query(finalQuery);
72
- let output = '';
73
- if (result.rows.length > 0 || result.command === 'SELECT' || result.command === 'EXPLAIN') {
74
- output += this.formatSelectResult(result.rows, result.fields, format);
75
- }
76
- else {
77
- const affectedRows = result.rowCount ?? 0;
78
- output += `Query executed successfully.\n`;
79
- output += `Affected rows: ${affectedRows}\n`;
80
- }
73
+ const isRead = result.rows.length > 0 || result.command === 'SELECT' || result.command === 'EXPLAIN';
74
+ const data = isRead
75
+ ? this.formatReadResult(result.rows, result.fields, format, notices)
76
+ : this.formatWriteResult(result.rowCount ?? 0, notices);
77
+ const notice = notices.join('\n\n');
78
+ // For human (table) output everything stays on stdout, exactly as before.
79
+ // For machine formats the data is returned alone and notices go to stderr.
81
80
  return {
82
- result: warningText + output,
81
+ notices: machineFormat ? notice : undefined,
82
+ result: machineFormat ? data : `${notice}\n\n${data}`,
83
83
  success: true,
84
84
  };
85
85
  }
@@ -185,12 +185,17 @@ export class PostgreSQLUtil {
185
185
  };
186
186
  }
187
187
  }
188
+ formatReadResult(rows, fields, format, notices) {
189
+ const rowCount = Array.isArray(rows) ? rows.length : 0;
190
+ notices.push(`Query executed successfully. Rows returned: ${rowCount}`);
191
+ return this.formatRows(rows, fields, format);
192
+ }
188
193
  formatRows(rows, fields, format) {
189
194
  return FORMATTERS[format](rows, fields);
190
195
  }
191
- formatSelectResult(rows, fields, format) {
192
- const rowCount = Array.isArray(rows) ? rows.length : 0;
193
- return `Query executed successfully. Rows returned: ${rowCount}\n\n` + this.formatRows(rows, fields, format);
196
+ formatWriteResult(affectedRows, notices) {
197
+ notices.push('Query executed successfully.');
198
+ return `Affected rows: ${affectedRows}\n`;
194
199
  }
195
200
  async getConnection(profileName) {
196
201
  const existing = this.connections.get(profileName);
@@ -6,10 +6,18 @@
6
6
  "description": "List all databases accessible on the PostgreSQL server",
7
7
  "examples": [
8
8
  "<%= config.bin %> <%= command.id %>",
9
- "<%= config.bin %> <%= command.id %> --profile staging"
9
+ "<%= config.bin %> <%= command.id %> -p staging"
10
10
  ],
11
11
  "flags": {
12
+ "json": {
13
+ "description": "Format output as json.",
14
+ "helpGroup": "GLOBAL",
15
+ "name": "json",
16
+ "allowNo": false,
17
+ "type": "boolean"
18
+ },
12
19
  "profile": {
20
+ "char": "p",
13
21
  "description": "Database profile name from config",
14
22
  "name": "profile",
15
23
  "required": false,
@@ -25,7 +33,7 @@
25
33
  "pluginName": "@hesed/psql",
26
34
  "pluginType": "core",
27
35
  "strict": true,
28
- "enableJsonFlag": false,
36
+ "enableJsonFlag": true,
29
37
  "isESM": true,
30
38
  "relativePath": [
31
39
  "dist",
@@ -46,9 +54,16 @@
46
54
  "description": "Describe the structure of a PostgreSQL table",
47
55
  "examples": [
48
56
  "<%= config.bin %> <%= command.id %> users",
49
- "<%= config.bin %> <%= command.id %> orders --format json --profile prod"
57
+ "<%= config.bin %> <%= command.id %> orders --format json -p prod"
50
58
  ],
51
59
  "flags": {
60
+ "json": {
61
+ "description": "Format output as json.",
62
+ "helpGroup": "GLOBAL",
63
+ "name": "json",
64
+ "allowNo": false,
65
+ "type": "boolean"
66
+ },
52
67
  "format": {
53
68
  "description": "Output format",
54
69
  "name": "format",
@@ -63,6 +78,7 @@
63
78
  "type": "option"
64
79
  },
65
80
  "profile": {
81
+ "char": "p",
66
82
  "description": "Database profile name from config",
67
83
  "name": "profile",
68
84
  "required": false,
@@ -78,7 +94,7 @@
78
94
  "pluginName": "@hesed/psql",
79
95
  "pluginType": "core",
80
96
  "strict": true,
81
- "enableJsonFlag": false,
97
+ "enableJsonFlag": true,
82
98
  "isESM": true,
83
99
  "relativePath": [
84
100
  "dist",
@@ -102,6 +118,13 @@
102
118
  "<%= config.bin %> <%= command.id %> \"SELECT * FROM orders JOIN users ON orders.user_id = users.id\" --format json"
103
119
  ],
104
120
  "flags": {
121
+ "json": {
122
+ "description": "Format output as json.",
123
+ "helpGroup": "GLOBAL",
124
+ "name": "json",
125
+ "allowNo": false,
126
+ "type": "boolean"
127
+ },
105
128
  "format": {
106
129
  "description": "Output format",
107
130
  "name": "format",
@@ -116,6 +139,7 @@
116
139
  "type": "option"
117
140
  },
118
141
  "profile": {
142
+ "char": "p",
119
143
  "description": "Database profile name from config",
120
144
  "name": "profile",
121
145
  "required": false,
@@ -131,7 +155,7 @@
131
155
  "pluginName": "@hesed/psql",
132
156
  "pluginType": "core",
133
157
  "strict": true,
134
- "enableJsonFlag": false,
158
+ "enableJsonFlag": true,
135
159
  "isESM": true,
136
160
  "relativePath": [
137
161
  "dist",
@@ -152,9 +176,16 @@
152
176
  "description": "Show indexes for a PostgreSQL table",
153
177
  "examples": [
154
178
  "<%= config.bin %> <%= command.id %> users",
155
- "<%= config.bin %> <%= command.id %> orders --format json --profile prod"
179
+ "<%= config.bin %> <%= command.id %> orders --format json -p prod"
156
180
  ],
157
181
  "flags": {
182
+ "json": {
183
+ "description": "Format output as json.",
184
+ "helpGroup": "GLOBAL",
185
+ "name": "json",
186
+ "allowNo": false,
187
+ "type": "boolean"
188
+ },
158
189
  "format": {
159
190
  "description": "Output format",
160
191
  "name": "format",
@@ -169,6 +200,7 @@
169
200
  "type": "option"
170
201
  },
171
202
  "profile": {
203
+ "char": "p",
172
204
  "description": "Database profile name from config",
173
205
  "name": "profile",
174
206
  "required": false,
@@ -184,7 +216,7 @@
184
216
  "pluginName": "@hesed/psql",
185
217
  "pluginType": "core",
186
218
  "strict": true,
187
- "enableJsonFlag": false,
219
+ "enableJsonFlag": true,
188
220
  "isESM": true,
189
221
  "relativePath": [
190
222
  "dist",
@@ -206,9 +238,16 @@
206
238
  "examples": [
207
239
  "<%= config.bin %> <%= command.id %> \"SELECT * FROM users LIMIT 10\"",
208
240
  "<%= config.bin %> <%= command.id %> \"UPDATE users SET email = 'user@email.com' WHERE id = 999\" --format json",
209
- "<%= config.bin %> <%= command.id %> \"DELETE FROM sessions\" --profile prod --skip-confirmation"
241
+ "<%= config.bin %> <%= command.id %> \"DELETE FROM sessions\" -p prod --skip-confirmation"
210
242
  ],
211
243
  "flags": {
244
+ "json": {
245
+ "description": "Format output as json.",
246
+ "helpGroup": "GLOBAL",
247
+ "name": "json",
248
+ "allowNo": false,
249
+ "type": "boolean"
250
+ },
212
251
  "format": {
213
252
  "description": "Output format",
214
253
  "name": "format",
@@ -224,6 +263,7 @@
224
263
  "type": "option"
225
264
  },
226
265
  "profile": {
266
+ "char": "p",
227
267
  "description": "Database profile name from config",
228
268
  "name": "profile",
229
269
  "required": false,
@@ -245,7 +285,7 @@
245
285
  "pluginName": "@hesed/psql",
246
286
  "pluginType": "core",
247
287
  "strict": true,
248
- "enableJsonFlag": false,
288
+ "enableJsonFlag": true,
249
289
  "isESM": true,
250
290
  "relativePath": [
251
291
  "dist",
@@ -260,10 +300,18 @@
260
300
  "description": "List all tables in the current PostgreSQL database",
261
301
  "examples": [
262
302
  "<%= config.bin %> <%= command.id %>",
263
- "<%= config.bin %> <%= command.id %> --profile local"
303
+ "<%= config.bin %> <%= command.id %> -p local"
264
304
  ],
265
305
  "flags": {
306
+ "json": {
307
+ "description": "Format output as json.",
308
+ "helpGroup": "GLOBAL",
309
+ "name": "json",
310
+ "allowNo": false,
311
+ "type": "boolean"
312
+ },
266
313
  "profile": {
314
+ "char": "p",
267
315
  "description": "Database profile name from config",
268
316
  "name": "profile",
269
317
  "required": false,
@@ -279,7 +327,7 @@
279
327
  "pluginName": "@hesed/psql",
280
328
  "pluginType": "core",
281
329
  "strict": true,
282
- "enableJsonFlag": false,
330
+ "enableJsonFlag": true,
283
331
  "isESM": true,
284
332
  "relativePath": [
285
333
  "dist",
@@ -634,5 +682,5 @@
634
682
  ]
635
683
  }
636
684
  },
637
- "version": "0.4.1"
685
+ "version": "0.5.1"
638
686
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hesed/psql",
3
3
  "description": "CLI for PostgreSQL database interaction",
4
- "version": "0.4.1",
4
+ "version": "0.5.1",
5
5
  "author": "Hesed",
6
6
  "bin": {
7
7
  "pg": "./bin/run.js"