text: #!/usr/bin/env node
  /**
   * Copyright (c) 2015-present, Facebook, Inc.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   */
  
  /**
   * Initially taken from the Create React App project.
   */
  
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //   /!\ DO NOT MODIFY THIS FILE /!\
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //
  // create-static-site is installed globally on people's computers. This means
  // that it is extremely difficult to have them upgrade the version and
  // because there's only one global version installed, it is very prone to
  // breaking changes.
  //
  // The only job of create-static-site is to init the repository and then
  // forward all the commands to the local version of create-static-site.
  //
  // If you need to add a new command, please add it to the scripts/ folder.
  //
  // The only reason to modify this file is to add more warnings and
  // troubleshooting information for the `create-static-site` command.
  //
  // Do not make breaking changes! We absolutely don't want to have to
  // tell people to update their global version of create-static-site.
  //
  // Also be careful with new language features.
  // This file must work on Node 6+.
  //
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //   /!\ DO NOT MODIFY THIS FILE /!\
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  "use strict"
  
  const validateProjectName = require("validate-npm-package-name")
  const chalk = require("chalk")
  const commander = require("commander")
  const fs = require("fs-extra")
  const path = require("path")
  const execSync = require("child_process").execSync
  const spawn = require("cross-spawn")
  const semver = require("semver")
  const dns = require("dns")
  const tmp = require("tmp")
  const unpack = require("tar-pack").unpack
  const url = require("url")
  const hyperquest = require("hyperquest")
  
  const packageJson = require("./package.json")
  
  let projectName
  
  const program = new commander.Command(packageJson.name)
    .version(packageJson.version)
    .arguments("<project-directory>")
    .usage(`${chalk.green("<project-directory>")} [options]`)
    .action(name => {
      projectName = name
    })
    .option("--template <template>", "hugo or jekyll")
    .option("--verbose", "print additional logs")
    .option(
      "--scripts-version <alternative-package>",
      "use a non-standard version of static-scripts"
    )
    .option("--use-npm")
    .allowUnknownOption()
    .on("--help", () => {
      console.log(`    Only ${chalk.green("<project-directory>")} is required.`)
      console.log()
      console.log(
        `    A custom ${chalk.cyan("--scripts-version")} can be one of:`
      )
      console.log(`      - a specific npm version: ${chalk.green("0.8.2")}`)
      console.log(
        `      - a custom fork published on npm: ${chalk.green(
          "my-static-scripts"
        )}`
      )
      console.log(
        `      - a .tgz archive: ${chalk.green(
          "https://mysite.com/my-static-scripts-0.8.2.tgz"
        )}`
      )
      console.log(
        `    It is not needed unless you specifically want to use a fork.`
      )
      console.log()
      console.log(
        `    If you have any problems, do not hesitate to file an issue:`
      )
      console.log(
        `      ${chalk.cyan(
          "https://github.com/forestryio/create-static-site/issues/new"
        )}`
      )
      console.log()
    })
    .parse(process.argv)
  
  if (typeof projectName === "undefined") {
    console.error("Please specify the project directory:")
    console.log(
      `  ${chalk.cyan(program.name())} ${chalk.green(
        "<project-directory> --template=<template>"
      )}`
    )
    console.log()
    console.log("For example:")
    console.log(
      `  ${chalk.cyan(program.name())} ${chalk.green(
        "my-hugo-site --template=hugo"
      )}`
    )
    console.log()
    console.log(
      `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
    )
    process.exit(1)
  }
  if (typeof program.template === "undefined") {
    console.error("Please specify the project template:")
    console.log(
      `  ${chalk.cyan(program.name())} ${chalk.green(
        "<project-directory> --template=<template>"
      )}`
    )
    console.log()
    console.log("For example:")
    console.log(
      `  ${chalk.cyan(program.name())} ${chalk.green(
        "my-hugo-site --template=hugo"
      )}`
    )
    console.log()
    console.log(
      `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
    )
    process.exit(1)
  }
  
  function printValidationResults(results) {
    if (typeof results !== "undefined") {
      results.forEach(error => {
        console.error(chalk.red(`  *  ${error}`))
      })
    }
  }
  
  createApp(
    projectName,
    program.verbose,
    program.scriptsVersion,
    program.useNpm,
    program.template
  )
  
  function createApp(name, verbose, version, useNpm, template) {
    const root = path.resolve(name)
    const appName = path.basename(root)
  
    checkAppName(appName)
    fs.ensureDirSync(name)
    if (!isSafeToCreateProjectIn(root, name)) {
      process.exit(1)
    }
  
    console.log(`Creating a new Static Site in ${chalk.green(root)}.`)
    console.log()
  
    const packageJson = {
      name: appName,
      version: "0.1.0",
    }
    fs.writeFileSync(
      path.join(root, "package.json"),
      JSON.stringify(packageJson, null, 2)
    )
  
    const useYarn = useNpm ? false : shouldUseYarn()
    const originalDirectory = process.cwd()
    process.chdir(root)
    if (!useYarn && !checkThatNpmCanReadCwd()) {
      process.exit(1)
    }
  
    if (!semver.satisfies(process.version, ">=6.0.0")) {
      console.log(
        chalk.yellow(
          `You are using Node ${
            process.version
          } so the project will be bootstrapped with an old unsupported version of tools.\n\n` +
            `Please update to Node 6 or higher for a better, fully supported experience.\n`
        )
      )
      process.exit(1)
    }
  
    if (!useYarn) {
      const npmInfo = checkNpmVersion()
      if (!npmInfo.hasMinNpm) {
        if (npmInfo.npmVersion) {
          console.log(
            chalk.yellow(
              `You are using npm ${
                npmInfo.npmVersion
              } so the project will be boostrapped with an old unsupported version of tools.\n\n` +
                `Please update to npm 3 or higher for a better, fully supported experience.\n`
            )
          )
        }
        // Fall back to latest supported static-scripts for npm 3
        process.exit(1)
      }
    }
    run(root, appName, version, verbose, originalDirectory, template, useYarn)
  }
  
  function shouldUseYarn() {
    try {
      execSync("yarnpkg --version", { stdio: "ignore" })
      return true
    } catch (e) {
      return false
    }
  }
  
  function install(root, useYarn, dependencies, verbose, isOnline) {
    return new Promise((resolve, reject) => {
      let command
      let args
      if (useYarn) {
        command = "yarnpkg"
        args = ["add", "--dev", "--exact"]
        if (!isOnline) {
          args.push("--offline")
        }
        ;[].push.apply(args, dependencies)
  
        // Explicitly set cwd() to work around issues like
        // https://github.com/forestryio/create-static-site/issues/3326.
        // Unfortunately we can only do this for Yarn because npm support for
        // equivalent --prefix flag doesn't help with this issue.
        // This is why for npm, we run checkThatNpmCanReadCwd() early instead.
        args.push("--cwd")
        args.push(root)
  
        if (!isOnline) {
          console.log(chalk.yellow("You appear to be offline."))
          console.log(chalk.yellow("Falling back to the local Yarn cache."))
          console.log()
        }
      } else {
        command = "npm"
        args = [
          "install",
          `--save-dev`,
          "--save-exact",
          "--loglevel",
          "error",
        ].concat(dependencies)
      }
  
      if (verbose) {
        args.push("--verbose")
      }
  
      const child = spawn(command, args, { stdio: "inherit" })
      child.on("close", code => {
        if (code !== 0) {
          reject({
            command: `${command} ${args.join(" ")}`,
          })
          return
        }
        resolve()
      })
    })
  }
  
  function run(
    root,
    appName,
    version,
    verbose,
    originalDirectory,
    template,
    useYarn
  ) {
    const packageToInstall = getInstallPackage(version)
  
    console.log("Installing packages. This might take a couple of minutes.")
    getPackageName(packageToInstall)
      .then(packageName =>
        checkIfOnline(useYarn).then(isOnline => ({
          isOnline: isOnline,
          packageName: packageName,
        }))
      )
      .then(info => {
        const isOnline = info.isOnline
        const packageName = info.packageName
        console.log(`Installing ${chalk.cyan(packageName)}...`)
        console.log()
        const devDependencies = [packageToInstall]
        return install(
          root,
          useYarn,
          devDependencies,
          verbose,
          isOnline,
          true
        ).then(() => packageName)
      })
      .then(packageName => {
        checkNodeVersion(packageName)
        setCaretRangeForRuntimeDeps(packageName)
  
        const scriptsPath = path.resolve(
          process.cwd(),
          "node_modules",
          packageName,
          "scripts",
          "init.js"
        )
        const init = require(scriptsPath)
        init(root, appName, verbose, originalDirectory, template)
      })
      .catch(reason => {
        console.log()
        console.log("Aborting installation.")
        if (reason.command) {
          console.log(`  ${chalk.cyan(reason.command)} has failed.`)
        } else {
          console.log(chalk.red("Unexpected error. Please report it as a bug:"))
          console.log(reason)
        }
        console.log()
  
        // On 'exit' we will delete these files from target directory.
        const knownGeneratedFiles = [
          "package.json",
          "npm-debug.log",
          "yarn-error.log",
          "yarn-debug.log",
          "node_modules",
        ]
        const currentFiles = fs.readdirSync(path.join(root))
        currentFiles.forEach(file => {
          knownGeneratedFiles.forEach(fileToMatch => {
            // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files
            // and the rest of knownGeneratedFiles.
            if (
              (fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) ||
              file === fileToMatch
            ) {
              console.log(`Deleting generated file... ${chalk.cyan(file)}`)
              fs.removeSync(path.join(root, file))
            }
          })
        })
        const remainingFiles = fs.readdirSync(path.join(root))
        if (!remainingFiles.length) {
          // Delete target folder if empty
          console.log(
            `Deleting ${chalk.cyan(`${appName} /`)} from ${chalk.cyan(
              path.resolve(root, "..")
            )}`
          )
          process.chdir(path.resolve(root, ".."))
          fs.removeSync(path.join(root))
        }
        console.log("Done.")
        process.exit(1)
      })
  }
  
  function getInstallPackage(version) {
    let packageToInstall = "static-scripts"
    const validSemver = semver.valid(version)
    if (validSemver) {
      packageToInstall += `@${validSemver}`
    } else if (version) {
      // for tar.gz or alternative paths
      packageToInstall = version
    }
    return packageToInstall
  }
  
  function getTemporaryDirectory() {
    return new Promise((resolve, reject) => {
      // Unsafe cleanup lets us recursively delete the directory if it contains
      // contents; by default it only allows removal if it's empty
      tmp.dir({ unsafeCleanup: true }, (err, tmpdir, callback) => {
        if (err) {
          reject(err)
        } else {
          resolve({
            tmpdir: tmpdir,
            cleanup: () => {
              try {
                callback()
              } catch (ignored) {
                // Callback might throw and fail, since it's a temp directory the
                // OS will clean it up eventually...
              }
            },
          })
        }
      })
    })
  }
  
  function extractStream(stream, dest) {
    return new Promise((resolve, reject) => {
      stream.pipe(
        unpack(dest, err => {
          if (err) {
            reject(err)
          } else {
            resolve(dest)
          }
        })
      )
    })
  }
  
  // Extract package name from tarball url or path.
  function getPackageName(installPackage) {
    if (installPackage.indexOf(".tgz") > -1) {
      return getTemporaryDirectory()
        .then(obj => {
          let stream
          if (/^http/.test(installPackage)) {
            stream = hyperquest(installPackage)
          } else {
            stream = fs.createReadStream(installPackage)
          }
          return extractStream(stream, obj.tmpdir).then(() => obj)
        })
        .then(obj => {
          const packageName = require(path.join(obj.tmpdir, "package.json")).name
          obj.cleanup()
          return packageName
        })
        .catch(err => {
          // The package name could be with or without semver version, e.g. static-scripts-0.2.0-alpha.1.tgz
          // However, this function returns package name only without semver version.
          console.log(
            `Could not extract the package name from the archive: ${err.message}`
          )
          const assumedProjectName = installPackage.match(
            /^.+\/(.+?)(?:-\d+.+)?\.tgz$/
          )[1]
          console.log(
            `Based on the filename, assuming it is "${chalk.cyan(
              assumedProjectName
            )}"`
          )
          return Promise.resolve(assumedProjectName)
        })
    } else if (installPackage.indexOf("git+") === 0) {
      // Pull package name out of git urls e.g:
      // git+https://github.com/mycompany/static-scripts.git
      // git+ssh://github.com/mycompany/static-scripts.git#v1.2.3
      return Promise.resolve(installPackage.match(/([^/]+)\.git(#.*)?$/)[1])
    } else if (installPackage.match(/.+@/)) {
      // Do not match @scope/ when stripping off @version or @tag
      return Promise.resolve(
        installPackage.charAt(0) + installPackage.substr(1).split("@")[0]
      )
    }
    return Promise.resolve(installPackage)
  }
  
  function checkNpmVersion() {
    let hasMinNpm = false
    let npmVersion = null
    try {
      npmVersion = execSync("npm --version")
        .toString()
        .trim()
      hasMinNpm = semver.gte(npmVersion, "3.0.0")
    } catch (err) {
      // ignore
    }
    return {
      hasMinNpm: hasMinNpm,
      npmVersion: npmVersion,
    }
  }
  
  function checkNodeVersion(packageName) {
    const packageJsonPath = path.resolve(
      process.cwd(),
      "node_modules",
      packageName,
      "package.json"
    )
    const packageJson = require(packageJsonPath)
    if (!packageJson.engines || !packageJson.engines.node) {
      return
    }
  
    if (!semver.satisfies(process.version, packageJson.engines.node)) {
      console.error(
        chalk.red(
          "You are running Node %s.\n" +
            "Create Static Sites requires Node %s or higher. \n" +
            "Please update your version of Node."
        ),
        process.version,
        packageJson.engines.node
      )
      process.exit(1)
    }
  }
  
  function checkAppName(appName) {
    const validationResult = validateProjectName(appName)
    if (!validationResult.validForNewPackages) {
      console.error(
        `Could not create a project called ${chalk.red(
          `"${appName}"`
        )} because of npm naming restrictions:`
      )
      printValidationResults(validationResult.errors)
      printValidationResults(validationResult.warnings)
      process.exit(1)
    }
  
    // TODO: there should be a single place that holds the dependencies
    const dependencies = ["static-scripts"].sort()
    if (dependencies.indexOf(appName) >= 0) {
      console.error(
        chalk.red(
          `We cannot create a project called ${chalk.green(
            appName
          )} because a dependency with the same name exists.\n` +
            `Due to the way npm works, the following names are not allowed:\n\n`
        ) +
          chalk.cyan(dependencies.map(depName => `  ${depName}`).join("\n")) +
          chalk.red("\n\nPlease choose a different project name.")
      )
      process.exit(1)
    }
  }
  
  function makeCaretRange(dependencies, name) {
    const version = dependencies[name]
  
    if (typeof version === "undefined") {
      console.error(chalk.red(`Missing ${name} dependency in package.json`))
      process.exit(1)
    }
  
    let patchedVersion = `^${version}`
  
    if (!semver.validRange(patchedVersion)) {
      console.error(
        `Unable to patch ${name} dependency version because version ${chalk.red(
          version
        )} will become invalid ${chalk.red(patchedVersion)}`
      )
      patchedVersion = version
    }
  
    dependencies[name] = patchedVersion
  }
  
  function setCaretRangeForRuntimeDeps(packageName) {
    const packagePath = path.join(process.cwd(), "package.json")
    const packageJson = require(packagePath)
  
    if (typeof packageJson.devDependencies === "undefined") {
      console.error(chalk.red("Missing devDependencies in package.json"))
      process.exit(1)
    }
  
    const packageVersion = packageJson.devDependencies[packageName]
    if (typeof packageVersion === "undefined") {
      console.error(chalk.red(`Unable to find ${packageName} in package.json`))
      process.exit(1)
    }
  
    // makeCaretRange(packageJson.devDependencies, "typescript")
  
    fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2))
  }
  
  // If project only contains files generated by GH, it’s safe.
  // We also special case IJ-based products .idea because it integrates with CRA:
  // https://github.com/forestryio/create-static-site/pull/368#issuecomment-243446094
  function isSafeToCreateProjectIn(root, name) {
    const validFiles = [
      ".DS_Store",
      "Thumbs.db",
      ".git",
      ".gitignore",
      ".idea",
      "README.md",
      "LICENSE",
      "web.iml",
      ".hg",
      ".hgignore",
      ".hgcheck",
    ]
    console.log()
  
    const conflicts = fs
      .readdirSync(root)
      .filter(file => !validFiles.includes(file))
    if (conflicts.length < 1) {
      return true
    }
  
    console.log(
      `The directory ${chalk.green(name)} contains files that could conflict:`
    )
    console.log()
    for (const file of conflicts) {
      console.log(`  ${file}`)
    }
    console.log()
    console.log(
      "Either try using a new directory name, or remove the files listed above."
    )
  
    return false
  }
  
  function checkThatNpmCanReadCwd() {
    const cwd = process.cwd()
    let childOutput = null
    try {
      // Note: intentionally using spawn over exec since
      // the problem doesn't reproduce otherwise.
      // `npm config list` is the only reliable way I could find
      // to reproduce the wrong path. Just printing process.cwd()
      // in a Node process was not enough.
      childOutput = spawn.sync("npm", ["config", "list"]).output.join("")
    } catch (err) {
      // Something went wrong spawning node.
      // Not great, but it means we can't do this check.
      // We might fail later on, but let's continue.
      return true
    }
    if (typeof childOutput !== "string") {
      return true
    }
    const lines = childOutput.split("\n")
    // `npm config list` output includes the following line:
    // "; cwd = C:\path\to\current\dir" (unquoted)
    // I couldn't find an easier way to get it.
    const prefix = "; cwd = "
    const line = lines.find(line => line.indexOf(prefix) === 0)
    if (typeof line !== "string") {
      // Fail gracefully. They could remove it.
      return true
    }
    const npmCWD = line.substring(prefix.length)
    if (npmCWD === cwd) {
      return true
    }
    console.error(
      chalk.red(
        `Could not start an npm process in the right directory.\n\n` +
          `The current directory is: ${chalk.bold(cwd)}\n` +
          `However, a newly started npm process runs in: ${chalk.bold(
            npmCWD
          )}\n\n` +
          `This is probably caused by a misconfigured system terminal shell.`
      )
    )
    if (process.platform === "win32") {
      console.error(
        chalk.red(`On Windows, this can usually be fixed by running:\n\n`) +
          `  ${chalk.cyan(
            "reg"
          )} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
          `  ${chalk.cyan(
            "reg"
          )} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
          chalk.red(`Try to run the above two lines in the terminal.\n`) +
          chalk.red(
            `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
          )
      )
    }
    return false
  }
  
  function checkIfOnline(useYarn) {
    if (!useYarn) {
      // Don't ping the Yarn registry.
      // We'll just assume the best case.
      return Promise.resolve(true)
    }
  
    return new Promise(resolve => {
      dns.lookup("registry.yarnpkg.com", err => {
        if (err != null && process.env.https_proxy) {
          // If a proxy is defined, we likely can't resolve external hostnames.
          // Try to resolve the proxy name as an indication of a connection.
          dns.lookup(url.parse(process.env.https_proxy).hostname, proxyErr => {
            resolve(proxyErr == null)
          })
        } else {
          resolve(err == null)
        }
      })
    })
  }
  