@netlify/config 22.0.1 → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api/client.d.ts +1 -1
- package/lib/api/client.js +1 -1
- package/lib/api/site_info.d.ts +12 -1
- package/lib/api/site_info.js +1 -1
- package/lib/env/envelope.d.ts +1 -1
- package/lib/main.js +15 -1
- package/lib/utils/extensions/auto-install-extensions.d.ts +17 -0
- package/lib/utils/extensions/auto-install-extensions.js +58 -0
- package/lib/utils/extensions/utils.d.ts +31 -0
- package/lib/utils/extensions/utils.js +44 -0
- package/package.json +3 -3
package/lib/api/client.d.ts
CHANGED
package/lib/api/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NetlifyAPI } from 'netlify';
|
|
1
|
+
import { NetlifyAPI } from '@netlify/api';
|
|
2
2
|
import { removeUndefined } from '../utils/remove_falsy.js';
|
|
3
3
|
// Retrieve Netlify API client, if an access token was passed
|
|
4
4
|
export const getApiClient = function ({ token, offline, testOpts = {}, host, scheme, pathPrefix }) {
|
package/lib/api/site_info.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NetlifyAPI } from 'netlify';
|
|
1
|
+
import { NetlifyAPI } from '@netlify/api';
|
|
2
2
|
import { IntegrationResponse } from '../types/api.js';
|
|
3
3
|
import { ModeOption, TestOptions } from '../types/options.js';
|
|
4
4
|
type GetSiteInfoOpts = {
|
|
@@ -40,4 +40,15 @@ export type MinimalAccount = {
|
|
|
40
40
|
type_slug: string;
|
|
41
41
|
members_count: number;
|
|
42
42
|
};
|
|
43
|
+
type GetIntegrationsOpts = {
|
|
44
|
+
siteId?: string;
|
|
45
|
+
accountId?: string;
|
|
46
|
+
testOpts: TestOptions;
|
|
47
|
+
offline: boolean;
|
|
48
|
+
token?: string;
|
|
49
|
+
featureFlags?: Record<string, boolean>;
|
|
50
|
+
extensionApiBaseUrl: string;
|
|
51
|
+
mode: ModeOption;
|
|
52
|
+
};
|
|
53
|
+
export declare const getIntegrations: ({ siteId, accountId, testOpts, offline, token, featureFlags, extensionApiBaseUrl, mode, }: GetIntegrationsOpts) => Promise<IntegrationResponse[]>;
|
|
43
54
|
export {};
|
package/lib/api/site_info.js
CHANGED
|
@@ -68,7 +68,7 @@ const getAccounts = async function (api) {
|
|
|
68
68
|
return throwUserError(`Failed retrieving user account: ${error.message}. ${ERROR_CALL_TO_ACTION}`);
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
-
const getIntegrations = async function ({ siteId, accountId, testOpts, offline, token, featureFlags, extensionApiBaseUrl, mode, }) {
|
|
71
|
+
export const getIntegrations = async function ({ siteId, accountId, testOpts, offline, token, featureFlags, extensionApiBaseUrl, mode, }) {
|
|
72
72
|
if (!siteId || offline) {
|
|
73
73
|
return [];
|
|
74
74
|
}
|
package/lib/env/envelope.d.ts
CHANGED
package/lib/main.js
CHANGED
|
@@ -18,6 +18,7 @@ import { UI_ORIGIN, CONFIG_ORIGIN, INLINE_ORIGIN } from './origin.js';
|
|
|
18
18
|
import { parseConfig } from './parse.js';
|
|
19
19
|
import { getConfigPath } from './path.js';
|
|
20
20
|
import { getRedirectsPath, addRedirects } from './redirects.js';
|
|
21
|
+
import { handleAutoInstallExtensions } from './utils/extensions/auto-install-extensions.js';
|
|
21
22
|
/**
|
|
22
23
|
* Load the configuration file.
|
|
23
24
|
* Takes an optional configuration file path as input and return the resolved
|
|
@@ -101,8 +102,21 @@ export const resolveConfig = async function (opts) {
|
|
|
101
102
|
});
|
|
102
103
|
// @todo Remove in the next major version.
|
|
103
104
|
const configA = addLegacyFunctionsDirectory(config);
|
|
105
|
+
const updatedIntegrations = await handleAutoInstallExtensions({
|
|
106
|
+
featureFlags,
|
|
107
|
+
accounts,
|
|
108
|
+
integrations,
|
|
109
|
+
siteId,
|
|
110
|
+
accountId,
|
|
111
|
+
token,
|
|
112
|
+
cwd,
|
|
113
|
+
extensionApiBaseUrl,
|
|
114
|
+
testOpts,
|
|
115
|
+
offline,
|
|
116
|
+
mode,
|
|
117
|
+
});
|
|
104
118
|
const mergedIntegrations = await mergeIntegrations({
|
|
105
|
-
apiIntegrations:
|
|
119
|
+
apiIntegrations: updatedIntegrations,
|
|
106
120
|
configIntegrations: configA.integrations,
|
|
107
121
|
context: context,
|
|
108
122
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type IntegrationResponse } from '../../types/api.js';
|
|
2
|
+
import { type ModeOption } from '../../types/options.js';
|
|
3
|
+
interface AutoInstallOptions {
|
|
4
|
+
featureFlags: any;
|
|
5
|
+
siteId: string;
|
|
6
|
+
accountId: string;
|
|
7
|
+
token: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
accounts: any;
|
|
10
|
+
integrations: IntegrationResponse[];
|
|
11
|
+
offline: boolean;
|
|
12
|
+
testOpts: any;
|
|
13
|
+
mode: ModeOption;
|
|
14
|
+
extensionApiBaseUrl: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function handleAutoInstallExtensions({ featureFlags, siteId, accountId, token, cwd, accounts, integrations, offline, testOpts, mode, extensionApiBaseUrl, }: AutoInstallOptions): Promise<IntegrationResponse[]>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getIntegrations } from '../../api/site_info.js';
|
|
4
|
+
import { fetchAutoInstallableExtensionsMeta, installExtension } from './utils.js';
|
|
5
|
+
function getPackageJSON(directory) {
|
|
6
|
+
const require = createRequire(join(directory, 'package.json'));
|
|
7
|
+
return require('./package.json');
|
|
8
|
+
}
|
|
9
|
+
export async function handleAutoInstallExtensions({ featureFlags, siteId, accountId, token, cwd, accounts, integrations, offline, testOpts = {}, mode, extensionApiBaseUrl, }) {
|
|
10
|
+
if (!featureFlags?.auto_install_required_extensions || !accountId || !siteId || !token || !cwd || offline) {
|
|
11
|
+
return integrations;
|
|
12
|
+
}
|
|
13
|
+
const account = accounts?.find((account) => account.id === accountId);
|
|
14
|
+
if (!account) {
|
|
15
|
+
return integrations;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const packageJson = getPackageJSON(cwd);
|
|
19
|
+
if (!packageJson?.dependencies ||
|
|
20
|
+
typeof packageJson?.dependencies !== 'object' ||
|
|
21
|
+
Object.keys(packageJson?.dependencies)?.length === 0) {
|
|
22
|
+
return integrations;
|
|
23
|
+
}
|
|
24
|
+
const autoInstallableExtensions = await fetchAutoInstallableExtensionsMeta();
|
|
25
|
+
const extensionsToInstall = autoInstallableExtensions.filter((ext) => {
|
|
26
|
+
return !integrations?.some((integration) => integration.slug === ext.slug);
|
|
27
|
+
});
|
|
28
|
+
if (extensionsToInstall.length === 0) {
|
|
29
|
+
return integrations;
|
|
30
|
+
}
|
|
31
|
+
const results = await Promise.all(extensionsToInstall.map(async (ext) => {
|
|
32
|
+
console.log(`Installing extension "${ext.slug}" on team "${account.name}" required by package(s): "${ext.packages.join('",')}"`);
|
|
33
|
+
return installExtension({
|
|
34
|
+
accountId,
|
|
35
|
+
netlifyToken: token,
|
|
36
|
+
slug: ext.slug,
|
|
37
|
+
hostSiteUrl: ext.hostSiteUrl,
|
|
38
|
+
});
|
|
39
|
+
}));
|
|
40
|
+
if (results.length > 0 && results.some((result) => !result.error)) {
|
|
41
|
+
return getIntegrations({
|
|
42
|
+
siteId,
|
|
43
|
+
accountId,
|
|
44
|
+
testOpts,
|
|
45
|
+
offline,
|
|
46
|
+
token,
|
|
47
|
+
featureFlags,
|
|
48
|
+
extensionApiBaseUrl,
|
|
49
|
+
mode,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return integrations;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`Failed to auto install extension(s): ${error.message}`, error);
|
|
56
|
+
return integrations;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type InstallExtensionResult = {
|
|
2
|
+
slug: string;
|
|
3
|
+
error: null;
|
|
4
|
+
} | {
|
|
5
|
+
slug: string;
|
|
6
|
+
error: {
|
|
7
|
+
code: string;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export declare const installExtension: ({ netlifyToken, accountId, slug, hostSiteUrl, }: {
|
|
12
|
+
netlifyToken: string;
|
|
13
|
+
accountId: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
hostSiteUrl: string;
|
|
16
|
+
}) => Promise<InstallExtensionResult>;
|
|
17
|
+
type AutoInstallableExtensionMeta = {
|
|
18
|
+
slug: string;
|
|
19
|
+
hostSiteUrl: string;
|
|
20
|
+
packages: string[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Fetches the list of extensions from Jigsaw that declare associated packages.
|
|
24
|
+
* Used to determine which extensions should be auto-installed based on the packages
|
|
25
|
+
* present in the package.json (e.g., if an extension lists '@netlify/neon',
|
|
26
|
+
* and that package exists in package.json, the extension will be auto-installed).
|
|
27
|
+
*
|
|
28
|
+
* @returns Array of extensions with their associated packages
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchAutoInstallableExtensionsMeta(): Promise<AutoInstallableExtensionMeta[]>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { EXTENSION_API_BASE_URL } from '../../integrations.js';
|
|
2
|
+
export const installExtension = async ({ netlifyToken, accountId, slug, hostSiteUrl, }) => {
|
|
3
|
+
const extensionOnInstallUrl = new URL('/.netlify/functions/handler/on-install', hostSiteUrl);
|
|
4
|
+
const installedResponse = await fetch(extensionOnInstallUrl, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
body: JSON.stringify({
|
|
7
|
+
teamId: accountId,
|
|
8
|
+
}),
|
|
9
|
+
headers: {
|
|
10
|
+
'netlify-token': netlifyToken,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
if (!installedResponse.ok && installedResponse.status !== 409) {
|
|
14
|
+
const text = await installedResponse.text();
|
|
15
|
+
return {
|
|
16
|
+
slug,
|
|
17
|
+
error: {
|
|
18
|
+
code: installedResponse.status.toString(),
|
|
19
|
+
message: text,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
slug,
|
|
25
|
+
error: null,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Fetches the list of extensions from Jigsaw that declare associated packages.
|
|
30
|
+
* Used to determine which extensions should be auto-installed based on the packages
|
|
31
|
+
* present in the package.json (e.g., if an extension lists '@netlify/neon',
|
|
32
|
+
* and that package exists in package.json, the extension will be auto-installed).
|
|
33
|
+
*
|
|
34
|
+
* @returns Array of extensions with their associated packages
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchAutoInstallableExtensionsMeta() {
|
|
37
|
+
const url = new URL(`/meta/auto-installable`, process.env.EXTENSION_API_BASE_URL ?? EXTENSION_API_BASE_URL);
|
|
38
|
+
const response = await fetch(url.toString());
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
throw new Error(`Failed to fetch extensions meta`);
|
|
41
|
+
}
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
return data;
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@netlify/config",
|
|
3
|
-
"version": "22.0
|
|
3
|
+
"version": "22.2.0",
|
|
4
4
|
"description": "Netlify config module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": "./lib/index.js",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"license": "MIT",
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@iarna/toml": "^2.2.5",
|
|
62
|
+
"@netlify/api": "^13.4.0",
|
|
62
63
|
"@netlify/headers-parser": "^8.0.0",
|
|
63
64
|
"@netlify/redirect-parser": "^14.5.1",
|
|
64
65
|
"chalk": "^5.0.0",
|
|
@@ -74,7 +75,6 @@
|
|
|
74
75
|
"is-plain-obj": "^4.0.0",
|
|
75
76
|
"js-yaml": "^4.0.0",
|
|
76
77
|
"map-obj": "^5.0.0",
|
|
77
|
-
"netlify": "^13.3.5",
|
|
78
78
|
"node-fetch": "^3.3.1",
|
|
79
79
|
"omit.js": "^2.0.2",
|
|
80
80
|
"p-locate": "^6.0.0",
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"engines": {
|
|
96
96
|
"node": "^14.16.0 || >=16.0.0"
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "03eae326c598d4d31b28fe8f20038c5d9508f772"
|
|
99
99
|
}
|