Skip to content

Commit 3c2c5f3

Browse files
committed
perf!: perform minimal accounts query when loading config
We've done this performance improvement many times elsewhere, but for posterity: When we load a user's configuration, we perform a query to the Netlify API requesting information about which accounts a user is a member of. To do this, we query the `/accounts` endpoint, which is an expensive operation: It frequently takes 4+ seconds to get a response from this endpoint. This query happens as part of every CLI startup cycle. By requesting a more minimal set of `accounts` data, we can shave 2-3 seconds off almost every command's startup time.
1 parent f26ce38 commit 3c2c5f3

File tree

10 files changed

+111
-43
lines changed

10 files changed

+111
-43
lines changed

package-lock.json

Lines changed: 77 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@netlify/blobs": "8.1.2",
6363
"@netlify/build": "30.1.1",
6464
"@netlify/build-info": "9.0.2",
65-
"@netlify/config": "21.0.6",
65+
"@netlify/config": "21.0.7",
6666
"@netlify/edge-bundler": "12.4.0",
6767
"@netlify/edge-functions": "2.11.1",
6868
"@netlify/headers-parser": "8.0.0",

src/commands/base-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ export default class BaseCommand extends Command {
581581
token,
582582
...apiUrlOpts,
583583
})
584-
const { accounts, buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
584+
const { accounts = [], buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
585585
let { env } = cachedConfig
586586
if (flags.offlineEnv) {
587587
env = {}

src/commands/sites/sites-create-template.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { configureRepo } from '../../utils/init/config.js'
2525
import { deployedSiteExists, getGitHubLink, getTemplateName } from '../../utils/sites/create-template.js'
2626
import { callLinkSite, createRepo, validateTemplate } from '../../utils/sites/utils.js'
2727
import { track } from '../../utils/telemetry/index.js'
28-
import type { Account, SiteInfo } from '../../utils/types.js'
28+
import type { SiteInfo } from '../../utils/types.js'
2929
import type BaseCommand from '../base-command.js'
3030

3131
import { getSiteNameInput } from './sites-create.js'
@@ -60,7 +60,7 @@ export const sitesCreateTemplate = async (repository: string, options: OptionVal
6060
type: 'list',
6161
name: 'accountSlug',
6262
message: 'Team:',
63-
choices: accounts.map((account: Account) => ({
63+
choices: accounts.map((account) => ({
6464
value: account.slug,
6565
name: account.name,
6666
})),

src/commands/status/status.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,8 @@ export const status = async (options: OptionValues, command: BaseCommand) => {
4242
Name: user.full_name,
4343
Email: user.email,
4444
GitHub: ghuser,
45+
Teams: accounts.map(({ name }) => name),
4546
}
46-
const teamsData = {}
47-
48-
accounts.forEach((team) => {
49-
// @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
50-
teamsData[team.name] = team.roles_allowed.join(' ')
51-
})
52-
53-
// @ts-expect-error TS(2339) FIXME: Property 'Teams' does not exist on type '{ Name: a... Remove this comment to see the full error message
54-
accountData.Teams = teamsData
5547

5648
// @ts-expect-error
5749
const cleanAccountData = clean(accountData)

src/commands/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NetlifyAPI } from 'netlify'
22

33
import type { FrameworksAPIPaths } from '../utils/frameworks-api.ts'
44
import type CLIState from '../utils/cli-state.js'
5-
import type { Account, GlobalConfigStore, SiteInfo } from '../utils/types.ts'
5+
import type { MinimalAccount, GlobalConfigStore, SiteInfo } from '../utils/types.ts'
66
import type { NormalizedCachedConfigConfig } from '../utils/command-helpers.js'
77
import type { CachedConfig } from '../lib/build.js'
88

@@ -22,7 +22,7 @@ export type NetlifySite = {
2222
* TODO(serhalp): Rename. These aren't options. They're more like context.
2323
*/
2424
export type NetlifyOptions = {
25-
accounts: Account[]
25+
accounts: MinimalAccount[]
2626
api: NetlifyAPI
2727
apiOpts: {
2828
userAgent: string

src/lib/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import tomlify from 'tomlify-j0.4'
77
import type { OptionValues } from 'commander'
88

99
import { getFeatureFlagsFromSiteInfo } from '../utils/feature-flags.js'
10-
import type { Account, EnvironmentVariables, Plugin, SiteInfo } from '../utils/types.js'
10+
import type { MinimalAccount, EnvironmentVariables, Plugin, SiteInfo } from '../utils/types.js'
1111

1212
import { getBootstrapURL } from './edge-functions/bootstrap.js'
1313
import { featureFlags as edgeFunctionsFeatureFlags } from './edge-functions/consts.js'
1414
import type { EdgeFunctionDeclaration } from './edge-functions/proxy.js'
1515

1616
export interface CachedConfig {
17-
accounts: Account[]
17+
accounts: MinimalAccount[] | undefined
1818
buildDir: string
1919
env: EnvironmentVariables
2020
repositoryRoot: string

src/utils/types.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,23 +184,17 @@ type EnvVarValue = {
184184
context: string
185185
}
186186

187-
export interface Account {
187+
export type MinimalAccount = {
188188
id: string
189189
name: string
190190
slug: string
191-
type: string
192-
capabilities: Record<string, { included: string; used: string }>
193-
billing_name: string
194-
billing_email: string
195-
billing_details: string
196-
billing_period: string
197-
payment_method_id: string
191+
default: boolean
192+
team_logo_url: string | null
193+
on_pro_trial: boolean
194+
organization_id: string | null
198195
type_name: string
199-
type_id: string
200-
owner_ids: string[]
201-
roles_allowed: string[]
202-
created_at: string
203-
updated_at: string
196+
type_slug: string
197+
members_count: number
204198
}
205199

206200
export interface GitHubRepo {

tests/integration/commands/status/__snapshots__/status.test.ts.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ exports[`fixture: empty-project > should print status for a linked site 2`] = `
1313
{
1414
"Email": "[email protected]",
1515
"Name": "Test User",
16+
"Teams": [
17+
"Test User",
18+
],
1619
}
1720
`;

tests/integration/commands/status/status.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest'
22

33
import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js'
44
import { getCLIOptions, withMockApi } from '../../utils/mock-api.js'
5+
import type { MinimalAccount } from '../../../../src/utils/types.js'
56

67
const siteInfo = {
78
account_slug: 'test-account',
@@ -13,7 +14,20 @@ const siteInfo = {
1314

1415
const user = { full_name: 'Test User', email: '[email protected]' }
1516

16-
const accounts = [{ slug: siteInfo.account_slug, name: user.full_name, roles_allowed: [] }]
17+
const accounts: MinimalAccount[] = [
18+
{
19+
id: 'user-id',
20+
name: user.full_name,
21+
slug: siteInfo.account_slug,
22+
default: true,
23+
team_logo_url: null,
24+
on_pro_trial: false,
25+
organization_id: null,
26+
type_name: 'placeholder',
27+
type_slug: 'placeholder',
28+
members_count: 1,
29+
},
30+
]
1731

1832
const routes = [
1933
{ path: 'sites/site_id', response: siteInfo },

0 commit comments

Comments
 (0)