| const fs = require('fs') | 
| const path = require('path') | 
| const debug = require('debug') | 
| const merge = require('webpack-merge') | 
| const Config = require('webpack-chain') | 
| const PluginAPI = require('./PluginAPI') | 
| const dotenv = require('dotenv') | 
| const dotenvExpand = require('dotenv-expand') | 
| const defaultsDeep = require('lodash.defaultsdeep') | 
| const { chalk, warn, error, isPlugin, resolvePluginId, loadModule, resolvePkg } = require('@vue/cli-shared-utils') | 
|   | 
| const { defaults, validate } = require('./options') | 
|   | 
| module.exports = class Service { | 
|   constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) { | 
|     process.VUE_CLI_SERVICE = this | 
|     this.initialized = false | 
|     this.context = context | 
|     this.inlineOptions = inlineOptions | 
|     this.webpackChainFns = [] | 
|     this.webpackRawConfigFns = [] | 
|     this.devServerConfigFns = [] | 
|     this.commands = {} | 
|     // Folder containing the target package.json for plugins | 
|     this.pkgContext = context | 
|     // package.json containing the plugins | 
|     this.pkg = this.resolvePkg(pkg) | 
|     // If there are inline plugins, they will be used instead of those | 
|     // found in package.json. | 
|     // When useBuiltIn === false, built-in plugins are disabled. This is mostly | 
|     // for testing. | 
|     this.plugins = this.resolvePlugins(plugins, useBuiltIn) | 
|     // pluginsToSkip will be populated during run() | 
|     this.pluginsToSkip = new Set() | 
|     // resolve the default mode to use for each command | 
|     // this is provided by plugins as module.exports.defaultModes | 
|     // so we can get the information without actually applying the plugin. | 
|     this.modes = this.plugins.reduce((modes, { apply: { defaultModes }}) => { | 
|       return Object.assign(modes, defaultModes) | 
|     }, {}) | 
|   } | 
|   | 
|   resolvePkg (inlinePkg, context = this.context) { | 
|     if (inlinePkg) { | 
|       return inlinePkg | 
|     } | 
|     const pkg = resolvePkg(context) | 
|     if (pkg.vuePlugins && pkg.vuePlugins.resolveFrom) { | 
|       this.pkgContext = path.resolve(context, pkg.vuePlugins.resolveFrom) | 
|       return this.resolvePkg(null, this.pkgContext) | 
|     } | 
|     return pkg | 
|   } | 
|   | 
|   init (mode = process.env.VUE_CLI_MODE) { | 
|     if (this.initialized) { | 
|       return | 
|     } | 
|     this.initialized = true | 
|     this.mode = mode | 
|   | 
|     // load mode .env | 
|     if (mode) { | 
|       this.loadEnv(mode) | 
|     } | 
|     // load base .env | 
|     this.loadEnv() | 
|   | 
|     // load user config | 
|     const userOptions = this.loadUserOptions() | 
|     this.projectOptions = defaultsDeep(userOptions, defaults()) | 
|   | 
|     debug('vue:project-config')(this.projectOptions) | 
|   | 
|     // apply plugins. | 
|     this.plugins.forEach(({ id, apply }) => { | 
|       if (this.pluginsToSkip.has(id)) return | 
|       apply(new PluginAPI(id, this), this.projectOptions) | 
|     }) | 
|   | 
|     // apply webpack configs from project config file | 
|     if (this.projectOptions.chainWebpack) { | 
|       this.webpackChainFns.push(this.projectOptions.chainWebpack) | 
|     } | 
|     if (this.projectOptions.configureWebpack) { | 
|       this.webpackRawConfigFns.push(this.projectOptions.configureWebpack) | 
|     } | 
|   } | 
|   | 
|   loadEnv (mode) { | 
|     const logger = debug('vue:env') | 
|     const basePath = path.resolve(this.context, `.env${mode ? `.${mode}` : ``}`) | 
|     const localPath = `${basePath}.local` | 
|   | 
|     const load = envPath => { | 
|       try { | 
|         const env = dotenv.config({ path: envPath, debug: process.env.DEBUG }) | 
|         dotenvExpand(env) | 
|         logger(envPath, env) | 
|       } catch (err) { | 
|         // only ignore error if file is not found | 
|         if (err.toString().indexOf('ENOENT') < 0) { | 
|           error(err) | 
|         } | 
|       } | 
|     } | 
|   | 
|     load(localPath) | 
|     load(basePath) | 
|   | 
|     // by default, NODE_ENV and BABEL_ENV are set to "development" unless mode | 
|     // is production or test. However the value in .env files will take higher | 
|     // priority. | 
|     if (mode) { | 
|       // always set NODE_ENV during tests | 
|       // as that is necessary for tests to not be affected by each other | 
|       const shouldForceDefaultEnv = ( | 
|         process.env.VUE_CLI_TEST && | 
|         !process.env.VUE_CLI_TEST_TESTING_ENV | 
|       ) | 
|       const defaultNodeEnv = (mode === 'production' || mode === 'test') | 
|         ? mode | 
|         : 'development' | 
|       if (shouldForceDefaultEnv || process.env.NODE_ENV == null) { | 
|         process.env.NODE_ENV = defaultNodeEnv | 
|       } | 
|       if (shouldForceDefaultEnv || process.env.BABEL_ENV == null) { | 
|         process.env.BABEL_ENV = defaultNodeEnv | 
|       } | 
|     } | 
|   } | 
|   | 
|   setPluginsToSkip (args) { | 
|     const skipPlugins = args['skip-plugins'] | 
|     const pluginsToSkip = skipPlugins | 
|       ? new Set(skipPlugins.split(',').map(id => resolvePluginId(id))) | 
|       : new Set() | 
|   | 
|     this.pluginsToSkip = pluginsToSkip | 
|   } | 
|   | 
|   resolvePlugins (inlinePlugins, useBuiltIn) { | 
|     const idToPlugin = id => ({ | 
|       id: id.replace(/^.\//, 'built-in:'), | 
|       apply: require(id) | 
|     }) | 
|   | 
|     let plugins | 
|   | 
|     const builtInPlugins = [ | 
|       './commands/serve', | 
|       './commands/build', | 
|       './commands/inspect', | 
|       './commands/help', | 
|       // config plugins are order sensitive | 
|       './config/base', | 
|       './config/css', | 
|       './config/prod', | 
|       './config/app' | 
|     ].map(idToPlugin) | 
|   | 
|     if (inlinePlugins) { | 
|       plugins = useBuiltIn !== false | 
|         ? builtInPlugins.concat(inlinePlugins) | 
|         : inlinePlugins | 
|     } else { | 
|       const projectPlugins = Object.keys(this.pkg.devDependencies || {}) | 
|         .concat(Object.keys(this.pkg.dependencies || {})) | 
|         .filter(isPlugin) | 
|         .map(id => { | 
|           if ( | 
|             this.pkg.optionalDependencies && | 
|             id in this.pkg.optionalDependencies | 
|           ) { | 
|             let apply = () => {} | 
|             try { | 
|               apply = require(id) | 
|             } catch (e) { | 
|               warn(`Optional dependency ${id} is not installed.`) | 
|             } | 
|   | 
|             return { id, apply } | 
|           } else { | 
|             return idToPlugin(id) | 
|           } | 
|         }) | 
|       plugins = builtInPlugins.concat(projectPlugins) | 
|     } | 
|   | 
|     // Local plugins | 
|     if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) { | 
|       const files = this.pkg.vuePlugins.service | 
|       if (!Array.isArray(files)) { | 
|         throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`) | 
|       } | 
|       plugins = plugins.concat(files.map(file => ({ | 
|         id: `local:${file}`, | 
|         apply: loadModule(`./${file}`, this.pkgContext) | 
|       }))) | 
|     } | 
|   | 
|     return plugins | 
|   } | 
|   | 
|   async run (name, args = {}, rawArgv = []) { | 
|     // resolve mode | 
|     // prioritize inline --mode | 
|     // fallback to resolved default modes from plugins or development if --watch is defined | 
|     const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name]) | 
|   | 
|     // --skip-plugins arg may have plugins that should be skipped during init() | 
|     this.setPluginsToSkip(args) | 
|   | 
|     // load env variables, load user config, apply plugins | 
|     this.init(mode) | 
|   | 
|     args._ = args._ || [] | 
|     let command = this.commands[name] | 
|     if (!command && name) { | 
|       error(`command "${name}" does not exist.`) | 
|       process.exit(1) | 
|     } | 
|     if (!command || args.help || args.h) { | 
|       command = this.commands.help | 
|     } else { | 
|       args._.shift() // remove command itself | 
|       rawArgv.shift() | 
|     } | 
|     const { fn } = command | 
|     return fn(args, rawArgv) | 
|   } | 
|   | 
|   resolveChainableWebpackConfig () { | 
|     const chainableConfig = new Config() | 
|     // apply chains | 
|     this.webpackChainFns.forEach(fn => fn(chainableConfig)) | 
|     return chainableConfig | 
|   } | 
|   | 
|   resolveWebpackConfig (chainableConfig = this.resolveChainableWebpackConfig()) { | 
|     if (!this.initialized) { | 
|       throw new Error('Service must call init() before calling resolveWebpackConfig().') | 
|     } | 
|     // get raw config | 
|     let config = chainableConfig.toConfig() | 
|     const original = config | 
|     // apply raw config fns | 
|     this.webpackRawConfigFns.forEach(fn => { | 
|       if (typeof fn === 'function') { | 
|         // function with optional return value | 
|         const res = fn(config) | 
|         if (res) config = merge(config, res) | 
|       } else if (fn) { | 
|         // merge literal values | 
|         config = merge(config, fn) | 
|       } | 
|     }) | 
|   | 
|     // #2206 If config is merged by merge-webpack, it discards the __ruleNames | 
|     // information injected by webpack-chain. Restore the info so that | 
|     // vue inspect works properly. | 
|     if (config !== original) { | 
|       cloneRuleNames( | 
|         config.module && config.module.rules, | 
|         original.module && original.module.rules | 
|       ) | 
|     } | 
|   | 
|     // check if the user has manually mutated output.publicPath | 
|     const target = process.env.VUE_CLI_BUILD_TARGET | 
|     if ( | 
|       !process.env.VUE_CLI_TEST && | 
|       (target && target !== 'app') && | 
|       config.output.publicPath !== this.projectOptions.publicPath | 
|     ) { | 
|       throw new Error( | 
|         `Do not modify webpack output.publicPath directly. ` + | 
|         `Use the "publicPath" option in vue.config.js instead.` | 
|       ) | 
|     } | 
|   | 
|     if ( | 
|       !process.env.VUE_CLI_ENTRY_FILES && | 
|       typeof config.entry !== 'function' | 
|     ) { | 
|       let entryFiles | 
|       if (typeof config.entry === 'string') { | 
|         entryFiles = [config.entry] | 
|       } else if (Array.isArray(config.entry)) { | 
|         entryFiles = config.entry | 
|       } else { | 
|         entryFiles = Object.values(config.entry || []).reduce((allEntries, curr) => { | 
|           return allEntries.concat(curr) | 
|         }, []) | 
|       } | 
|   | 
|       entryFiles = entryFiles.map(file => path.resolve(this.context, file)) | 
|       process.env.VUE_CLI_ENTRY_FILES = JSON.stringify(entryFiles) | 
|     } | 
|   | 
|     return config | 
|   } | 
|   | 
|   loadUserOptions () { | 
|     // vue.config.c?js | 
|     let fileConfig, pkgConfig, resolved, resolvedFrom | 
|     const esm = this.pkg.type && this.pkg.type === 'module' | 
|   | 
|     const possibleConfigPaths = [ | 
|       process.env.VUE_CLI_SERVICE_CONFIG_PATH, | 
|       './vue.config.js', | 
|       './vue.config.cjs' | 
|     ] | 
|   | 
|     let fileConfigPath | 
|     for (const p of possibleConfigPaths) { | 
|       const resolvedPath = p && path.resolve(this.context, p) | 
|       if (resolvedPath && fs.existsSync(resolvedPath)) { | 
|         fileConfigPath = resolvedPath | 
|         break | 
|       } | 
|     } | 
|   | 
|     if (fileConfigPath) { | 
|       if (esm && fileConfigPath === './vue.config.js') { | 
|         throw new Error(`Please rename ${chalk.bold('vue.config.js')} to ${chalk.bold('vue.config.cjs')} when ECMAScript modules is enabled`) | 
|       } | 
|   | 
|       try { | 
|         fileConfig = loadModule(fileConfigPath, this.context) | 
|   | 
|         if (typeof fileConfig === 'function') { | 
|           fileConfig = fileConfig() | 
|         } | 
|   | 
|         if (!fileConfig || typeof fileConfig !== 'object') { | 
|           // TODO: show throw an Error here, to be fixed in v5 | 
|           error( | 
|             `Error loading ${chalk.bold(fileConfigPath)}: should export an object or a function that returns object.` | 
|           ) | 
|           fileConfig = null | 
|         } | 
|       } catch (e) { | 
|         error(`Error loading ${chalk.bold(fileConfigPath)}:`) | 
|         throw e | 
|       } | 
|     } | 
|   | 
|     // package.vue | 
|     pkgConfig = this.pkg.vue | 
|     if (pkgConfig && typeof pkgConfig !== 'object') { | 
|       error( | 
|         `Error loading vue-cli config in ${chalk.bold(`package.json`)}: ` + | 
|         `the "vue" field should be an object.` | 
|       ) | 
|       pkgConfig = null | 
|     } | 
|   | 
|     if (fileConfig) { | 
|       if (pkgConfig) { | 
|         warn( | 
|           `"vue" field in package.json ignored ` + | 
|           `due to presence of ${chalk.bold('vue.config.js')}.` | 
|         ) | 
|         warn( | 
|           `You should migrate it into ${chalk.bold('vue.config.js')} ` + | 
|           `and remove it from package.json.` | 
|         ) | 
|       } | 
|       resolved = fileConfig | 
|       resolvedFrom = 'vue.config.js' | 
|     } else if (pkgConfig) { | 
|       resolved = pkgConfig | 
|       resolvedFrom = '"vue" field in package.json' | 
|     } else { | 
|       resolved = this.inlineOptions || {} | 
|       resolvedFrom = 'inline options' | 
|     } | 
|   | 
|     if (resolved.css && typeof resolved.css.modules !== 'undefined') { | 
|       if (typeof resolved.css.requireModuleExtension !== 'undefined') { | 
|         warn( | 
|           `You have set both "css.modules" and "css.requireModuleExtension" in ${chalk.bold('vue.config.js')}, ` + | 
|           `"css.modules" will be ignored in favor of "css.requireModuleExtension".` | 
|         ) | 
|       } else { | 
|         warn( | 
|           `"css.modules" option in ${chalk.bold('vue.config.js')} ` + | 
|           `is deprecated now, please use "css.requireModuleExtension" instead.` | 
|         ) | 
|         resolved.css.requireModuleExtension = !resolved.css.modules | 
|       } | 
|     } | 
|   | 
|     // normalize some options | 
|     ensureSlash(resolved, 'publicPath') | 
|     if (typeof resolved.publicPath === 'string') { | 
|       resolved.publicPath = resolved.publicPath.replace(/^\.\//, '') | 
|     } | 
|     removeSlash(resolved, 'outputDir') | 
|   | 
|     // validate options | 
|     validate(resolved, msg => { | 
|       error( | 
|         `Invalid options in ${chalk.bold(resolvedFrom)}: ${msg}` | 
|       ) | 
|     }) | 
|   | 
|     return resolved | 
|   } | 
| } | 
|   | 
| function ensureSlash (config, key) { | 
|   const val = config[key] | 
|   if (typeof val === 'string') { | 
|     config[key] = val.replace(/([^/])$/, '$1/') | 
|   } | 
| } | 
|   | 
| function removeSlash (config, key) { | 
|   if (typeof config[key] === 'string') { | 
|     config[key] = config[key].replace(/\/$/g, '') | 
|   } | 
| } | 
|   | 
| function cloneRuleNames (to, from) { | 
|   if (!to || !from) { | 
|     return | 
|   } | 
|   from.forEach((r, i) => { | 
|     if (to[i]) { | 
|       Object.defineProperty(to[i], '__ruleNames', { | 
|         value: r.__ruleNames | 
|       }) | 
|       cloneRuleNames(to[i].oneOf, r.oneOf) | 
|     } | 
|   }) | 
| } |