@netlify/config
Version:
Netlify config module
105 lines (104 loc) • 4.29 kB
JavaScript
import { existsSync } from 'fs';
import { resolve, relative, parse, join } from 'path';
import { getProperty, setProperty, deleteProperty } from 'dot-prop';
import { throwUserError } from './error.js';
import { mergeConfigs } from './merge.js';
import { isTruthy } from './utils/remove_falsy.js';
/**
* We allow paths in configuration file to start with /
* In that case, those are actually relative paths not absolute.
*/
const LEADING_SLASH_REGEXP = /^\/+/;
/**
* All file paths in the configuration file are relative to `buildDir`
* (if `baseRelDir` is `true`).
*/
const FILE_PATH_CONFIG_PROPS = [
'functionsDirectory',
'functions.*.deno_import_map',
'build.publish',
'build.edge_functions',
];
/**
* Make configuration paths relative to `buildDir` and converts them to
* absolute paths
*/
export const resolveConfigPaths = function (options) {
const baseRel = options.baseRelDir ? options.buildDir : options.repositoryRoot;
const config = resolvePaths({
config: options.config,
propNames: FILE_PATH_CONFIG_PROPS,
baseRel,
repositoryRoot: options.repositoryRoot,
});
return addDefaultPaths({
config,
repositoryRoot: options.repositoryRoot,
baseRel,
packagePath: options.packagePath,
});
};
const resolvePaths = function ({ config, propNames, baseRel, repositoryRoot, }) {
return propNames.reduce((configA, propName) => resolvePathProp(configA, propName, baseRel, repositoryRoot), config);
};
const resolvePathProp = function (config, propName, baseRel, repositoryRoot) {
const path = getProperty(config, propName);
if (!isTruthy(path)) {
deleteProperty(config, propName);
return config;
}
return setProperty(config, propName, resolvePath(repositoryRoot, baseRel, path, propName));
};
export const resolvePath = (repositoryRoot, baseRel, originalPath, propName) => {
if (!isTruthy(originalPath)) {
return;
}
const path = originalPath.replace(LEADING_SLASH_REGEXP, '');
const pathA = resolve(baseRel, path);
validateInsideRoot(originalPath, pathA, repositoryRoot, propName);
return pathA;
};
/**
* We ensure all file paths are within the repository root directory.
* However, we allow file paths to be outside the build directory, since this
* can be convenient in monorepo setups.
*/
const validateInsideRoot = (originalPath, path, repositoryRoot, propName) => {
if (relative(repositoryRoot, path).startsWith('..') || getWindowsDrive(repositoryRoot) !== getWindowsDrive(path)) {
throwUserError(`Configuration property "${propName}" "${originalPath}" must be inside the repository root directory.`);
}
};
const getWindowsDrive = (path) => parse(path).root;
/**
* Some configuration properties have default values that are only set if a
* specific directory/file exists in the build directory
*/
const addDefaultPaths = ({ config, repositoryRoot, baseRel, packagePath, }) => {
const defaultPathsConfigs = DEFAULT_PATHS.map(({ defaultPath, getConfig, propName }) => addDefaultPath({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName })).filter(Boolean);
return mergeConfigs([...defaultPathsConfigs, config]);
};
const DEFAULT_PATHS = [
// @todo Remove once we drop support for the legacy default functions directory.
{
getConfig: (directory) => ({ functionsDirectory: directory, functionsDirectoryOrigin: 'default-v1' }),
defaultPath: 'netlify-automatic-functions',
propName: 'functions.directory',
},
{
getConfig: (directory) => ({ functionsDirectory: directory, functionsDirectoryOrigin: 'default' }),
defaultPath: 'netlify/functions',
propName: 'functions.directory',
},
{
getConfig: (directory) => ({ build: { edge_functions: directory } }),
defaultPath: 'netlify/edge-functions',
propName: 'build.edge_functions',
},
];
const addDefaultPath = ({ repositoryRoot, packagePath, baseRel, defaultPath, getConfig, propName, }) => {
const absolutePath = resolvePath(repositoryRoot, join(baseRel, packagePath || ''), defaultPath, propName);
if (!absolutePath || !existsSync(absolutePath)) {
return;
}
return getConfig(absolutePath);
};