diff --git a/packages/config/src/api/site_info.ts b/packages/config/src/api/site_info.ts index 93d08df1ee..62d45248cd 100644 --- a/packages/config/src/api/site_info.ts +++ b/packages/config/src/api/site_info.ts @@ -9,8 +9,8 @@ import { ModeOption, TestOptions } from '../types/options.js' type GetSiteInfoOpts = { siteId: string + accountId?: string mode: ModeOption - siteFeatureFlagPrefix: string offline?: boolean api?: NetlifyAPI context?: string @@ -29,14 +29,47 @@ type GetSiteInfoOpts = { export const getSiteInfo = async function ({ api, siteId, + accountId, mode, - siteFeatureFlagPrefix, context, offline = false, testOpts = {}, + featureFlags = {}, }: GetSiteInfoOpts) { const { env: testEnv = false } = testOpts + const useV2Endpoint = !!accountId && featureFlags.cli_integration_installations_meta + + if (useV2Endpoint) { + if (api === undefined || mode === 'buildbot' || testEnv) { + const siteInfo = siteId === undefined ? {} : { id: siteId } + + const integrations = + mode === 'buildbot' && !offline + ? await getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }) + : [] + + return { siteInfo, accounts: [], addons: [], integrations } + } + + const promises = [ + getSite(api, siteId), + getAccounts(api), + getAddons(api, siteId), + getIntegrations({ siteId, testOpts, offline, useV2Endpoint, accountId }), + ] + + const [siteInfo, accounts, addons, integrations] = await Promise.all(promises) + + if (siteInfo.use_envelope) { + const envelope = await getEnvelope({ api, accountId: siteInfo.account_slug, siteId, context }) + + siteInfo.build_settings.env = envelope + } + + return { siteInfo, accounts, addons, integrations } + } + if (api === undefined || mode === 'buildbot' || testEnv) { const siteInfo = siteId === undefined ? {} : { id: siteId } @@ -46,7 +79,7 @@ export const getSiteInfo = async function ({ } const promises = [ - getSite(api, siteId, siteFeatureFlagPrefix), + getSite(api, siteId), getAccounts(api), getAddons(api, siteId), getIntegrations({ siteId, testOpts, offline }), @@ -63,13 +96,13 @@ export const getSiteInfo = async function ({ return { siteInfo, accounts, addons, integrations } } -const getSite = async function (api: NetlifyAPI, siteId: string, siteFeatureFlagPrefix: string | null = null) { +const getSite = async function (api: NetlifyAPI, siteId: string) { if (siteId === undefined) { return {} } try { - const site = await (api as any).getSite({ siteId, feature_flags: siteFeatureFlagPrefix }) + const site = await (api as any).getSite({ siteId }) return { ...site, id: siteId } } catch (error) { throwUserError(`Failed retrieving site data for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`) @@ -100,14 +133,18 @@ const getAddons = async function (api: NetlifyAPI, siteId: string) { type GetIntegrationsOpts = { siteId?: string + accountId?: string testOpts: TestOptions offline: boolean + useV2Endpoint?: boolean } const getIntegrations = async function ({ siteId, + accountId, testOpts, offline, + useV2Endpoint, }: GetIntegrationsOpts): Promise { if (!siteId || offline) { return [] @@ -117,13 +154,19 @@ const getIntegrations = async function ({ const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`) + const url = useV2Endpoint + ? `${baseUrl}team/${accountId}/integrations/installations/meta` + : `${baseUrl}site/${siteId}/integrations/safe` + try { - const response = await fetch(`${baseUrl}site/${siteId}/integrations/safe`) + const response = await fetch(url) const integrations = await response.json() return Array.isArray(integrations) ? integrations : [] } catch (error) { - // for now, we'll just ignore errors, as this is early days + // Integrations should not block the build if they fail to load + // TODO: We should consider blocking the build as integrations are a critical part of the build process + // https://linear.app/netlify/issue/CT-1214/implement-strategy-in-builds-to-deal-with-integrations-that-we-fail-to return [] } } diff --git a/packages/config/src/bin/flags.ts b/packages/config/src/bin/flags.ts index 388651c3b9..485defa77e 100644 --- a/packages/config/src/bin/flags.ts +++ b/packages/config/src/bin/flags.ts @@ -123,6 +123,10 @@ The NETLIFY_AUTH_TOKEN environment variable can be used as well.`, string: true, describe: `Netlify Site ID.`, }, + accountId: { + string: true, + describe: 'Netlify Account ID. This will only be available in buildbot mode.', + }, context: { string: true, describe: `Build context. diff --git a/packages/config/src/main.ts b/packages/config/src/main.ts index 1971acb574..d3ffeaeb23 100644 --- a/packages/config/src/main.ts +++ b/packages/config/src/main.ts @@ -25,19 +25,8 @@ import { getRedirectsPath, addRedirects } from './redirects.js' * `config` together with related properties such as the `configPath`. */ export const resolveConfig = async function (opts) { - const { - cachedConfig, - cachedConfigPath, - host, - scheme, - packagePath, - pathPrefix, - testOpts, - token, - offline, - siteFeatureFlagPrefix, - ...optsA - } = addDefaultOpts(opts) as $TSFixMe + const { cachedConfig, cachedConfigPath, host, scheme, packagePath, pathPrefix, testOpts, token, offline, ...optsA } = + addDefaultOpts(opts) as $TSFixMe // `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig` const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts }) @@ -57,6 +46,7 @@ export const resolveConfig = async function (opts) { base, branch, siteId, + accountId, deployId, buildId, baseRelDir, @@ -70,9 +60,9 @@ export const resolveConfig = async function (opts) { api, context, siteId, + accountId, mode, offline, - siteFeatureFlagPrefix, featureFlags, testOpts, }) diff --git a/packages/config/tests/api/tests.js b/packages/config/tests/api/tests.js index 61f9a2a113..b13d6d920c 100644 --- a/packages/config/tests/api/tests.js +++ b/packages/config/tests/api/tests.js @@ -26,6 +26,17 @@ const SITE_INTEGRATIONS_RESPONSE = { ], } +const TEAM_INSTALLATIONS_META_RESPONSE = { + path: '/team/account1/integrations/installations/meta', + response: [ + { + slug: 'test', + version: 'so-cool', + has_build: true, + }, + ], +} + const SITE_INTEGRATIONS_EMPTY_RESPONSE = { path: '/site/test/integrations/safe', response: [], @@ -307,9 +318,10 @@ test('In integration dev mode, integration specified in config is returned and b t.assert(config.integrations[0].version === undefined) }) -test('Integrations are returned if feature flag is true, mode buildbot', async (t) => { +test('Integrations are not returned if offline', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ + offline: true, siteId: 'test', mode: 'buildbot', }) @@ -317,6 +329,22 @@ test('Integrations are returned if feature flag is true, mode buildbot', async ( const config = JSON.parse(output) + t.assert(config.integrations) + t.assert(config.integrations.length === 0) +}) + +test('Integrations are returned if feature flag is false and mode is buildbot', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'buildbot', + accountId: 'account1', + token: 'test', + }) + .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + t.assert(config.integrations) t.assert(config.integrations.length === 1) t.assert(config.integrations[0].slug === 'test') @@ -324,19 +352,66 @@ test('Integrations are returned if feature flag is true, mode buildbot', async ( t.assert(config.integrations[0].has_build === true) }) -test('Integrations are not returned if offline', async (t) => { +test('Integrations are returned if feature flag is false and mode is dev', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'dev', + token: 'test', + }) + .runConfigServer([SITE_INFO_DATA, SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + + t.assert(config.integrations) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) +}) + +test('Integrations are returned if flag is true for site and mode is buildbot', async (t) => { const { output } = await new Fixture('./fixtures/base') .withFlags({ - offline: true, siteId: 'test', mode: 'buildbot', + token: 'test', + accountId: 'account1', + featureFlags: { + cli_integration_installations_meta: true, + }, }) - .runConfigServer([SITE_INTEGRATIONS_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) const config = JSON.parse(output) t.assert(config.integrations) - t.assert(config.integrations.length === 0) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) +}) + +test('Integrations are returned if flag is true for site and mode is dev', async (t) => { + const { output } = await new Fixture('./fixtures/base') + .withFlags({ + siteId: 'test', + mode: 'dev', + token: 'test', + accountId: 'account1', + featureFlags: { + cli_integration_installations_meta: true, + }, + }) + .runConfigServer([SITE_INFO_DATA, TEAM_INSTALLATIONS_META_RESPONSE, FETCH_INTEGRATIONS_EMPTY_RESPONSE]) + + const config = JSON.parse(output) + + t.assert(config.integrations) + t.assert(config.integrations.length === 1) + t.assert(config.integrations[0].slug === 'test') + t.assert(config.integrations[0].version === 'so-cool') + t.assert(config.integrations[0].has_build === true) }) test('baseRelDir is true if build.base is overridden', async (t) => { diff --git a/packages/config/tests/cli/snapshots/tests.js.md b/packages/config/tests/cli/snapshots/tests.js.md index 08fe267bcb..4e8ed0f0e3 100644 --- a/packages/config/tests/cli/snapshots/tests.js.md +++ b/packages/config/tests/cli/snapshots/tests.js.md @@ -37,6 +37,8 @@ Generated by [AVA](https://avajs.dev). The NETLIFY_AUTH_TOKEN␊ environment variable can be used as well. [string]␊ --siteId Netlify Site ID. [string]␊ + --accountId Netlify Account ID. This will only be available in buildbot␊ + mode. [string]␊ --context Build context.␊ Default: 'production' [string]␊ --branch Repository branch.␊ diff --git a/packages/config/tests/cli/snapshots/tests.js.snap b/packages/config/tests/cli/snapshots/tests.js.snap index c97b653c67..db27e8ec20 100644 Binary files a/packages/config/tests/cli/snapshots/tests.js.snap and b/packages/config/tests/cli/snapshots/tests.js.snap differ