@netlify/config 20.6.4 → 20.8.0-rc.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.
@@ -2,28 +2,26 @@ import fetch from 'node-fetch';
2
2
  import { getEnvelope } from '../env/envelope.js';
3
3
  import { throwUserError } from '../error.js';
4
4
  import { ERROR_CALL_TO_ACTION } from '../log/messages.js';
5
- // Retrieve Netlify Site information, if available.
6
- // Used to retrieve local build environment variables and UI build settings.
7
- // This is not used in production builds since the buildbot passes this
8
- // information instead.
9
- // Requires knowing the `siteId` and having the access `token`.
10
- // Silently ignore API errors. For example the network connection might be down,
11
- // but local builds should still work regardless.
12
- export const getSiteInfo = async function ({ api, siteId, mode, siteFeatureFlagPrefix, featureFlags = {}, testOpts = {}, }) {
5
+ /**
6
+ * Retrieve Netlify Site information, if available.
7
+ * Used to retrieve local build environment variables and UI build settings.
8
+ * This is not used in production builds since the buildbot passes this
9
+ * information instead.
10
+ * Requires knowing the `siteId` and having the access `token`.
11
+ * Silently ignore API errors. For example the network connection might be down,
12
+ * but local builds should still work regardless.
13
+ */
14
+ export const getSiteInfo = async function ({ api, siteId, mode, siteFeatureFlagPrefix, offline = false, featureFlags = {}, testOpts = {}, }) {
13
15
  const { env: testEnv = false } = testOpts;
14
16
  const fetchIntegrations = featureFlags.buildbot_fetch_integrations;
15
17
  if (api === undefined || mode === 'buildbot' || testEnv) {
16
18
  const siteInfo = siteId === undefined ? {} : { id: siteId };
17
- let integrations = [];
18
- if (fetchIntegrations && api !== undefined && !testEnv) {
19
- // we still want to fetch integrations within buildbot
20
- integrations = await getIntegrations({ api, ownerType: 'site', ownerId: siteId, testOpts });
21
- }
19
+ const integrations = fetchIntegrations && mode === 'buildbot' && !offline ? await getIntegrations({ siteId, testOpts }) : [];
22
20
  return { siteInfo, accounts: [], addons: [], integrations };
23
21
  }
24
22
  const promises = [getSite(api, siteId, siteFeatureFlagPrefix), getAccounts(api), getAddons(api, siteId)];
25
23
  if (fetchIntegrations) {
26
- promises.push(getIntegrations({ api, ownerType: 'site', ownerId: siteId, testOpts }));
24
+ promises.push(getIntegrations({ siteId, testOpts }));
27
25
  }
28
26
  const [siteInfo, accounts, addons, integrations = []] = await Promise.all(promises);
29
27
  if (siteInfo.use_envelope) {
@@ -65,19 +63,14 @@ const getAddons = async function (api, siteId) {
65
63
  throwUserError(`Failed retrieving addons for site ${siteId}: ${error.message}. ${ERROR_CALL_TO_ACTION}`);
66
64
  }
67
65
  };
68
- const getIntegrations = async function ({ api, ownerType, ownerId, testOpts }) {
69
- if (ownerId === undefined) {
66
+ const getIntegrations = async function ({ siteId, testOpts }) {
67
+ if (!siteId) {
70
68
  return [];
71
69
  }
72
70
  const { host } = testOpts;
73
- const baseUrl = host ? `http://${host}` : `https://api.netlifysdk.com`;
71
+ const baseUrl = new URL(host ? `http://${host}` : `https://api.netlifysdk.com`);
74
72
  try {
75
- const token = api.accessToken;
76
- const response = await fetch(`${baseUrl}/${ownerType}/${ownerId}/integrations`, {
77
- headers: {
78
- Authorization: `Bearer ${token}`,
79
- },
80
- });
73
+ const response = await fetch(`${baseUrl}site/${siteId}/integrations/safe`);
81
74
  const integrations = await response.json();
82
75
  return Array.isArray(integrations) ? integrations : [];
83
76
  }
package/lib/base.js CHANGED
@@ -1,26 +1,34 @@
1
1
  import { resolvePath } from './files.js';
2
- // Retrieve the first `base` directory used to load the first config file.
3
- export const getInitialBase = function ({ repositoryRoot, defaultConfig: { build: { base: defaultBase } = {} }, inlineConfig: { build: { base: initialBase = defaultBase } = {} }, }) {
2
+ /**
3
+ * Retrieve the first `base` directory used to load the first config file.
4
+ */
5
+ export const getInitialBase = function ({ repositoryRoot,
6
+ // @ts-expect-error TODO: enhance later
7
+ defaultConfig: { build: { base: defaultBase } = {} }, inlineConfig: { build: { base: initialBase = defaultBase } = {} }, }) {
4
8
  return resolveBase(repositoryRoot, initialBase);
5
9
  };
6
- // Two config files can be used:
7
- // - The first one, using the `config` property or doing a default lookup
8
- // of `netlify.toml`
9
- // - If the first one has a `base` property pointing to a directory with
10
- // another `netlify.toml`, that second config file is used instead.
11
- // This retrieves the final `base` directory used:
12
- // - To load the second config file
13
- // - As the `buildDir`
14
- // - To resolve file paths
15
- // If the second file has a `base` property, it is ignored, i.e. it is not
16
- // recursive.
10
+ /**
11
+ * Two config files can be used:
12
+ * - The first one, using the `config` property or doing a default lookup
13
+ * of `netlify.toml`
14
+ * - If the first one has a `base` property pointing to a directory with
15
+ * another `netlify.toml`, that second config file is used instead.
16
+ * This retrieves the final `base` directory used:
17
+ * - To load the second config file
18
+ * - As the `buildDir`
19
+ * - To resolve file paths
20
+ * If the second file has a `base` property, it is ignored, i.e. it is not
21
+ * recursive.
22
+ */
17
23
  export const getBase = function (base, repositoryRoot, config) {
18
24
  return base === undefined ? resolveBase(repositoryRoot, config.build.base) : base;
19
25
  };
20
26
  const resolveBase = function (repositoryRoot, base) {
21
27
  return resolvePath(repositoryRoot, repositoryRoot, base, 'build.base');
22
28
  };
23
- // Also `config.build.base`.
29
+ /**
30
+ * Also `config.build.base`.
31
+ */
24
32
  export const addBase = function (config, base) {
25
33
  return { ...config, build: { ...config.build, base } };
26
34
  };
package/lib/bin/flags.js CHANGED
@@ -72,6 +72,10 @@ Default: empty array.`,
72
72
  describe: `Current directory. Used to retrieve the configuration file.
73
73
  Default: current directory`,
74
74
  },
75
+ packagePath: {
76
+ string: true,
77
+ describe: `A relative path from the repository root to the package. Used inside monorepos to specify a package`,
78
+ },
75
79
  repositoryRoot: {
76
80
  string: true,
77
81
  describe: `Git repository root directory. Used to retrieve the configuration file.
package/lib/build_dir.js CHANGED
@@ -1,19 +1,23 @@
1
1
  import { isDirectory } from 'path-type';
2
2
  import { throwUserError } from './error.js';
3
- // Retrieve the build directory used to resolve most paths.
4
- // This is (in priority order):
5
- // - `build.base`
6
- // - `--repositoryRoot`
7
- // - the current directory (default value of `--repositoryRoot`)
3
+ /**
4
+ * Retrieve the build directory used to resolve most paths.
5
+ * This is (in priority order):
6
+ * - `build.base`
7
+ * - `--repositoryRoot`
8
+ * - the current directory (default value of `--repositoryRoot`)
9
+ */
8
10
  export const getBuildDir = async function (repositoryRoot, base) {
9
11
  const buildDir = base === undefined ? repositoryRoot : base;
10
12
  await checkBuildDir(buildDir, repositoryRoot);
11
13
  return buildDir;
12
14
  };
13
- // The build directory is used as the current directory of build commands and
14
- // build plugins. Therefore, it must exist.
15
- // We already check `repositoryRoot` earlier in the code, so only need to check
16
- // `buildDir` when it is the base directory instead.
15
+ /**
16
+ * The build directory is used as the current directory of build commands and
17
+ * build plugins. Therefore, it must exist.
18
+ * We already check `repositoryRoot` earlier in the code, so only need to check
19
+ * `buildDir` when it is the base directory instead.
20
+ */
17
21
  const checkBuildDir = async function (buildDir, repositoryRoot) {
18
22
  if (buildDir === repositoryRoot || (await isDirectory(buildDir))) {
19
23
  return;
package/lib/files.js CHANGED
@@ -1,63 +1,87 @@
1
1
  import { existsSync } from 'fs';
2
- import { resolve, relative, parse } from 'path';
2
+ import { resolve, relative, parse, join } from 'path';
3
3
  import { getProperty, setProperty, deleteProperty } from 'dot-prop';
4
4
  import { throwUserError } from './error.js';
5
5
  import { mergeConfigs } from './merge.js';
6
6
  import { isTruthy } from './utils/remove_falsy.js';
7
- // Make configuration paths relative to `buildDir` and converts them to
8
- // absolute paths
9
- export const resolveConfigPaths = function ({ config, repositoryRoot, buildDir, baseRelDir }) {
10
- const baseRel = baseRelDir ? buildDir : repositoryRoot;
11
- const configA = resolvePaths(config, FILE_PATH_CONFIG_PROPS, baseRel, repositoryRoot);
12
- const configB = addDefaultPaths(configA, repositoryRoot, baseRel);
13
- return configB;
14
- };
15
- // All file paths in the configuration file are relative to `buildDir`
16
- // (if `baseRelDir` is `true`).
7
+ /**
8
+ * We allow paths in configuration file to start with /
9
+ * In that case, those are actually relative paths not absolute.
10
+ */
11
+ const LEADING_SLASH_REGEXP = /^\/+/;
12
+ /**
13
+ * All file paths in the configuration file are relative to `buildDir`
14
+ * (if `baseRelDir` is `true`).
15
+ */
17
16
  const FILE_PATH_CONFIG_PROPS = [
18
17
  'functionsDirectory',
19
18
  'functions.*.deno_import_map',
20
19
  'build.publish',
21
20
  'build.edge_functions',
22
21
  ];
23
- const resolvePaths = function (config, propNames, baseRel, repositoryRoot) {
24
- return propNames.reduce((configA, propName) => resolvePathProp(configA, propName, baseRel, repositoryRoot), config);
22
+ /**
23
+ * Make configuration paths relative to `buildDir` and converts them to
24
+ * absolute paths
25
+ */
26
+ export const resolveConfigPaths = function (options) {
27
+ const baseRel = options.baseRelDir ? options.buildDir : options.repositoryRoot;
28
+ const config = resolvePaths({
29
+ config: options.config,
30
+ packagePath: options.packagePath,
31
+ propNames: FILE_PATH_CONFIG_PROPS,
32
+ baseRel,
33
+ repositoryRoot: options.repositoryRoot,
34
+ });
35
+ return addDefaultPaths({
36
+ config,
37
+ repositoryRoot: options.repositoryRoot,
38
+ baseRel,
39
+ packagePath: options.packagePath,
40
+ });
41
+ };
42
+ const resolvePaths = function ({ config, propNames, baseRel, packagePath, repositoryRoot, }) {
43
+ return propNames.reduce((configA, propName) => resolvePathProp(configA, propName, baseRel, repositoryRoot, packagePath), config);
25
44
  };
26
- const resolvePathProp = function (config, propName, baseRel, repositoryRoot) {
45
+ const resolvePathProp = function (config, propName, baseRel, repositoryRoot, packagePath) {
27
46
  const path = getProperty(config, propName);
28
47
  if (!isTruthy(path)) {
29
48
  deleteProperty(config, propName);
30
49
  return config;
31
50
  }
32
- return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName));
51
+ return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName, packagePath));
33
52
  };
34
- export const resolvePath = function (repositoryRoot, baseRel, originalPath, propName) {
53
+ export const resolvePath = (repositoryRoot, baseRel, originalPath, propName,
54
+ // @ts-expect-error depends on the survey outcome see comment below
55
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
+ packagePath) => {
35
57
  if (!isTruthy(originalPath)) {
36
58
  return;
37
59
  }
38
60
  const path = originalPath.replace(LEADING_SLASH_REGEXP, '');
61
+ // TODO: Based on survey outcome: https://netlify.slack.com/archives/C05556LEX28/p1691423437855069
62
+ // If we like option B then:
63
+ // const pathA = resolve(baseRel, packagePath || '', path)
39
64
  const pathA = resolve(baseRel, path);
40
65
  validateInsideRoot(originalPath, pathA, repositoryRoot, propName);
41
66
  return pathA;
42
67
  };
43
- // We allow paths in configuration file to start with /
44
- // In that case, those are actually relative paths not absolute.
45
- const LEADING_SLASH_REGEXP = /^\/+/;
46
- // We ensure all file paths are within the repository root directory.
47
- // However, we allow file paths to be outside the build directory, since this
48
- // can be convenient in monorepo setups.
49
- const validateInsideRoot = function (originalPath, path, repositoryRoot, propName) {
68
+ /**
69
+ * We ensure all file paths are within the repository root directory.
70
+ * However, we allow file paths to be outside the build directory, since this
71
+ * can be convenient in monorepo setups.
72
+ */
73
+ const validateInsideRoot = (originalPath, path, repositoryRoot, propName) => {
50
74
  if (relative(repositoryRoot, path).startsWith('..') || getWindowsDrive(repositoryRoot) !== getWindowsDrive(path)) {
51
75
  throwUserError(`Configuration property "${propName}" "${originalPath}" must be inside the repository root directory.`);
52
76
  }
53
77
  };
54
- const getWindowsDrive = function (path) {
55
- return parse(path).root;
56
- };
57
- // Some configuration properties have default values that are only set if a
58
- // specific directory/file exists in the build directory
59
- const addDefaultPaths = function (config, repositoryRoot, baseRel) {
60
- const defaultPathsConfigs = DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) => addDefaultPath({ repositoryRoot, baseRel, defaultPath, getConfig, propName })).filter(Boolean);
78
+ const getWindowsDrive = (path) => parse(path).root;
79
+ /**
80
+ * Some configuration properties have default values that are only set if a
81
+ * specific directory/file exists in the build directory
82
+ */
83
+ const addDefaultPaths = ({ config, repositoryRoot, baseRel, packagePath, }) => {
84
+ const defaultPathsConfigs = DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) => addDefaultPath({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName })).filter(Boolean);
61
85
  return mergeConfigs([...defaultPathsConfigs, config]);
62
86
  };
63
87
  const DEFAULT_PATHS = [
@@ -78,9 +102,9 @@ const DEFAULT_PATHS = [
78
102
  propName: 'build.edge_functions',
79
103
  },
80
104
  ];
81
- const addDefaultPath = function ({ repositoryRoot, baseRel, defaultPath, getConfig, propName }) {
82
- const absolutePath = resolvePath(repositoryRoot, baseRel, defaultPath, propName);
83
- if (!existsSync(absolutePath)) {
105
+ const addDefaultPath = ({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName, }) => {
106
+ const absolutePath = resolvePath(repositoryRoot, join(baseRel, packagePath || ''), defaultPath, propName);
107
+ if (!absolutePath || !existsSync(absolutePath)) {
84
108
  return;
85
109
  }
86
110
  return getConfig(absolutePath);
package/lib/main.js CHANGED
@@ -17,11 +17,13 @@ import { UI_ORIGIN, CONFIG_ORIGIN, INLINE_ORIGIN } from './origin.js';
17
17
  import { parseConfig } from './parse.js';
18
18
  import { getConfigPath } from './path.js';
19
19
  import { getRedirectsPath, addRedirects } from './redirects.js';
20
- // Load the configuration file.
21
- // Takes an optional configuration file path as input and return the resolved
22
- // `config` together with related properties such as the `configPath`.
20
+ /**
21
+ * Load the configuration file.
22
+ * Takes an optional configuration file path as input and return the resolved
23
+ * `config` together with related properties such as the `configPath`.
24
+ */
23
25
  export const resolveConfig = async function (opts) {
24
- const { cachedConfig, cachedConfigPath, host, scheme, pathPrefix, testOpts, token, offline, siteFeatureFlagPrefix, ...optsA } = addDefaultOpts(opts);
26
+ const { cachedConfig, cachedConfigPath, host, scheme, packagePath, pathPrefix, testOpts, token, offline, siteFeatureFlagPrefix, ...optsA } = addDefaultOpts(opts);
25
27
  // `api` is not JSON-serializable, so we cannot cache it inside `cachedConfig`
26
28
  const api = getApiClient({ token, offline, host, scheme, pathPrefix, testOpts });
27
29
  const parsedCachedConfig = await getCachedConfig({ cachedConfig, cachedConfigPath, token, api });
@@ -33,9 +35,10 @@ export const resolveConfig = async function (opts) {
33
35
  api,
34
36
  siteId,
35
37
  mode,
36
- testOpts,
38
+ offline,
37
39
  siteFeatureFlagPrefix,
38
40
  featureFlags,
41
+ testOpts,
39
42
  });
40
43
  const { defaultConfig: defaultConfigA, baseRelDir: baseRelDirA } = parseDefaultConfig({
41
44
  defaultConfig,
@@ -51,6 +54,7 @@ export const resolveConfig = async function (opts) {
51
54
  cwd,
52
55
  context,
53
56
  repositoryRoot,
57
+ packagePath,
54
58
  branch,
55
59
  defaultConfig: defaultConfigA,
56
60
  inlineConfig: inlineConfigA,
@@ -94,8 +98,10 @@ export const resolveConfig = async function (opts) {
94
98
  logResult(result, { logs, debug });
95
99
  return result;
96
100
  };
97
- // Adds a `build.functions` property that mirrors `functionsDirectory`, for
98
- // backward compatibility.
101
+ /**
102
+ * Adds a `build.functions` property that mirrors `functionsDirectory`, for
103
+ * backward compatibility.
104
+ */
99
105
  const addLegacyFunctionsDirectory = (config) => {
100
106
  if (!config.functionsDirectory) {
101
107
  return config;
@@ -108,10 +114,12 @@ const addLegacyFunctionsDirectory = (config) => {
108
114
  },
109
115
  };
110
116
  };
111
- // Try to load the configuration file in two passes.
112
- // The first pass uses the `defaultConfig`'s `build.base` (if defined).
113
- // The second pass uses the `build.base` from the first pass (if defined).
114
- const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, branch, defaultConfig, inlineConfig, baseRelDir, logs, featureFlags, }) {
117
+ /**
118
+ * Try to load the configuration file in two passes.
119
+ * The first pass uses the `defaultConfig`'s `build.base` (if defined).
120
+ * The second pass uses the `build.base` from the first pass (if defined).
121
+ */
122
+ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, logs, featureFlags, }) {
115
123
  const initialBase = getInitialBase({ repositoryRoot, defaultConfig, inlineConfig });
116
124
  const { configPath, config, buildDir, base, redirectsPath, headersPath } = await getFullConfig({
117
125
  configOpt,
@@ -122,6 +130,7 @@ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, br
122
130
  defaultConfig,
123
131
  inlineConfig,
124
132
  baseRelDir,
133
+ packagePath,
125
134
  configBase: initialBase,
126
135
  logs,
127
136
  featureFlags,
@@ -157,9 +166,17 @@ const loadConfig = async function ({ configOpt, cwd, context, repositoryRoot, br
157
166
  headersPath: headersPathA,
158
167
  };
159
168
  };
160
- // Load configuration file and normalize it, merge contexts, etc.
161
- const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot, branch, defaultConfig, inlineConfig, baseRelDir, configBase, base, logs, featureFlags, }) {
162
- const configPath = await getConfigPath({ configOpt, cwd, repositoryRoot, configBase });
169
+ /**
170
+ * Load configuration file and normalize it, merge contexts, etc.
171
+ */
172
+ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot, packagePath, branch, defaultConfig, inlineConfig, baseRelDir, configBase, base, logs, featureFlags, }) {
173
+ const configPath = await getConfigPath({
174
+ configOpt,
175
+ cwd,
176
+ repositoryRoot,
177
+ packagePath,
178
+ configBase,
179
+ });
163
180
  try {
164
181
  const config = await parseConfig(configPath);
165
182
  const configA = mergeAndNormalizeConfig({
@@ -169,8 +186,9 @@ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot,
169
186
  context,
170
187
  branch,
171
188
  logs,
189
+ packagePath,
172
190
  });
173
- const { config: configB, buildDir, base: baseA, } = await resolveFiles({ config: configA, repositoryRoot, base, baseRelDir });
191
+ const { config: configB, buildDir, base: baseA, } = await resolveFiles({ packagePath, config: configA, repositoryRoot, base, baseRelDir });
174
192
  const headersPath = getHeadersPath(configB);
175
193
  const configC = await addHeaders({ config: configB, headersPath, logs, featureFlags });
176
194
  const redirectsPath = getRedirectsPath(configC);
@@ -183,33 +201,36 @@ const getFullConfig = async function ({ configOpt, cwd, context, repositoryRoot,
183
201
  throw error;
184
202
  }
185
203
  };
186
- // Merge:
187
- // - `--defaultConfig`: UI build settings and UI-installed plugins
188
- // - `inlineConfig`: Netlify CLI flags
189
- // Then merge context-specific configuration.
190
- // Before and after those steps, also performs validation and normalization.
191
- // Those need to be done at different stages depending on whether they should
192
- // happen before/after the merges mentioned above.
193
- const mergeAndNormalizeConfig = function ({ config, defaultConfig, inlineConfig, context, branch, logs }) {
204
+ /**
205
+ * Merge:
206
+ * - `--defaultConfig`: UI build settings and UI-installed plugins
207
+ * - `inlineConfig`: Netlify CLI flags
208
+ * Then merge context-specific configuration.
209
+ * Before and after those steps, also performs validation and normalization.
210
+ * Those need to be done at different stages depending on whether they should
211
+ * happen before/after the merges mentioned above.
212
+ */
213
+ const mergeAndNormalizeConfig = function ({ config, defaultConfig, inlineConfig, context, branch, logs, packagePath }) {
194
214
  const configA = normalizeConfigAndContext(config, CONFIG_ORIGIN);
195
215
  const defaultConfigA = normalizeConfigAndContext(defaultConfig, UI_ORIGIN);
196
216
  const inlineConfigA = normalizeConfigAndContext(inlineConfig, INLINE_ORIGIN);
197
217
  const configB = mergeConfigs([defaultConfigA, configA]);
198
218
  const configC = mergeContext({ config: configB, context, branch, logs });
199
219
  const configD = mergeConfigs([configC, inlineConfigA]);
200
- const configE = normalizeAfterConfigMerge(configD);
201
- return configE;
220
+ return normalizeAfterConfigMerge(configD, packagePath);
202
221
  };
203
222
  const normalizeConfigAndContext = function (config, origin) {
204
223
  const configA = normalizeBeforeConfigMerge(config, origin);
205
224
  const configB = normalizeContextProps({ config: configA, origin });
206
225
  return configB;
207
226
  };
208
- // Find base directory, build directory and resolve all paths to absolute paths
209
- const resolveFiles = async function ({ config, repositoryRoot, base, baseRelDir }) {
227
+ /**
228
+ * Find base directory, build directory and resolve all paths to absolute paths
229
+ */
230
+ const resolveFiles = async function ({ config, repositoryRoot, base, packagePath, baseRelDir, }) {
210
231
  const baseA = getBase(base, repositoryRoot, config);
211
232
  const buildDir = await getBuildDir(repositoryRoot, baseA);
212
- const configA = resolveConfigPaths({ config, repositoryRoot, buildDir, baseRelDir });
233
+ const configA = resolveConfigPaths({ config, packagePath, repositoryRoot, buildDir, baseRelDir });
213
234
  const configB = addBase(configA, baseA);
214
235
  return { config: configB, buildDir, base: baseA };
215
236
  };
@@ -3,10 +3,12 @@ import { normalizeConfig } from './normalize.js';
3
3
  import { addOrigins } from './origin.js';
4
4
  import { validateIdenticalPlugins } from './validate/identical.js';
5
5
  import { validatePreCaseNormalize, validatePreMergeConfig, validatePreNormalizeConfig, validatePostNormalizeConfig, } from './validate/main.js';
6
- // Perform validation and normalization logic to apply to all of:
7
- // - config, defaultConfig, inlineConfig
8
- // - context-specific configs
9
- // Therefore, this is performing before merging those together.
6
+ /**
7
+ * Perform validation and normalization logic to apply to all of:
8
+ * - config, defaultConfig, inlineConfig
9
+ * - context-specific configs
10
+ * Therefore, this is performing before merging those together.
11
+ */
10
12
  export const normalizeBeforeConfigMerge = function (config, origin) {
11
13
  validatePreCaseNormalize(config);
12
14
  const configA = normalizeConfigCase(config);
@@ -15,10 +17,12 @@ export const normalizeBeforeConfigMerge = function (config, origin) {
15
17
  validateIdenticalPlugins(configB);
16
18
  return configB;
17
19
  };
18
- // Validation and normalization logic performed after merging
19
- export const normalizeAfterConfigMerge = function (config) {
20
+ /**
21
+ * Validation and normalization logic performed after merging
22
+ */
23
+ export const normalizeAfterConfigMerge = function (config, packagePath) {
20
24
  validatePreNormalizeConfig(config);
21
- const configA = normalizeConfig(config);
25
+ const configA = normalizeConfig(config, packagePath);
22
26
  validatePostNormalizeConfig(configA);
23
27
  return configA;
24
28
  };
package/lib/normalize.js CHANGED
@@ -2,31 +2,35 @@ import { normalizeFunctionsProps, WILDCARD_ALL } from './functions_config.js';
2
2
  import { mergeConfigs } from './merge.js';
3
3
  import { DEFAULT_ORIGIN } from './origin.js';
4
4
  import { removeFalsy } from './utils/remove_falsy.js';
5
- // Normalize configuration object
6
- export const normalizeConfig = function (config) {
5
+ /**
6
+ * Normalize configuration object
7
+ */
8
+ export const normalizeConfig = function (config, packagePath) {
7
9
  const configA = removeEmpty(config);
8
- const { build, functions, plugins, ...configB } = mergeConfigs([DEFAULT_CONFIG, configA]);
10
+ const { build, functions, plugins, ...configB } = mergeConfigs([DEFAULT_CONFIG(packagePath), configA]);
9
11
  const { build: buildA, functions: functionsA, functionsDirectoryProps } = normalizeFunctionsProps(build, functions);
10
12
  const pluginsA = plugins.map(normalizePlugin);
11
13
  return { ...configB, build: buildA, functions: functionsA, plugins: pluginsA, ...functionsDirectoryProps };
12
14
  };
13
- // Remove empty strings.
14
- // This notably ensures that empty strings in the build command are removed.
15
- // Otherwise, those would be run during builds, making the build fail.
15
+ /**
16
+ * Remove empty strings.
17
+ * This notably ensures that empty strings in the build command are removed.
18
+ * Otherwise, those would be run during builds, making the build fail.
19
+ */
16
20
  const removeEmpty = function ({ build, ...config }) {
17
21
  return removeFalsy({ ...config, build: removeFalsy(build) });
18
22
  };
19
- const DEFAULT_CONFIG = {
23
+ const DEFAULT_CONFIG = (packagePath) => ({
20
24
  build: {
21
25
  environment: {},
22
- publish: '.',
26
+ publish: packagePath || '.',
23
27
  publishOrigin: DEFAULT_ORIGIN,
24
28
  processing: { css: {}, html: {}, images: {}, js: {} },
25
29
  services: {},
26
30
  },
27
31
  functions: { [WILDCARD_ALL]: {} },
28
32
  plugins: [],
29
- };
33
+ });
30
34
  const normalizePlugin = function ({ inputs = {}, ...plugin }) {
31
35
  return removeFalsy({ ...plugin, inputs });
32
36
  };
package/lib/path.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { existsSync } from 'fs';
2
- import { resolve } from 'path';
2
+ import { join, resolve } from 'path';
3
3
  import { findUp } from 'find-up';
4
4
  import pLocate from 'p-locate';
5
+ const FILENAME = 'netlify.toml';
5
6
  /**
6
7
  * Configuration location can be:
7
8
  * - a local path with the --config CLI flag
@@ -9,10 +10,10 @@ import pLocate from 'p-locate';
9
10
  * - a `netlify.*` file in the `repositoryRoot`
10
11
  * - a `netlify.*` file in the current directory or any parent
11
12
  */
12
- export const getConfigPath = async function ({ configOpt, cwd, repositoryRoot, configBase }) {
13
+ export const getConfigPath = async function ({ configOpt, cwd, repositoryRoot, configBase, packagePath, }) {
13
14
  const configPath = await pLocate([
14
15
  searchConfigOpt(cwd, configOpt),
15
- searchBaseConfigFile(configBase),
16
+ searchBaseConfigFile(repositoryRoot, configBase, packagePath),
16
17
  searchConfigFile(repositoryRoot),
17
18
  findUp(FILENAME, { cwd }),
18
19
  ], Boolean);
@@ -26,13 +27,13 @@ const searchConfigOpt = function (cwd, configOpt) {
26
27
  return resolve(cwd, configOpt);
27
28
  };
28
29
  /**
29
- * Look for `repositoryRoot/{base}/netlify.*`
30
+ * Look for `repositoryRoot/{base}/{packagePath || '}/netlify.*`
30
31
  */
31
- const searchBaseConfigFile = function (configBase) {
32
- if (configBase === undefined) {
32
+ const searchBaseConfigFile = function (repoRoot, base, packagePath) {
33
+ if (base === undefined && packagePath === undefined) {
33
34
  return;
34
35
  }
35
- return searchConfigFile(configBase);
36
+ return searchConfigFile(join(base ? base : repoRoot, packagePath || ''));
36
37
  };
37
38
  /**
38
39
  * Look for several file extensions for `netlify.*`
@@ -44,4 +45,3 @@ const searchConfigFile = function (cwd) {
44
45
  }
45
46
  return path;
46
47
  };
47
- const FILENAME = 'netlify.toml';
package/lib/redirects.js CHANGED
@@ -6,13 +6,14 @@ export const getRedirectsPath = function ({ build: { publish } }) {
6
6
  return resolve(publish, REDIRECTS_FILENAME);
7
7
  };
8
8
  const REDIRECTS_FILENAME = '_redirects';
9
- // Add `config.redirects`
10
- export const addRedirects = async function ({ config: { redirects: configRedirects, ...config }, redirectsPath, logs, featureFlags, }) {
9
+ /**
10
+ * Add `config.redirects`
11
+ */
12
+ export const addRedirects = async function ({ config: { redirects: configRedirects, ...config }, redirectsPath, logs, }) {
11
13
  const { redirects, errors } = await parseAllRedirects({
12
14
  redirectsFiles: [redirectsPath],
13
15
  configRedirects,
14
16
  minimal: true,
15
- featureFlags,
16
17
  });
17
18
  warnRedirectsParsing(logs, errors);
18
19
  return { ...config, redirects };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,14 +1,10 @@
1
1
  import { includeKeys } from 'filter-obj';
2
- // Remove falsy values from object
2
+ /**
3
+ * Remove falsy values from object
4
+ */
3
5
  export const removeFalsy = function (obj) {
4
- return includeKeys(obj, (key, value) => isTruthy(value));
5
- };
6
- export const removeUndefined = function (obj) {
7
- return includeKeys(obj, (key, value) => isDefined(value));
8
- };
9
- export const isTruthy = function (value) {
10
- return isDefined(value) && (typeof value !== 'string' || value.trim() !== '');
11
- };
12
- export const isDefined = function (value) {
13
- return value !== undefined && value !== null;
6
+ return includeKeys(obj, (_key, value) => isTruthy(value));
14
7
  };
8
+ export const removeUndefined = (obj) => includeKeys(obj, (_key, value) => isDefined(value));
9
+ export const isTruthy = (value) => isDefined(value) && (typeof value !== 'string' || value.trim() !== '');
10
+ export const isDefined = (value) => value !== undefined && value !== null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/config",
3
- "version": "20.6.4",
3
+ "version": "20.8.0-rc.0",
4
4
  "description": "Netlify config module",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -72,8 +72,8 @@
72
72
  "map-obj": "^5.0.0",
73
73
  "netlify": "^13.1.10",
74
74
  "netlify-headers-parser": "^7.1.2",
75
- "netlify-redirect-parser": "^14.1.3",
76
75
  "node-fetch": "^3.3.1",
76
+ "netlify-redirect-parser": "^14.1.3",
77
77
  "omit.js": "^2.0.2",
78
78
  "p-locate": "^6.0.0",
79
79
  "path-type": "^5.0.0",
@@ -93,6 +93,5 @@
93
93
  },
94
94
  "engines": {
95
95
  "node": "^14.16.0 || >=16.0.0"
96
- },
97
- "gitHead": "5e684d0509bb094d0e11ff89559abf7eb2d806fc"
96
+ }
98
97
  }