<commit_before>/*
 * Copyright 2014-2018 Simon Edwards <simon@simonzone.com>
 *
 * This source code is licensed under the MIT license which is detailed in the LICENSE.txt file.
 */
require('shelljs/global');
const fs = require('fs');
const path = require('path');
const packager = require('electron-packager');
const getRepoInfo = require('git-repo-info');

const log = console.log.bind(console);
const BUILD_TMP = 'build_tmp';
const MODULE_VERSON = 53; // This version number also appears in thememanager.ts

function main() {
  "use strict";

  if ( ! test('-f', './package.json')) {
    echo("This script was called from the wrong directory.");
    return;
  }

  const linuxZipOnly = process.argv.indexOf("--linux-zip-only") !== -1;

  const srcRootDir = "" + pwd();
  if (test('-d', BUILD_TMP)) {
    rm('-rf', BUILD_TMP);
  }
  mkdir(BUILD_TMP);

  const packageJson = fs.readFileSync('package.json');
  const packageData = JSON.parse(packageJson);

  const gitUrl = packageData.repository.url.replace("git://github.com/", "https://github.com/");
  const info = getRepoInfo();

  echo("Fetching a clean copy of the source code from " + gitUrl);

  cd(BUILD_TMP);

  // For some reason pwd() is returning "not quite strings" which path.join() doesn't like. Thus "" + ...
  const buildTmpPath = "" + pwd();

  exec("git clone --depth 1 -b " + info.branch + " " + gitUrl);
  cd("extraterm");

  echo("Setting up the run time dependencies in " + BUILD_TMP);

  exec("yarn install");
  exec("yarn run electron-rebuild");
  exec("yarn run build");

  echo("Removing development dependencies");
  exec("yarn install --production=true");

  // Create the commands zip
  echo("Creating commands.zip");
  const commandsDir = packageData.name + "-commands-" + packageData.version;
  cp("-r", "extraterm/src/commands", path.join(buildTmpPath, commandsDir));
  const codeDir = pwd();
  cd(buildTmpPath);
  exec(`zip -y -r ${commandsDir}.zip ${commandsDir}`);
  cd(codeDir);

  const electronVersion = packageData.devDependencies['electron'];

  const ignoreRegExp = [
    /^\/build_scripts\b/,
    /^\/extraterm-web-component-decorators\b/,
    /^\/extraterm-extension-api\b/,
    /^\/test\b/,
    /^\/build_tmp\b/,
    /^\/src\/typedocs\b/,
    /\.ts$/,
    /\.js\.map$/,
    /^\/\.git\//,
    /^\/docs\b/,
    /^\/resources\/extra_icons\b/,
    /^\/src\/test\b/,
    /^\/src\/testfiles\b/
  ];

  const ignoreFunc = function ignoreFunc(filePath) {
    const result = ignoreRegExp.some( (exp) => exp.test(filePath));
    return result;
  };

  function appDir(platform) {
    return platform === "darwin" ? "Extraterm.app/Contents/Resources/app" : "resources/app";
  }

  function pruneNodeSass(versionedOutputDir, arch, platform) {
    const gutsDir = appDir(platform);
    const nodeSassVendorDir = path.join(versionedOutputDir, gutsDir, "node_modules/node-sass/vendor");

    rm('-rf', nodeSassVendorDir);

    const nodeSassBinaryDir = path.join(versionedOutputDir, gutsDir, "src/node-sass-binary");
    ["darwin-x64", "linux-ia32", "linux-x64", "win32-x64"].forEach( (name) => {
      if (name !== platform + "-" + arch) {
        rm('-rf', path.join(nodeSassBinaryDir, name + "-" + MODULE_VERSON));
      }
    });
  }

  function pruneEmojiOne(versionedOutputDir, platform) {
    if (platform !== "linux") {
      const emojiOnePath = path.join(versionedOutputDir, appDir(platform), "extraterm/resources/themes/default/emojione-android.ttf");
      rm(emojiOnePath);
    }
  }

  function hoistSubprojectsModules(versionedOutputDir, platform) {
    const modulesDir = path.join(versionedOutputDir, appDir(platform), "node_modules");

    // Delete the symlinks.
    for (const item of ls(modulesDir)) {
      const itemPath = path.join(modulesDir, item);
      if (test('-L', itemPath)) {
        echo(`Deleting symlink ${item} in ${modulesDir}`);
        rm(itemPath);
      }
    }

    // Move the 'packages' subprojects up into this node_modules dir.
    const packagesDir = path.join(versionedOutputDir, appDir(platform), "packages");
    for (const item of ls(packagesDir)) {
      const destDir = path.join(modulesDir, item);
      echo(`Moving ${item} in to ${destDir}`);
      mv(path.join(packagesDir, item), destDir);
    }
  }

  function pruneNodeModules(versionedOutputDir, platform) {
    const prevDir = pwd();

    cd(path.join(versionedOutputDir, appDir(platform)));
    exec("modclean -n default:safe -r");
    pruneSpecificNodeModules();

    cd(prevDir);
  }

  function pruneSpecificNodeModules() {
    [
      "codemirror/src",
      "node-sass/src",
      "node-sass/node_modules/nan",
      "node-sass/vendor",
      "node-gyp",
      "ajv",
      "globule",
      "vue/src",
      "vue/dist/vue.esm.browser.js",
      "vue/dist/vue.esm.js",
      "vue/dist/vue.js",
      "vue/dist/vue.min.js",
      "vue/dist/vue.runtime.esm.js",
      "vue/dist/vue.runtime.js",
      "vue/dist/vue.runtime.min.js",
      "font-manager/src",
      ".bin"
    ].forEach( (subpath) => {
      const fullPath = path.join("node_modules", subpath);

      echo("Deleting " + fullPath);

      if (test('-d', fullPath)) {
        rm('-rf', fullPath);
      } else if (test('-f', fullPath)) {
          rm(fullPath);
      } else {
        echo("----------- Unable to find path "+ fullPath);
      }
    });

  }

  function makePackage(arch, platform) {
    log("");
    return new Promise(function(resolve, reject) {

      // Clean up the output dirs and files first.
      const versionedOutputDir = packageData.name + "-" + packageData.version + "-" + platform + "-" + arch;
      if (test('-d', versionedOutputDir)) {
        rm('-rf', versionedOutputDir);
      }

      const outputZip = path.join(buildTmpPath, versionedOutputDir + ".zip");

      const packagerOptions = {
        arch: arch,
        dir: ".",
        platform: platform,
        version: electronVersion,
        ignore: ignoreFunc,
        name: platform === "darwin" ? "Extraterm" : "extraterm",
        overwrite: true,
        out: buildTmpPath,
        packageManager: "yarn",
        afterPrune: [
          (buildPath, electronVersion, platform, arch, callback) => {
            replaceDirs(path.join(buildPath, "node_modules"), path.join("" + codeDir,`build_scripts/node_modules-${platform}-${arch}`));
            callback();
          }
        ]
      };
      if (platform === "win32") {
        packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.ico";
        packagerOptions.win32metadata = {
          FileDescription: "Extraterm",
          ProductName: "Extraterm",
          LegalCopyright: "(C) 2018 Simon Edwards"
        };
      } else if (platform === "darwin") {
        packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.icns";
      }

      packager(packagerOptions, function done(err, appPath) {
        if (err !== null) {
          log(err);
          reject();
        } else {
          // Rename the output dir to a one with a version number in it.
          mv(appPath[0], path.join(buildTmpPath, versionedOutputDir));

          const thisCD = pwd();
          cd(buildTmpPath);

          hoistSubprojectsModules(versionedOutputDir, platform);
          pruneNodeModules(versionedOutputDir, platform);

          // Prune any unneeded node-sass binaries.
          pruneNodeSass(versionedOutputDir, arch, platform);

          pruneEmojiOne(versionedOutputDir, platform);

          // Zip it up.
          log("Zipping up the package");

          mv(path.join(versionedOutputDir, "LICENSE"), path.join(versionedOutputDir, "LICENSE_electron.txt"));
          cp("extraterm/README.md", versionedOutputDir);
          cp("extraterm/LICENSE.txt", versionedOutputDir);

          exec(`zip -y -r ${outputZip} ${versionedOutputDir}`);
          cd(thisCD);

          log("App bundle written to " + versionedOutputDir);
          resolve();
        }
      });
    });
  }

  function replaceDirs(targetDir, replacementsDir) {
    const prevDir = pwd();
    cd(srcRootDir);
    const replacements = ls(replacementsDir);
    replacements.forEach( (rDir) => {
      const targetSubDir = path.join(targetDir, rDir);
      if (test('-d', targetSubDir)) {
        rm('-r', targetSubDir);
      }
      cp('-r', path.join(replacementsDir, rDir), targetSubDir);
    });
    cd(prevDir);
  }

  function makeDmg() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building dmg file for macOS");
    echo("---------------------------------------------------");

    const darwinPath = path.join(buildTmpPath, `extraterm-${packageData.version}-darwin-x64`);
    for (const f of ls(darwinPath)) {
      if ( ! f.endsWith(".app")) {
        echo(`Deleting ${f}`);
        rm(path.join(darwinPath, f));
      }
    }

    cp(path.join(srcRootDir, "build_scripts/resources/macos/.DS_Store"), path.join(darwinPath, ".DS_Store"));
    cp(path.join(srcRootDir, "build_scripts/resources/macos/.VolumeIcon.icns"), path.join(darwinPath, ".VolumeIcon.icns"));
    mkdir(path.join(darwinPath,".background"));
    cp(path.join(srcRootDir, "build_scripts/resources/macos/.background/extraterm_background.png"), path.join(darwinPath, ".background/extraterm_background.png"));

    ln("-s", "/Applications", path.join(darwinPath, "Applications"));

    exec(`docker run --rm -v "${buildTmpPath}:/files" sporsh/create-dmg Extraterm /files/extraterm-${packageData.version}-darwin-x64/ /files/extraterm_${packageData.version}.dmg`);
  }

  function makeNsis() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building NSIS based installer for Windows");
    echo("---------------------------------------------------");

    const windowsBuildDirName = `extraterm-${packageData.version}-win32-x64`;
    const windowsPath = path.join(buildTmpPath, windowsBuildDirName);

    const versionSplit = packageData.version.split(".");
    const majorVersion = versionSplit[0];
    const minorVersion = versionSplit[1];
    const patchVersion = versionSplit[2];

    const installerScript = `
!include "MUI2.nsh"
!include "FileFunc.nsh"

!define APPNAME "Extraterm"
!define DESCRIPTION "Terminal emulator"
!define COMPANYNAME "extraterm.org"
!define VERSIONMAJOR ${majorVersion}
!define VERSIONMINOR ${minorVersion}
!define VERSIONBUILD ${patchVersion}

!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!define MUI_INSTFILESPAGE_COLORS "3db54a 000000"
!define MUI_ICON "${windowsBuildDirName}\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Name "Extraterm"
BrandingText " "
OutFile "extraterm-setup-${packageData.version}.exe"
InstallDir "$PROGRAMFILES64\\Extraterm"
InstallDirRegKey HKLM "Software\\Extraterm" "InstallLocation"

ShowInstDetails show # This will always show the installation details.

Section "Extraterm"
  SetOutPath $INSTDIR
  File /r "${windowsBuildDirName}\\*"

  WriteUninstaller "$INSTDIR\\Uninstall.exe"

  createShortCut "$SMPROGRAMS\\Extraterm.lnk" "$INSTDIR\\extraterm.exe" "" "$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

  WriteRegStr HKLM "Software\\Extraterm" "InstallLocation" "$\\"$INSTDIR$\\""
  WriteRegStr HKLM "Software\\Extraterm" "Version" "${packageData.version}"

  # Registry information for add/remove programs
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayName" "\${APPNAME} - \${DESCRIPTION}"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "QuietUninstallString" "$\\"$INSTDIR\\uninstall.exe$\\" /S"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "InstallLocation" "$\\"$INSTDIR$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayIcon" "$\\"$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "Publisher" "\${COMPANYNAME}"

	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayVersion" "\${VERSIONMAJOR}.\${VERSIONMINOR}.\${VERSIONBUILD}"
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMajor" \${VERSIONMAJOR}
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMinor" \${VERSIONMINOR}
	# There is no option for modifying or repairing the install
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoModify" 1
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoRepair" 1
	# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size

  # Record the installation size
  \${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
  IntFmt $0 "0x%08X" $0
  WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "EstimatedSize" "\$0"

SectionEnd

Section "Uninstall"
  # Remove Start Menu launcher
	Delete "$SMPROGRAMS\\Extraterm.lnk"

  Delete "$INSTDIR\\*.*"
  Delete "$INSTDIR\\Uninstall.exe"
  RMDir /r "$INSTDIR"

  DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}"
  DeleteRegKey HKLM "Software\\Extraterm"
SectionEnd
`;
    fs.writeFileSync(path.join(buildTmpPath, "installer.nsi"), installerScript, {encoding: "utf-8"});

    exec(`docker run -t -v ${buildTmpPath}:/wine/drive_c/src/ cdrx/nsis`);
  }

  if (linuxZipOnly) {
    makePackage("x64", "linux");
  } else {
    makePackage("x64", "win32")
      .then(() => makePackage("x64", "linux"))
      .then(() => makePackage("x64", "darwin"))
      .then(makeDmg)
      .then(makeNsis)
      .then(() => { log("Done"); } );
  }
}

