UNPKG

28.5 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.loadConfiguration = exports.createConfiguration = exports.validatePluginLoadResult = exports.expandCliFlags = exports.DEFAULT_PACKAGES_LOCAL_CONFIG = void 0;
7const deepmerge_1 = require("deepmerge");
8const fs_1 = require("fs");
9const is_plain_object_1 = require("is-plain-object");
10const jsonschema_1 = require("jsonschema");
11const colors_1 = require("kleur/colors");
12const os_1 = __importDefault(require("os"));
13const path_1 = __importDefault(require("path"));
14const logger_1 = require("./logger");
15const plugin_esbuild_1 = require("./plugins/plugin-esbuild");
16const util_1 = require("./util");
17const CONFIG_NAME = 'snowpack';
18const ALWAYS_EXCLUDE = ['**/node_modules/**/*'];
19// default settings
20const DEFAULT_ROOT = process.cwd();
21const DEFAULT_CONFIG = {
22 root: DEFAULT_ROOT,
23 plugins: [],
24 alias: {},
25 exclude: [],
26 routes: [],
27 devOptions: {
28 secure: false,
29 hostname: 'localhost',
30 port: 8080,
31 open: 'default',
32 output: 'dashboard',
33 hmrDelay: 0,
34 hmrPort: undefined,
35 hmrErrorOverlay: true,
36 },
37 buildOptions: {
38 out: 'build',
39 baseUrl: '/',
40 metaUrlPath: '_snowpack',
41 clean: true,
42 sourcemap: false,
43 watch: false,
44 htmlFragments: false,
45 ssr: false,
46 },
47 testOptions: {
48 files: ['__tests__/**/*', '**/*.@(spec|test).*'],
49 },
50 packageOptions: { source: 'local' },
51};
52exports.DEFAULT_PACKAGES_LOCAL_CONFIG = {
53 source: 'local',
54 external: [],
55 packageLookupFields: [],
56 knownEntrypoints: [],
57};
58const REMOTE_PACKAGE_ORIGIN = 'https://pkg.snowpack.dev';
59const DEFAULT_PACKAGES_REMOTE_CONFIG = {
60 source: 'remote',
61 origin: REMOTE_PACKAGE_ORIGIN,
62 external: [],
63 cache: '.snowpack',
64 types: false,
65};
66const configSchema = {
67 type: 'object',
68 properties: {
69 extends: { type: 'string' },
70 exclude: { type: 'array', items: { type: 'string' } },
71 plugins: { type: 'array' },
72 alias: {
73 type: 'object',
74 additionalProperties: { type: 'string' },
75 },
76 mount: {
77 type: 'object',
78 additionalProperties: {
79 oneOf: [
80 { type: 'string' },
81 {
82 type: ['object'],
83 properties: {
84 url: { type: 'string' },
85 static: { type: 'boolean' },
86 resolve: { type: 'boolean' },
87 },
88 },
89 ],
90 },
91 },
92 devOptions: {
93 type: 'object',
94 properties: {
95 secure: { type: 'boolean' },
96 port: { type: 'number' },
97 bundle: { type: 'boolean' },
98 open: { type: 'string' },
99 output: { type: 'string', enum: ['stream', 'dashboard'] },
100 hmr: { type: 'boolean' },
101 hmrDelay: { type: 'number' },
102 hmrPort: { type: 'number' },
103 hmrErrorOverlay: { type: 'boolean' },
104 },
105 },
106 packageOptions: {
107 type: 'object',
108 properties: {
109 dest: { type: 'string' },
110 external: { type: 'array', items: { type: 'string' } },
111 treeshake: { type: 'boolean' },
112 installTypes: { type: 'boolean' },
113 polyfillNode: { type: 'boolean' },
114 env: {
115 type: 'object',
116 additionalProperties: {
117 oneOf: [
118 { id: 'EnvVarString', type: 'string' },
119 { id: 'EnvVarNumber', type: 'number' },
120 { id: 'EnvVarTrue', type: 'boolean', enum: [true] },
121 ],
122 },
123 },
124 rollup: {
125 type: 'object',
126 properties: {
127 context: { type: 'string' },
128 plugins: { type: 'array', items: { type: 'object' } },
129 dedupe: {
130 type: 'array',
131 items: { type: 'string' },
132 },
133 },
134 },
135 },
136 },
137 buildOptions: {
138 type: ['object'],
139 properties: {
140 out: { type: 'string' },
141 baseUrl: { type: 'string' },
142 clean: { type: 'boolean' },
143 sourcemap: { type: 'boolean' },
144 watch: { type: 'boolean' },
145 ssr: { type: 'boolean' },
146 htmlFragments: { type: 'boolean' },
147 jsxFactory: { type: 'string' },
148 jsxFragment: { type: 'string' },
149 },
150 },
151 testOptions: {
152 type: 'object',
153 properties: {
154 files: { type: 'array', items: { type: 'string' } },
155 },
156 },
157 experiments: {
158 type: ['object'],
159 properties: {},
160 },
161 optimize: {
162 type: ['object'],
163 properties: {},
164 },
165 proxy: {
166 type: 'object',
167 },
168 },
169};
170/**
171 * Convert CLI flags to an incomplete Snowpack config representation.
172 * We need to be careful about setting properties here if the flag value
173 * is undefined, since the deep merge strategy would then overwrite good
174 * defaults with 'undefined'.
175 */
176function expandCliFlags(flags) {
177 const result = {
178 packageOptions: {},
179 devOptions: {},
180 buildOptions: {},
181 experiments: {},
182 };
183 const { help, version, reload, config, ...relevantFlags } = flags;
184 const CLI_ONLY_FLAGS = ['quiet', 'verbose'];
185 for (const [flag, val] of Object.entries(relevantFlags)) {
186 if (flag === '_' || flag.includes('-')) {
187 continue;
188 }
189 if (configSchema.properties[flag]) {
190 result[flag] = val;
191 continue;
192 }
193 if (flag === 'source') {
194 result.packageOptions = { source: val };
195 continue;
196 }
197 if (configSchema.properties.experiments.properties[flag]) {
198 result.experiments[flag] = val;
199 continue;
200 }
201 if (configSchema.properties.optimize.properties[flag]) {
202 result.optimize = result.optimize || {};
203 result.optimize[flag] = val;
204 continue;
205 }
206 if (configSchema.properties.packageOptions.properties[flag]) {
207 result.packageOptions[flag] = val;
208 continue;
209 }
210 if (configSchema.properties.devOptions.properties[flag]) {
211 result.devOptions[flag] = val;
212 continue;
213 }
214 if (configSchema.properties.buildOptions.properties[flag]) {
215 result.buildOptions[flag] = val;
216 continue;
217 }
218 if (CLI_ONLY_FLAGS.includes(flag)) {
219 continue;
220 }
221 logger_1.logger.error(`Unknown CLI flag: "${flag}"`);
222 process.exit(1);
223 }
224 if (result.packageOptions.env) {
225 result.packageOptions.env = result.packageOptions.env.reduce((acc, id) => {
226 const index = id.indexOf('=');
227 const [key, val] = index > 0 ? [id.substr(0, index), id.substr(index + 1)] : [id, true];
228 acc[key] = val;
229 return acc;
230 }, {});
231 }
232 return result;
233}
234exports.expandCliFlags = expandCliFlags;
235/** load and normalize plugins from config */
236function loadPlugins(config) {
237 const plugins = [];
238 function execPluginFactory(pluginFactory, pluginOptions = {}) {
239 let plugin = null;
240 plugin = pluginFactory(config, pluginOptions);
241 return plugin;
242 }
243 function loadPluginFromConfig(pluginLoc, options) {
244 if (!path_1.default.isAbsolute(pluginLoc)) {
245 throw new Error(`Snowpack Internal Error: plugin ${pluginLoc} should have been resolved to an absolute path.`);
246 }
247 const pluginRef = util_1.NATIVE_REQUIRE(pluginLoc);
248 let plugin;
249 try {
250 plugin = typeof pluginRef.default === 'function' ? pluginRef.default : pluginRef;
251 if (typeof plugin !== 'function')
252 logger_1.logger.error(`plugin ${pluginLoc} must export a function.`);
253 plugin = execPluginFactory(plugin, options);
254 }
255 catch (err) {
256 logger_1.logger.error(err.toString());
257 throw err;
258 }
259 if (!plugin.name) {
260 plugin.name = path_1.default.relative(process.cwd(), pluginLoc);
261 }
262 // Add any internal plugin methods. Placeholders are okay when individual
263 // commands implement these differently.
264 plugin.markChanged = (file) => {
265 logger_1.logger.debug(`clearCache(${file}) called, but function not yet hooked up.`, {
266 name: plugin.name,
267 });
268 };
269 // Finish up.
270 validatePlugin(plugin);
271 return plugin;
272 }
273 // 2. config.plugins
274 config.plugins.forEach((ref) => {
275 const pluginName = Array.isArray(ref) ? ref[0] : ref;
276 const pluginOptions = Array.isArray(ref) ? ref[1] : {};
277 const plugin = loadPluginFromConfig(pluginName, pluginOptions);
278 logger_1.logger.debug(`loaded plugin: ${pluginName}`);
279 plugins.push(plugin);
280 });
281 // add internal JS handler plugin
282 plugins.push(execPluginFactory(plugin_esbuild_1.esbuildPlugin, { input: ['.mjs', '.jsx', '.ts', '.tsx'] }));
283 const extensionMap = plugins.reduce((map, { resolve }) => {
284 if (resolve) {
285 for (const inputExt of resolve.input) {
286 map[inputExt] = resolve.output;
287 }
288 }
289 return map;
290 }, {});
291 return {
292 plugins,
293 extensionMap,
294 };
295}
296function normalizeMount(config) {
297 var _a, _b;
298 const mountedDirs = config.mount || {};
299 const normalizedMount = {};
300 for (const [mountDir, rawMountEntry] of Object.entries(mountedDirs)) {
301 const mountEntry = typeof rawMountEntry === 'string'
302 ? { url: rawMountEntry, static: false, resolve: true }
303 : rawMountEntry;
304 if (!mountEntry.url) {
305 handleConfigError(`mount[${mountDir}]: Object "${mountEntry.url}" missing required "url" option.`);
306 return normalizedMount;
307 }
308 if (mountEntry.url[0] !== '/') {
309 handleConfigError(`mount[${mountDir}]: Value "${mountEntry.url}" must be a URL path, and start with a "/"`);
310 }
311 normalizedMount[util_1.removeTrailingSlash(mountDir)] = {
312 url: mountEntry.url === '/' ? '/' : util_1.removeTrailingSlash(mountEntry.url),
313 static: (_a = mountEntry.static) !== null && _a !== void 0 ? _a : false,
314 resolve: (_b = mountEntry.resolve) !== null && _b !== void 0 ? _b : true,
315 };
316 }
317 // if no mounted directories, mount the root directory to the base URL
318 if (!Object.keys(normalizedMount).length) {
319 normalizedMount[process.cwd()] = {
320 url: '/',
321 static: false,
322 resolve: true,
323 };
324 }
325 return normalizedMount;
326}
327function normalizeRoutes(routes) {
328 return routes.map(({ src, dest, match }, i) => {
329 // Normalize
330 if (typeof dest === 'string') {
331 dest = util_1.addLeadingSlash(dest);
332 }
333 if (!src.startsWith('^')) {
334 src = '^' + src;
335 }
336 if (!src.endsWith('$')) {
337 src = src + '$';
338 }
339 // Validate
340 try {
341 return { src, dest, match: match || 'all', _srcRegex: new RegExp(src) };
342 }
343 catch (err) {
344 throw new Error(`config.routes[${i}].src: invalid regular expression syntax "${src}"`);
345 }
346 });
347}
348/** resolve --dest relative to cwd, etc. */
349function normalizeConfig(_config) {
350 var _a, _b, _c, _d, _e, _f, _g, _h;
351 // TODO: This function is really fighting with TypeScript. Now that we have an accurate
352 // SnowpackUserConfig type, we can have this function construct a fresh config object
353 // from scratch instead of trying to modify the user's config object in-place.
354 let config = _config;
355 if (config.packageOptions.source === 'local') {
356 config.packageOptions.rollup = config.packageOptions.rollup || {};
357 config.packageOptions.rollup.plugins = config.packageOptions.rollup.plugins || [];
358 }
359 config.exclude = Array.from(new Set([...ALWAYS_EXCLUDE, `${config.buildOptions.out}/**/*`, ...config.exclude]));
360 // normalize config URL/path values
361 config.buildOptions.out = util_1.removeTrailingSlash(config.buildOptions.out);
362 config.buildOptions.baseUrl = util_1.addTrailingSlash(config.buildOptions.baseUrl);
363 config.buildOptions.metaUrlPath = util_1.removeTrailingSlash(util_1.addLeadingSlash(config.buildOptions.metaUrlPath));
364 config.mount = normalizeMount(config);
365 config.routes = normalizeRoutes(config.routes);
366 if (config.optimize && JSON.stringify(config.optimize) !== '{}') {
367 config.optimize = {
368 entrypoints: (_a = config.optimize.entrypoints) !== null && _a !== void 0 ? _a : 'auto',
369 preload: (_b = config.optimize.preload) !== null && _b !== void 0 ? _b : false,
370 bundle: (_c = config.optimize.bundle) !== null && _c !== void 0 ? _c : false,
371 splitting: (_d = config.optimize.splitting) !== null && _d !== void 0 ? _d : false,
372 treeshake: (_e = config.optimize.treeshake) !== null && _e !== void 0 ? _e : true,
373 manifest: (_f = config.optimize.manifest) !== null && _f !== void 0 ? _f : false,
374 target: (_g = config.optimize.target) !== null && _g !== void 0 ? _g : 'es2020',
375 minify: (_h = config.optimize.minify) !== null && _h !== void 0 ? _h : false,
376 };
377 }
378 else {
379 config.optimize = undefined;
380 }
381 // new pipeline
382 const { plugins, extensionMap } = loadPlugins(config);
383 config.plugins = plugins;
384 config._extensionMap = extensionMap;
385 // If any plugins defined knownEntrypoints, add them here
386 for (const { knownEntrypoints } of config.plugins) {
387 if (knownEntrypoints && config.packageOptions.source === 'local') {
388 config.packageOptions.knownEntrypoints = config.packageOptions.knownEntrypoints.concat(knownEntrypoints);
389 }
390 }
391 plugins.forEach((plugin) => {
392 if (plugin.config) {
393 plugin.config(config);
394 }
395 });
396 return config;
397}
398function handleConfigError(msg) {
399 logger_1.logger.error(msg);
400 process.exit(1);
401}
402function handleValidationErrors(filepath, err) {
403 logger_1.logger.error(`! ${filepath}\n${err.message}`);
404 logger_1.logger.info(colors_1.dim(`See https://www.snowpack.dev for more info.`));
405 process.exit(1);
406}
407function handleDeprecatedConfigError(mainMsg, ...msgs) {
408 logger_1.logger.error(`${mainMsg}
409${msgs.join('\n')}
410See https://www.snowpack.dev for more info.`);
411 process.exit(1);
412}
413function valdiateDeprecatedConfig(rawConfig) {
414 var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
415 if (rawConfig.scripts) {
416 handleDeprecatedConfigError('[v3.0] Legacy "scripts" config is deprecated in favor of "plugins". Safe to remove if empty.');
417 }
418 if (rawConfig.proxy) {
419 handleDeprecatedConfigError('[v3.0] Legacy "proxy" config is deprecated in favor of "routes". Safe to remove if empty.');
420 }
421 if ((_a = rawConfig.buildOptions) === null || _a === void 0 ? void 0 : _a.metaDir) {
422 handleDeprecatedConfigError('[v3.0] "config.buildOptions.metaDir" is now "config.buildOptions.metaUrlPath".');
423 }
424 if ((_b = rawConfig.buildOptions) === null || _b === void 0 ? void 0 : _b.webModulesUrl) {
425 handleDeprecatedConfigError('[v3.0] "config.buildOptions.webModulesUrl" is now always set within the "config.buildOptions.metaUrlPath" directory.');
426 }
427 if ((_c = rawConfig.buildOptions) === null || _c === void 0 ? void 0 : _c.sourceMaps) {
428 handleDeprecatedConfigError('[v3.0] "config.buildOptions.sourceMaps" is now "config.buildOptions.sourcemap".');
429 }
430 if (rawConfig.installOptions) {
431 handleDeprecatedConfigError('[v3.0] "config.installOptions" is now "config.packageOptions". Safe to remove if empty.');
432 }
433 if ((_d = rawConfig.packageOptions) === null || _d === void 0 ? void 0 : _d.externalPackage) {
434 handleDeprecatedConfigError('[v3.0] "config.installOptions.externalPackage" is now "config.packageOptions.external".');
435 }
436 if ((_e = rawConfig.packageOptions) === null || _e === void 0 ? void 0 : _e.treeshake) {
437 handleDeprecatedConfigError('[v3.0] "config.installOptions.treeshake" is now "config.optimize.treeshake".');
438 }
439 if (rawConfig.install) {
440 handleDeprecatedConfigError('[v3.0] "config.install" is now "config.packageOptions.knownEntrypoints". Safe to remove if empty.');
441 }
442 if ((_f = rawConfig.experiments) === null || _f === void 0 ? void 0 : _f.source) {
443 handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.source" is now "config.packageOptions.source".');
444 }
445 if (((_g = rawConfig.packageOptions) === null || _g === void 0 ? void 0 : _g.source) === 'skypack') {
446 handleDeprecatedConfigError('[v3.0] Renamed! "config.experiments.source=skypack" is now "config.packageOptions.source=remote".');
447 }
448 if ((_h = rawConfig.experiments) === null || _h === void 0 ? void 0 : _h.ssr) {
449 handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.ssr" is now "config.buildOptions.ssr".');
450 }
451 if ((_j = rawConfig.experiments) === null || _j === void 0 ? void 0 : _j.optimize) {
452 handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.optimize" is now "config.optimize".');
453 }
454 if ((_k = rawConfig.experiments) === null || _k === void 0 ? void 0 : _k.routes) {
455 handleDeprecatedConfigError('[v3.0] Experiment promoted! "config.experiments.routes" is now "config.routes".');
456 }
457 if ((_l = rawConfig.devOptions) === null || _l === void 0 ? void 0 : _l.fallback) {
458 handleDeprecatedConfigError('[v3.0] Deprecated! "devOptions.fallback" is now replaced by "routes".\n' +
459 'More info: https://www.snowpack.dev/guides/routing');
460 }
461}
462function validatePlugin(plugin) {
463 const pluginName = plugin.name;
464 if (plugin.resolve && !plugin.load) {
465 handleConfigError(`[${pluginName}] "resolve" config found but "load()" method missing.`);
466 }
467 if (!plugin.resolve && plugin.load) {
468 handleConfigError(`[${pluginName}] "load" method found but "resolve()" config missing.`);
469 }
470 if (plugin.resolve && !Array.isArray(plugin.resolve.input)) {
471 handleConfigError(`[${pluginName}] "resolve.input" should be an array of input file extensions.`);
472 }
473 if (plugin.resolve && !Array.isArray(plugin.resolve.output)) {
474 handleConfigError(`[${pluginName}] "resolve.output" should be an array of output file extensions.`);
475 }
476}
477function validatePluginLoadResult(plugin, result) {
478 const pluginName = plugin.name;
479 if (!result) {
480 return;
481 }
482 const isValidSingleResultType = typeof result === 'string' || Buffer.isBuffer(result);
483 if (isValidSingleResultType && plugin.resolve.output.length !== 1) {
484 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.`);
485 }
486 const unexpectedOutput = typeof result === 'object' &&
487 Object.keys(result).find((fileExt) => !plugin.resolve.output.includes(fileExt));
488 if (unexpectedOutput) {
489 handleConfigError(`[plugin=${pluginName}] "load()" returned entry "${unexpectedOutput}" not found in "resolve.output": ${plugin.resolve.output}`);
490 }
491}
492exports.validatePluginLoadResult = validatePluginLoadResult;
493/**
494 * Get the config base path, that all relative config values should resolve to. In order:
495 * - The directory of the config file path, if it exists.
496 * - The config.root value, if given.
497 * - Otherwise, the current working directory of the process.
498 */
499function getConfigBasePath(configFileLoc, configRoot) {
500 return ((configFileLoc && path_1.default.dirname(configFileLoc)) ||
501 (configRoot && path_1.default.resolve(process.cwd(), configRoot)) ||
502 process.cwd());
503}
504function resolveRelativeConfigAlias(aliasConfig, configBase) {
505 const cleanAliasConfig = {};
506 for (const [target, replacement] of Object.entries(aliasConfig)) {
507 const isDirectory = target.endsWith('/');
508 const isPath = replacement === '.' ||
509 replacement === '..' ||
510 replacement.startsWith('./') ||
511 replacement.startsWith('../') ||
512 replacement.startsWith('/');
513 if (isPath) {
514 cleanAliasConfig[target] = isDirectory
515 ? util_1.addTrailingSlash(path_1.default.resolve(configBase, replacement))
516 : util_1.removeTrailingSlash(path_1.default.resolve(configBase, replacement));
517 }
518 else {
519 cleanAliasConfig[target] = replacement;
520 }
521 }
522 return cleanAliasConfig;
523}
524function resolveRelativeConfigMount(mountConfig, configBase) {
525 const cleanMountConfig = {};
526 for (const [target, replacement] of Object.entries(mountConfig)) {
527 cleanMountConfig[path_1.default.resolve(configBase, target)] = replacement;
528 }
529 return cleanMountConfig;
530}
531function resolveRelativeConfig(config, configBase) {
532 var _a, _b;
533 if (config.root) {
534 config.root = path_1.default.resolve(configBase, config.root);
535 }
536 if ((_a = config.buildOptions) === null || _a === void 0 ? void 0 : _a.out) {
537 config.buildOptions.out = path_1.default.resolve(configBase, config.buildOptions.out);
538 }
539 if (((_b = config.packageOptions) === null || _b === void 0 ? void 0 : _b.source) === 'remote' && config.packageOptions.cache) {
540 config.packageOptions.cache = path_1.default.resolve(configBase, config.packageOptions.cache);
541 }
542 if (config.extends && /^[\.\/\\]/.test(config.extends)) {
543 config.extends = path_1.default.resolve(configBase, config.extends);
544 }
545 if (config.plugins) {
546 config.plugins = config.plugins.map((plugin) => {
547 const name = Array.isArray(plugin) ? plugin[0] : plugin;
548 const absName = path_1.default.isAbsolute(name) ? name : require.resolve(name, { paths: [configBase] });
549 if (Array.isArray(plugin)) {
550 plugin.splice(0, 1, absName);
551 return plugin;
552 }
553 return absName;
554 });
555 }
556 if (config.mount) {
557 config.mount = resolveRelativeConfigMount(config.mount, configBase);
558 }
559 if (config.alias) {
560 config.alias = resolveRelativeConfigAlias(config.alias, configBase);
561 }
562 return config;
563}
564class ConfigValidationError extends Error {
565 constructor(errors) {
566 super(`Configuration Error:\n${errors.map((err) => ` - ${err.toString()}`).join(os_1.default.EOL)}`);
567 }
568}
569function validateConfig(config) {
570 for (const mountDir of Object.keys(config.mount)) {
571 if (!fs_1.existsSync(mountDir)) {
572 logger_1.logger.warn(`config.mount[${mountDir}]: mounted directory does not exist.`);
573 }
574 }
575}
576function createConfiguration(config = {}) {
577 var _a;
578 // Validate the configuration object against our schema. Report any errors.
579 const { errors: validationErrors } = jsonschema_1.validate(config, configSchema, {
580 propertyName: CONFIG_NAME,
581 allowUnknownAttributes: false,
582 });
583 if (validationErrors.length > 0) {
584 throw new ConfigValidationError(validationErrors);
585 }
586 // Inherit any undefined values from the default configuration. If no config argument
587 // was passed (or no configuration file found in loadConfiguration) then this function
588 // will effectively return a copy of the DEFAULT_CONFIG object.
589 const mergedConfig = deepmerge_1.all([
590 DEFAULT_CONFIG,
591 {
592 packageOptions: ((_a = config.packageOptions) === null || _a === void 0 ? void 0 : _a.source) === 'remote'
593 ? DEFAULT_PACKAGES_REMOTE_CONFIG
594 : exports.DEFAULT_PACKAGES_LOCAL_CONFIG,
595 },
596 config,
597 ], {
598 isMergeableObject: (val) => is_plain_object_1.isPlainObject(val) || Array.isArray(val),
599 });
600 // Resolve relative config values. If using loadConfiguration, all config values should
601 // already be resolved relative to the config file path so that this should be a no-op.
602 // But, we still need to run it in case you called this function directly.
603 const configBase = getConfigBasePath(undefined, config.root);
604 resolveRelativeConfig(mergedConfig, configBase);
605 const normalizedConfig = normalizeConfig(mergedConfig);
606 validateConfig(normalizedConfig);
607 return normalizedConfig;
608}
609exports.createConfiguration = createConfiguration;
610function loadConfigurationFile(filename) {
611 const loc = path_1.default.resolve(process.cwd(), filename);
612 if (!fs_1.existsSync(loc)) {
613 return null;
614 }
615 return { filepath: loc, config: util_1.NATIVE_REQUIRE(loc) };
616}
617async function loadConfiguration(overrides = {}, configPath) {
618 let result = null;
619 // if user specified --config path, load that
620 if (configPath) {
621 result = loadConfigurationFile(configPath);
622 if (!result) {
623 throw new Error(`Snowpack config file could not be found: ${configPath}`);
624 }
625 }
626 // If no config was found above, search for one.
627 result =
628 result ||
629 loadConfigurationFile('snowpack.config.mjs') ||
630 loadConfigurationFile('snowpack.config.cjs') ||
631 loadConfigurationFile('snowpack.config.js') ||
632 loadConfigurationFile('snowpack.config.json');
633 // Support package.json "snowpack" config
634 if (!result) {
635 const potentialPackageJsonConfig = loadConfigurationFile('package.json');
636 if (potentialPackageJsonConfig && potentialPackageJsonConfig.config.snowpack) {
637 result = {
638 filepath: potentialPackageJsonConfig.filepath,
639 config: potentialPackageJsonConfig.config.snowpack,
640 };
641 }
642 }
643 if (!result) {
644 logger_1.logger.warn('Hint: run "snowpack init" to create a project config file. Using defaults...');
645 result = { filepath: undefined, config: {} };
646 }
647 const { config, filepath } = result;
648 const configBase = getConfigBasePath(filepath, config.root);
649 valdiateDeprecatedConfig(config);
650 valdiateDeprecatedConfig(overrides);
651 resolveRelativeConfig(config, configBase);
652 let extendConfig = {};
653 if (config.extends) {
654 const extendConfigLoc = require.resolve(config.extends, { paths: [configBase] });
655 const extendResult = loadConfigurationFile(extendConfigLoc);
656 if (!extendResult) {
657 handleConfigError(`Could not locate "extends" config at ${extendConfigLoc}`);
658 process.exit(1);
659 }
660 extendConfig = extendResult.config;
661 const extendValidation = jsonschema_1.validate(extendConfig, configSchema, {
662 allowUnknownAttributes: false,
663 propertyName: CONFIG_NAME,
664 });
665 if (extendValidation.errors && extendValidation.errors.length > 0) {
666 handleValidationErrors(extendConfigLoc, new ConfigValidationError(extendValidation.errors));
667 }
668 valdiateDeprecatedConfig(extendConfig);
669 resolveRelativeConfig(extendConfig, configBase);
670 }
671 // if valid, apply config over defaults
672 const mergedConfig = deepmerge_1.all([extendConfig, config, overrides], {
673 isMergeableObject: (val) => is_plain_object_1.isPlainObject(val) || Array.isArray(val),
674 });
675 try {
676 return createConfiguration(mergedConfig);
677 }
678 catch (err) {
679 if (err instanceof ConfigValidationError) {
680 handleValidationErrors(filepath, err);
681 }
682 throw err;
683 }
684}
685exports.loadConfiguration = loadConfiguration;