<commit_before>const
  path = require('path'),
  fs = require('fs'),
  merge = require('webpack-merge'),
  chokidar = require('chokidar'),
  debounce = require('lodash.debounce')

const
  appPaths = require('./app-paths'),
  logger = require('./helpers/logger'),
  log = logger('app:quasar-conf'),
  warn = logger('app:quasar-conf', 'red'),
  legacyValidations = require('./legacy-validations')

function getQuasarConfigCtx (opts) {
  const ctx = {
    dev: opts.dev || false,
    prod: opts.prod || false,
    theme: {},
    themeName: opts.theme,
    mode: {},
    modeName: opts.mode,
    target: {},
    targetName: opts.target,
    emulator: opts.emulator,
    arch: {},
    archName: opts.arch,
    bundler: {},
    bundlerName: opts.bundler,
    debug: opts.debug
  }
  ctx.theme[opts.theme] = true
  ctx.mode[opts.mode] = true

  if (opts.target) {
    ctx.target[opts.target] = true
  }
  if (opts.arch) {
    ctx.arch[opts.arch] = true
  }
  if (opts.bundler) {
    ctx.bundler[opts.bundler] = true
  }

  return ctx
}

function encode (obj) {
  return JSON.stringify(obj, (key, value) => {
    return typeof value === 'function'
      ? `/fn(${value.toString()})`
      : value
  })
}

function formatPublicPath (path) {
  if (!path) {
    return path
  }
  if (!path.startsWith('/')) {
    path = `/${path}`
  }
  if (!path.endsWith('/')) {
    path = `${path}/`
  }
  return path
}

function parseBuildEnv (env) {
  const obj = {}
  Object.keys(env).forEach(key => {
    try {
      obj[key] = JSON.parse(env[key])
    }
    catch (e) {
      obj[key] = ''
    }
  })
  return obj
}

/*
 * this.buildConfig           - Compiled Object from quasar.conf.js
 * this.webpackConfig         - Webpack config object for main thread
 * this.electronWebpackConfig - Webpack config object for electron main thread
 */

class QuasarConfig {
  constructor (opts) {
    this.filename = appPaths.resolve.app('quasar.conf.js')
    this.pkg = require(appPaths.resolve.app('package.json'))
    this.opts = opts
    this.ctx = getQuasarConfigCtx(opts)
    this.watch = opts.onBuildChange || opts.onAppChange

    if (this.watch) {
      // Start watching for quasar.config.js changes
      chokidar
      .watch(this.filename, { watchers: { chokidar: { ignoreInitial: true } } })
      .on('change', debounce(async () => {
        console.log()
        log(`quasar.conf.js changed`)

        try {
          await this.prepare()
        }
        catch (e) {
          if (e.message !== 'NETWORK_ERROR') {
            console.log(e)
            warn(`quasar.conf.js has JS errors. Please fix them then save file again.`)
            warn()
          }

          return
        }

        this.compile()

        if (this.webpackConfigChanged) {
          opts.onBuildChange()
        }
        else {
          opts.onAppChange()
        }
      }, 1000))

      if (this.ctx.mode.ssr) {
        this.ssrExtensionFile = appPaths.resolve.ssr('extension.js')

        chokidar
        .watch(this.ssrExtensionFile, { watchers: { chokidar: { ignoreInitial: true } } })
        .on('change', debounce(async () => {
          console.log()
          log(`src-ssr/extension.js changed`)

          try {
            this.readSSRextension()
          }
          catch (e) {
            if (e.message !== 'NETWORK_ERROR') {
              console.log(e)
              warn(`src-ssr/extension.js has JS errors. Please fix them then save file again.`)
              warn()
            }

            return
          }

          opts.onBuildChange()
        }, 1000))
      }
    }
  }