main();
<commit_msg>When packaging, get the git repo URL from git itself, not package.json.<commit_after>/*
 * Copyright 2014-2018 Simon Edwards <simon@simonzone.com>
 *
 * This source code is licensed under the MIT license which is detailed in the LICENSE.txt file.
 */
require('shelljs/global');
const fs = require('fs');
const path = require('path');
const packager = require('electron-packager');
const getRepoInfo = require('git-repo-info');

const log = console.log.bind(console);
const BUILD_TMP = 'build_tmp';
const MODULE_VERSON = 53; // This version number also appears in thememanager.ts

function main() {
  "use strict";

  if ( ! test('-f', './package.json')) {
    echo("This script was called from the wrong directory.");
    return;
  }

  const linuxZipOnly = process.argv.indexOf("--linux-zip-only") !== -1;

  const srcRootDir = "" + pwd();
  if (test('-d', BUILD_TMP)) {
    rm('-rf', BUILD_TMP);
  }
  mkdir(BUILD_TMP);

  const packageJson = fs.readFileSync('package.json');
  const packageData = JSON.parse(packageJson);

  const gitUrl = exec("git config --get remote.origin.url").trim();
  const info = getRepoInfo();

  echo("Fetching a clean copy of the source code from " + gitUrl);

  cd(BUILD_TMP);

  // For some reason pwd() is returning "not quite strings" which path.join() doesn't like. Thus "" + ...
  const buildTmpPath = "" + pwd();

  exec("git clone --depth 1 -b " + info.branch + " " + gitUrl);
  cd("extraterm");

  echo("Setting up the run time dependencies in " + BUILD_TMP);

  exec("yarn install");
  exec("yarn run electron-rebuild");
  exec("yarn run build");

  echo("Removing development dependencies");
  exec("yarn install --production=true");

  // Create the commands zip
  echo("Creating commands.zip");
  const commandsDir = packageData.name + "-commands-" + packageData.version;
  cp("-r", "extraterm/src/commands", path.join(buildTmpPath, commandsDir));
  const codeDir = pwd();
  cd(buildTmpPath);
  exec(`zip -y -r ${commandsDir}.zip ${commandsDir}`);
  cd(codeDir);

  const electronVersion = packageData.devDependencies['electron'];

  const ignoreRegExp = [
    /^\/build_scripts\b/,
    /^\/extraterm-web-component-decorators\b/,
    /^\/extraterm-extension-api\b/,
    /^\/test\b/,
    /^\/build_tmp\b/,
    /^\/src\/typedocs\b/,
    /\.ts$/,
    /\.js\.map$/,
    /^\/\.git\//,
    /^\/docs\b/,
    /^\/resources\/extra_icons\b/,
    /^\/src\/test\b/,
    /^\/src\/testfiles\b/
  ];

  const ignoreFunc = function ignoreFunc(filePath) {
    const result = ignoreRegExp.some( (exp) => exp.test(filePath));
    return result;
  };

  function appDir(platform) {
    return platform === "darwin" ? "Extraterm.app/Contents/Resources/app" : "resources/app";
  }

  function pruneNodeSass(versionedOutputDir, arch, platform) {
    const gutsDir = appDir(platform);
    const nodeSassVendorDir = path.join(versionedOutputDir, gutsDir, "node_modules/node-sass/vendor");

    rm('-rf', nodeSassVendorDir);

    const nodeSassBinaryDir = path.join(versionedOutputDir, gutsDir, "src/node-sass-binary");
    ["darwin-x64", "linux-ia32", "linux-x64", "win32-x64"].forEach( (name) => {
      if (name !== platform + "-" + arch) {
        rm('-rf', path.join(nodeSassBinaryDir, name + "-" + MODULE_VERSON));
      }
    });
  }

  function pruneEmojiOne(versionedOutputDir, platform) {
    if (platform !== "linux") {
      const emojiOnePath = path.join(versionedOutputDir, appDir(platform), "extraterm/resources/themes/default/emojione-android.ttf");
      rm(emojiOnePath);
    }
  }

  function hoistSubprojectsModules(versionedOutputDir, platform) {
    const modulesDir = path.join(versionedOutputDir, appDir(platform), "node_modules");

    // Delete the symlinks.
    for (const item of ls(modulesDir)) {
      const itemPath = path.join(modulesDir, item);
      if (test('-L', itemPath)) {
        echo(`Deleting symlink ${item} in ${modulesDir}`);
        rm(itemPath);
      }
    }

    // Move the 'packages' subprojects up into this node_modules dir.
    const packagesDir = path.join(versionedOutputDir, appDir(platform), "packages");
    for (const item of ls(packagesDir)) {
      const destDir = path.join(modulesDir, item);
      echo(`Moving ${item} in to ${destDir}`);
      mv(path.join(packagesDir, item), destDir);
    }
  }

  function pruneNodeModules(versionedOutputDir, platform) {
    const prevDir = pwd();

    cd(path.join(versionedOutputDir, appDir(platform)));
    exec("modclean -n default:safe -r");
    pruneSpecificNodeModules();

    cd(prevDir);
  }

  function pruneSpecificNodeModules() {
    [
      "codemirror/src",
      "node-sass/src",
      "node-sass/node_modules/nan",
      "node-sass/vendor",
      "node-gyp",
      "ajv",
      "globule",
      "vue/src",
      "vue/dist/vue.esm.browser.js",
      "vue/dist/vue.esm.js",
      "vue/dist/vue.js",
      "vue/dist/vue.min.js",
      "vue/dist/vue.runtime.esm.js",
      "vue/dist/vue.runtime.js",
      "vue/dist/vue.runtime.min.js",
      "font-manager/src",
      ".bin"
    ].forEach( (subpath) => {
      const fullPath = path.join("node_modules", subpath);

      echo("Deleting " + fullPath);

      if (test('-d', fullPath)) {
        rm('-rf', fullPath);
      } else if (test('-f', fullPath)) {
          rm(fullPath);
      } else {
        echo("----------- Unable to find path "+ fullPath);
      }
    });

  }

  function makePackage(arch, platform) {
    log("");
    return new Promise(function(resolve, reject) {

      // Clean up the output dirs and files first.
      const versionedOutputDir = packageData.name + "-" + packageData.version + "-" + platform + "-" + arch;
      if (test('-d', versionedOutputDir)) {
        rm('-rf', versionedOutputDir);
      }

      const outputZip = path.join(buildTmpPath, versionedOutputDir + ".zip");

      const packagerOptions = {
        arch: arch,
        dir: ".",
        platform: platform,
        version: electronVersion,
        ignore: ignoreFunc,
        name: platform === "darwin" ? "Extraterm" : "extraterm",
        overwrite: true,
        out: buildTmpPath,
        packageManager: "yarn",
        afterPrune: [
          (buildPath, electronVersion, platform, arch, callback) => {
            replaceDirs(path.join(buildPath, "node_modules"), path.join("" + codeDir,`build_scripts/node_modules-${platform}-${arch}`));
            callback();
          }
        ]
      };
      if (platform === "win32") {
        packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.ico";
        packagerOptions.win32metadata = {
          FileDescription: "Extraterm",
          ProductName: "Extraterm",
          LegalCopyright: "(C) 2018 Simon Edwards"
        };
      } else if (platform === "darwin") {
        packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.icns";
      }

      packager(packagerOptions, function done(err, appPath) {
        if (err !== null) {
          log(err);
          reject();
        } else {
          // Rename the output dir to a one with a version number in it.
          mv(appPath[0], path.join(buildTmpPath, versionedOutputDir));

          const thisCD = pwd();
          cd(buildTmpPath);

          hoistSubprojectsModules(versionedOutputDir, platform);
          pruneNodeModules(versionedOutputDir, platform);

          // Prune any unneeded node-sass binaries.
          pruneNodeSass(versionedOutputDir, arch, platform);

          pruneEmojiOne(versionedOutputDir, platform);

          // Zip it up.
          log("Zipping up the package");

          mv(path.join(versionedOutputDir, "LICENSE"), path.join(versionedOutputDir, "LICENSE_electron.txt"));
          cp("extraterm/README.md", versionedOutputDir);
          cp("extraterm/LICENSE.txt", versionedOutputDir);

          exec(`zip -y -r ${outputZip} ${versionedOutputDir}`);
          cd(thisCD);

          log("App bundle written to " + versionedOutputDir);
          resolve();
        }
      });
    });
  }

  function replaceDirs(targetDir, replacementsDir) {
    const prevDir = pwd();
    cd(srcRootDir);
    const replacements = ls(replacementsDir);
    replacements.forEach( (rDir) => {
      const targetSubDir = path.join(targetDir, rDir);
      if (test('-d', targetSubDir)) {
        rm('-r', targetSubDir);
      }
      cp('-r', path.join(replacementsDir, rDir), targetSubDir);
    });
    cd(prevDir);
  }

  function makeDmg() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building dmg file for macOS");
    echo("---------------------------------------------------");

    const darwinPath = path.join(buildTmpPath, `extraterm-${packageData.version}-darwin-x64`);
    for (const f of ls(darwinPath)) {
      if ( ! f.endsWith(".app")) {
        echo(`Deleting ${f}`);
        rm(path.join(darwinPath, f));
      }
    }

    cp(path.join(srcRootDir, "build_scripts/resources/macos/.DS_Store"), path.join(darwinPath, ".DS_Store"));
    cp(path.join(srcRootDir, "build_scripts/resources/macos/.VolumeIcon.icns"), path.join(darwinPath, ".VolumeIcon.icns"));
    mkdir(path.join(darwinPath,".background"));
    cp(path.join(srcRootDir, "build_scripts/resources/macos/.background/extraterm_background.png"), path.join(darwinPath, ".background/extraterm_background.png"));

    ln("-s", "/Applications", path.join(darwinPath, "Applications"));

    exec(`docker run --rm -v "${buildTmpPath}:/files" sporsh/create-dmg Extraterm /files/extraterm-${packageData.version}-darwin-x64/ /files/extraterm_${packageData.version}.dmg`);
  }

  function makeNsis() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building NSIS based installer for Windows");
    echo("---------------------------------------------------");

    const windowsBuildDirName = `extraterm-${packageData.version}-win32-x64`;
    const windowsPath = path.join(buildTmpPath, windowsBuildDirName);

    const versionSplit = packageData.version.split(".");
    const majorVersion = versionSplit[0];
    const minorVersion = versionSplit[1];
    const patchVersion = versionSplit[2];

    const installerScript = `
!include "MUI2.nsh"
!include "FileFunc.nsh"

!define APPNAME "Extraterm"
!define DESCRIPTION "Terminal emulator"
!define COMPANYNAME "extraterm.org"
!define VERSIONMAJOR ${majorVersion}
!define VERSIONMINOR ${minorVersion}
!define VERSIONBUILD ${patchVersion}

!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!define MUI_INSTFILESPAGE_COLORS "3db54a 000000"
!define MUI_ICON "${windowsBuildDirName}\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Name "Extraterm"
BrandingText " "
OutFile "extraterm-setup-${packageData.version}.exe"
InstallDir "$PROGRAMFILES64\\Extraterm"
InstallDirRegKey HKLM "Software\\Extraterm" "InstallLocation"

ShowInstDetails show # This will always show the installation details.

Section "Extraterm"
  SetOutPath $INSTDIR
  File /r "${windowsBuildDirName}\\*"

  WriteUninstaller "$INSTDIR\\Uninstall.exe"

  createShortCut "$SMPROGRAMS\\Extraterm.lnk" "$INSTDIR\\extraterm.exe" "" "$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

  WriteRegStr HKLM "Software\\Extraterm" "InstallLocation" "$\\"$INSTDIR$\\""
  WriteRegStr HKLM "Software\\Extraterm" "Version" "${packageData.version}"

  # Registry information for add/remove programs
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayName" "\${APPNAME} - \${DESCRIPTION}"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "QuietUninstallString" "$\\"$INSTDIR\\uninstall.exe$\\" /S"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "InstallLocation" "$\\"$INSTDIR$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayIcon" "$\\"$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "Publisher" "\${COMPANYNAME}"

	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayVersion" "\${VERSIONMAJOR}.\${VERSIONMINOR}.\${VERSIONBUILD}"
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMajor" \${VERSIONMAJOR}
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMinor" \${VERSIONMINOR}
	# There is no option for modifying or repairing the install
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoModify" 1
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoRepair" 1
	# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size

  # Record the installation size
  \${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
  IntFmt $0 "0x%08X" $0
  WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "EstimatedSize" "\$0"

SectionEnd

Section "Uninstall"
  # Remove Start Menu launcher
	Delete "$SMPROGRAMS\\Extraterm.lnk"

  Delete "$INSTDIR\\*.*"
  Delete "$INSTDIR\\Uninstall.exe"
  RMDir /r "$INSTDIR"

  DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}"
  DeleteRegKey HKLM "Software\\Extraterm"
SectionEnd
`;
    fs.writeFileSync(path.join(buildTmpPath, "installer.nsi"), installerScript, {encoding: "utf-8"});

    exec(`docker run -t -v ${buildTmpPath}:/wine/drive_c/src/ cdrx/nsis`);
  }

  if (linuxZipOnly) {
    makePackage("x64", "linux");
  } else {
    makePackage("x64", "win32")
      .then(() => makePackage("x64", "linux"))
      .then(() => makePackage("x64", "darwin"))
      .then(makeDmg)
      .then(makeNsis)
      .then(() => { log("Done"); } );
  }
}

