/*!
 * @nuxt/cli v2.15.8 (c) 2016-2021
 * Released under the MIT License
 * Repository: https://github.com/nuxt/nuxt.js
 * Website: https://nuxtjs.org
*/
'use strict';

const utils = require('@nuxt/utils');
const consola = require('consola');
const index = require('./cli-index.js');
const path = require('path');
const upath = require('upath');
const fs = require('fs-extra');
const crc32 = require('crc/lib/crc32');
const globby = require('globby');
const destr = require('destr');
require('@nuxt/config');
require('exit');
require('chalk');
require('std-env');
require('wrap-ansi');
require('boxen');
require('minimist');
require('hable');
require('defu');
require('semver');
require('fs');
require('execa');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

const consola__default = /*#__PURE__*/_interopDefaultLegacy(consola);
const path__default = /*#__PURE__*/_interopDefaultLegacy(path);
const upath__default = /*#__PURE__*/_interopDefaultLegacy(upath);
const fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
const crc32__default = /*#__PURE__*/_interopDefaultLegacy(crc32);
const globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
const destr__default = /*#__PURE__*/_interopDefaultLegacy(destr);

async function generate$1 (cmd) {
  const nuxt = await getNuxt({ server: true }, cmd);
  const generator = await cmd.getGenerator(nuxt);

  await nuxt.server.listen(0);
  const { errors } = await generator.generate({ build: false });
  await nuxt.close();
  if (cmd.argv['fail-on-error'] && errors.length > 0) {
    throw new Error('Error generating pages, exiting with non-zero code')
  }
}

async function ensureBuild (cmd) {
  const nuxt = await getNuxt({ _build: true, server: false }, cmd);
  const { options } = nuxt;

  if (options.generate.cache === false || destr__default['default'](process.env.NUXT_BUILD) || cmd.argv['force-build']) {
    const builder = await cmd.getBuilder(nuxt);
    await builder.build();
    await nuxt.close();
    return
  }

  // Default build ignore files
  const ignore = [
    options.buildDir,
    options.dir.static,
    options.generate.dir,
    'node_modules',
    '.**/*',
    '.*',
    'README.md'
  ];

  // Extend ignore
  const { generate } = options;
  if (typeof generate.cache.ignore === 'function') {
    generate.cache.ignore = generate.cache.ignore(ignore);
  } else if (Array.isArray(generate.cache.ignore)) {
    generate.cache.ignore = generate.cache.ignore.concat(ignore);
  }
  await nuxt.callHook('generate:cache:ignore', generate.cache.ignore);

  // Take a snapshot of current project
  const snapshotOptions = {
    rootDir: options.rootDir,
    ignore: generate.cache.ignore.map(upath__default['default'].normalize),
    globbyOptions: generate.cache.globbyOptions
  };

  const currentBuildSnapshot = await snapshot(snapshotOptions);

  // Detect process.env usage in nuxt.config
  const processEnv = {};
  if (nuxt.options._nuxtConfigFile) {
    const configSrc = await fs__default['default'].readFile(nuxt.options._nuxtConfigFile);
    const envRegex = /process.env.(\w+)/g;
    let match;
    // eslint-disable-next-line no-cond-assign
    while (match = envRegex.exec(configSrc)) {
      processEnv[match[1]] = process.env[match[1]];
    }
  }

  // Current build meta
  const currentBuild = {
    // @ts-ignore
    nuxtVersion: nuxt.constructor.version,
    ssr: nuxt.options.ssr,
    target: nuxt.options.target,
    snapshot: currentBuildSnapshot,
    env: nuxt.options.env,
    'process.env': processEnv
  };

  // Check if build can be skipped
  const nuxtBuildFile = path__default['default'].resolve(nuxt.options.buildDir, 'build.json');
  if (fs__default['default'].existsSync(nuxtBuildFile)) {
    const previousBuild = destr__default['default'](fs__default['default'].readFileSync(nuxtBuildFile, 'utf-8')) || {};

    // Quick diff
    let needBuild = false;
    for (const field of ['nuxtVersion', 'ssr', 'target', 'env', 'process.env']) {
      if (JSON.stringify(previousBuild[field]) !== JSON.stringify(currentBuild[field])) {
        needBuild = true;
        consola__default['default'].info(`Doing webpack rebuild because ${field} changed`);
        break
      }
    }

    // Full snapshot diff
    if (!needBuild) {
      const changed = compareSnapshots(previousBuild.snapshot, currentBuild.snapshot);
      if (!changed) {
        consola__default['default'].success('Skipping webpack build as no changes detected');
        return
      } else {
        consola__default['default'].info(`Doing webpack rebuild because ${changed} modified`);
      }
    }
  }

  // Webpack build
  const builder = await cmd.getBuilder(nuxt);
  await builder.build();

  // Write build.json
  fs__default['default'].writeFileSync(nuxtBuildFile, JSON.stringify(currentBuild, null, 2), 'utf-8');

  await nuxt.close();
}

