Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/commands/sites/sites-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { chalk, logAndThrowError, log, logJson, warn, type APIError } from '../.
import getRepoData from '../../utils/get-repo-data.js'
import { configureRepo } from '../../utils/init/config.js'
import { isInteractive } from '../../utils/scripted-commands.js'
import { resolveTeamForNonInteractive } from '../../utils/team.js'
import { requireTeams, resolveTeamForNonInteractive } from '../../utils/team.js'
import { track } from '../../utils/telemetry/index.js'
import type { SiteInfo } from '../../utils/types.js'
import { MAX_SITE_NAME_LENGTH } from '../../utils/validation.js'
Expand Down Expand Up @@ -41,9 +41,11 @@ export const sitesCreate = async (options: OptionValues, command: BaseCommand) =
accountSlug = team.slug
log(`Using team: ${team.name}`)
} else {
// select prompts crash when choices is empty
const teams = requireTeams(accounts)
accountSlug = await select({
message: 'Team:',
choices: accounts.map((account) => ({
choices: teams.map((account) => ({
value: account.slug,
name: account.name,
})),
Expand Down
16 changes: 11 additions & 5 deletions src/utils/team.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { logAndThrowError } from './command-helpers.js'
import type { MinimalAccount } from './types.js'

export const requireTeams = (accounts: MinimalAccount[] | undefined): MinimalAccount[] => {
const teams = accounts ?? []
if (teams.length === 0) {
return logAndThrowError('No teams available. Please ensure you have access to at least one team.')
}
return teams
}

export const resolveTeam = (accounts: MinimalAccount[]): MinimalAccount | undefined => {
if (accounts.length === 1) {
return accounts[0]
Expand All @@ -9,16 +17,14 @@ export const resolveTeam = (accounts: MinimalAccount[]): MinimalAccount | undefi
}

export const resolveTeamForNonInteractive = (accounts: MinimalAccount[], commandExample: string): MinimalAccount => {
if (accounts.length === 0) {
return logAndThrowError('No teams available. Please ensure you have access to at least one team.')
}
const teams = requireTeams(accounts)

const team = resolveTeam(accounts)
const team = resolveTeam(teams)
if (team) {
return team
}

const availableTeams = accounts.map((t) => t.slug).join(', ')
const availableTeams = teams.map((t) => t.slug).join(', ')
return logAndThrowError(
`Multiple teams available. Please specify which team to use.\n` +
`Available teams: ${availableTeams}\n\n` +
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/commands/sites/sites-create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, expect, test, vi } from 'vitest'

const { mockSelect } = vi.hoisted(() => ({
mockSelect: vi.fn(),
}))

vi.mock('@inquirer/prompts', () => ({
input: vi.fn(),
select: mockSelect,
}))

vi.mock('../../../../src/utils/scripted-commands.js', () => ({
isInteractive: () => true,
}))

vi.mock('../../../../src/utils/telemetry/report-error.js', () => ({
reportError: vi.fn(),
}))

vi.mock('../../../../src/utils/telemetry/index.js', () => ({
track: vi.fn(),
}))

vi.mock('../../../../src/commands/link/link.js', () => ({
link: vi.fn(),
}))

import { sitesCreate } from '../../../../src/commands/sites/sites-create.js'
import type BaseCommand from '../../../../src/commands/base-command.js'

const commandWithAccounts = (accounts: unknown[]) =>
({
authenticate: vi.fn().mockResolvedValue(undefined),
netlify: {
accounts,
api: {},
},
}) as unknown as BaseCommand

describe('sitesCreate', () => {
test('throws a team error instead of opening an empty list prompt', async () => {
await expect(sitesCreate({}, commandWithAccounts([]))).rejects.toThrowError(/No teams available/)
expect(mockSelect).not.toHaveBeenCalled()
})
})
58 changes: 58 additions & 0 deletions tests/unit/utils/team.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, test, vi } from 'vitest'

vi.mock('../../../src/utils/telemetry/report-error.js', () => ({
reportError: vi.fn(),
}))

import { requireTeams, resolveTeam, resolveTeamForNonInteractive } from '../../../src/utils/team.js'
import type { MinimalAccount } from '../../../src/utils/types.js'

const team = (overrides: Partial<MinimalAccount> = {}): MinimalAccount => ({
id: 'acc-1',
name: 'Acme',
slug: 'acme',
default: false,
team_logo_url: null,
on_pro_trial: false,
organization_id: null,
type_name: 'Pro',
type_slug: 'pro',
members_count: 1,
...overrides,
})

describe('requireTeams', () => {
test('throws when the account list is empty', () => {
expect(() => requireTeams([])).toThrowError(/No teams available/)
})

test('throws when the account list is missing', () => {
expect(() => requireTeams(undefined)).toThrowError(/No teams available/)
})

test('returns the account list when at least one team exists', () => {
const accounts = [team()]
expect(requireTeams(accounts)).toBe(accounts)
})
})

describe('resolveTeam', () => {
test('returns the only team', () => {
const only = team()
expect(resolveTeam([only])).toBe(only)
})

test('returns the default team when several exist', () => {
const fallback = team({ id: 'acc-2', slug: 'other', name: 'Other' })
const preferred = team({ default: true })
expect(resolveTeam([fallback, preferred])).toBe(preferred)
})
})

describe('resolveTeamForNonInteractive', () => {
test('throws when no teams can be selected', () => {
expect(() => resolveTeamForNonInteractive([], 'netlify sites:create --account-slug <TEAM_SLUG>')).toThrowError(
/No teams available/,
)
})
})