main();
<|endoftext|>


<commit_before>describe("sinon extensions", function() {
    function itReturnsStatus(code) {
        it("returns a status code of " + code, function() {
            expect(this.fakeRequest.status).toBe(code);
        });
    }

    function itIncludesErrorAndResponse() {
        it("returns an error message in the 'message' field", function() {
            expect(JSON.parse(this.fakeRequest.responseText).errors).toEqual(this.errors);
        });

        it("includes the response if one is given", function() {
            expect(JSON.parse(this.fakeRequest.responseText).response).toEqual(this.response);
        });
    }

    beforeEach(function() {
        this.errors = { record: "not accessible" };
        this.response = { dataSourceId: 1 };
        this.fakeRequest = new sinon.FakeXMLHttpRequest();
    });

    describe("XHR#succeed", function() {
        beforeEach(function() {
            var resource = new chorus.models.User();
            resource.fetch();
            this.xhr = this.server.lastFetch();
        });

        context("when given a model", function() {
            beforeEach(function() {
                this.model = new chorus.models.Base({ foo: "bar" });
                this.xhr.succeed(this.model);
            });

            it("returns a 200 status", function() {
                this.xhr.succeed(rspecFixtures.user());
                expect(this.xhr.status).toBe(200);
            });

            it("returns the right content-type", function() {
                this.xhr.succeed(rspecFixtures.user());
                expect(this.xhr.responseHeaders["Content-Type"]).toBe("application/json");
            });

            it("makes 'response' key a hash of the model's attributes", function() {
                expect(JSON.parse(this.xhr.responseText).response).toEqual({
                    foo: "bar"
                });
            });
        });

        context("when given an array of models", function() {
            beforeEach(function() {
                this.xhr.succeed([
                    new chorus.models.Base({ foo: "bar" }),
                    new chorus.models.Base({ baz: "quux" })
                ], {
                    page: 1,
                    total: 5,
                    records: 100
                });
            });

            it("makes the 'response' key an array containing each model's attributes", function() {
                expect(JSON.parse(this.xhr.responseText).response).toEqual([
                    { foo: "bar" },
                    { baz: "quux" }
                ]);
            });

            it("includes the 'pagination' key, if passed", function() {
                expect(JSON.parse(this.xhr.responseText).pagination).toEqual({
                    page: 1,
                    total: 5,
                    records: 100
                });
            });
        });
    });

    describe("#lastFetchFor", function() {
        context("when #fetchAll was called on a collection (setting a non-default 'per_page' parameter)", function() {
            beforeEach(function() {
                this.collection = new chorus.collections.UserSet();
                this.collection.fetchAll();
            });

            it("returns the request made by #fetchAll", function() {
                expect(this.server.lastFetchFor(this.collection)).toBeDefined();
            });
        });
    });

    describe("#completeFetchFor", function() {
        beforeEach(function() {
            this.model = new chorus.models.User({ id: '1' });
            this.collection = new chorus.collections.UserSet();

            this.model.fetch();
            this.collection.fetch();
        });

        context("it is called with a specified result", function() {
            beforeEach(function() {
                this.server.completeFetchFor(this.model, { name: "John Smith", secretIdentity: "Neo" });
            });

            it("succeeds with the specified result", function() {
                expect(this.model.get('name')).toBe("John Smith");
                expect(this.model.loaded).toBeTruthy();
            });
        });

        context("when no result is passed", function() {
            context("it is called with a backbone model", function() {
                beforeEach(function() {
                    var user = new chorus.models.User({ id: '1', name: "Keanu Reeves" });
                    this.server.completeFetchFor(user);
                });

                it("uses the current attributes of the model", function() {
                    expect(this.model.get('name')).toBe("Keanu Reeves");
                    expect(this.model.loaded).toBeTruthy();
                });
            });

            context("it is called with a collection", function() {
                beforeEach(function() {
                    var otherCollection = new chorus.collections.UserSet([], { group: "Agents" });
                    this.server.completeFetchFor(otherCollection);
                });

                it("uses an empty model set", function() {
                    expect(this.collection.models.length).toBe(0);
                    expect(this.collection.get('group')).toBeUndefined();
                    expect(this.collection.loaded).toBeTruthy();
                });
            });
        });
    });

    describe("#makeFakeResponse(modelOrCollection, response)", function() {
        context("when called with a specified response", function() {
            it("returns the specified response", function() {
                var fakeRequest = this.server.makeFakeResponse(this.model, {
                    name: "John Smith",
                    secretIdentity: "Neo"
                });
                expect(fakeRequest).toEqual({
                    name: "John Smith",
                    secretIdentity: "Neo"
                });
            });
        });

        context("when no response is passed", function() {
            context("and it is called with a backbone model", function() {
                it("uses the current attributes of the model", function() {
                    var user = new chorus.models.User({ id: '1', name: "Keanu Reeves" });
                    var fakeRequest = this.server.makeFakeResponse(user);
                    expect(fakeRequest).toEqual({ id: 1, name: "Keanu Reeves" });
                });

                it("does not convert the id to an integer if it is non-numeric", function() {
                    var user = new chorus.models.User({ id: 'hiya' });
                    var fakeRequest = this.server.makeFakeResponse(user);
                    expect(fakeRequest).toEqual({ id: 'hiya' });
                });
            });

            context("it is called with a collection", function() {
                it("uses the model set", function() {
                    var collection = new chorus.collections.UserSet([
                        { id: '1', name: "Keanu Reeves" }
                    ], { group: "Agents" });
                    expect(this.server.makeFakeResponse(collection)).toEqual([
                        { id: 1, name: "Keanu Reeves" }
                    ]);
                });

                it("does not convert the ids to an integer if it is non-numeric", function() {
                    var collection = new chorus.collections.UserSet([
                        { id: 'hiya' }
                    ], { group: "Agents" });
                    expect(this.server.makeFakeResponse(collection)).toEqual([
                        { id: 'hiya' }
                    ]);
                });
            });
        });
    });

    describe("#respondJson", function() {
        beforeEach(function() {
            this.fakeRequest.respondJson(403, { response: { foo: "bar" } });
        });

        itReturnsStatus(403);

        it("sets the content-type to 'application/json'", function() {
            expect(this.fakeRequest.responseHeaders["Content-Type"]).toBe("application/json");
        });

        it("returns the given json as the body", function() {
            expect(JSON.parse(this.fakeRequest.responseText)).toEqual({
                response: { foo: "bar" }
            });
        });
    });

    describe("#fail", function() {
        beforeEach(function() {
            this.fakeRequest.fail();
        });

        itReturnsStatus(200);

        it("returns an error message in the 'message' field", function() {
            expect(this.fakeRequest.responseText).toContain("something went wrong!");
        });

        it("returns a 'fail' in the 'status' field", function() {
            expect(this.fakeRequest.responseText).toContain("fail");
        });
    });

    describe("#failForbidden", function() {
        beforeEach(function() {
            this.fakeRequest.failForbidden(this.errors);
        });

        itReturnsStatus(403);
        itIncludesErrorAndResponse();
    });

    describe("#failUnauthorized", function() {
        beforeEach(function() {
            this.fakeRequest.failUnauthorized(this.errors);
        });

        itReturnsStatus(401);
        itIncludesErrorAndResponse();
    });

    describe("#failNotFound", function() {
        beforeEach(function() {
            this.fakeRequest = new sinon.FakeXMLHttpRequest();
            this.fakeRequest.failNotFound(this.errors);
        });

        itReturnsStatus(404);
        itIncludesErrorAndResponse();
    });

    describe("#failUnprocessableEntity", function() {
        beforeEach(function() {
            this.fakeRequest.failUnprocessableEntity(this.errors);
        });

        itReturnsStatus(422);
        itIncludesErrorAndResponse();
    });
});
<commit_msg>fix failing spec<commit_after>describe("sinon extensions", function() {
    function itReturnsStatus(code) {
        it("returns a status code of " + code, function() {
            expect(this.fakeRequest.status).toBe(code);
        });
    }

    function itIncludesError() {
        it("returns an error message in the 'message' field", function() {
            expect(JSON.parse(this.fakeRequest.responseText).errors).toEqual(this.errors);
        });
    }

    beforeEach(function() {
        this.errors = { record: "not accessible" };
        this.response = { dataSourceId: 1 };
        this.fakeRequest = new sinon.FakeXMLHttpRequest();
    });

    describe("XHR#succeed", function() {
        beforeEach(function() {
            var resource = new chorus.models.User();
            resource.fetch();
            this.xhr = this.server.lastFetch();
        });

        context("when given a model", function() {
            beforeEach(function() {
                this.model = new chorus.models.Base({ foo: "bar" });
                this.xhr.succeed(this.model);
            });

            it("returns a 200 status", function() {
                this.xhr.succeed(rspecFixtures.user());
                expect(this.xhr.status).toBe(200);
            });

            it("returns the right content-type", function() {
                this.xhr.succeed(rspecFixtures.user());
                expect(this.xhr.responseHeaders["Content-Type"]).toBe("application/json");
            });

            it("makes 'response' key a hash of the model's attributes", function() {
                expect(JSON.parse(this.xhr.responseText).response).toEqual({
                    foo: "bar"
                });
            });
        });

        context("when given an array of models", function() {
            beforeEach(function() {
                this.xhr.succeed([
                    new chorus.models.Base({ foo: "bar" }),
                    new chorus.models.Base({ baz: "quux" })
                ], {
                    page: 1,
                    total: 5,
                    records: 100
                });
            });

            it("makes the 'response' key an array containing each model's attributes", function() {
                expect(JSON.parse(this.xhr.responseText).response).toEqual([
                    { foo: "bar" },
                    { baz: "quux" }
                ]);
            });

            it("includes the 'pagination' key, if passed", function() {
                expect(JSON.parse(this.xhr.responseText).pagination).toEqual({
                    page: 1,
                    total: 5,
                    records: 100
                });
            });
        });
    });

    describe("#lastFetchFor", function() {
        context("when #fetchAll was called on a collection (setting a non-default 'per_page' parameter)", function() {
            beforeEach(function() {
                this.collection = new chorus.collections.UserSet();
                this.collection.fetchAll();
            });

            it("returns the request made by #fetchAll", function() {
                expect(this.server.lastFetchFor(this.collection)).toBeDefined();
            });
        });
    });

    describe("#completeFetchFor", function() {
        beforeEach(function() {
            this.model = new chorus.models.User({ id: '1' });
            this.collection = new chorus.collections.UserSet();

            this.model.fetch();
            this.collection.fetch();
        });

        context("it is called with a specified result", function() {
            beforeEach(function() {
                this.server.completeFetchFor(this.model, { name: "John Smith", secretIdentity: "Neo" });
            });

            it("succeeds with the specified result", function() {
                expect(this.model.get('name')).toBe("John Smith");
                expect(this.model.loaded).toBeTruthy();
            });
        });

        context("when no result is passed", function() {
            context("it is called with a backbone model", function() {
                beforeEach(function() {
                    var user = new chorus.models.User({ id: '1', name: "Keanu Reeves" });
                    this.server.completeFetchFor(user);
                });

                it("uses the current attributes of the model", function() {
                    expect(this.model.get('name')).toBe("Keanu Reeves");
                    expect(this.model.loaded).toBeTruthy();
                });
            });

            context("it is called with a collection", function() {
                beforeEach(function() {
                    var otherCollection = new chorus.collections.UserSet([], { group: "Agents" });
                    this.server.completeFetchFor(otherCollection);
                });

                it("uses an empty model set", function() {
                    expect(this.collection.models.length).toBe(0);
                    expect(this.collection.get('group')).toBeUndefined();
                    expect(this.collection.loaded).toBeTruthy();
                });
            });
        });
    });

    describe("#makeFakeResponse(modelOrCollection, response)", function() {
        context("when called with a specified response", function() {
            it("returns the specified response", function() {
                var fakeRequest = this.server.makeFakeResponse(this.model, {
                    name: "John Smith",
                    secretIdentity: "Neo"
                });
                expect(fakeRequest).toEqual({
                    name: "John Smith",
                    secretIdentity: "Neo"
                });
            });
        });

        context("when no response is passed", function() {
            context("and it is called with a backbone model", function() {
                it("uses the current attributes of the model", function() {
                    var user = new chorus.models.User({ id: '1', name: "Keanu Reeves" });
                    var fakeRequest = this.server.makeFakeResponse(user);
                    expect(fakeRequest).toEqual({ id: 1, name: "Keanu Reeves" });
                });

                it("does not convert the id to an integer if it is non-numeric", function() {
                    var user = new chorus.models.User({ id: 'hiya' });
                    var fakeRequest = this.server.makeFakeResponse(user);
                    expect(fakeRequest).toEqual({ id: 'hiya' });
                });
            });

            context("it is called with a collection", function() {
                it("uses the model set", function() {
                    var collection = new chorus.collections.UserSet([
                        { id: '1', name: "Keanu Reeves" }
                    ], { group: "Agents" });
                    expect(this.server.makeFakeResponse(collection)).toEqual([
                        { id: 1, name: "Keanu Reeves" }
                    ]);
                });

                it("does not convert the ids to an integer if it is non-numeric", function() {
                    var collection = new chorus.collections.UserSet([
                        { id: 'hiya' }
                    ], { group: "Agents" });
                    expect(this.server.makeFakeResponse(collection)).toEqual([
                        { id: 'hiya' }
                    ]);
                });
            });
        });
    });

    describe("#respondJson", function() {
        beforeEach(function() {
            this.fakeRequest.respondJson(403, { response: { foo: "bar" } });
        });

        itReturnsStatus(403);

        it("sets the content-type to 'application/json'", function() {
            expect(this.fakeRequest.responseHeaders["Content-Type"]).toBe("application/json");
        });

        it("returns the given json as the body", function() {
            expect(JSON.parse(this.fakeRequest.responseText)).toEqual({
                response: { foo: "bar" }
            });
        });
    });

    describe("#fail", function() {
        beforeEach(function() {
            this.fakeRequest.fail();
        });

        itReturnsStatus(200);

        it("returns an error message in the 'message' field", function() {
            expect(this.fakeRequest.responseText).toContain("something went wrong!");
        });

        it("returns a 'fail' in the 'status' field", function() {
            expect(this.fakeRequest.responseText).toContain("fail");
        });
    });

    describe("#failForbidden", function() {
        beforeEach(function() {
            this.fakeRequest.failForbidden(this.errors);
        });

        itReturnsStatus(403);
        itIncludesError();
    });

    describe("#failUnauthorized", function() {
        beforeEach(function() {
            this.fakeRequest.failUnauthorized(this.errors);
        });

        itReturnsStatus(401);
        itIncludesError();
    });

    describe("#failNotFound", function() {
        beforeEach(function() {
            this.fakeRequest = new sinon.FakeXMLHttpRequest();
            this.fakeRequest.failNotFound(this.errors);
        });

        itReturnsStatus(404);
        itIncludesError();
    });

    describe("#failUnprocessableEntity", function() {
        beforeEach(function() {
            this.fakeRequest.failUnprocessableEntity(this.errors);
        });

        itReturnsStatus(422);
        itIncludesError();
    });
});
<|endoftext|>