async function getNuxt (args, cmd) {
  const config = await cmd.getNuxtConfig({ dev: false, ...args });

  if (config.target === utils.TARGETS.static) {
    config._export = true;
  } else {
    config._legacyGenerate = true;
  }
  config.buildDir = (config.static && config.static.cacheDir) || path__default['default'].resolve(config.rootDir, 'node_modules/.cache/nuxt');
  config.build = config.build || {};
  // https://github.com/nuxt/nuxt.js/issues/7390
  config.build.parallel = false;
  config.build.transpile = config.build.transpile || [];
  if (!config.static || !config.static.cacheDir) {
    config.build.transpile.push('.cache/nuxt');
  }

  const nuxt = await cmd.getNuxt(config);

  return nuxt
}

function compareSnapshots (from, to) {
  const allKeys = Array.from(new Set([
    ...Object.keys(from).sort(),
    ...Object.keys(to).sort()
  ]));

  for (const key of allKeys) {
    if (JSON.stringify(from[key]) !== JSON.stringify(to[key])) {
      return key
    }
  }

  return false
}

async function snapshot ({ globbyOptions, ignore, rootDir }) {
  const snapshot = {};

  const files = await globby__default['default']('**/*.*', {
    ...globbyOptions,
    ignore,
    cwd: rootDir,
    absolute: true
  });

  await Promise.all(files.map(async (p) => {
    const key = path.relative(rootDir, p);
    try {
      const fileContent = await fs__default['default'].readFile(p);
      snapshot[key] = {
        checksum: await crc32__default['default'](fileContent).toString(16)
      };
    } catch (e) {
      // TODO: Check for other errors like permission denied
      snapshot[key] = {
        exists: false
      };
    }
  }));

  return snapshot
}

const generate = {
  name: 'generate',
  description: 'Generate a static web application (server-rendered)',
  usage: 'generate <dir>',
  options: {
    ...index.common,
    ...index.locking,
    build: {
      type: 'boolean',
      default: true,
      description: 'Only generate pages for dynamic routes, used for incremental builds. Generate has to be run once without this option before using it'
    },
    devtools: {
      type: 'boolean',
      default: false,
      description: 'Enable Vue devtools',
      prepare (cmd, options, argv) {
        options.vue = options.vue || {};
        options.vue.config = options.vue.config || {};
        if (argv.devtools) {
          options.vue.config.devtools = true;
        }
      }
    },
    quiet: {
      alias: 'q',
      type: 'boolean',
      description: 'Disable output except for errors',
      prepare (cmd, options, argv) {
        // Silence output when using --quiet
        options.build = options.build || {};
        if (argv.quiet) {
          options.build.quiet = true;
        }
      }
    },
    modern: {
      ...index.common.modern,
      description: 'Generate app in modern build (modern mode can be only client)',
      prepare (cmd, options, argv) {
        if (index.normalizeArg(argv.modern)) {
          options.modern = 'client';
        }
      }
    },
    'force-build': {
      type: 'boolean',
      default: false,
      description: 'Force to build the application with webpack'
    },
    'fail-on-error': {
      type: 'boolean',
      default: false,
      description: 'Exit with non-zero status code if there are errors when generating pages'
    }
  },
  async run (cmd) {
    const config = await cmd.getNuxtConfig({ dev: false });

    // Disable analyze if set by the nuxt config
    config.build = config.build || {};
    config.build.analyze = false;

    // Full static
    if (config.target === utils.TARGETS.static) {
      await ensureBuild(cmd);
      await generate$1(cmd);
      return
    }

    // Forcing static target anyway
    config.target = utils.TARGETS.static;
    consola__default['default'].warn(`When using \`nuxt generate\`, you should set \`target: 'static'\` in your \`nuxt.config\`\n       👉 Learn more about it on https://go.nuxtjs.dev/static-target`);

    // Set flag to keep the prerendering behaviour
    config._legacyGenerate = true;
    if (config.build) {
      // https://github.com/nuxt/nuxt.js/issues/7390
      config.build.parallel = false;
    }

    const nuxt = await cmd.getNuxt(config);

    if (cmd.argv.lock) {
      await cmd.setLock(await index.createLock({
        id: 'build',
        dir: nuxt.options.buildDir,
        root: config.rootDir
      }));

      nuxt.hook('build:done', async () => {
        await cmd.releaseLock();

        await cmd.setLock(await index.createLock({
          id: 'generate',
          dir: nuxt.options.generate.dir,
          root: config.rootDir
        }));
      });
    }

    const generator = await cmd.getGenerator(nuxt);
    await nuxt.server.listen(0);

    const { errors } = await generator.generate({
      init: true,
      build: cmd.argv.build
    });

    await nuxt.close();
    if (cmd.argv['fail-on-error'] && errors.length > 0) {
      throw new Error('Error generating pages, exiting with non-zero code')
    }
  }
};

exports.default = generate;
