|
| 1 | +import { Flags } from '@oclif/core' |
| 2 | +import { AuthCommand } from '../authCommand' |
| 3 | +import { outputFlag } from '../../helpers/flags' |
| 4 | +import * as api from '../../rest/api' |
| 5 | +import type { OutputFormat } from '../../formatters/render' |
| 6 | +import type { |
| 7 | + AccountMemberRole, |
| 8 | + AccountMemberStatus, |
| 9 | + AccountMemberType, |
| 10 | + AccountMembersListParams, |
| 11 | +} from '../../rest/account-members' |
| 12 | +import { |
| 13 | + formatAccountMembers, |
| 14 | + formatCursorNavigationHints, |
| 15 | + formatCursorPaginationInfo, |
| 16 | +} from '../../formatters/account-members' |
| 17 | + |
| 18 | +const accountMemberTypes = ['member', 'invite'] as const |
| 19 | +const accountMemberRoles = ['OWNER', 'ADMIN', 'READ_WRITE', 'READ_RUN', 'READ_ONLY'] as const |
| 20 | +const accountMemberStatuses = ['ACTIVE', 'PENDING', 'EXPIRED'] as const |
| 21 | +const accountMemberRoleOptions = accountMemberRoles.map(role => role.toLowerCase()) |
| 22 | +const accountMemberStatusOptions = accountMemberStatuses.map(status => status.toLowerCase()) |
| 23 | + |
| 24 | +function isAccountMemberType (value: string): value is AccountMemberType { |
| 25 | + return accountMemberTypes.includes(value as AccountMemberType) |
| 26 | +} |
| 27 | + |
| 28 | +function isAccountMemberRole (value: string): value is AccountMemberRole { |
| 29 | + return accountMemberRoles.includes(value as AccountMemberRole) |
| 30 | +} |
| 31 | + |
| 32 | +function isAccountMemberStatus (value: string): value is AccountMemberStatus { |
| 33 | + return accountMemberStatuses.includes(value as AccountMemberStatus) |
| 34 | +} |
| 35 | + |
| 36 | +export function normalizeAccountMemberType (value: string | undefined): AccountMemberType | undefined { |
| 37 | + if (value === undefined) return undefined |
| 38 | + const normalized = value.trim().toLowerCase() |
| 39 | + return isAccountMemberType(normalized) ? normalized : undefined |
| 40 | +} |
| 41 | + |
| 42 | +export function normalizeAccountMemberRole (value: string | undefined): AccountMemberRole | undefined { |
| 43 | + if (value === undefined) return undefined |
| 44 | + const normalized = value.trim().toUpperCase() |
| 45 | + return isAccountMemberRole(normalized) ? normalized : undefined |
| 46 | +} |
| 47 | + |
| 48 | +export function normalizeAccountMemberStatus (value: string | undefined): AccountMemberStatus | undefined { |
| 49 | + if (value === undefined) return undefined |
| 50 | + const normalized = value.trim().toUpperCase() |
| 51 | + return isAccountMemberStatus(normalized) ? normalized : undefined |
| 52 | +} |
| 53 | + |
| 54 | +export default class AccountMembers extends AuthCommand { |
| 55 | + static hidden = false |
| 56 | + static readOnly = true |
| 57 | + static idempotent = true |
| 58 | + static description = 'List account members and pending invites.' |
| 59 | + |
| 60 | + static flags = { |
| 61 | + 'search': Flags.string({ |
| 62 | + char: 's', |
| 63 | + description: 'Search members and invites by name or email.', |
| 64 | + }), |
| 65 | + 'type': Flags.string({ |
| 66 | + description: 'Filter by item type: member or invite.', |
| 67 | + }), |
| 68 | + 'role': Flags.string({ |
| 69 | + description: `Filter by member or invite role: ${accountMemberRoleOptions.join(', ')}.`, |
| 70 | + }), |
| 71 | + 'status': Flags.string({ |
| 72 | + description: `Filter by member or invite status: ${accountMemberStatusOptions.join(', ')}.`, |
| 73 | + }), |
| 74 | + 'limit': Flags.integer({ |
| 75 | + char: 'l', |
| 76 | + description: 'Number of account members to return (1-100). Enables cursor pagination.', |
| 77 | + }), |
| 78 | + 'next-id': Flags.string({ |
| 79 | + description: 'Cursor for next page. Requires --limit.', |
| 80 | + }), |
| 81 | + 'hide-id': Flags.boolean({ |
| 82 | + description: 'Hide member and invite IDs in table output.', |
| 83 | + default: false, |
| 84 | + }), |
| 85 | + 'output': outputFlag({ default: 'table' }), |
| 86 | + } |
| 87 | + |
| 88 | + async run (): Promise<void> { |
| 89 | + const { flags } = await this.parse(AccountMembers) |
| 90 | + this.style.outputFormat = flags.output |
| 91 | + const limit = flags.limit |
| 92 | + |
| 93 | + if (limit !== undefined && (limit < 1 || limit > 100)) { |
| 94 | + this.error('--limit must be an integer between 1 and 100.') |
| 95 | + } |
| 96 | + |
| 97 | + if (flags['next-id'] && limit === undefined) { |
| 98 | + this.error('Cannot use --next-id without --limit.') |
| 99 | + } |
| 100 | + |
| 101 | + const type = normalizeAccountMemberType(flags.type) |
| 102 | + if (flags.type && !type) { |
| 103 | + this.error(`Invalid --type "${flags.type}". Valid values: ${accountMemberTypes.join(', ')}.`) |
| 104 | + } |
| 105 | + |
| 106 | + const role = normalizeAccountMemberRole(flags.role) |
| 107 | + if (flags.role && !role) { |
| 108 | + this.error(`Invalid --role "${flags.role}". Valid values: ${accountMemberRoleOptions.join(', ')}.`) |
| 109 | + } |
| 110 | + |
| 111 | + const status = normalizeAccountMemberStatus(flags.status) |
| 112 | + if (flags.status && !status) { |
| 113 | + this.error(`Invalid --status "${flags.status}". Valid values: ${accountMemberStatusOptions.join(', ')}.`) |
| 114 | + } |
| 115 | + |
| 116 | + const params: AccountMembersListParams = { |
| 117 | + search: flags.search, |
| 118 | + type, |
| 119 | + role, |
| 120 | + status, |
| 121 | + limit, |
| 122 | + nextId: flags['next-id'], |
| 123 | + } |
| 124 | + |
| 125 | + try { |
| 126 | + const { data } = await api.accountMembers.getAll(params) |
| 127 | + |
| 128 | + if (flags.output === 'json') { |
| 129 | + this.log(JSON.stringify(data, null, 2)) |
| 130 | + return |
| 131 | + } |
| 132 | + |
| 133 | + if (data.members.length === 0) { |
| 134 | + this.log('No account members found.') |
| 135 | + return |
| 136 | + } |
| 137 | + |
| 138 | + const fmt: OutputFormat = flags.output === 'md' ? 'md' : 'terminal' |
| 139 | + if (fmt === 'md') { |
| 140 | + this.log(formatAccountMembers(data.members, fmt, { showId: !flags['hide-id'] })) |
| 141 | + return |
| 142 | + } |
| 143 | + |
| 144 | + const output = [ |
| 145 | + formatAccountMembers(data.members, fmt, { showId: !flags['hide-id'] }), |
| 146 | + ] |
| 147 | + |
| 148 | + if (limit !== undefined) { |
| 149 | + output.push('') |
| 150 | + output.push(formatCursorPaginationInfo(data.length, data.nextId)) |
| 151 | + |
| 152 | + const navHints = formatCursorNavigationHints(data.nextId) |
| 153 | + if (navHints) { |
| 154 | + output.push('') |
| 155 | + output.push(navHints.replace('<limit>', String(limit))) |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + this.log(output.join('\n')) |
| 160 | + } catch (err: any) { |
| 161 | + this.style.longError('Failed to list account members.', err) |
| 162 | + process.exitCode = 1 |
| 163 | + } |
| 164 | + } |
| 165 | +} |
0 commit comments