<commit_before>import axios from 'axios';
import joi from 'joi';

import db from '../utils/db';

import RSS from '../models/rss';
import Podcast from '../models/podcast';
import logger from '../utils/logger';
import { ProcessStreamQueue } from '../asyncTasks';
import { timeIt } from '../utils/statsd';
import { sendFeedToCollections } from '../utils/collections';
import { setupAxiosRedirectInterceptor } from '../utils/axios';

if (require.main === module) {
	setupAxiosRedirectInterceptor(axios);

	logger.info('Starting the Stream worker');
	ProcessStreamQueue(1, streamProcessor);
}

export async function streamProcessor(job) {
	logger.info(`Processing Stream feeds for feed ${job.data.rss}`);
	// just intercept error handling before it goes to Bull
	try {
		await handleStream(job);
	} catch (err) {
		const tags = { queue: 'stream' };
		const extra = { JobRSS: job.data.rss, JobArticles: job.data.articles };
		logger.error('Stream feed job ecountered an error', { err, tags, extra });
	}
	logger.info(`Completed processing Stream feeds for feed ${job.data.rss}`);
}

const joiObjectId = joi.alternatives().try(
	joi.string().length(12),
	joi.string().length(24).regex(/^[0-9a-fA-F]{24}$/)
);

const schema = joi.object().keys({
	rss: joiObjectId,
	podcast: joiObjectId,
}).xor('rss', 'podcast');

export async function handleStream(job) {
	const validation = joi.validate(job.data, schema);
	if (!!validation.error) {
		logger.warn(`Stream job validation failed: ${validation.error.message} for '${JSON.stringify(job.data)}'`);
		return;
	}

	const type = 'rss' in job.data ? 'rss' : 'podcast';
	const model = 'rss' in job.data ? RSS : Podcast;

	await model.update({ _id: job.data[type] }, { "queueState.isSynchronizingWithStream": false });

	const feed = await model.findById(job.data[type]);
	await timeIt('winds.handle_stream.send_to_collections', () => sendFeedToCollections(type, feed));
}
<commit_msg>Increase stream worker concurrency<commit_after>import axios from 'axios';
import joi from 'joi';

import db from '../utils/db';

import RSS from '../models/rss';
import Podcast from '../models/podcast';
import logger from '../utils/logger';
import { ProcessStreamQueue } from '../asyncTasks';
import { timeIt } from '../utils/statsd';
import { sendFeedToCollections } from '../utils/collections';
import { setupAxiosRedirectInterceptor } from '../utils/axios';

if (require.main === module) {
	setupAxiosRedirectInterceptor(axios);

	logger.info('Starting the Stream worker');
	ProcessStreamQueue(2, streamProcessor);
}