  // synchronous for build
  async prepare () {
    this.readConfig()

    if (this.watch && this.ctx.mode.ssr) {
      this.readSSRextension()
    }

    const cfg = merge({
      ctx: this.ctx,
      css: false,
      plugins: false,
      animations: false,
      extras: false
    }, this.quasarConfigFunction(this.ctx))

    if (this.ctx.dev) {
      cfg.devServer = cfg.devServer || {}

      if (this.opts.host) {
        cfg.devServer.host = this.opts.host
      }
      else if (!cfg.devServer.host) {
        cfg.devServer.host = '0.0.0.0'
      }

      if (this.opts.port) {
        cfg.devServer.port = this.opts.port
      }
      else if (!cfg.devServer.port) {
        cfg.devServer.port = 8080
      }

      if (
        this.address &&
        this.address.from.host === cfg.devServer.host &&
        this.address.from.port === cfg.devServer.port
      ) {
        cfg.devServer.host = this.address.to.host
        cfg.devServer.port = this.address.to.port
      }
      else {
        const addr = {
          host: cfg.devServer.host,
          port: cfg.devServer.port
        }
        const to = await this.opts.onAddress(addr)

        // if network error while running
        if (to === null) {
          throw new Error('NETWORK_ERROR')
        }

        cfg.devServer = merge(cfg.devServer, to)
        this.address = {
          from: addr,
          to: {
            host: cfg.devServer.host,
            port: cfg.devServer.port
          }
        }
      }
    }

    this.quasarConfig = cfg
  }

  getBuildConfig () {
    return this.buildConfig
  }

  getWebpackConfig () {
    return this.webpackConfig
  }

  readConfig () {
    log(`Reading quasar.conf.js`)

    if (fs.existsSync(this.filename)) {
      delete require.cache[this.filename]
      this.quasarConfigFunction = require(this.filename)
    }
    else {
      warn(`⚠️  [FAIL] Could not load quasar.conf.js config file`)
      process.exit(1)
    }
  }

  readSSRextension () {
    log(`Reading src-ssr/extension.js`)

    if (fs.existsSync(this.ssrExtensionFile)) {
      delete require.cache[this.ssrExtensionFile]
      this.ssrExtension = require(this.ssrExtensionFile)
    }
    else {
      warn(`⚠️  [FAIL] Could not load src-ssr/extension.js file`)
      process.exit(1)
    }
  }

