snowpack
Version:
The ESM-powered frontend build tool. Fast, lightweight, unbundled.
745 lines (744 loc) • 31.3 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadConfiguration = exports.createConfiguration = exports.validatePluginLoadResult = exports.expandCliFlags = exports.REMOTE_PACKAGE_ORIGIN = exports.DEFAULT_PACKAGES_LOCAL_CONFIG = void 0;
const crypto_1 = __importDefault(require("crypto"));
const deepmerge_1 = require("deepmerge");
const find_cache_dir_1 = __importDefault(require("find-cache-dir"));
const fs_1 = require("fs");
const is_plain_object_1 = require("is-plain-object");
const jsonschema_1 = require("jsonschema");
const colors_1 = require("kleur/colors");
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const logger_1 = require("./logger");
const plugin_esbuild_1 = require("./plugins/plugin-esbuild");
const util_1 = require("./util");
const util_2 = require("./sources/util");
const CONFIG_NAME = 'snowpack';
const ALWAYS_EXCLUDE = ['**/_*.{sass,scss}', '**.d.ts'];
const DEFAULT_PROJECT_CACHE_DIR = find_cache_dir_1.default({ name: 'snowpack' }) ||
// If `projectCacheDir()` is null, no node_modules directory exists.
// Use the current path (hashed) to create a cache entry in the global cache instead.
// Because this is specifically for dependencies, this fallback should rarely be used.
path_1.default.join(util_2.GLOBAL_CACHE_DIR, crypto_1.default.createHash('md5').update(process.cwd()).digest('hex'));
// default settings
const DEFAULT_ROOT = process.cwd();
const DEFAULT_CONFIG = {
root: DEFAULT_ROOT,
plugins: [],
alias: {},
env: {},
exclude: [],
routes: [],
dependencies: {},
devOptions: {
secure: false,
hostname: 'localhost',
port: 8080,
hmrDelay: 0,
hmrPort: undefined,
hmrErrorOverlay: true,
},
buildOptions: {
out: 'build',
baseUrl: '/',
metaUrlPath: '_snowpack',
cacheDirPath: DEFAULT_PROJECT_CACHE_DIR,
clean: true,
sourcemap: false,
watch: false,
htmlFragments: false,
ssr: false,
resolveProxyImports: true,
},
testOptions: {
files: ['__tests__/**/*', '**/*.@(spec|test).*'],
},
packageOptions: { source: 'local' },
};
exports.DEFAULT_PACKAGES_LOCAL_CONFIG = {
source: 'local',
external: [],
packageLookupFields: [],
knownEntrypoints: [],
};
exports.REMOTE_PACKAGE_ORIGIN = 'https://pkg.snowpack.dev';
const DEFAULT_PACKAGES_REMOTE_CONFIG = {
source: 'remote',
origin: exports.REMOTE_PACKAGE_ORIGIN,
external: [],
knownEntrypoints: [],
cache: '.snowpack',
types: false,
};
const configSchema = {
type: 'object',
properties: {
mode: { type: 'string', enum: ['test', 'development', 'production'] },
extends: { type: 'string' },
exclude: { type: 'array', items: { type: 'string' } },
plugins: { type: 'array' },
env: { type: 'object' },
alias: {
type: 'object',
additionalProperties: { type: 'string' },
},
mount: {
type: 'object',
additionalProperties: {
oneOf: [
{ type: 'string' },
{
type: ['object'],
properties: {
url: { type: 'string' },
static: { type: 'boolean' },
resolve: { type: 'boolean' },
dot: { type: 'boolean' },
},
},
],
},
},
devOptions: {
type: 'object',
properties: {
secure: {
oneOf: [
{ type: 'boolean' },
{
type: 'object',
properties: {
cert: {},
key: {},
},
},
],
},
port: { type: 'number' },
openUrl: { type: 'string' },
open: { type: 'string' },
output: { type: 'string', enum: ['stream', 'dashboard'] },
hmr: { type: 'boolean' },
hmrDelay: { type: 'number' },
hmrPort: { type: 'number' },
hmrErrorOverlay: { type: 'boolean' },
tailwindConfig: { type: 'string' },
},
},
packageOptions: {
type: 'object',
properties: {
dest: { type: 'string' },
external: { type: 'array', items: { type: 'string' } },
treeshake: { type: 'boolean' },
installTypes: { type: 'boolean' },
polyfillNode: { type: 'boolean' },
env: {
type: 'object',
additionalProperties: {
oneOf: [
{ id: 'EnvVarString', type: 'string' },
{ id: 'EnvVarNumber', type: 'number' },
{ id: 'EnvVarTrue', type: 'boolean', enum: [true] },
],
},
},
rollup: {
type: 'object',
properties: {
context: { type: 'string' },
plugins: { type: 'array', items: { type: 'object' } },
dedupe: {
type: 'array',
items: { type: 'string' },
},
},
},
},
},
buildOptions: {
type: ['object'],
properties: {
out: { type: 'string' },
baseUrl: { type: 'string' },
cacheDirPath: { type: 'string' },
clean: { type: 'boolean' },
sourcemap: {
oneOf: [{ type: 'boolean' }, { type: 'string', enum: ['inline'] }],
},
watch: { type: 'boolean' },
ssr: { type: 'boolean' },
htmlFragments: { type: 'boolean' },
jsxFactory: { type: 'string' },
jsxFragment: { type: 'string' },
jsxInject: { type: 'string' },
},
},
testOptions: {
type: 'object',
properties: {
files: { type: 'array', items: { type: 'string' } },
},
},
experiments: {
type: ['object'],
properties: {},
},
optimize: {
type: ['object'],
properties: {
preload: { type: 'boolean' },
bundle: { type: 'boolean' },
loader: { type: 'object' },
splitting: { type: 'boolean' },
treeshake: { type: 'boolean' },
manifest: { type: 'boolean' },
minify: { type: 'boolean' },
target: { type: 'string' },
},
},
proxy: {
type: 'object',
},
},
};
/**
* Convert CLI flags to an incomplete Snowpack config representation.
* We need to be careful about setting properties here if the flag value
* is undefined, since the deep merge strategy would then overwrite good
* defaults with 'undefined'.
*/
function expandCliFlags(flags) {
const result = {
packageOptions: {},
devOptions: {},
buildOptions: {},
experiments: {},
};
const { help, version, reload, config, ...relevantFlags } = flags;
const CLI_ONLY_FLAGS = ['quiet', 'verbose'];
for (const [flag, val] of Object.entries(relevantFlags)) {
if (flag === '_' || flag.includes('-')) {
continue;
}
if (configSchema.properties[flag]) {
result[flag] = val;
continue;
}
if (flag === 'source') {
result.packageOptions = { source: val };
continue;
}
if (configSchema.properties.experiments.properties[flag]) {
result.experiments[flag] = val;
continue;
}
if (configSchema.properties.optimize.properties[flag]) {
result.optimize = result.optimize || {};
result.optimize[flag] = val;
continue;
}
if (configSchema.properties.packageOptions.properties[flag]) {
result.packageOptions[flag] = val;
continue;
}
if (configSchema.properties.devOptions.properties[flag]) {
result.devOptions[flag] = val;
continue;
}
if (configSchema.properties.buildOptions.properties[flag]) {
result.buildOptions[flag] = val;
continue;
}
if (CLI_ONLY_FLAGS.includes(flag)) {
continue;
}
logger_1.logger.error(`Unknown CLI flag: "${flag}"`);
process.exit(1);
}
if (result.packageOptions.env) {
result.packageOptions.env = result.packageOptions.env.reduce((acc, id) => {
const index = id.indexOf('=');
const [key, val] = index > 0 ? [id.substr(0, index), id.substr(index + 1)] : [id, true];
acc[key] = val;
return acc;
}, {});
}
return result;
}
exports.expandCliFlags = expandCliFlags;
/** load and normalize plugins from config */
function loadPlugins(config) {
const plugins = [];
function execPluginFactory(pluginFactory, pluginOptions = {}) {
let plugin = null;
plugin = pluginFactory(config, pluginOptions);
return plugin;
}
function loadPluginFromConfig(pluginLoc, options, config) {
if (!path_1.default.isAbsolute(pluginLoc)) {
throw new Error(`Snowpack Internal Error: plugin ${pluginLoc} should have been resolved to an absolute path.`);
}
const pluginRef = util_1.NATIVE_REQUIRE(pluginLoc, { paths: [config.root] });
let plugin;
try {
plugin = typeof pluginRef.default === 'function' ? pluginRef.default : pluginRef;
if (typeof plugin !== 'function')
logger_1.logger.error(`plugin ${pluginLoc} must export a function.`);
plugin = execPluginFactory(plugin, options);
}
catch (err) {
logger_1.logger.error(err.toString());
throw err;
}
if (!plugin.name) {
plugin.name = path_1.default.relative(process.cwd(), pluginLoc);
}
// Add any internal plugin methods. Placeholders are okay when individual
// commands implement these differently.
plugin.markChanged = (file) => {
logger_1.logger.debug(`clearCache(${file}) called, but function not yet hooked up.`, {
name: plugin.name,
});
};
// Finish up.
validatePlugin(plugin);
return plugin;
}
// 2. config.plugins
config.plugins.forEach((ref) => {
const pluginName = Array.isArray(ref) ? ref[0] : ref;
const pluginOptions = Array.isArray(ref) ? ref[1] : {};
const plugin = loadPluginFromConfig(pluginName, pluginOptions, config);
logger_1.logger.debug(`loaded plugin: ${pluginName}`);
plugins.push(plugin);
});
// add internal JS handler plugin
plugins.push(execPluginFactory(plugin_esbuild_1.esbuildPlugin, { input: ['.mjs', '.jsx', '.ts', '.tsx'] }));
const extensionMap = plugins.reduce((map, { resolve }) => {
if (resolve) {
for (const inputExt of resolve.input) {
map[inputExt] = resolve.output;
}
}
return map;
}, {});
return {
plugins,
extensionMap,
};
}
function normalizeMount(config) {
var _a, _b, _c;
const mountedDirs = config.mount || {};
const normalizedMount = {};
for (const [mountDir, rawMountEntry] of Object.entries(mountedDirs)) {
const mountEntry = typeof rawMountEntry === 'string'
? { url: rawMountEntry, static: false, resolve: true, dot: false }
: rawMountEntry;
if (!mountEntry.url) {
handleConfigError(`mount[${mountDir}]: Object "${mountEntry.url}" missing required "url" option.`);
return normalizedMount;
}
if (mountEntry.url[0] !== '/') {
handleConfigError(`mount[${mountDir}]: Value "${mountEntry.url}" must be a URL path, and start with a "/"`);
}
normalizedMount[util_1.removeTrailingSlash(mountDir)] = {
url: mountEntry.url === '/' ? '/' : util_1.removeTrailingSlash(mountEntry.url),
static: (_a = mountEntry.static) !== null && _a !== void 0 ? _a : false,
resolve: (_b = mountEntry.resolve) !== null && _b !== void 0 ? _b : true,
dot: (_c = mountEntry.dot) !== null && _c !== void 0 ? _c : false,
};
}
// if no mounted directories, mount the root directory to the base URL
if (!Object.keys(normalizedMount).length) {
normalizedMount[config.root] = {
url: '/',
static: false,
resolve: true,
dot: false,
};
}
return normalizedMount;
}
function normalizeRoutes(routes) {
return routes.map(({ src, dest, upgrade, match }, i) => {
// Normalize
if (typeof dest === 'string') {
dest = util_1.addLeadingSlash(dest);
}
if (!src.startsWith('^')) {
src = '^' + src;
}
if (!src.endsWith('$')) {
src = src + '$';
}
// Validate
try {
return { src, dest, upgrade, match: match || 'all', _srcRegex: new RegExp(src) };
}
catch (err) {
throw new Error(`config.routes[${i}].src: invalid regular expression syntax "${src}"`);
}
});
}
/** resolve --dest relative to cwd, etc. */
function normalizeConfig(_config) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
// TODO: This function is really fighting with TypeScript. Now that we have an accurate
// SnowpackUserConfig type, we can have this function construct a fresh config object
// from scratch instead of trying to modify the user's config object in-place.
let config = _config;
config.mode = config.mode || process.env.NODE_ENV;
if (config.packageOptions.source === 'local') {
config.packageOptions.rollup = config.packageOptions.rollup || {};
config.packageOptions.rollup.plugins = config.packageOptions.rollup.plugins || [];
}
// normalize config URL/path values
config.buildOptions.out = util_1.removeTrailingSlash(config.buildOptions.out);
config.buildOptions.baseUrl = util_1.addTrailingSlash(config.buildOptions.baseUrl);
config.buildOptions.metaUrlPath = util_1.removeTrailingSlash(util_1.addLeadingSlash(config.buildOptions.metaUrlPath));
config.mount = normalizeMount(config);
config.routes = normalizeRoutes(config.routes);
config.exclude = Array.from(new Set([
...ALWAYS_EXCLUDE,
// Always ignore the final build directory.
`${config.buildOptions.out}/**`,
// We want to ignore all node_modules directories.
`**/node_modules/**`,
// If a node_modules directory is explicity mounted, it should be treated as source.
// In that case, we add the mounted directory to "picomatch.ignore" elsewhere
...config.exclude,
]));
if (config.optimize && JSON.stringify(config.optimize) !== '{}') {
config.optimize = {
entrypoints: (_a = config.optimize.entrypoints) !== null && _a !== void 0 ? _a : 'auto',
preload: (_b = config.optimize.preload) !== null && _b !== void 0 ? _b : false,
bundle: (_c = config.optimize.bundle) !== null && _c !== void 0 ? _c : false,
loader: config.optimize.loader,
sourcemap: (_d = config.optimize.sourcemap) !== null && _d !== void 0 ? _d : true,
splitting: (_e = config.optimize.splitting) !== null && _e !== void 0 ? _e : false,
treeshake: (_f = config.optimize.treeshake) !== null && _f !== void 0 ? _f : true,
manifest: (_g = config.optimize.manifest) !== null && _g !== void 0 ? _g : false,
target: (_h = config.optimize.target) !== null && _h !== void 0 ? _h : 'es2020',
minify: (_j = config.optimize.minify) !== null && _j !== void 0 ? _j : false,
};
}
else {
config.optimize = undefined;
}
// new pipeline
const { plugins, extensionMap } = loadPlugins(config);
config.plugins = plugins;
config._extensionMap = extensionMap;
// If any plugins defined knownEntrypoints, add them here
for (const { knownEntrypoints } of config.plugins) {
if (knownEntrypoints) {
config.packageOptions.knownEntrypoints =
config.packageOptions.knownEntrypoints.concat(knownEntrypoints);
}
}
plugins.forEach((plugin) => {
if (plugin.config) {
plugin.config(config);
}
});
return config;
}
function handleConfigError(msg) {
logger_1.logger.error(msg);
throw new Error(msg);
}
function handleValidationErrors(filepath, err) {
const msg = `! ${filepath}\n${err.message}`;
logger_1.logger.error(msg);
logger_1.logger.info(colors_1.dim(`See https://www.snowpack.dev for more info.`));
throw new Error(msg);
}
function handleDeprecatedConfigError(mainMsg, ...msgs) {
const msg = `${mainMsg}\n${msgs.join('\n')}See https://www.snowpack.dev for more info.`;
logger_1.logger.error(msg);
throw new Error(msg);
}
function valdiateDeprecatedConfig(rawConfig) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
if (rawConfig.scripts) {
handleDeprecatedConfigError('[v3.0] Legacy "scripts" config is deprecated in favor of "plugins". Safe to remove if empty.');
}
if (rawConfig.proxy) {
handleDeprecatedConfigError('[v3.0] Legacy "proxy" config is deprecated in favor of "routes". Safe to remove if empty.');
}
if ((_a = rawConfig.buildOptions) === null || _a === void 0 ? void 0 : _a.metaDir) {
handleDeprecatedConfigError('[v3.0] "config.buildOptions.metaDir" is now "config.buildOptions.metaUrlPath".');
}
if ((_b = rawConfig.buildOptions) === null || _b === void 0 ? void 0 : _b.webModulesUrl) {
handleDeprecatedConfigError('[v3.0] "config.buildOptions.webModulesUrl" is now always set within the "config.buildOptions.metaUrlPath" directory.');
}
if ((_c = rawConfig.buildOptions) === null || _c === void 0 ? void 0 : _c.sourceMaps) {
handleDeprecatedConfigError('[v3.0] "config.buildOptions.sourceMaps" is now "config.buildOptions.sourcemap".');
}
if (rawConfig.installOptions) {
handleDeprecatedConfigError('[v3.0] "config.installOptions" is now "config.packageOptions". Safe to remove if empty.');
}
if ((_d = rawConfig.packageOptions) === null || _d === void 0 ? void 0 : _d.externalPackage) {
handleDeprecatedConfigError('[v3.0] "config.installOptions.externalPackage" is now "config.packageOptions.external".');
}
if ((_e = rawConfig.packageOptions) === null || _e === void 0 ? void 0 : _e.treeshake) {
handleDeprecatedConfigError('[v3.0] "config.installOptions.treeshake" is now "config.optimize.treeshake".');
}
if (rawConfig.install) {
handleDeprecatedConfigError('[v3.0] "config.install" is now "config.packageOptions.knownEntrypoints". Safe to remove if empty.');
}
if ((_f = rawConfig.experiments) === null || _f === void 0 ? void 0 : _f.source) {
handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.source" is now "config.packageOptions.source".');
}
if (((_g = rawConfig.packageOptions) === null || _g === void 0 ? void 0 : _g.source) === 'skypack') {
handleDeprecatedConfigError('[v3.0] Renamed! "config.experiments.source=skypack" is now "config.packageOptions.source=remote".');
}
if ((_h = rawConfig.experiments) === null || _h === void 0 ? void 0 : _h.ssr) {
handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.ssr" is now "config.buildOptions.ssr".');
}
if ((_j = rawConfig.experiments) === null || _j === void 0 ? void 0 : _j.optimize) {
handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.optimize" is now "config.optimize".');
}
if ((_k = rawConfig.experiments) === null || _k === void 0 ? void 0 : _k.routes) {
handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.routes" is now "config.routes".');
}
if ((_l = rawConfig.devOptions) === null || _l === void 0 ? void 0 : _l.fallback) {
handleDeprecatedConfigError('[v3.0] Deprecated! "devOptions.fallback" is now replaced by "routes".\n' +
'More info: https://www.snowpack.dev/guides/routing');
}
}
function validatePlugin(plugin) {
const pluginName = plugin.name;
if (plugin.resolve && !plugin.load) {
handleConfigError(`[${pluginName}] "resolve" config found but "load()" method missing.`);
}
if (!plugin.resolve && plugin.load) {
handleConfigError(`[${pluginName}] "load" method found but "resolve()" config missing.`);
}
if (plugin.resolve && !Array.isArray(plugin.resolve.input)) {
handleConfigError(`[${pluginName}] "resolve.input" should be an array of input file extensions.`);
}
if (plugin.resolve && !Array.isArray(plugin.resolve.output)) {
handleConfigError(`[${pluginName}] "resolve.output" should be an array of output file extensions.`);
}
}
function validatePluginLoadResult(plugin, result) {
const pluginName = plugin.name;
if (!result) {
return;
}
const isValidSingleResultType = typeof result === 'string' || Buffer.isBuffer(result);
if (isValidSingleResultType && plugin.resolve.output.length !== 1) {
handleConfigError(`[plugin=${pluginName}] "load()" returned a string, but "resolve.output" contains multiple possible outputs. If multiple outputs are expected, the object return format is required.`);
}
const unexpectedOutput = typeof result === 'object' &&
Object.keys(result).find((fileExt) => !plugin.resolve.output.includes(fileExt));
if (unexpectedOutput) {
handleConfigError(`[plugin=${pluginName}] "load()" returned entry "${unexpectedOutput}" not found in "resolve.output": ${plugin.resolve.output}`);
}
}
exports.validatePluginLoadResult = validatePluginLoadResult;
/**
* Get the config base path, that all relative config values should resolve to. In order:
* - The directory of the config file path, if it exists.
* - The config.root value, if given.
* - Otherwise, the current working directory of the process.
*/
function getConfigBasePath(configFileLoc, configRoot) {
return ((configFileLoc && path_1.default.dirname(configFileLoc)) ||
(configRoot && path_1.default.resolve(process.cwd(), configRoot)) ||
process.cwd());
}
function resolveRelativeConfigAlias(aliasConfig, configBase) {
const cleanAliasConfig = {};
for (const [target, replacement] of Object.entries(aliasConfig)) {
const isDirectory = target.endsWith('/');
if (util_1.isPathImport(replacement)) {
cleanAliasConfig[target] = isDirectory
? util_1.addTrailingSlash(path_1.default.resolve(configBase, replacement))
: util_1.removeTrailingSlash(path_1.default.resolve(configBase, replacement));
}
else {
cleanAliasConfig[target] = replacement;
}
}
return cleanAliasConfig;
}
function resolveRelativeConfigMount(mountConfig, configBase) {
const cleanMountConfig = {};
for (const [target, replacement] of Object.entries(mountConfig)) {
cleanMountConfig[path_1.default.resolve(configBase, target)] = replacement;
}
return cleanMountConfig;
}
function resolveRelativeConfig(config, configBase) {
var _a, _b;
if (config.root) {
config.root = path_1.default.resolve(configBase, config.root);
}
if (config.workspaceRoot) {
config.workspaceRoot = path_1.default.resolve(configBase, config.workspaceRoot);
}
if ((_a = config.buildOptions) === null || _a === void 0 ? void 0 : _a.out) {
config.buildOptions.out = path_1.default.resolve(configBase, config.buildOptions.out);
}
if (((_b = config.packageOptions) === null || _b === void 0 ? void 0 : _b.source) === 'remote' && config.packageOptions.cache) {
config.packageOptions.cache = path_1.default.resolve(configBase, config.packageOptions.cache);
}
if (config.extends && /^[\.\/\\]/.test(config.extends)) {
config.extends = path_1.default.resolve(configBase, config.extends);
}
if (config.plugins) {
config.plugins = config.plugins.map((plugin) => {
const name = Array.isArray(plugin) ? plugin[0] : plugin;
const absName = path_1.default.isAbsolute(name) ? name : require.resolve(name, { paths: [configBase] });
if (Array.isArray(plugin)) {
plugin.splice(0, 1, absName);
return plugin;
}
return absName;
});
}
if (config.mount) {
config.mount = resolveRelativeConfigMount(config.mount, configBase);
}
if (config.alias) {
config.alias = resolveRelativeConfigAlias(config.alias, configBase);
}
return config;
}
class ConfigValidationError extends Error {
constructor(errors) {
super(`Configuration Error:\n${errors.map((err) => ` - ${err.toString()}`).join(os_1.default.EOL)}`);
}
}
function validateConfig(config) {
for (const mountDir of Object.keys(config.mount)) {
if (!fs_1.existsSync(mountDir)) {
logger_1.logger.warn(`config.mount[${mountDir}]: mounted directory does not exist.`);
}
}
}
function createConfiguration(config = {}) {
var _a;
// Validate the configuration object against our schema. Report any errors.
const { errors: validationErrors } = jsonschema_1.validate(config, configSchema, {
propertyName: CONFIG_NAME,
allowUnknownAttributes: false,
});
if (validationErrors.length > 0) {
throw new ConfigValidationError(validationErrors);
}
// Inherit any undefined values from the default configuration. If no config argument
// was passed (or no configuration file found in loadConfiguration) then this function
// will effectively return a copy of the DEFAULT_CONFIG object.
const mergedConfig = deepmerge_1.all([
DEFAULT_CONFIG,
{
packageOptions: ((_a = config.packageOptions) === null || _a === void 0 ? void 0 : _a.source) === 'remote'
? DEFAULT_PACKAGES_REMOTE_CONFIG
: exports.DEFAULT_PACKAGES_LOCAL_CONFIG,
},
config,
], {
isMergeableObject: (val) => is_plain_object_1.isPlainObject(val) || Array.isArray(val),
});
// Resolve relative config values. If using loadConfiguration, all config values should
// already be resolved relative to the config file path so that this should be a no-op.
// But, we still need to run it in case you called this function directly.
const configBase = getConfigBasePath(undefined, config.root);
resolveRelativeConfig(mergedConfig, configBase);
const normalizedConfig = normalizeConfig(mergedConfig);
validateConfig(normalizedConfig);
return normalizedConfig;
}
exports.createConfiguration = createConfiguration;
async function loadConfigurationFile(filename, overrides = {}) {
const loc = path_1.default.resolve(overrides.root || process.cwd(), filename);
if (!fs_1.existsSync(loc))
return null;
const config = await util_1.REQUIRE_OR_IMPORT(loc);
return { filepath: loc, config };
}
async function loadConfiguration(overrides = {}, configPath) {
let result = null;
// if user specified --config path, load that
if (configPath) {
result = await loadConfigurationFile(configPath, overrides);
if (!result) {
throw new Error(`Snowpack config file could not be found: ${configPath}`);
}
}
const configs = [
'snowpack.config.mjs',
'snowpack.config.cjs',
'snowpack.config.js',
'snowpack.config.json',
];
// If no config was found above, search for one.
if (!result) {
for (const potentialConfigurationFile of configs) {
if (result)
break;
result = await loadConfigurationFile(potentialConfigurationFile, overrides);
}
}
// Support package.json "snowpack" config
if (!result) {
const potentialPackageJsonConfig = await loadConfigurationFile('package.json', overrides);
if (potentialPackageJsonConfig && potentialPackageJsonConfig.config.snowpack) {
result = {
filepath: potentialPackageJsonConfig.filepath,
config: potentialPackageJsonConfig.config.snowpack,
};
}
}
if (!result) {
logger_1.logger.warn('Hint: run "snowpack init" to create a project config file. Using defaults...');
result = { filepath: undefined, config: {} };
}
const { config, filepath } = result;
const configBase = getConfigBasePath(filepath, config.root);
valdiateDeprecatedConfig(config);
valdiateDeprecatedConfig(overrides);
resolveRelativeConfig(config, configBase);
let extendConfig = {};
if (config.extends) {
const extendConfigLoc = require.resolve(config.extends, { paths: [configBase] });
const extendResult = await loadConfigurationFile(extendConfigLoc, {});
if (!extendResult) {
handleConfigError(`Could not locate "extends" config at ${extendConfigLoc}`);
process.exit(1);
}
extendConfig = extendResult.config;
const extendValidation = jsonschema_1.validate(extendConfig, configSchema, {
allowUnknownAttributes: false,
propertyName: CONFIG_NAME,
});
if (extendValidation.errors && extendValidation.errors.length > 0) {
handleValidationErrors(extendConfigLoc, new ConfigValidationError(extendValidation.errors));
}
valdiateDeprecatedConfig(extendConfig);
resolveRelativeConfig(extendConfig, configBase);
}
// if valid, apply config over defaults
const mergedConfig = deepmerge_1.all([extendConfig, config, overrides], {
isMergeableObject: (val) => is_plain_object_1.isPlainObject(val) || Array.isArray(val),
});
try {
return createConfiguration(mergedConfig);
}
catch (err) {
if (err instanceof ConfigValidationError) {
handleValidationErrors(filepath, err);
}
throw err;
}
}
exports.loadConfiguration = loadConfiguration;
;