export async function streamProcessor(job) {
	logger.info(`Processing Stream feeds for feed ${job.data.rss}`);
	// just intercept error handling before it goes to Bull
	try {
		await handleStream(job);
	} catch (err) {
		const tags = { queue: 'stream' };
		const extra = { JobRSS: job.data.rss, JobArticles: job.data.articles };
		logger.error('Stream feed job ecountered an error', { err, tags, extra });
	}
	logger.info(`Completed processing Stream feeds for feed ${job.data.rss}`);
}

const joiObjectId = joi.alternatives().try(
	joi.string().length(12),
	joi.string().length(24).regex(/^[0-9a-fA-F]{24}$/)
);

const schema = joi.object().keys({
	rss: joiObjectId,
	podcast: joiObjectId,
}).xor('rss', 'podcast');

export async function handleStream(job) {
	const validation = joi.validate(job.data, schema);
	if (!!validation.error) {
		logger.warn(`Stream job validation failed: ${validation.error.message} for '${JSON.stringify(job.data)}'`);
		return;
	}

	const type = 'rss' in job.data ? 'rss' : 'podcast';
	const model = 'rss' in job.data ? RSS : Podcast;

	await model.update({ _id: job.data[type] }, { "queueState.isSynchronizingWithStream": false });

	const feed = await model.findById(job.data[type]);
	await timeIt('winds.handle_stream.send_to_collections', () => sendFeedToCollections(type, feed));
}
<|endoftext|>


<commit_before>/*
 * Copyright 2014-2019 Simon Edwards <simon@simonzone.com>
 *
 * This source code is licensed under the MIT license which is detailed in the LICENSE.txt file.
 */
require('shelljs/global');
const fs = require('fs');
const path = require('path');
const packager = require('electron-packager');
const getRepoInfo = require('git-repo-info');

const log = console.log.bind(console);
const MODULE_VERSON = 69; // This version number also appears in thememanager.ts

async function main() {
  "use strict";

  if ( ! test('-f', './package.json')) {
    echo("This script was called from the wrong directory.");
    return;
  }

  const linuxZipOnly = process.argv.indexOf("--linux-zip-only") !== -1;

  const ROOT_SRC_DIR = "" + pwd();
  const BUILD_TMP_DIR = path.join(ROOT_SRC_DIR, 'build_tmp');
  if (test('-d', BUILD_TMP_DIR)) {
    rm('-rf', BUILD_TMP_DIR);
  }
  mkdir(BUILD_TMP_DIR);

  const packageJson = fs.readFileSync('package.json');
  const packageData = JSON.parse(packageJson);

  const gitUrl = exec("git config --get remote.origin.url").trim();
  const info = getRepoInfo();

  echo("Fetching a clean copy of the source code from " + gitUrl);

  cd(BUILD_TMP_DIR);

  exec("git clone -b " + info.branch + " " + gitUrl);
  cd("extraterm");
  const SRC_DIR = "" + pwd();
  cd(SRC_DIR);

  echo("Setting up the run time dependencies in " + SRC_DIR);

  exec("yarn install");
  exec("yarn run electron-rebuild");
  exec("yarn run build");

  echo("Removing development dependencies");
  exec("yarn install --production=true");

  // Create the commands zip
  echo("Creating commands.zip");
  const commandsDir = packageData.name + "-commands-" + packageData.version;
  cp("-r", "extraterm/src/commands", path.join(BUILD_TMP_DIR, commandsDir));
  cd(BUILD_TMP_DIR);
  exec(`zip -y -r ${commandsDir}.zip ${commandsDir}`);
  cd(SRC_DIR);

  const electronVersion = packageData.devDependencies['electron'];

  const ignoreRegExp = [
    /^\/build_scripts\b/,
    /^\/extraterm-web-component-decorators\b/,
    /^\/extraterm-extension-api\b/,
    /^\/test\b/,
    /^\/build_tmp\b/,
    /^\/src\/typedocs\b/,
    /\.ts$/,
    /\.js\.map$/,
    /^\/\.git\//,
    /^\/docs\b/,
    /^\/resources\/extra_icons\b/,
    /^\/src\/test\b/,
    /^\/src\/testfiles\b/
  ];

  const ignoreFunc = function ignoreFunc(filePath) {
    const result = ignoreRegExp.some( (exp) => exp.test(filePath));
    return result;
  };

  function appDir(platform) {
    return platform === "darwin" ? "Extraterm.app/Contents/Resources/app" : "resources/app";
  }

  function pruneNodeSass(versionedOutputDir, arch, platform) {
    const gutsDir = appDir(platform);
    const nodeSassVendorDir = path.join(versionedOutputDir, gutsDir, "node_modules/node-sass/vendor");

    rm('-rf', nodeSassVendorDir);

    const nodeSassBinaryDir = path.join(versionedOutputDir, gutsDir, "src/node-sass-binary");
    ["darwin-x64", "linux-ia32", "linux-x64", "win32-x64"].forEach( (name) => {
      if (name !== platform + "-" + arch) {
        rm('-rf', path.join(nodeSassBinaryDir, name + "-" + MODULE_VERSON));
      }
    });
  }

  function pruneEmojiOne(versionedOutputDir, platform) {
    if (platform !== "linux") {
      const emojiOnePath = path.join(versionedOutputDir, appDir(platform), "extraterm/resources/themes/default/emojione-android.ttf");
      rm(emojiOnePath);
    }
  }

  function hoistSubprojectsModules(versionedOutputDir, platform) {
    const modulesDir = path.join(versionedOutputDir, appDir(platform), "node_modules");

    // Delete the symlinks.
    for (const item of ls(modulesDir)) {
      const itemPath = path.join(modulesDir, item);
      if (test('-L', itemPath)) {
        echo(`Deleting symlink ${item} in ${modulesDir}`);
        rm(itemPath);
      }
    }

    // Move the 'packages' subprojects up into this node_modules dir.
    const packagesDir = path.join(versionedOutputDir, appDir(platform), "packages");
    for (const item of ls(packagesDir)) {
      const destDir = path.join(modulesDir, item);
      echo(`Moving ${item} in to ${destDir}`);
      mv(path.join(packagesDir, item), destDir);
    }
  }

  function pruneNodeModules(versionedOutputDir, platform) {
    const prevDir = pwd();

    cd(path.join(versionedOutputDir, appDir(platform)));
    exec("modclean -n default:safe -r");
    pruneSpecificNodeModules();

    cd(prevDir);
  }

  function pruneSpecificNodeModules() {
    [
      "node-sass/src",
      "node-sass/node_modules/nan",
      "node-sass/vendor",
      "node-gyp",
      "ajv",
      "globule",
      "vue/src",
      "vue/dist/vue.esm.browser.js",
      "vue/dist/vue.esm.js",
      "vue/dist/vue.js",
      "vue/dist/vue.min.js",
      "vue/dist/vue.runtime.esm.js",
      "vue/dist/vue.runtime.js",
      "vue/dist/vue.runtime.min.js",
      "font-manager/src",
      ".bin"
    ].forEach( (subpath) => {
      const fullPath = path.join("node_modules", subpath);

      echo("Deleting " + fullPath);

      if (test('-d', fullPath)) {
        rm('-rf', fullPath);
      } else if (test('-f', fullPath)) {
          rm(fullPath);
      } else {
        echo("----------- Unable to find path "+ fullPath);
      }
    });

  }

  async function makePackage(arch, platform) {
    log("");

    // Clean up the output dirs and files first.
    const versionedOutputDir = packageData.name + "-" + packageData.version + "-" + platform + "-" + arch;
    if (test('-d', versionedOutputDir)) {
      rm('-rf', versionedOutputDir);
    }

    const outputZip = path.join(BUILD_TMP_DIR, versionedOutputDir + ".zip");

    const packagerOptions = {
      arch: arch,
      dir: ".",
      platform: platform,
      version: electronVersion,
      ignore: ignoreFunc,
      name: platform === "darwin" ? "Extraterm" : "extraterm",
      overwrite: true,
      out: BUILD_TMP_DIR,
      prune: false
    };
    if (platform === "win32") {
      packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.ico";
      packagerOptions.win32metadata = {
        FileDescription: "Extraterm",
        ProductName: "Extraterm",
        LegalCopyright: "(C) 2018 Simon Edwards"
      };
    } else if (platform === "darwin") {
      packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.icns";
    }

    const appPath = await packager(packagerOptions);

    const dirsDest = path.join(BUILD_TMP_DIR, `extraterm-${platform}-${arch}`, "node_modules");
    const dirsSource = path.join("" + SRC_DIR,`build_scripts/node_modules-${platform}-${arch}`);
    log(`copy ${dirsSource} => ${dirsDest}`);
    replaceDirs(dirsDest,dirsSource);

    // Rename the output dir to a one with a version number in it.
    mv(appPath[0], path.join(BUILD_TMP_DIR, versionedOutputDir));

    const thisCD = pwd();
    cd(BUILD_TMP_DIR);

    hoistSubprojectsModules(versionedOutputDir, platform);
    pruneNodeModules(versionedOutputDir, platform);

    // Prune any unneeded node-sass binaries.
    pruneNodeSass(versionedOutputDir, arch, platform);

    pruneEmojiOne(versionedOutputDir, platform);

    // Zip it up.
    log("Zipping up the package");

    mv(path.join(versionedOutputDir, "LICENSE"), path.join(versionedOutputDir, "LICENSE_electron.txt"));
    cp("extraterm/README.md", versionedOutputDir);
    cp("extraterm/LICENSE.txt", versionedOutputDir);

    exec(`zip -y -r ${outputZip} ${versionedOutputDir}`);
    cd(thisCD);

    log("App bundle written to " + versionedOutputDir);
    return true;
  }

  function replaceDirs(targetDir, replacementsDir) {
    const prevDir = pwd();
    cd(ROOT_SRC_DIR);
    const replacements = ls(replacementsDir);
    replacements.forEach( (rDir) => {
      const targetSubDir = path.join(targetDir, rDir);
      if (test('-d', targetSubDir)) {
        rm('-r', targetSubDir);
      }
      cp('-r', path.join(replacementsDir, rDir), targetSubDir);
    });
    cd(prevDir);
  }

  function makeDmg() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building dmg file for macOS");
    echo("---------------------------------------------------");

    const darwinPath = path.join(BUILD_TMP_DIR, `extraterm-${packageData.version}-darwin-x64`);
    for (const f of ls(darwinPath)) {
      if ( ! f.endsWith(".app")) {
        echo(`Deleting ${f}`);
        rm(path.join(darwinPath, f));
      }
    }

    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.DS_Store"), path.join(darwinPath, ".DS_Store"));
    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.VolumeIcon.icns"), path.join(darwinPath, ".VolumeIcon.icns"));
    mkdir(path.join(darwinPath,".background"));
    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.background/extraterm_background.png"), path.join(darwinPath, ".background/extraterm_background.png"));

    ln("-s", "/Applications", path.join(darwinPath, "Applications"));

    exec(`docker run --rm -v "${BUILD_TMP_DIR}:/files" sporsh/create-dmg Extraterm /files/extraterm-${packageData.version}-darwin-x64/ /files/extraterm_${packageData.version}.dmg`);
    return true;
  }

  function makeNsis() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building NSIS based installer for Windows");
    echo("---------------------------------------------------");

    const windowsBuildDirName = `extraterm-${packageData.version}-win32-x64`;
    const versionSplit = packageData.version.split(".");
    const majorVersion = versionSplit[0];
    const minorVersion = versionSplit[1];
    const patchVersion = versionSplit[2];

    const installerScript = `
!include "MUI2.nsh"
!include "FileFunc.nsh"

!define APPNAME "Extraterm"
!define DESCRIPTION "Terminal emulator"
!define COMPANYNAME "extraterm.org"
!define VERSIONMAJOR ${majorVersion}
!define VERSIONMINOR ${minorVersion}
!define VERSIONBUILD ${patchVersion}

!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!define MUI_INSTFILESPAGE_COLORS "3db54a 000000"
!define MUI_ICON "${windowsBuildDirName}\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Name "Extraterm"
BrandingText " "
OutFile "extraterm-setup-${packageData.version}.exe"
InstallDir "$PROGRAMFILES64\\Extraterm"
InstallDirRegKey HKLM "Software\\Extraterm" "InstallLocation"

ShowInstDetails show # This will always show the installation details.

Section "Extraterm"
  SetOutPath $INSTDIR
  File /r "${windowsBuildDirName}\\*"

  WriteUninstaller "$INSTDIR\\Uninstall.exe"

  createShortCut "$SMPROGRAMS\\Extraterm.lnk" "$INSTDIR\\extraterm.exe" "" "$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

  WriteRegStr HKLM "Software\\Extraterm" "InstallLocation" "$\\"$INSTDIR$\\""
  WriteRegStr HKLM "Software\\Extraterm" "Version" "${packageData.version}"

  # Registry information for add/remove programs
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayName" "\${APPNAME} - \${DESCRIPTION}"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "QuietUninstallString" "$\\"$INSTDIR\\uninstall.exe$\\" /S"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "InstallLocation" "$\\"$INSTDIR$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayIcon" "$\\"$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "Publisher" "\${COMPANYNAME}"

	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayVersion" "\${VERSIONMAJOR}.\${VERSIONMINOR}.\${VERSIONBUILD}"
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMajor" \${VERSIONMAJOR}
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMinor" \${VERSIONMINOR}
	# There is no option for modifying or repairing the install
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoModify" 1
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoRepair" 1
	# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size

  # Record the installation size
  \${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
  IntFmt $0 "0x%08X" $0
  WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "EstimatedSize" "\$0"

SectionEnd

Section "Uninstall"
  # Remove Start Menu launcher
	Delete "$SMPROGRAMS\\Extraterm.lnk"

  Delete "$INSTDIR\\*.*"
  Delete "$INSTDIR\\Uninstall.exe"
  RMDir /r "$INSTDIR"

  DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}"
  DeleteRegKey HKLM "Software\\Extraterm"
SectionEnd
`;
    fs.writeFileSync(path.join(BUILD_TMP_DIR, "installer.nsi"), installerScript, {encoding: "utf-8"});

    exec(`docker run --rm -t -v ${BUILD_TMP_DIR}:/wine/drive_c/src/ cdrx/nsis`);
    return true;
  }

  if (linuxZipOnly) {
    await makePackage("x64", "linux");
    log("Done");
  } else {
    if (! await makePackage("x64", "win32")) {
      return;
    }

    if (! await makePackage("x64", "linux")) {
      return;
    }

    if (! await makePackage("x64", "darwin")) {
      return;
    }

    if (! makeDmg()) {
      return;
    }

    if (! makeNsis()) {
      return;
    }
    log("Done");
  }
}

