ardent-cli-beta 0.0.14 → 0.0.15

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.
@@ -2,7 +2,7 @@ import { ArdentCommand } from '../../command.js';
2
2
  import { listBranches } from '../../branches/client.js';
3
3
  import { replaceBranchCache } from '../../branches/context.js';
4
4
  import { requireConnector } from '../../connectors/context.js';
5
- import { terminalText } from '../../terminal.js';
5
+ import { renderTable } from '../../table.js';
6
6
  export default class BranchList extends ArdentCommand {
7
7
  static description = 'List branches in the selected connector';
8
8
  static helpGroup = 'Branches';
@@ -13,9 +13,15 @@ export default class BranchList extends ArdentCommand {
13
13
  if (!this.jsonEnabled()) {
14
14
  if (branches.length === 0)
15
15
  this.log('No branches found.');
16
- const width = Math.max(0, ...branches.map(({ name }) => terminalText(name).length));
17
- for (const branch of branches)
18
- this.log(`${branch.id === updated.selectedBranch?.id ? '*' : ' '} ${terminalText(branch.name).padEnd(width)} ${terminalText(branch.state)}`);
16
+ else {
17
+ const lines = renderTable(branches, [
18
+ { header: 'Selected', value: ({ id }) => id === updated.selectedBranch?.id ? '●' : '' },
19
+ { header: 'Title', maxWidth: 30, shrinkPriority: 0, value: ({ name }) => name },
20
+ { header: 'Status', shrinkPriority: 1, value: ({ state }) => state },
21
+ ], process.stdout.columns ?? 120);
22
+ for (const line of lines)
23
+ this.log(line);
24
+ }
19
25
  }
20
26
  return { branches };
21
27
  }
@@ -2,7 +2,7 @@ import { loadSession } from '../../auth/credentials.js';
2
2
  import { ArdentCommand } from '../../command.js';
3
3
  import { listConnectors } from '../../connectors/client.js';
4
4
  import { replaceConnectorCache } from '../../connectors/context.js';
5
- import { terminalText } from '../../terminal.js';
5
+ import { renderTable } from '../../table.js';
6
6
  export default class ConnectorList extends ArdentCommand {
7
7
  static description = 'List connectors in the selected project';
8
8
  static helpGroup = 'Connectors';
@@ -17,9 +17,20 @@ export default class ConnectorList extends ArdentCommand {
17
17
  if (!this.jsonEnabled()) {
18
18
  if (connectors.length === 0)
19
19
  this.log('No connectors found.');
20
- const nameWidth = Math.max(0, ...connectors.map(({ name }) => terminalText(name).length));
21
- for (const connector of connectors) {
22
- this.log(`${terminalText(connector.name).padEnd(nameWidth)} ${terminalText(connector.status)}`);
20
+ else {
21
+ const lines = renderTable(connectors, [
22
+ { header: 'Title', maxWidth: 30, shrinkPriority: 1, value: ({ name }) => name },
23
+ { header: 'Status', shrinkPriority: 2, value: ({ status }) => status },
24
+ {
25
+ header: 'Source',
26
+ maxWidth: 50,
27
+ minWidth: 20,
28
+ shrinkPriority: 0,
29
+ value: ({ source }) => `${source.host}:${source.port}/${source.database}`,
30
+ },
31
+ ], process.stdout.columns ?? 120);
32
+ for (const line of lines)
33
+ this.log(line);
23
34
  }
24
35
  }
25
36
  return { connectors };
@@ -1,7 +1,7 @@
1
1
  import { loadSession } from '../../auth/credentials.js';
2
2
  import { ArdentCommand } from '../../command.js';
3
3
  import { listMemberships, listReadableInvitations } from '../../members/client.js';
4
- import { terminalText } from '../../terminal.js';
4
+ import { renderTable } from '../../table.js';
5
5
  export default class MemberList extends ArdentCommand {
6
6
  static description = 'List organization members and pending invitations';
7
7
  static helpGroup = 'Members';
@@ -17,22 +17,27 @@ export default class MemberList extends ArdentCommand {
17
17
  if (!this.jsonEnabled()) {
18
18
  const rows = [
19
19
  ...memberships.map((membership) => ({
20
- email: terminalText(membership.email),
21
- role: terminalText(membership.role_display_name),
20
+ email: membership.email,
21
+ role: membership.role_display_name,
22
22
  status: 'Active',
23
23
  })),
24
24
  ...readableInvitations.map((invitation) => ({
25
- email: terminalText(invitation.email),
26
- role: terminalText(invitation.role_display_name),
25
+ email: invitation.email,
26
+ role: invitation.role_display_name,
27
27
  status: 'Pending',
28
28
  })),
29
29
  ];
30
30
  if (rows.length === 0)
31
31
  this.log('No members found.');
32
- const emailWidth = Math.max(0, ...rows.map(({ email }) => email.length));
33
- const roleWidth = Math.max(0, ...rows.map(({ role }) => role.length));
34
- for (const row of rows)
35
- this.log(`${row.email.padEnd(emailWidth)} ${row.role.padEnd(roleWidth)} ${row.status}`);
32
+ else {
33
+ const lines = renderTable(rows, [
34
+ { header: 'Email', maxWidth: 40, minWidth: 12, shrinkPriority: 0, value: ({ email }) => email },
35
+ { header: 'Role', maxWidth: 20, shrinkPriority: 1, value: ({ role }) => role },
36
+ { header: 'Status', shrinkPriority: 2, value: ({ status }) => status },
37
+ ], process.stdout.columns ?? 120);
38
+ for (const line of lines)
39
+ this.log(line);
40
+ }
36
41
  if (invitations === undefined)
37
42
  this.log('Pending invitations are not visible with this login.');
38
43
  }
@@ -3,7 +3,7 @@ import { loadSession } from '../../auth/credentials.js';
3
3
  import { ArdentCommand } from '../../command.js';
4
4
  import { listProjects } from '../../projects/client.js';
5
5
  import { replaceProjectCache } from '../../projects/context.js';
6
- import { terminalText } from '../../terminal.js';
6
+ import { renderTable } from '../../table.js';
7
7
  export default class ProjectList extends ArdentCommand {
8
8
  static description = 'List projects';
9
9
  static helpGroup = 'Projects';
@@ -18,10 +18,23 @@ export default class ProjectList extends ArdentCommand {
18
18
  if (!this.jsonEnabled()) {
19
19
  if (projects.length === 0)
20
20
  this.log('No projects found.');
21
- for (const project of projects) {
22
- const name = terminalText(project.name);
23
- const line = project.id === refreshedSession.selectedProject?.id ? `● ${name}` : ` ${name}`;
24
- this.log(project.id === refreshedSession.selectedProject?.id ? ux.colorize('green', line) : line);
21
+ else {
22
+ const lines = renderTable(projects, [
23
+ {
24
+ header: 'Title',
25
+ maxWidth: 30,
26
+ shrinkPriority: 0,
27
+ value: ({ id, name }) => `${id === refreshedSession.selectedProject?.id ? '●' : ' '} ${name}`,
28
+ },
29
+ ], process.stdout.columns ?? 120);
30
+ const selectedIndex = projects.findIndex(({ id }) => id === refreshedSession.selectedProject?.id);
31
+ if (selectedIndex >= 0) {
32
+ const lineIndex = selectedIndex + 3;
33
+ const value = lines[lineIndex].slice(2, -2).trimEnd();
34
+ lines[lineIndex] = `${lines[lineIndex].slice(0, 2)}${ux.colorize('green', value)}${lines[lineIndex].slice(value.length + 2)}`;
35
+ }
36
+ for (const line of lines)
37
+ this.log(line);
25
38
  }
26
39
  }
27
40
  return { projects };
@@ -0,0 +1,8 @@
1
+ export interface TableColumn<Row> {
2
+ header: string;
3
+ maxWidth?: number;
4
+ minWidth?: number;
5
+ shrinkPriority?: number;
6
+ value: (row: Row) => string;
7
+ }
8
+ export declare function renderTable<Row>(rows: Row[], columns: TableColumn<Row>[], terminalWidth?: number): string[];
package/dist/table.js ADDED
@@ -0,0 +1,53 @@
1
+ import stringWidth from 'string-width';
2
+ import { terminalText } from './terminal.js';
3
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
4
+ export function renderTable(rows, columns, terminalWidth = 120) {
5
+ const headers = columns.map(({ header }) => terminalText(header));
6
+ const values = rows.map((row) => columns.map(({ value }) => terminalText(value(row))));
7
+ const widths = columns.map((column, index) => {
8
+ const headerWidth = stringWidth(headers[index]);
9
+ const naturalWidth = Math.max(headerWidth, ...values.map((row) => stringWidth(row[index])));
10
+ return Math.min(naturalWidth, Math.max(headerWidth, column.maxWidth ?? naturalWidth));
11
+ });
12
+ const preferredMinimumWidths = columns.map((column, index) => Math.min(widths[index], Math.max(stringWidth(headers[index]), column.minWidth ?? stringWidth(headers[index]))));
13
+ const frameWidth = columns.length * 3 + 1;
14
+ const availableWidth = Number.isFinite(terminalWidth) ? Math.floor(terminalWidth) : 120;
15
+ const narrowestTableWidth = frameWidth + columns.length;
16
+ let overflow = widths.reduce((total, width) => total + width, frameWidth) - Math.max(narrowestTableWidth, availableWidth);
17
+ const shrinkOrder = columns
18
+ .map((column, index) => ({ index, priority: column.shrinkPriority ?? index }))
19
+ .sort((left, right) => left.priority - right.priority);
20
+ for (const minimumWidths of [preferredMinimumWidths, columns.map(() => 1)]) {
21
+ for (const { index } of shrinkOrder) {
22
+ const reduction = Math.min(Math.max(0, overflow), widths[index] - minimumWidths[index]);
23
+ widths[index] -= reduction;
24
+ overflow -= reduction;
25
+ }
26
+ }
27
+ const border = (left, divider, right) => `${left}${widths.map((width) => '─'.repeat(width + 2)).join(divider)}${right}`;
28
+ const cell = (value, width) => {
29
+ let display = value;
30
+ if (stringWidth(value) > width) {
31
+ const contentWidth = width - stringWidth('…');
32
+ let truncatedWidth = 0;
33
+ display = '';
34
+ for (const { segment } of graphemeSegmenter.segment(value)) {
35
+ const segmentWidth = stringWidth(segment);
36
+ if (truncatedWidth + segmentWidth > contentWidth)
37
+ break;
38
+ display += segment;
39
+ truncatedWidth += segmentWidth;
40
+ }
41
+ display += '…';
42
+ }
43
+ return display + ' '.repeat(width - stringWidth(display));
44
+ };
45
+ const line = (row) => `│ ${row.map((value, index) => cell(value, widths[index])).join(' │ ')} │`;
46
+ return [
47
+ border('┌', '┬', '┐'),
48
+ line(headers),
49
+ border('├', '┼', '┤'),
50
+ ...values.map(line),
51
+ border('└', '┴', '┘'),
52
+ ];
53
+ }
@@ -574,5 +574,5 @@
574
574
  ]
575
575
  }
576
576
  },
577
- "version": "0.0.14"
577
+ "version": "0.0.15"
578
578
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ardent-cli-beta",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "The Ardent command-line interface",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -41,7 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@oclif/core": "^5.0.0",
44
- "@oclif/plugin-autocomplete": "^4.0.0"
44
+ "@oclif/plugin-autocomplete": "^4.0.0",
45
+ "string-width": "^4.2.3"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@types/node": "^22.0.0",