  compile () {
    let cfg = this.quasarConfig

    // if watching for changes,
    // then determine the type (webpack related or not)
    if (this.watch) {
      const newConfigSnapshot = [
        cfg.build ? encode(cfg.build) : '',
        cfg.ssr ? cfg.ssr.pwa : '',
        cfg.framework.all,
        cfg.devServer ? encode(cfg.devServer) : '',
        cfg.pwa ? encode(cfg.pwa) : '',
        cfg.electron ? encode(cfg.electron) : ''
      ].join('')

      if (this.oldConfigSnapshot) {
        this.webpackConfigChanged = newConfigSnapshot !== this.oldConfigSnapshot
      }

      this.oldConfigSnapshot = newConfigSnapshot
    }

    // make sure it exists
    cfg.supportIE = this.ctx.mode.electron
      ? false
      : (cfg.supportIE || false)

    cfg.vendor = merge({
      vendor: {
        add: false,
        remove: false
      }
    }, cfg.vendor || {})

    if (cfg.vendor.add) {
      cfg.vendor.add = cfg.vendor.add.filter(v => v).join('|')
      if (cfg.vendor.add) {
        cfg.vendor.add = new RegExp(cfg.vendor.add)
      }
    }
    if (cfg.vendor.remove) {
      cfg.vendor.remove = cfg.vendor.remove.filter(v => v).join('|')
      if (cfg.vendor.remove) {
        cfg.vendor.remove = new RegExp(cfg.vendor.remove)
      }
    }

    if (cfg.css) {
      cfg.css = cfg.css.filter(_ => _).map(
        asset => asset[0] === '~' ? asset.substring(1) : `src/css/${asset}`
      )

      if (cfg.css.length === 0) {
        cfg.css = false
      }
    }

    if (cfg.plugins) {
      cfg.plugins = cfg.plugins.filter(_ => _).map(asset => {
        return typeof asset === 'string'
          ? { path: asset }
          : asset
      }).filter(asset => asset.path)

      if (cfg.plugins.length === 0) {
        cfg.plugins = false
      }
    }

    cfg.build = merge({
      showProgress: true,
      scopeHoisting: true,
      productName: this.pkg.productName,
      productDescription: this.pkg.description,
      extractCSS: this.ctx.prod,
      sourceMap: this.ctx.dev,
      minify: this.ctx.prod,
      distDir: path.join('dist', `${this.ctx.modeName}-${this.ctx.themeName}`),
      htmlFilename: 'index.html',
      webpackManifest: this.ctx.prod,
      vueRouterMode: 'hash',
      preloadChunks: true,
      devtool: this.ctx.dev
        ? '#cheap-module-eval-source-map'
        : '#source-map',
      env: {
        NODE_ENV: `"${this.ctx.prod ? 'production' : 'development'}"`,
        CLIENT: true,
        SERVER: false,
        DEV: this.ctx.dev,
        PROD: this.ctx.prod,
        THEME: `"${this.ctx.themeName}"`,
        MODE: `"${this.ctx.modeName}"`
      },
      uglifyOptions: {
        compress: {
          // turn off flags with small gains to speed up minification
          arrows: false,
          collapse_vars: false, // 0.3kb
          comparisons: false,
          computed_props: false,
          hoist_funs: false,
          hoist_props: false,
          hoist_vars: false,
          inline: false,
          loops: false,
          negate_iife: false,
          properties: false,
          reduce_funcs: false,
          reduce_vars: false,
          switches: false,
          toplevel: false,
          typeofs: false,

          // a few flags with noticable gains/speed ratio
          // numbers based on out of the box vendor bundle
          booleans: true, // 0.7kb
          if_return: true, // 0.4kb
          sequences: true, // 0.7kb
          unused: true, // 2.3kb

          // required features to drop conditional branches
          conditionals: true,
          dead_code: true,
          evaluate: true
        },
        mangle: {
          /*
            Support non-standard Safari 10/11.
            By default `uglify-es` will not work around
            Safari 10/11 bugs.
          */
          safari10: true
        }
      }
    }, cfg.build || {})

    if (cfg.framework === void 0 || cfg.framework === 'all') {
      cfg.framework = {
        all: true
      }
    }
    cfg.framework.config = cfg.framework.config || {}

    cfg.__loadingBar = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('LoadingBar'))
    cfg.__meta = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('Meta'))

    if (this.ctx.dev || this.ctx.debug) {
      Object.assign(cfg.build, {
        minify: false,
        extractCSS: false,
        gzip: false
      })
    }
    if (this.ctx.debug) {
      cfg.build.sourceMap = true
    }

    if (this.ctx.mode.ssr) {
      Object.assign(cfg.build, {
        extractCSS: false,<commit_msg>feat: Improve quasar.conf > framework prop fault tolerance<commit_after>const
  path = require('path'),
  fs = require('fs'),
  merge = require('webpack-merge'),
  chokidar = require('chokidar'),
  debounce = require('lodash.debounce')

const
  appPaths = require('./app-paths'),
  logger = require('./helpers/logger'),
  log = logger('app:quasar-conf'),
  warn = logger('app:quasar-conf', 'red'),
  legacyValidations = require('./legacy-validations')

function getQuasarConfigCtx (opts) {
  const ctx = {
    dev: opts.dev || false,
    prod: opts.prod || false,
    theme: {},
    themeName: opts.theme,
    mode: {},
    modeName: opts.mode,
    target: {},
    targetName: opts.target,
    emulator: opts.emulator,
    arch: {},
    archName: opts.arch,
    bundler: {},
    bundlerName: opts.bundler,
    debug: opts.debug
  }
  ctx.theme[opts.theme] = true
  ctx.mode[opts.mode] = true

  if (opts.target) {
    ctx.target[opts.target] = true
  }
  if (opts.arch) {
    ctx.arch[opts.arch] = true
  }
  if (opts.bundler) {
    ctx.bundler[opts.bundler] = true
  }

  return ctx
}

function encode (obj) {
  return JSON.stringify(obj, (key, value) => {
    return typeof value === 'function'
      ? `/fn(${value.toString()})`
      : value
  })
}

function formatPublicPath (path) {
  if (!path) {
    return path
  }
  if (!path.startsWith('/')) {
    path = `/${path}`
  }
  if (!path.endsWith('/')) {
    path = `${path}/`
  }
  return path
}

function parseBuildEnv (env) {
  const obj = {}
  Object.keys(env).forEach(key => {
    try {
      obj[key] = JSON.parse(env[key])
    }
    catch (e) {
      obj[key] = ''
    }
  })
  return obj
}

/*
 * this.buildConfig           - Compiled Object from quasar.conf.js
 * this.webpackConfig         - Webpack config object for main thread
 * this.electronWebpackConfig - Webpack config object for electron main thread
 */

class QuasarConfig {
  constructor (opts) {
    this.filename = appPaths.resolve.app('quasar.conf.js')
    this.pkg = require(appPaths.resolve.app('package.json'))
    this.opts = opts
    this.ctx = getQuasarConfigCtx(opts)
    this.watch = opts.onBuildChange || opts.onAppChange

    if (this.watch) {
      // Start watching for quasar.config.js changes
      chokidar
      .watch(this.filename, { watchers: { chokidar: { ignoreInitial: true } } })
      .on('change', debounce(async () => {
        console.log()
        log(`quasar.conf.js changed`)

        try {
          await this.prepare()
        }
        catch (e) {
          if (e.message !== 'NETWORK_ERROR') {
            console.log(e)
            warn(`quasar.conf.js has JS errors. Please fix them then save file again.`)
            warn()
          }

          return
        }

        this.compile()

        if (this.webpackConfigChanged) {
          opts.onBuildChange()
        }
        else {
          opts.onAppChange()
        }
      }, 1000))

      if (this.ctx.mode.ssr) {
        this.ssrExtensionFile = appPaths.resolve.ssr('extension.js')

        chokidar
        .watch(this.ssrExtensionFile, { watchers: { chokidar: { ignoreInitial: true } } })
        .on('change', debounce(async () => {
          console.log()
          log(`src-ssr/extension.js changed`)

          try {
            this.readSSRextension()
          }
          catch (e) {
            if (e.message !== 'NETWORK_ERROR') {
              console.log(e)
              warn(`src-ssr/extension.js has JS errors. Please fix them then save file again.`)
              warn()
            }

            return
          }

          opts.onBuildChange()
        }, 1000))
      }
    }
  }

  // synchronous for build
  async prepare () {
    this.readConfig()

    if (this.watch && this.ctx.mode.ssr) {
      this.readSSRextension()
    }

    const cfg = merge({
      ctx: this.ctx,
      css: false,
      plugins: false,
      animations: false,
      extras: false
    }, this.quasarConfigFunction(this.ctx))

    if (cfg.framework === void 0 || cfg.framework === 'all') {
      cfg.framework = {
        all: true
      }
    }
    cfg.framework.config = cfg.framework.config || {}

    if (this.ctx.dev) {
      cfg.devServer = cfg.devServer || {}

      if (this.opts.host) {
        cfg.devServer.host = this.opts.host
      }
      else if (!cfg.devServer.host) {
        cfg.devServer.host = '0.0.0.0'
      }

      if (this.opts.port) {
        cfg.devServer.port = this.opts.port
      }
      else if (!cfg.devServer.port) {
        cfg.devServer.port = 8080
      }

      if (
        this.address &&
        this.address.from.host === cfg.devServer.host &&
        this.address.from.port === cfg.devServer.port
      ) {
        cfg.devServer.host = this.address.to.host
        cfg.devServer.port = this.address.to.port
      }
      else {
        const addr = {
          host: cfg.devServer.host,
          port: cfg.devServer.port
        }
        const to = await this.opts.onAddress(addr)

        // if network error while running
        if (to === null) {
          throw new Error('NETWORK_ERROR')
        }

        cfg.devServer = merge(cfg.devServer, to)
        this.address = {
          from: addr,
          to: {
            host: cfg.devServer.host,
            port: cfg.devServer.port
          }
        }
      }
    }

    this.quasarConfig = cfg
  }

  getBuildConfig () {
    return this.buildConfig
  }

  getWebpackConfig () {
    return this.webpackConfig
  }

  readConfig () {
    log(`Reading quasar.conf.js`)

    if (fs.existsSync(this.filename)) {
      delete require.cache[this.filename]
      this.quasarConfigFunction = require(this.filename)
    }
    else {
      warn(`⚠️  [FAIL] Could not load quasar.conf.js config file`)
      process.exit(1)
    }
  }

  readSSRextension () {
    log(`Reading src-ssr/extension.js`)

    if (fs.existsSync(this.ssrExtensionFile)) {
      delete require.cache[this.ssrExtensionFile]
      this.ssrExtension = require(this.ssrExtensionFile)
    }
    else {
      warn(`⚠️  [FAIL] Could not load src-ssr/extension.js file`)
      process.exit(1)
    }
  }

  compile () {
    let cfg = this.quasarConfig

    // if watching for changes,
    // then determine the type (webpack related or not)
    if (this.watch) {
      const newConfigSnapshot = [
        cfg.build ? encode(cfg.build) : '',
        cfg.ssr ? cfg.ssr.pwa : '',
        cfg.framework.all,
        cfg.devServer ? encode(cfg.devServer) : '',
        cfg.pwa ? encode(cfg.pwa) : '',
        cfg.electron ? encode(cfg.electron) : ''
      ].join('')

      if (this.oldConfigSnapshot) {
        this.webpackConfigChanged = newConfigSnapshot !== this.oldConfigSnapshot
      }

      this.oldConfigSnapshot = newConfigSnapshot
    }

    // make sure it exists
    cfg.supportIE = this.ctx.mode.electron
      ? false
      : (cfg.supportIE || false)

    cfg.vendor = merge({
      vendor: {
        add: false,
        remove: false
      }
    }, cfg.vendor || {})

    if (cfg.vendor.add) {
      cfg.vendor.add = cfg.vendor.add.filter(v => v).join('|')
      if (cfg.vendor.add) {
        cfg.vendor.add = new RegExp(cfg.vendor.add)
      }
    }
    if (cfg.vendor.remove) {
      cfg.vendor.remove = cfg.vendor.remove.filter(v => v).join('|')
      if (cfg.vendor.remove) {
        cfg.vendor.remove = new RegExp(cfg.vendor.remove)
      }
    }

    if (cfg.css) {
      cfg.css = cfg.css.filter(_ => _).map(
        asset => asset[0] === '~' ? asset.substring(1) : `src/css/${asset}`
      )

      if (cfg.css.length === 0) {
        cfg.css = false
      }
    }

    if (cfg.plugins) {
      cfg.plugins = cfg.plugins.filter(_ => _).map(asset => {
        return typeof asset === 'string'
          ? { path: asset }
          : asset
      }).filter(asset => asset.path)

      if (cfg.plugins.length === 0) {
        cfg.plugins = false
      }
    }

    cfg.build = merge({
      showProgress: true,
      scopeHoisting: true,
      productName: this.pkg.productName,
      productDescription: this.pkg.description,
      extractCSS: this.ctx.prod,
      sourceMap: this.ctx.dev,
      minify: this.ctx.prod,
      distDir: path.join('dist', `${this.ctx.modeName}-${this.ctx.themeName}`),
      htmlFilename: 'index.html',
      webpackManifest: this.ctx.prod,
      vueRouterMode: 'hash',
      preloadChunks: true,
      devtool: this.ctx.dev
        ? '#cheap-module-eval-source-map'
        : '#source-map',
      env: {
        NODE_ENV: `"${this.ctx.prod ? 'production' : 'development'}"`,
        CLIENT: true,
        SERVER: false,
        DEV: this.ctx.dev,
        PROD: this.ctx.prod,
        THEME: `"${this.ctx.themeName}"`,
        MODE: `"${this.ctx.modeName}"`
      },
      uglifyOptions: {
        compress: {
          // turn off flags with small gains to speed up minification
          arrows: false,
          collapse_vars: false, // 0.3kb
          comparisons: false,
          computed_props: false,
          hoist_funs: false,
          hoist_props: false,
          hoist_vars: false,
          inline: false,
          loops: false,
          negate_iife: false,
          properties: false,
          reduce_funcs: false,
          reduce_vars: false,
          switches: false,
          toplevel: false,
          typeofs: false,

          // a few flags with noticable gains/speed ratio
          // numbers based on out of the box vendor bundle
          booleans: true, // 0.7kb
          if_return: true, // 0.4kb
          sequences: true, // 0.7kb
          unused: true, // 2.3kb

          // required features to drop conditional branches
          conditionals: true,
          dead_code: true,
          evaluate: true
        },
        mangle: {
          /*
            Support non-standard Safari 10/11.
            By default `uglify-es` will not work around
            Safari 10/11 bugs.
          */
          safari10: true
        }
      }
    }, cfg.build || {})

    cfg.__loadingBar = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('LoadingBar'))
    cfg.__meta = cfg.framework.all || (cfg.framework.plugins && cfg.framework.plugins.includes('Meta'))

    if (this.ctx.dev || this.ctx.debug) {
      Object.assign(cfg.build, {
        minify: false,
        extractCSS: false,
        gzip: false
      })
    }
    if (this.ctx.debug) {
      cfg.build.sourceMap = true
    }

    if (this.ctx.mode.ssr) {
      Object.assign(cfg.build, {
        extractCSS: false,
{'fraction_non_alphanumeric': 0.13853952465209698, 'fraction_numerical': 0.004112667973793697, 'mean_word_length': 1.8603474216933389, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1960463', 'n_tokens_mistral': 6784, 'n_tokens_neox': 6651, 'n_words': 1339}