main();
<commit_msg>Fix the package script to copy the native modules over correctly.<commit_after>/*
 * Copyright 2014-2019 Simon Edwards <simon@simonzone.com>
 *
 * This source code is licensed under the MIT license which is detailed in the LICENSE.txt file.
 */
require('shelljs/global');
const fs = require('fs');
const path = require('path');
const packager = require('electron-packager');
const getRepoInfo = require('git-repo-info');

const log = console.log.bind(console);
const MODULE_VERSON = 69; // This version number also appears in thememanager.ts

async function main() {
  "use strict";

  if ( ! test('-f', './package.json')) {
    echo("This script was called from the wrong directory.");
    return;
  }

  const linuxZipOnly = process.argv.indexOf("--linux-zip-only") !== -1;

  const ROOT_SRC_DIR = "" + pwd();
  const BUILD_TMP_DIR = path.join(ROOT_SRC_DIR, 'build_tmp');
  if (test('-d', BUILD_TMP_DIR)) {
    rm('-rf', BUILD_TMP_DIR);
  }
  mkdir(BUILD_TMP_DIR);

  const packageJson = fs.readFileSync('package.json');
  const packageData = JSON.parse(packageJson);

  const gitUrl = exec("git config --get remote.origin.url").trim();
  const info = getRepoInfo();

  echo("Fetching a clean copy of the source code from " + gitUrl);

  cd(BUILD_TMP_DIR);

  exec("git clone -b " + info.branch + " " + gitUrl);
  cd("extraterm");
  const SRC_DIR = "" + pwd();
  cd(SRC_DIR);

  echo("Setting up the run time dependencies in " + SRC_DIR);

  exec("yarn install");
  exec("yarn run electron-rebuild");
  exec("yarn run build");

  echo("Removing development dependencies");
  exec("yarn install --production=true");

  // Create the commands zip
  echo("Creating commands.zip");
  const commandsDir = packageData.name + "-commands-" + packageData.version;
  cp("-r", "extraterm/src/commands", path.join(BUILD_TMP_DIR, commandsDir));
  cd(BUILD_TMP_DIR);
  exec(`zip -y -r ${commandsDir}.zip ${commandsDir}`);
  cd(SRC_DIR);

  const electronVersion = packageData.devDependencies['electron'];

  const ignoreRegExp = [
    /^\/build_scripts\b/,
    /^\/extraterm-web-component-decorators\b/,
    /^\/extraterm-extension-api\b/,
    /^\/test\b/,
    /^\/build_tmp\b/,
    /^\/src\/typedocs\b/,
    /\.ts$/,
    /\.js\.map$/,
    /^\/\.git\//,
    /^\/docs\b/,
    /^\/resources\/extra_icons\b/,
    /^\/src\/test\b/,
    /^\/src\/testfiles\b/
  ];

  const ignoreFunc = function ignoreFunc(filePath) {
    const result = ignoreRegExp.some( (exp) => exp.test(filePath));
    return result;
  };

  function appDir(platform) {
    return platform === "darwin" ? "Extraterm.app/Contents/Resources/app" : "resources/app";
  }

  function pruneNodeSass(versionedOutputDir, arch, platform) {
    const gutsDir = appDir(platform);
    const nodeSassVendorDir = path.join(versionedOutputDir, gutsDir, "node_modules/node-sass/vendor");

    rm('-rf', nodeSassVendorDir);

    const nodeSassBinaryDir = path.join(versionedOutputDir, gutsDir, "src/node-sass-binary");
    ["darwin-x64", "linux-ia32", "linux-x64", "win32-x64"].forEach( (name) => {
      if (name !== platform + "-" + arch) {
        rm('-rf', path.join(nodeSassBinaryDir, name + "-" + MODULE_VERSON));
      }
    });
  }

  function pruneEmojiOne(versionedOutputDir, platform) {
    if (platform !== "linux") {
      const emojiOnePath = path.join(versionedOutputDir, appDir(platform), "extraterm/resources/themes/default/emojione-android.ttf");
      rm(emojiOnePath);
    }
  }

  function hoistSubprojectsModules(versionedOutputDir, platform) {
    const modulesDir = path.join(versionedOutputDir, appDir(platform), "node_modules");

    // Delete the symlinks.
    for (const item of ls(modulesDir)) {
      const itemPath = path.join(modulesDir, item);
      if (test('-L', itemPath)) {
        echo(`Deleting symlink ${item} in ${modulesDir}`);
        rm(itemPath);
      }
    }

    // Move the 'packages' subprojects up into this node_modules dir.
    const packagesDir = path.join(versionedOutputDir, appDir(platform), "packages");
    for (const item of ls(packagesDir)) {
      const destDir = path.join(modulesDir, item);
      echo(`Moving ${item} in to ${destDir}`);
      mv(path.join(packagesDir, item), destDir);
    }
  }

  function pruneNodeModules(versionedOutputDir, platform) {
    const prevDir = pwd();

    cd(path.join(versionedOutputDir, appDir(platform)));
    exec("modclean -n default:safe -r");
    pruneSpecificNodeModules();

    cd(prevDir);
  }

  function pruneSpecificNodeModules() {
    [
      "node-sass/src",
      "node-sass/node_modules/nan",
      "node-sass/vendor",
      "node-gyp",
      "ajv",
      "globule",
      "vue/src",
      "vue/dist/vue.esm.browser.js",
      "vue/dist/vue.esm.js",
      "vue/dist/vue.js",
      "vue/dist/vue.min.js",
      "vue/dist/vue.runtime.esm.js",
      "vue/dist/vue.runtime.js",
      "vue/dist/vue.runtime.min.js",
      "font-manager/src",
      ".bin"
    ].forEach( (subpath) => {
      const fullPath = path.join("node_modules", subpath);

      echo("Deleting " + fullPath);

      if (test('-d', fullPath)) {
        rm('-rf', fullPath);
      } else if (test('-f', fullPath)) {
          rm(fullPath);
      } else {
        echo("----------- Unable to find path "+ fullPath);
      }
    });

  }

  async function makePackage(arch, platform) {
    log("");

    // Clean up the output dirs and files first.
    const versionedOutputDir = packageData.name + "-" + packageData.version + "-" + platform + "-" + arch;
    if (test('-d', versionedOutputDir)) {
      rm('-rf', versionedOutputDir);
    }

    const outputZip = path.join(BUILD_TMP_DIR, versionedOutputDir + ".zip");

    const packagerOptions = {
      arch: arch,
      dir: ".",
      platform: platform,
      version: electronVersion,
      ignore: ignoreFunc,
      name: platform === "darwin" ? "Extraterm" : "extraterm",
      overwrite: true,
      out: BUILD_TMP_DIR,
      prune: false
    };
    if (platform === "win32") {
      packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.ico";
      packagerOptions.win32metadata = {
        FileDescription: "Extraterm",
        ProductName: "Extraterm",
        LegalCopyright: "(C) 2018 Simon Edwards"
      };
    } else if (platform === "darwin") {
      packagerOptions.icon = "extraterm/resources/logo/extraterm_small_logo.icns";
    }

    const appPath = await packager(packagerOptions);

    // Rename the output dir to a one with a version number in it.
    mv(appPath[0], path.join(BUILD_TMP_DIR, versionedOutputDir));

    const dirsDest = path.join(BUILD_TMP_DIR, versionedOutputDir, "resources/app/node_modules");
    const dirsSource = path.join("" + SRC_DIR,`build_scripts/node_modules-${platform}-${arch}`);
    replaceDirs(dirsDest, dirsSource);

    const thisCD = pwd();
    cd(BUILD_TMP_DIR);

    hoistSubprojectsModules(versionedOutputDir, platform);
    pruneNodeModules(versionedOutputDir, platform);

    // Prune any unneeded node-sass binaries.
    pruneNodeSass(versionedOutputDir, arch, platform);

    pruneEmojiOne(versionedOutputDir, platform);

    // Zip it up.
    log("Zipping up the package");

    mv(path.join(versionedOutputDir, "LICENSE"), path.join(versionedOutputDir, "LICENSE_electron.txt"));
    cp("extraterm/README.md", versionedOutputDir);
    cp("extraterm/LICENSE.txt", versionedOutputDir);

    exec(`zip -y -r ${outputZip} ${versionedOutputDir}`);
    cd(thisCD);

    log("App bundle written to " + versionedOutputDir);
    return true;
  }

  function replaceDirs(targetDir, replacementsDir) {
    const prevDir = pwd();
    cd(ROOT_SRC_DIR);
    const replacements = ls(replacementsDir);
    replacements.forEach( (rDir) => {
      const targetSubDir = path.join(targetDir, rDir);
      if (test('-d', targetSubDir)) {
        rm('-r', targetSubDir);
      }
      cp('-r', path.join(replacementsDir, rDir), targetSubDir);
    });
    cd(prevDir);
  }

  function makeDmg() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building dmg file for macOS");
    echo("---------------------------------------------------");

    const darwinPath = path.join(BUILD_TMP_DIR, `extraterm-${packageData.version}-darwin-x64`);
    for (const f of ls(darwinPath)) {
      if ( ! f.endsWith(".app")) {
        echo(`Deleting ${f}`);
        rm(path.join(darwinPath, f));
      }
    }

    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.DS_Store"), path.join(darwinPath, ".DS_Store"));
    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.VolumeIcon.icns"), path.join(darwinPath, ".VolumeIcon.icns"));
    mkdir(path.join(darwinPath,".background"));
    cp(path.join(ROOT_SRC_DIR, "build_scripts/resources/macos/.background/extraterm_background.png"), path.join(darwinPath, ".background/extraterm_background.png"));

    ln("-s", "/Applications", path.join(darwinPath, "Applications"));

    exec(`docker run --rm -v "${BUILD_TMP_DIR}:/files" sporsh/create-dmg Extraterm /files/extraterm-${packageData.version}-darwin-x64/ /files/extraterm_${packageData.version}.dmg`);
    return true;
  }

  function makeNsis() {
    echo("");
    echo("---------------------------------------------------");
    echo("Building NSIS based installer for Windows");
    echo("---------------------------------------------------");

    const windowsBuildDirName = `extraterm-${packageData.version}-win32-x64`;
    const versionSplit = packageData.version.split(".");
    const majorVersion = versionSplit[0];
    const minorVersion = versionSplit[1];
    const patchVersion = versionSplit[2];

    const installerScript = `
!include "MUI2.nsh"
!include "FileFunc.nsh"

!define APPNAME "Extraterm"
!define DESCRIPTION "Terminal emulator"
!define COMPANYNAME "extraterm.org"
!define VERSIONMAJOR ${majorVersion}
!define VERSIONMINOR ${minorVersion}
!define VERSIONBUILD ${patchVersion}

!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!define MUI_INSTFILESPAGE_COLORS "3db54a 000000"
!define MUI_ICON "${windowsBuildDirName}\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
!insertmacro MUI_PAGE_DIRECTORY # In which folder install page.
!insertmacro MUI_PAGE_INSTFILES # Installing page.
!insertmacro MUI_PAGE_FINISH # Finished installation page.

!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES

!insertmacro MUI_LANGUAGE "English"

Name "Extraterm"
BrandingText " "
OutFile "extraterm-setup-${packageData.version}.exe"
InstallDir "$PROGRAMFILES64\\Extraterm"
InstallDirRegKey HKLM "Software\\Extraterm" "InstallLocation"

ShowInstDetails show # This will always show the installation details.

Section "Extraterm"
  SetOutPath $INSTDIR
  File /r "${windowsBuildDirName}\\*"

  WriteUninstaller "$INSTDIR\\Uninstall.exe"

  createShortCut "$SMPROGRAMS\\Extraterm.lnk" "$INSTDIR\\extraterm.exe" "" "$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico"

  WriteRegStr HKLM "Software\\Extraterm" "InstallLocation" "$\\"$INSTDIR$\\""
  WriteRegStr HKLM "Software\\Extraterm" "Version" "${packageData.version}"

  # Registry information for add/remove programs
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayName" "\${APPNAME} - \${DESCRIPTION}"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "UninstallString" "$\\"$INSTDIR\\uninstall.exe$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "QuietUninstallString" "$\\"$INSTDIR\\uninstall.exe$\\" /S"
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "InstallLocation" "$\\"$INSTDIR$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayIcon" "$\\"$INSTDIR\\resources\\app\\extraterm\\resources\\logo\\extraterm_small_logo.ico$\\""
	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "Publisher" "\${COMPANYNAME}"

	WriteRegStr HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "DisplayVersion" "\${VERSIONMAJOR}.\${VERSIONMINOR}.\${VERSIONBUILD}"
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMajor" \${VERSIONMAJOR}
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "VersionMinor" \${VERSIONMINOR}
	# There is no option for modifying or repairing the install
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoModify" 1
	WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "NoRepair" 1
	# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size

  # Record the installation size
  \${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
  IntFmt $0 "0x%08X" $0
  WriteRegDWORD HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}" "EstimatedSize" "\$0"

SectionEnd

Section "Uninstall"
  # Remove Start Menu launcher
	Delete "$SMPROGRAMS\\Extraterm.lnk"

  Delete "$INSTDIR\\*.*"
  Delete "$INSTDIR\\Uninstall.exe"
  RMDir /r "$INSTDIR"

  DeleteRegKey HKLM "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\\${APPNAME}"
  DeleteRegKey HKLM "Software\\Extraterm"
SectionEnd
`;
    fs.writeFileSync(path.join(BUILD_TMP_DIR, "installer.nsi"), installerScript, {encoding: "utf-8"});

    exec(`docker run --rm -t -v ${BUILD_TMP_DIR}:/wine/drive_c/src/ cdrx/nsis`);
    return true;
  }

  if (linuxZipOnly) {
    await makePackage("x64", "linux");
    log("Done");
  } else {
    if (! await makePackage("x64", "win32")) {
      return;
    }

    if (! await makePackage("x64", "linux")) {
      return;
    }

    if (! await makePackage("x64", "darwin")) {
      return;
    }

    if (! makeDmg()) {
      return;
    }

    if (! makeNsis()) {
      return;
    }
    log("Done");
  }
}

