sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
118 lines (95 loc) • 3.73 kB
text/typescript
import {type CliCommandDefinition} from '@sanity/cli'
import {size, sortBy} from 'lodash'
import {type Invite, type PartialProjectResponse, type User} from './types'
const sortFields = ['id', 'name', 'role', 'date']
const helpText = `
Options
--no-invitations Don't include pending invitations
--no-robots Don't include robots (token users)
--sort <field> Sort users by specified column: ${sortFields.join(', ')}
--order <asc/desc> Sort output ascending/descending
Examples
# List all users of the project
sanity users list
# List all users of the project, but exclude pending invitations and robots
sanity users list --no-invitations --no-robots
# List all users, sorted by role
sanity users list --sort role
`
const listUsersCommand: CliCommandDefinition = {
name: 'list',
group: 'users',
signature: '',
helpText,
description: 'List all users of the project',
action: async (args, context) => {
const {apiClient, output, chalk} = context
const {sort, order, robots, invitations} = {
sort: 'date',
order: 'asc',
robots: true,
invitations: true,
...args.extOptions,
}
if (!sortFields.includes(sort)) {
throw new Error(`Can't sort by field "${sort}". Must be one of ${sortFields.join(', ')}`)
}
if (order !== 'asc' && order !== 'desc') {
throw new Error(`Unknown sort order "${order}", must be either "asc" or "desc"`)
}
const client = apiClient()
const globalClient = client.clone().config({useProjectHostname: false})
const {projectId} = client.config()
const useGlobalApi = true
const [pendingInvitations, project] = await Promise.all([
invitations
? globalClient
.request<Invite[]>({uri: `/invitations/project/${projectId}`, useGlobalApi})
.then(getPendingInvitations)
: [],
globalClient.request<PartialProjectResponse>({uri: `/projects/${projectId}`, useGlobalApi}),
])
const memberIds = project.members.map((member) => member.id)
const users = await globalClient
.request<User | User[]>({uri: `/users/${memberIds.join(',')}`, useGlobalApi})
.then((user) => (Array.isArray(user) ? user : [user]))
const projectMembers = project.members
.map((member) => ({
...member,
...getUserProps(users.find((candidate) => candidate.id === member.id)),
}))
.filter((member) => !member.isRobot || robots)
const members = [...projectMembers, ...pendingInvitations]
const ordered = sortBy(
members.map(({id, name, role, date}) => [id, name, role, date]),
[sortFields.indexOf(sort)],
)
const rows = order === 'asc' ? ordered : ordered.reverse()
const maxWidths = rows.reduce(
(max, row) => row.map((current, index) => Math.max(size(current), max[index])),
sortFields.map((str) => size(str)),
)
const printRow = (row: string[]) => {
const isInvite = row[0] === '<pending>'
const textRow = row.map((col, i) => `${col}`.padEnd(maxWidths[i])).join(' ')
return isInvite ? chalk.dim(textRow) : textRow
}
output.print(chalk.cyan(printRow(sortFields)))
rows.forEach((row) => output.print(printRow(row)))
},
}
function getUserProps(user: User | undefined) {
const {displayName: name, createdAt: date} = user || {}
return {name: name || '', date: date || ''}
}
function getPendingInvitations(invitations: Invite[]) {
return invitations
.filter((invite) => !invite.isAccepted && !invite.isRevoked && !invite.acceptedByUserId)
.map((invite) => ({
id: '<pending>',
name: invite.email,
role: invite.role,
date: invite.createdAt,
}))
}
export default listUsersCommand