main();
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {Link, browserHistory} from 'react-router';

export default class Students extends Component {
  constructor (props) {
    super(props);
    this.state = {
      students: [],
      baseQuery: {},
      letter: ''
    };
    this.fields = {
      _id: -1
    };
  }

  componentDidMount() {
    this.setState({letter: this.props.params.letter && this.props.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  componentWillReceiveProps(nextProps) {
    this.setState({letter: nextProps.params.letter && nextProps.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  handleLetterClick(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.getAttribute('href')}`);
  }
  handleSelectChange(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.value}`);
  }
  handleStudentClick(e) {
    e.preventDefault();
    browserHistory.push(`/student/${e.target.getAttribute('href')}`);
  }
  makeQuery(letter) {
    let regex = new RegExp(`^${letter}`, 'i');
    let query = {
      "name.last": regex
    };
    this.setState({baseQuery: query}, () => this.query(query, {
      limit: 0,
      sort: {
        "name.last":1,
        "name.first":1
      },
      fields: this.fields
    }));

  }
  query(q, p) {
    this.props.db.students.find(q, p).fetch((data) => {
      this.setState({students: data});
    });
  }

  render() {
    const letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    return (

              {letters.map((char, i) => (
                <li
                  className={this.state.letter === char ? 'current' : ''}
                  key={`${char}_${i}`}>
                   this.handleLetterClick(e)}>{char}

)
              )}

            <select className="pagination text-center show-for-small-only" role="navigation" aria-label="Pagination" onChange={(e) => this.handleSelectChange(e)}>
              {letters.map((char, i) => <option value={char} key={`${char}_${i}`}>{char}</option>)}
            </select>

              {this.state.students.length === 0 &&
Sorry, no results for {this.state.letter}.
}
              {this.state.students && this.state.students.map((student) => (

                  <Link to={`/student/${student.sid}`}>
                    <img className="student-image-small float-left" src={`${student.picture.thumbnail}`} alt={`${student.name.last} ${student.name.first}`}/>
                    {`${student.name.last} ${student.name.first}`}
                  </Link>

))
              }

    )
  }
}
<commit_msg>widen medium-up<commit_after>import React, {Component} from 'react';
import {Link, browserHistory} from 'react-router';

export default class Students extends Component {
  constructor (props) {
    super(props);
    this.state = {
      students: [],
      baseQuery: {},
      letter: ''
    };
    this.fields = {
      _id: -1
    };
  }

  componentDidMount() {
    this.setState({letter: this.props.params.letter && this.props.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  componentWillReceiveProps(nextProps) {
    this.setState({letter: nextProps.params.letter && nextProps.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  handleLetterClick(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.getAttribute('href')}`);
  }
  handleSelectChange(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.value}`);
  }
  handleStudentClick(e) {
    e.preventDefault();
    browserHistory.push(`/student/${e.target.getAttribute('href')}`);
  }
  makeQuery(letter) {
    let regex = new RegExp(`^${letter}`, 'i');
    let query = {
      "name.last": regex
    };
    this.setState({baseQuery: query}, () => this.query(query, {
      limit: 0,
      sort: {
        "name.last":1,
        "name.first":1
      },
      fields: this.fields
    }));

  }
  query(q, p) {
    this.props.db.students.find(q, p).fetch((data) => {
      this.setState({students: data});
    });
  }

  render() {
    const letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    return (

              {letters.map((char, i) => (
                <li
                  className={this.state.letter === char ? 'current' : ''}
                  key={`${char}_${i}`}>
                   this.handleLetterClick(e)}>{char}

)
              )}

            <select className="pagination text-center show-for-small-only" role="navigation" aria-label="Pagination" onChange={(e) => this.handleSelectChange(e)}>
              {letters.map((char, i) => <option value={char} key={`${char}_${i}`}>{char}</option>)}
            </select>

              {this.state.students.length === 0 &&
Sorry, no results for {this.state.letter}.
}
              {this.state.students && this.state.students.map((student) => (

                  <Link to={`/student/${student.sid}`}>
                    <img className="student-image-small float-left" src={`${student.picture.thumbnail}`} alt={`${student.name.last} ${student.name.first}`}/>
                    {`${student.name.last} ${student.name.first}`}
                  </Link>

))
              }

    )
  }
}
<|endoftext|>


<commit_before>import { VS_HUMAN } from "../reducers/Game.js";
import computeMove from "./AI.js";

// Game action types
export const APPLY_MOVE = "APPLY_MOVE";
export const RESET_GAME = "RESET_GAME";
export const SET_GAME_MODE = "SET_GAME_MODE";
export const START_NEXT_MATCH = "START_NEXT_MATCH";

const MOVE_TIMEOUT = 200;

// GRID_CHANGED action creator
export const applyMoveAction = function ({x, y}) {
  return {
    x: x,
    y: y,
    type: APPLY_MOVE
  };
};

export const computerMoveAction = function (state) {
  return applyMoveAction(computeMove(state));
};

export const applyComputerMoveAfterTimeout = function (state, timeout) {
  return dispatch => setTimeout(
      () => dispatch(computerMoveAction(state)),
      timeout
    );
};

export const resetGameAction = function () {
  return {
    type: RESET_GAME
  };
};

export const setGameModeAction = function (mode) {
  return {
    mode: mode,
    type: SET_GAME_MODE
  };
};

export const startNextMatchAction = function () {
  return {
    type: START_NEXT_MATCH
  }
};

const shouldHumanMove = function (state, {x, y}) {
  let cellAvaliable = state.gameGrid[x][y] === "",
      playingVersusHuman = state.gameMode === VS_HUMAN,
      nextTurnIsUpToX = state.currentTurn === "x",
      isNotComputerTurn = nextTurnIsUpToX !== state.AI.playingX,
      isHumanTurn = playingVersusHuman || isNotComputerTurn;

  return cellAvaliable && isHumanTurn;
};

const shouldComputerMove = function (state) {
  let playingVersusComputer = state.gameMode !== VS_HUMAN,
      gameIsNotOver = !state.gameOver,
      nextTurnIsUpToX = state.currentTurn === "x",
      isComputerTurn = nextTurnIsUpToX === state.AI.playingX;

  return playingVersusComputer && gameIsNotOver && isComputerTurn;
};

const gameTick = function (move) {
  return (dispatch, getState) => {
    let state = getState().game.toJS();
    if(move) {
      if(state.gameOver) {
        dispatch(startNextMatchAction());
        return;
      }
      if(shouldHumanMove(state, move)) {
        dispatch(applyMoveAction(move));
      }
      return;
    }
    if(shouldComputerMove(state)) {
      dispatch(applyComputerMoveAfterTimeout(state, MOVE_TIMEOUT));
    }
  }
};

export default gameTick;
<commit_msg>Update import path for AI computeMove function<commit_after>import { VS_HUMAN } from "../reducers/Game.js";
import computeMove from "../utils/AI.js";

// Game action types
export const APPLY_MOVE = "APPLY_MOVE";
export const RESET_GAME = "RESET_GAME";
export const SET_GAME_MODE = "SET_GAME_MODE";
export const START_NEXT_MATCH = "START_NEXT_MATCH";

const MOVE_TIMEOUT = 200;

// GRID_CHANGED action creator
export const applyMoveAction = function ({x, y}) {
  return {
    x: x,
    y: y,
    type: APPLY_MOVE
  };
};

export const computerMoveAction = function (state) {
  return applyMoveAction(computeMove(state));
};

export const applyComputerMoveAfterTimeout = function (state, timeout) {
  return dispatch => setTimeout(
      () => dispatch(computerMoveAction(state)),
      timeout
    );
};

export const resetGameAction = function () {
  return {
    type: RESET_GAME
  };
};

export const setGameModeAction = function (mode) {
  return {
    mode: mode,
    type: SET_GAME_MODE
  };
};

export const startNextMatchAction = function () {
  return {
    type: START_NEXT_MATCH
  }
};

const shouldHumanMove = function (state, {x, y}) {
  let cellAvaliable = state.gameGrid[x][y] === "",
      playingVersusHuman = state.gameMode === VS_HUMAN,
      nextTurnIsUpToX = state.currentTurn === "x",
      isNotComputerTurn = nextTurnIsUpToX !== state.AI.playingX,
      isHumanTurn = playingVersusHuman || isNotComputerTurn;

  return cellAvaliable && isHumanTurn;
};

const shouldComputerMove = function (state) {
  let playingVersusComputer = state.gameMode !== VS_HUMAN,
      gameIsNotOver = !state.gameOver,
      nextTurnIsUpToX = state.currentTurn === "x",
      isComputerTurn = nextTurnIsUpToX === state.AI.playingX;

  return playingVersusComputer && gameIsNotOver && isComputerTurn;
};

const gameTick = function (move) {
  return (dispatch, getState) => {
    let state = getState().game.toJS();
    if(move) {
      if(state.gameOver) {
        dispatch(startNextMatchAction());
        return;
      }
      if(shouldHumanMove(state, move)) {
        dispatch(applyMoveAction(move));
      }
      return;
    }
    if(shouldComputerMove(state)) {
      dispatch(applyComputerMoveAfterTimeout(state, MOVE_TIMEOUT));
    }
  }
};

export default gameTick;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Bar from './Bar';
import './Terminal.css';
import ObjectInspector from 'react-object-inspector';

function handleLogging(method, addToOutput) {
  const old = console[method];
  console[method] = (...args) => {
    old(...args);
    const res = [...args].map((arg, i) => {
      switch (typeof arg) {
        case 'object':
          return <ObjectInspector data={arg} key={i} />;
        case 'function':
          return `${arg}`;
        default:
          return arg;
      }
    });
    addToOutput(res);
  };
}

class Terminal extends Component {
  static displayName='Terminal';

  static propTypes = {
    msg: PropTypes.string,
    color: PropTypes.string,
    prompt: PropTypes.string,
    barColor: PropTypes.string,
    backgroundColor: PropTypes.string,
    commands: PropTypes.objectOf(PropTypes.func),
    description: PropTypes.objectOf(PropTypes.string),
    watchConsoleLogging: PropTypes.bool,
  };

  state = {
    prompt: '>',
    summary: [],
    commands: {},
    description: {},
    watchConsoleLogging: false
  };

  componentDidMount = () => {
    const node = this.refs.com;
    node.focus();
    this.allCommands();
    this.setDescription();
    this.showMsg();

    if (this.props.watchConsoleLogging) {
      this.watchConsoleLogging();
    }
  };

  adder = (inp) => {
    let summary = this.state.summary;
    summary.push(inp);
    this.setState({ summary, });
  }

  watchConsoleLogging = () => {
    handleLogging('log', this.adder);
    handleLogging('warn', this.adder);
    handleLogging('error', this.adder);
    handleLogging('info', this.adder);
  }

  allCommands = () => {
    this.setState({
      commands: {
        'clear': this.clearScreen,
        'show': this.showMsg,
        'help': this.showHelp,
        ...this.props.commands
      }
    });
  }

  setDescription = () => {
    this.setState({
      description: {
        'clear': 'clear the screen',
        'show': 'show the msg',
        'help': 'list all the commands',
        ...this.props.description
      }
    })
  }

  clearScreen = () => {
    this.setState({
      summary: []
    });
  }

  showMsg = () => {
    this.adder(this.props.msg);
  }

  showHelp = () => {
    const options = Object.keys(this.state.commands);
    const description = this.state.description
    for (var option of options) {
      this.adder(`${option} - ${description[option]}`);
    }
  }

  handleChange = (e) => {
    if (e.key === "Enter") {
      var inputText = this.refs.com.value;
      var inputArray = inputText.split(' ');
      var input = inputArray[0];
      var arg = inputArray[1]; // Undefined for function call
      var command = this.state.commands[input];
      this.adder(this.state.prompt + " " + inputText);

      if (command === undefined) {
        this.adder("-bash:" + input + ": command not found");
      }

      else if (input === 'clear') {
        this.clearScreen();
      }

      else {
        this.adder(command(arg));
      }

      this.refs.com.value = "";
    }
  }

  render () {
    const inputStyles = { backgroundColor: this.props.backgroundColor, color: this.props.color };
    const prompt = { color: this.props.prompt };
    const barColor = { backgroundColor: this.props.barColor };
    const backgroundColor = { backgroundColor: this.props.backgroundColor }

    const output = this.state.summary.map((content, i) => {
      return (

{content}

      );
    });

    return (

        <Bar style={ barColor }/>

                {output}

                  {this.state.prompt}
                  <input className="terminal-main-input" style={inputStyles} type="text" ref="com" onKeyPress={this.handleChange} />

    );
  }

}

export default Terminal;
<commit_msg>fix chrome duplication of warn and error calls<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Bar from './Bar';
import './Terminal.css';
import ObjectInspector from 'react-object-inspector';

const oldConsoleLog = console['log'];

function handleLogging(method, addToOutput) {
  console[method] = (...args) => {
    oldConsoleLog(`[${method}]`, ...args);
    const res = [...args].map((arg, i) => {
      switch (typeof arg) {
        case 'object':
          return <ObjectInspector data={arg} key={i} />;
        case 'function':
          return `${arg}`;
        default:
          return arg;
      }
    });
    addToOutput(res);
  };
}

class Terminal extends Component {
  static displayName='Terminal';

  static propTypes = {
    msg: PropTypes.string,
    color: PropTypes.string,
    prompt: PropTypes.string,
    barColor: PropTypes.string,
    backgroundColor: PropTypes.string,
    commands: PropTypes.objectOf(PropTypes.func),
    description: PropTypes.objectOf(PropTypes.string),
    watchConsoleLogging: PropTypes.bool,
  };

  state = {
    prompt: '>',
    summary: [],
    commands: {},
    description: {},
    watchConsoleLogging: false
  };

  componentDidMount = () => {
    const node = this.refs.com;
    node.focus();
    this.allCommands();
    this.setDescription();
    this.showMsg();

    if (this.props.watchConsoleLogging) {
      this.watchConsoleLogging();
    }
  };

  adder = (inp) => {
    let summary = this.state.summary;
    summary.push(inp);
    this.setState({ summary, });
  }

  watchConsoleLogging = () => {
    handleLogging('log', this.adder);
    handleLogging('info', this.adder);
    handleLogging('warn', this.adder);
    handleLogging('error', this.adder);
  }

  allCommands = () => {
    this.setState({
      commands: {
        'clear': this.clearScreen,
        'show': this.showMsg,
        'help': this.showHelp,
        ...this.props.commands
      }
    });
  }

  setDescription = () => {
    this.setState({
      description: {
        'clear': 'clear the screen',
        'show': 'show the msg',
        'help': 'list all the commands',
        ...this.props.description
      }
    })
  }

  clearScreen = () => {
    this.setState({
      summary: []
    });
  }

  showMsg = () => {
    this.adder(this.props.msg);
  }

  showHelp = () => {
    const options = Object.keys(this.state.commands);
    const description = this.state.description
    for (var option of options) {
      this.adder(`${option} - ${description[option]}`);
    }
  }

  handleChange = (e) => {
    if (e.key === "Enter") {
      var inputText = this.refs.com.value;
      var inputArray = inputText.split(' ');
      var input = inputArray[0];
      var arg = inputArray[1]; // Undefined for function call
      var command = this.state.commands[input];
      this.adder(this.state.prompt + " " + inputText);

      if (command === undefined) {
        this.adder("-bash:" + input + ": command not found");
      }

      else if (input === 'clear') {
        this.clearScreen();
      }

      else {
        this.adder(command(arg));
      }

      this.refs.com.value = "";
    }
  }

  render () {
    const inputStyles = { backgroundColor: this.props.backgroundColor, color: this.props.color };
    const prompt = { color: this.props.prompt };
    const barColor = { backgroundColor: this.props.barColor };
    const backgroundColor = { backgroundColor: this.props.backgroundColor }

    const output = this.state.summary.map((content, i) => {
      return (

{content}

      );
    });

    return (

        <Bar style={ barColor }/>

                {output}

                  {this.state.prompt}
                  <input className="terminal-main-input" style={inputStyles} type="text" ref="com" onKeyPress={this.handleChange} />

    );
  }

}

export default Terminal;
<|endoftext|>