Procházet zdrojové kódy

electron 11 cleanup

Eugene Pankov před 5 roky
rodič
revize
0ca971a289
87 změnil soubory, kde provedl 9714 přidání a 8790 odebrání
  1. 17 1
      .eslintrc.yml
  2. 15 4
      .github/workflows/macos.yml
  3. 13 13
      app/lib/app.ts
  4. 1 1
      app/lib/config.ts
  5. 2 2
      app/lib/lru.ts
  6. 1 1
      app/lib/portable.ts
  7. 1 1
      app/lib/sentry.ts
  8. 8 8
      app/lib/window.ts
  9. 6 13
      app/package.json
  10. 1 1
      app/src/plugins.ts
  11. 8 38
      app/yarn.lock
  12. 0 78
      binding.gyp_hack
  13. 1 4
      electron-builder.yml
  14. 6 14
      package.json
  15. 0 22
      scripts/build-macos-arm64.js
  16. 1 0
      scripts/build-macos.js
  17. 1 0
      scripts/build-native.js
  18. 0 2
      scripts/install-deps.js
  19. 0 7
      scripts/local-release-macos.sh
  20. 8 7
      scripts/prepackage-plugins.js
  21. 1 1
      terminus-community-color-schemes/package.json
  22. 3 4
      terminus-core/package.json
  23. 2 1
      terminus-core/src/api/tabContextMenuProvider.ts
  24. 4 3
      terminus-core/src/components/tabHeader.component.ts
  25. 0 1
      terminus-core/src/index.ts
  26. 2 2
      terminus-core/src/services/config.service.ts
  27. 6 5
      terminus-core/src/services/docking.service.ts
  28. 12 12
      terminus-core/src/services/electron.service.ts
  29. 1 1
      terminus-core/src/services/homeBase.service.ts
  30. 12 4
      terminus-core/src/services/hostApp.service.ts
  31. 2 2
      terminus-core/src/services/log.service.ts
  32. 2 2
      terminus-core/src/services/shellIntegration.service.ts
  33. 3 3
      terminus-core/src/services/touchbar.service.ts
  34. 9 8
      terminus-core/src/tabContextMenu.ts
  35. 1 9
      terminus-core/yarn.lock
  36. 4 4
      terminus-plugin-manager/package.json
  37. 2 2
      terminus-serial/package.json
  38. 2 2
      terminus-serial/src/components/serialTab.component.ts
  39. 1 1
      terminus-settings/package.json
  40. 2 6
      terminus-settings/src/components/settingsTab.component.ts
  41. 1 1
      terminus-ssh/package.json
  42. 6 6
      terminus-ssh/src/api.ts
  43. 2 2
      terminus-ssh/src/components/sshTab.component.ts
  44. 4 4
      terminus-ssh/src/services/ssh.service.ts
  45. 3 2
      terminus-ssh/src/winSCPIntegration.ts
  46. 1 1
      terminus-terminal/package.json
  47. 8 7
      terminus-terminal/src/api/baseTerminalTab.component.ts
  48. 2 1
      terminus-terminal/src/api/contextMenuProvider.ts
  49. 1 1
      terminus-terminal/src/api/interfaces.ts
  50. 2 2
      terminus-terminal/src/colorSchemes.ts
  51. 0 1
      terminus-terminal/src/features/zmodem.ts
  52. 10 10
      terminus-terminal/src/frontends/xtermFrontend.ts
  53. 1 1
      terminus-terminal/src/index.ts
  54. 2 2
      terminus-terminal/src/services/sessions.service.ts
  55. 8 7
      terminus-terminal/src/tabContextMenu.ts
  56. binární
      tmp/assets/activity.png
  57. 55 0
      tmp/assets/logo.svg
  58. binární
      tmp/assets/tray-darwinHighlightTemplate.png
  59. binární
      tmp/assets/[email protected]
  60. binární
      tmp/assets/tray-darwinTemplate.png
  61. binární
      tmp/assets/[email protected]
  62. binární
      tmp/assets/tray.png
  63. 4 0
      tmp/dev-app-update.yml
  64. 22 0
      tmp/index.pug
  65. 230 0
      tmp/lib/app.ts
  66. 45 0
      tmp/lib/cli.ts
  67. 13 0
      tmp/lib/config.ts
  68. 68 0
      tmp/lib/index.ts
  69. 17 0
      tmp/lib/lru.ts
  70. 24 0
      tmp/lib/portable.ts
  71. 21 0
      tmp/lib/sentry.ts
  72. 389 0
      tmp/lib/window.ts
  73. 54 0
      tmp/package.json
  74. 33 0
      tmp/src/app.module.ts
  75. 9 0
      tmp/src/entry.preload.ts
  76. 65 0
      tmp/src/entry.ts
  77. 97 0
      tmp/src/global.scss
  78. 188 0
      tmp/src/plugins.ts
  79. 60 0
      tmp/src/preload.scss
  80. 6 0
      tmp/src/root.component.ts
  81. 21 0
      tmp/src/toastr.scss
  82. 32 0
      tmp/tsconfig.json
  83. 30 0
      tmp/tsconfig.main.json
  84. 86 0
      tmp/webpack.config.js
  85. 53 0
      tmp/webpack.main.config.js
  86. 1 1
      tsconfig.json
  87. 7879 8451
      yarn.lock

+ 17 - 1
.eslintrc.yml

@@ -37,6 +37,7 @@ rules:
   '@typescript-eslint/strict-boolean-expressions': off
   '@typescript-eslint/no-misused-promises': off
   '@typescript-eslint/typedef': off
+  '@typescript-eslint/consistent-type-imports': off
   '@typescript-eslint/no-use-before-define':
   - error
   - classes: false
@@ -53,7 +54,8 @@ rules:
   computed-property-spacing:
   - error
   - never
-  comma-dangle:
+  comma-dangle: off
+  '@typescript-eslint/comma-dangle':
   - error
   - always-multiline
   curly: error
@@ -104,3 +106,17 @@ rules:
   '@typescript-eslint/no-unsafe-call': off
   '@typescript-eslint/no-unsafe-return': off
   '@typescript-eslint/no-base-to-string': off # broken in typescript-eslint
+  '@typescript-eslint/no-unsafe-assignment': off
+  '@typescript-eslint/naming-convention': off
+  '@typescript-eslint/lines-between-class-members':
+  - error
+  - exceptAfterSingleLine: true
+  '@typescript-eslint/non-nullable-type-assertion-style': off
+  '@typescript-eslint/dot-notation': off
+  '@typescript-eslint/no-confusing-void-expression': off
+  '@typescript-eslint/prefer-enum-initializers': off
+  '@typescript-eslint/no-implicit-any-catch': off
+  '@typescript-eslint/member-ordering': off
+  '@typescript-eslint/consistent-indexed-object-style': off
+  '@typescript-eslint/no-var-requires': off
+  '@typescript-eslint/no-shadow': off

+ 15 - 4
.github/workflows/macos.yml

@@ -3,6 +3,11 @@ on: [push, pull_request]
 jobs:
   build:
     runs-on: macOS-latest
+    strategy:
+      matrix:
+        include:
+          - arch: x86_64
+          - arch: arm64
 
     steps:
     - name: Checkout
@@ -24,12 +29,16 @@ jobs:
 
     - name: Build native deps
       run: scripts/build-native.js
+      env:
+        ARCH: ${{arch}}
 
     - name: Webpack
       run: yarn run build
 
     - name: Prepackage plugins
       run: scripts/prepackage-plugins.js
+      env:
+        ARCH: ${{arch}}
 
     - run: sed -i '' 's/updateInfo = await/\/\/updateInfo = await/g' node_modules/app-builder-lib/out/targets/ArchiveTarget.js
 
@@ -37,18 +46,20 @@ jobs:
       run: scripts/build-macos.js
       if: github.repository == 'Eugeny/terminus' && github.event_name == 'push'
       env:
-        #DEBUG: electron-builder,electron-builder:*
+        ARCH: ${{arch}}
         GH_TOKEN: ${{ secrets.GH_TOKEN }}
         CSC_LINK: ${{ secrets.CSC_LINK }}
         CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
         APPSTORE_USERNAME: ${{ secrets.APPSTORE_USERNAME }}
         APPSTORE_PASSWORD: ${{ secrets.APPSTORE_PASSWORD }}
+        # DEBUG: electron-builder,electron-builder:*
 
     - name: Build packages without signing
       run: scripts/build-macos.js
       if: github.repository != 'Eugeny/terminus' || github.event_name != 'push'
       env:
-        DEBUG: electron-builder,electron-builder:*
+        ARCH: ${{arch}}
+        # DEBUG: electron-builder,electron-builder:*
 
     - name: Package artifacts
       run: |
@@ -60,11 +71,11 @@ jobs:
     - uses: actions/upload-artifact@master
       name: Upload PKG
       with:
-        name: macOS .pkg
+        name: macOS .pkg (${{arch}})
         path: artifact-pkg
 
     - uses: actions/upload-artifact@master
       name: Upload ZIP
       with:
-        name: macOS .zip
+        name: macOS .zip (${{arch}})
         path: artifact-zip

+ 13 - 13
app/lib/app.ts

@@ -1,6 +1,4 @@
-import { app, ipcMain, Menu, Tray, shell, globalShortcut } from 'electron'
-// eslint-disable-next-line no-duplicate-imports
-import * as electron from 'electron'
+import { app, ipcMain, Menu, Tray, shell, screen, globalShortcut, MenuItemConstructorOptions } from 'electron'
 import { loadConfig } from './config'
 import { Window, WindowOptions } from './window'
 
@@ -15,7 +13,7 @@ export class Application {
 
         ipcMain.on('app:register-global-hotkey', (_event, specs) => {
             globalShortcut.unregisterAll()
-            for (let spec of specs) {
+            for (const spec of specs) {
                 globalShortcut.register(spec, () => {
                     this.onGlobalHotkey()
                 })
@@ -41,11 +39,13 @@ export class Application {
     }
 
     init (): void {
-        electron.screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
+        screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
+        screen.on('display-added', () => this.broadcast('host:displays-changed'))
+        screen.on('display-removed', () => this.broadcast('host:displays-changed'))
     }
 
     async newWindow (options?: WindowOptions): Promise<Window> {
-        let window = new Window(options)
+        const window = new Window(options)
         this.windows.push(window)
         window.visible$.subscribe(visible => {
             if (visible) {
@@ -66,29 +66,29 @@ export class Application {
 
     onGlobalHotkey (): void {
         if (this.windows.some(x => x.isFocused())) {
-            for (let window of this.windows) {
+            for (const window of this.windows) {
                 window.hide()
             }
         } else {
-            for (let window of this.windows) {
+            for (const window of this.windows) {
                 window.present()
             }
         }
     }
 
     presentAllWindows (): void {
-        for (let window of this.windows) {
+        for (const window of this.windows) {
             window.present()
         }
     }
 
-    broadcast (event: string, ...args): void {
+    broadcast (event: string, ...args: any[]): void {
         for (const window of this.windows) {
             window.send(event, ...args)
         }
     }
 
-    async send (event: string, ...args): Promise<void> {
+    async send (event: string, ...args: any[]): Promise<void> {
         if (!this.hasWindows()) {
             await this.newWindow()
         }
@@ -132,7 +132,7 @@ export class Application {
     }
 
     focus (): void {
-        for (let window of this.windows) {
+        for (const window of this.windows) {
             window.show()
         }
     }
@@ -143,7 +143,7 @@ export class Application {
     }
 
     private setupMenu () {
-        let template: Electron.MenuItemConstructorOptions[] = [
+        const template: MenuItemConstructorOptions[] = [
             {
                 label: 'Application',
                 submenu: [

+ 1 - 1
app/lib/config.ts

@@ -4,7 +4,7 @@ import * as yaml from 'js-yaml'
 import { app } from 'electron'
 
 export function loadConfig (): any {
-    let configPath = path.join(app.getPath('userData'), 'config.yaml')
+    const configPath = path.join(app.getPath('userData'), 'config.yaml')
     if (fs.existsSync(configPath)) {
         return yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
     } else {

+ 2 - 2
app/lib/lru.ts

@@ -1,6 +1,6 @@
-import * as createLRU from 'lru-cache'
+import * as LRU from 'lru-cache'
 import * as fs from 'fs'
-const lru = new createLRU({ max: 256, maxAge: 250 })
+const lru = new LRU({ max: 256, maxAge: 250 })
 const origLstat = fs.realpathSync.bind(fs)
 
 // NB: The biggest offender of thrashing realpathSync is the node module system

+ 1 - 1
app/lib/portable.ts

@@ -9,7 +9,7 @@ try {
 }
 
 if (null != appPath) {
-    if(fs.existsSync(path.join(appPath, 'terminus-data'))) {
+    if (fs.existsSync(path.join(appPath, 'terminus-data'))) {
         fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
     }
     const portableData = path.join(appPath, 'data')

+ 1 - 1
app/lib/sentry.ts

@@ -3,7 +3,7 @@ import * as isDev from 'electron-is-dev'
 
 
 const SENTRY_DSN = 'https://[email protected]/181876'
-let release
+let release = null
 try {
     release = require('electron').app.getVersion()
 } catch {

+ 8 - 8
app/lib/window.ts

@@ -5,7 +5,7 @@ if (process.platform === 'win32' || process.platform === 'linux') {
 
 import { Subject, Observable } from 'rxjs'
 import { debounceTime } from 'rxjs/operators'
-import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen } from 'electron'
+import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions } from 'electron'
 import ElectronConfig = require('electron-config')
 import * as os from 'os'
 import * as path from 'path'
@@ -13,7 +13,7 @@ import * as path from 'path'
 import { parseArgs } from './cli'
 import { loadConfig } from './config'
 
-let DwmEnableBlurBehindWindow: any
+let DwmEnableBlurBehindWindow: any = null
 if (process.platform === 'win32') {
     DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
 }
@@ -45,13 +45,13 @@ export class Window {
         this.windowConfig = new ElectronConfig({ name: 'window' })
         this.windowBounds = this.windowConfig.get('windowBoundaries')
 
-        let maximized = this.windowConfig.get('maximized')
-        let bwOptions: Electron.BrowserWindowConstructorOptions = {
+        const maximized = this.windowConfig.get('maximized')
+        const bwOptions: BrowserWindowConstructorOptions = {
             width: 800,
             height: 600,
             title: 'Terminus',
             minWidth: 400,
-            minHeight: 300,   
+            minHeight: 300,
             webPreferences: {
                 nodeIntegration: true,
                 preload: path.join(__dirname, 'sentry.js'),
@@ -139,7 +139,7 @@ export class Window {
             } else {
                 DwmEnableBlurBehindWindow(this.window, enabled)
             }
-        } else if (process.platform ==='linux') {
+        } else if (process.platform === 'linux') {
             glasstron.update(this.window, {
                 linux: { requestBlur: enabled },
             })
@@ -158,7 +158,7 @@ export class Window {
         this.window.focus()
     }
 
-    send (event: string, ...args): void {
+    send (event: string, ...args: any[]): void {
         if (!this.window) {
             return
         }
@@ -225,7 +225,7 @@ export class Window {
             this.visible.next(false)
         })
 
-        let moveSubscription = new Observable<void>(observer => {
+        const moveSubscription = new Observable<void>(observer => {
             this.window.on('move', () => observer.next())
         }).pipe(debounceTime(250)).subscribe(() => {
             this.send('host:window-moved')

+ 6 - 13
app/package.json

@@ -21,8 +21,7 @@
     "@angular/platform-browser": "^9.1.9",
     "@angular/platform-browser-dynamic": "^9.1.9",
     "@ng-bootstrap/ng-bootstrap": "^6.1.0",
-    "@terminus-term/node-pty": "0.10.0-beta9",
-    "devtron": "1.4.0",
+    "@terminus-term/node-pty": "0.10.0-beta10",
     "electron-config": "2.0.0",
     "electron-debug": "^3.0.1",
     "electron-is-dev": "1.1.0",
@@ -30,18 +29,12 @@
     "glasstron": "AryToNeX/Glasstron#dependabot/npm_and_yarn/electron-11.1.0",
     "js-yaml": "3.14.0",
     "keytar": "^7.2.0",
-    "macos-native-processlist": "^2.0.0",
     "mz": "^2.7.0",
     "ngx-toastr": "^12.0.1",
-    "node-gyp": "^7.1.2",
     "npm": "7.0.15",
     "path": "0.12.7",
     "rxjs": "^6.5.5",
     "rxjs-compat": "^6.6.0",
-    "serialport": "^9.0.4",
-    "windows-blurbehind": "^1.0.1",
-    "windows-native-registry": "^3.0.0",
-    "windows-process-tree": "^0.2.4",
     "yargs": "^15.4.1",
     "zone.js": "^0.11.3"
   },
@@ -55,15 +48,15 @@
   "devDependencies": {
     "@types/mz": "0.0.32",
     "@types/node": "14.14.14",
-    "node-abi": "github:lgeiger/node-abi"
+    "node-abi": "2.19.3"
   },
   "peerDependencies": {
+    "terminus-community-color-schemes": "*",
     "terminus-core": "*",
-    "terminus-settings": "*",
-    "terminus-serial": "*",
     "terminus-plugin-manager": "*",
-    "terminus-community-color-schemes": "*",
+    "terminus-serial": "*",
+    "terminus-settings": "*",
     "terminus-ssh": "*",
     "terminus-terminal": "*"
-  } 
+  }
 }

+ 1 - 1
app/src/plugins.ts

@@ -83,7 +83,7 @@ const originalRequire = (global as any).require
     if (cachedBuiltinModules[query]) {
         return cachedBuiltinModules[query]
     }
-    return originalRequire.apply(this, arguments)
+    return originalRequire.apply(this, [query])
 }
 
 const originalModuleRequire = nodeModule.prototype.require

+ 8 - 38
app/yarn.lock

@@ -266,12 +266,12 @@
   dependencies:
     debug "^4.1.1"
 
-"@terminus-term/[email protected]9":
-  version "0.10.0-beta9"
-  resolved "https://registry.npmjs.org/@terminus-term/node-pty/-/node-pty-0.10.0-beta9.tgz"
-  integrity sha512-wnttx12b9gxP9CPB9uqBMQx/Vp4EboUDGOY3xRP0Nvhec6pSF2qFZD6bwMbNzFIopbaohluEYcbEul0jTQcdeQ==
+"@terminus-term/[email protected]10":
+  version "0.10.0-beta10"
+  resolved "https://registry.yarnpkg.com/@terminus-term/node-pty/-/node-pty-0.10.0-beta10.tgz#de9dade3d7549d44b0906ec0d0b9e1bb411f1f21"
+  integrity sha512-j9RJk7sD/es4vR6+AR5M/p3SicVxY6kZEeE0UQKhHNcaAla90/mcGeBNicAWPaAkjO1uQZVbYh5cJMMu5unQgA==
   dependencies:
-    nan "^2.13.2"
+    nan "^2.14.0"
 
 "@tootallnate/once@1":
   version "1.1.2"
@@ -295,11 +295,6 @@ abbrev@1, abbrev@~1.1.1:
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz"
   integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
 
-accessibility-developer-tools@^2.11.0:
-  version "2.12.0"
-  resolved "https://registry.npmjs.org/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz"
-  integrity sha1-PaDM6dbsY3OWS4TzXbfPw996tRQ=
-
 agent-base@6:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz"
@@ -738,15 +733,6 @@ detect-libc@^1.0.3:
   resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz"
   integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
 
[email protected]:
-  version "1.4.0"
-  resolved "https://registry.npmjs.org/devtron/-/devtron-1.4.0.tgz"
-  integrity sha1-tedIvW6Vu+cL/MaKrm/mlhGUQeE=
-  dependencies:
-    accessibility-developer-tools "^2.11.0"
-    highlight.js "^9.3.0"
-    humanize-plus "^1.8.1"
-
 dezalgo@^1.0.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz"
@@ -1022,11 +1008,6 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-highlight.js@^9.3.0:
-  version "9.18.5"
-  resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz"
-  integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA==
-
 hosted-git-info@^3.0.6:
   version "3.0.7"
   resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz"
@@ -1072,11 +1053,6 @@ humanize-ms@^1.2.1:
   dependencies:
     ms "^2.0.0"
 
-humanize-plus@^1.8.1:
-  version "1.8.2"
-  resolved "https://registry.npmjs.org/humanize-plus/-/humanize-plus-1.8.2.tgz"
-  integrity sha1-pls0RZrWNnrbs3B6gqPJ+RYWcDA=
-
 iconv-lite@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz"
@@ -1585,9 +1561,9 @@ mz@^2.7.0:
     object-assign "^4.0.1"
     thenify-all "^1.0.0"
 
-nan@^2.13.2, nan@^2.14.2:
+nan@^2.13.2, nan@^2.14.0, nan@^2.14.2:
   version "2.14.2"
-  resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
   integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
 
 napi-build-utils@^1.0.1:
@@ -1602,19 +1578,13 @@ ngx-toastr@^12.0.1:
   dependencies:
     tslib "^1.10.0"
 
-node-abi@^2.7.0:
+[email protected], node-abi@^2.7.0:
   version "2.19.3"
   resolved "https://registry.npmjs.org/node-abi/-/node-abi-2.19.3.tgz"
   integrity sha512-9xZrlyfvKhWme2EXFKQhZRp1yNWT/uI1luYPr3sFl+H4keYY4xR+1jO7mvTTijIsHf1M+QDe9uWuKeEpLInIlg==
   dependencies:
     semver "^5.4.1"
 
-"node-abi@github:lgeiger/node-abi":
-  version "0.0.0-development"
-  resolved "https://codeload.github.com/lgeiger/node-abi/tar.gz/d7a3f00c93cb16b5f4fbb3ae8c106e798cc52042"
-  dependencies:
-    semver "^5.4.1"
-
 [email protected]:
   version "3.0.0"
   resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz"

+ 0 - 78
binding.gyp_hack

@@ -1,78 +0,0 @@
-{
-  'conditions': [
-    ['OS=="win"', {
-      'targets': [
-        {
-          'target_name': 'conpty',
-          'include_dirs' : [
-            '<!(node -e "require(\'nan\')")'
-          ],
-          'sources' : [
-            'src/win/conpty.cc',
-            'src/win/path_util.cc'
-          ],
-          'libraries': [
-            'shlwapi.lib'
-          ]
-        },
-        {
-          'target_name': 'conpty_console_list',
-          'include_dirs' : [
-            '<!(node -e "require(\'nan\')")'
-          ],
-          'sources' : [
-            'src/win/conpty_console_list.cc'
-          ]
-        },
-        {
-          'target_name': 'pty',
-          'include_dirs' : [
-            '<!(node -e "require(\'nan\')")',
-            'deps/winpty/src/include',
-          ],
-          # Disabled due to winpty
-          'msvs_disabled_warnings': [ 4506, 4530 ],
-          'dependencies' : [
-            'deps/winpty/src/winpty.gyp:winpty-agent',
-            'deps/winpty/src/winpty.gyp:winpty',
-          ],
-          'sources' : [
-            'src/win/winpty.cc',
-            'src/win/path_util.cc'
-          ],
-          'libraries': [
-            'shlwapi.lib'
-          ],
-        }
-      ]
-    }, { # OS!="win"
-      'targets': [{
-        'target_name': 'pty',
-        'include_dirs' : [
-          '<!(node -e "require(\'nan\')")'
-        ],
-        'sources': [
-          'src/unix/pty.cc'
-        ],
-        'libraries': [
-          '-lutil'
-        ],
-        'conditions': [
-          # http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html
-          #   One some systems (at least including Cygwin, Interix,
-          #   OSF/1 4 and 5, and Mac OS X) linking with -lutil is not required.
-          ['OS=="mac" or OS=="solaris"', {
-            'libraries!': [
-              '-lutil'
-            ]
-          }],
-          ['OS=="mac"', {
-            "xcode_settings": {
-              "MACOSX_DEPLOYMENT_TARGET":"10.7"
-            }
-          }]
-        ]
-      }]
-    }]
-  ]
-}

+ 1 - 4
electron-builder.yml

@@ -25,7 +25,7 @@ nsis:
 mac:
   category: public.app-category.video
   icon: "./build/mac/icon.icns"
-  artifactName: terminus-${version}-macos.${ext}
+  artifactName: terminus-${version}-macos-${env.ARCH}.${ext}
   hardenedRuntime: true
   entitlements: "./build/mac/entitlements.plist"
   entitlementsInherit: "./build/mac/entitlements.plist"
@@ -40,9 +40,6 @@ mac:
     NSNetworkVolumesUsageDescription: 'A subprocess requests access to files on a network volume.'
     NSRemovableVolumesUsageDescription: 'A subprocess requests access to files on a removable volume.'
 
-pkg:
-  artifactName: terminus-${version}-macos.pkg
-
 linux:
   category: Utilities
   icon: "./build/icons"

+ 6 - 14
package.json

@@ -9,8 +9,8 @@
     "@types/js-yaml": "^3.12.5",
     "@types/node": "14.14.14",
     "@types/webpack-env": "^1.16.0",
-    "@typescript-eslint/eslint-plugin": "^4.1.0",
-    "@typescript-eslint/parser": "^4.1.0",
+    "@typescript-eslint/eslint-plugin": "^4.11.0",
+    "@typescript-eslint/parser": "^4.11.0",
     "apply-loader": "2.0.0",
     "awesome-typescript-loader": "^5.2.1",
     "core-js": "^3.8.1",
@@ -22,13 +22,13 @@
     "electron-installer-snap": "^5.1.0",
     "electron-notarize": "^1.0.0",
     "electron-rebuild": "^2.3.4",
-    "eslint": "^7.16.0",
-    "eslint-plugin-import": "^2.22.1",
+    "eslint": "^7.6.0",
+    "eslint-plugin-import": "^2.21.1",
     "file-loader": "^5.1.0",
     "graceful-fs": "^4.2.4",
     "html-loader": "0.5.5",
     "json-loader": "0.5.7",
-    "node-abi": "lgeiger/node-abi#d7a3f00c93cb16b5f4fbb3ae8c106e798cc52042",
+    "node-abi": "^2.19.3",
     "node-gyp": "^7.1.2",
     "node-sass": "^5.0.0",
     "npmlog": "4.1.2",
@@ -39,7 +39,6 @@
     "pug-loader": "^2.4.0",
     "pug-static-loader": "2.0.0",
     "raw-loader": "4.0.1",
-    "run-script-os": "^1.1.3",
     "sass-loader": "^10.1.0",
     "shelljs": "0.8.4",
     "source-code-pro": "^2.30.2",
@@ -55,8 +54,6 @@
     "val-loader": "2.1.1",
     "webpack": "^5.11.0",
     "webpack-cli": "^4.2.0",
-    "winston": "^3.3.3",
-    "winston-transport": "winstonjs/winston-transport",
     "yaml-loader": "0.6.0"
   },
   "resolutions": {
@@ -76,10 +73,5 @@
   },
   "repository": "eugeny/terminus",
   "author": "Eugene Pankov",
-  "license": "MIT",
-  "dependencies": {
-    "ssh2-streams": "^0.4.10",
-    "winston": "^3.3.3",
-    "yarn": "^1.22.10"
-  }
+  "license": "MIT"
 }

+ 0 - 22
scripts/build-macos-arm64.js

@@ -1,22 +0,0 @@
-#!/usr/bin/env node
-const builder = require('electron-builder').build
-const vars = require('./vars')
-const fs = require('fs')
-const signHook = require('../build/mac/afterSignHook')
-
-const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
-
-builder({
-    dir: true,
-    mac: ['pkg', 'zip'],
-    arm64: true, 
-    config: {
-        extraMetadata: {
-            version: vars.version,
-        },
-    },
-    publish: isTag ? 'always' : 'onTag',
-}).catch(e => {
-    console.error(e)
-    process.exit(1)
-})

+ 1 - 0
scripts/build-macos.js

@@ -9,6 +9,7 @@ const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
 builder({
     dir: true,
     mac: ['pkg', 'zip'],
+    arm64: (process.env.ARCH ?? process.arch) === 'arm64',
     config: {
         extraMetadata: {
             version: vars.version,

+ 1 - 0
scripts/build-native.js

@@ -8,6 +8,7 @@ for (let dir of ['app', 'terminus-core', 'terminus-ssh', 'terminus-terminal']) {
     const build = rebuild({
         buildPath: path.resolve(__dirname, '../' + dir),
         electronVersion: vars.electronVersion,
+        arch: process.env.ARCH ?? process.arch,
         force: true,
     })
     build.catch(e => {

+ 0 - 2
scripts/install-deps.js

@@ -20,8 +20,6 @@ vars.builtinPlugins.forEach(plugin => {
   sh.cd('..')
 })
 
-sh.cp('binding.gyp_hack', "app/node_modules/@terminus-term/node-pty/binding.gyp")
-
 if (['darwin', 'linux'].includes(process.platform)) {
   sh.cd('node_modules')
   for (let x of vars.builtinPlugins) {

+ 0 - 7
scripts/local-release-macos.sh

@@ -1,7 +0,0 @@
-#This script creates local release for macos
-
- ./scripts/install-deps.js
- ./scripts/build-native.js
- ./scripts/prepackage-plugins.js
- npm run build
- ./scripts/build-macos-arm64.js

+ 8 - 7
scripts/prepackage-plugins.js

@@ -6,9 +6,6 @@ const fs = require('fs')
 const vars = require('./vars')
 const log = require('npmlog')
 
-const localBinPath = path.resolve(__dirname, '../node_modules/.bin');
-const npx = `${localBinPath}/npx`;
-
 let target = path.resolve(__dirname, '../builtin-plugins')
 sh.mkdir('-p', target)
 fs.writeFileSync(path.join(target, 'package.json'), '{}')
@@ -18,13 +15,17 @@ vars.builtinPlugins.forEach(plugin => {
   sh.cp('-r', path.join('..', plugin), '.')
   sh.rm('-rf', path.join(plugin, 'node_modules'))
   sh.cd(plugin)
-  //sh.exec(`npm install --only=prod`)
-  sh.exec(`${npx} yarn install --force`)
+  sh.exec(`yarn install --force --production`)
+
 
-  
   log.info('rebuild', 'native')
   if (fs.existsSync('node_modules')) {
-    rebuild(path.resolve('.'), vars.electronVersion, process.arch, [], true)
+    rebuild({
+      buildPath: path.resolve('.'),
+      electronVersion: vars.electronVersion,
+      arch: process.env.ARCH ?? process.arch,
+      force: true,
+    })
   }
   sh.cd('..')
 })

+ 1 - 1
terminus-community-color-schemes/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-community-color-schemes",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Community color schemes for Terminus",
   "keywords": [
     "terminus-builtin-plugin"

+ 3 - 4
terminus-core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-core",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Terminus core",
   "keywords": [
     "terminus-builtin-plugin"
@@ -29,11 +29,10 @@
     "mixpanel": "^0.10.2",
     "ng2-dnd": "^5.0.2",
     "ngx-perfect-scrollbar": "^8.0.0",
-    "readable-stream": "^2.3.7",
+    "readable-stream": "2.3.7",
     "shell-escape": "^0.2.0",
     "uuid": "^8.0.0",
-    "winston": "^3.3.3",
-    "winston-transport": "winstonjs/winston-transport"
+    "winston": "^3.3.3"
   },
   "peerDependencies": {
     "@angular/animations": "^9.1.9",

+ 2 - 1
terminus-core/src/api/tabContextMenuProvider.ts

@@ -1,3 +1,4 @@
+import type { MenuItemConstructorOptions } from 'electron'
 import { BaseTabComponent } from '../components/baseTab.component'
 import { TabHeaderComponent } from '../components/tabHeader.component'
 
@@ -7,5 +8,5 @@ import { TabHeaderComponent } from '../components/tabHeader.component'
 export abstract class TabContextMenuItemProvider {
     weight = 0
 
-    abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]>
+    abstract async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]>
 }

+ 4 - 3
terminus-core/src/components/tabHeader.component.ts

@@ -1,4 +1,5 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import type { MenuItemConstructorOptions } from 'electron'
 import { Component, Input, Optional, Inject, HostBinding, HostListener, ViewChild, ElementRef } from '@angular/core'
 import { SortableComponent } from 'ng2-dnd'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
@@ -13,7 +14,7 @@ import { ConfigService } from '../services/config.service'
 
 /** @hidden */
 export interface SortableComponentProxy {
-    setDragHandle (_: HTMLElement)
+    setDragHandle: (_: HTMLElement) => void
 }
 
 /** @hidden */
@@ -71,8 +72,8 @@ export class TabHeaderComponent {
         }).catch(() => null)
     }
 
-    async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
-        let items: Electron.MenuItemConstructorOptions[] = []
+    async buildContextMenu (): Promise<MenuItemConstructorOptions[]> {
+        let items: MenuItemConstructorOptions[] = []
         for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this.tab, this)))) {
             items.push({ type: 'separator' })
             items = items.concat(section)

+ 0 - 1
terminus-core/src/index.ts

@@ -38,7 +38,6 @@ import { CoreConfigProvider } from './config'
 import { AppHotkeyProvider } from './hotkeys'
 import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu } from './tabContextMenu'
 
-//import 'ngx-perfect-scrollbar/dist/lib/perfect-scrollbar.component.css'
 import 'perfect-scrollbar/css/perfect-scrollbar.css'
 import 'ng2-dnd/bundles/style.css'
 

+ 2 - 2
terminus-core/src/services/config.service.ts

@@ -97,7 +97,7 @@ export class ConfigService {
     private changed = new Subject<void>()
     private _store: any
     private defaults: any
-    private servicesCache: { [id: string]: Function[] }|null = null
+    private servicesCache: Record<string, Function[]>|null = null // eslint-disable-line @typescript-eslint/ban-types
 
     get changed$ (): Observable<void> { return this.changed }
 
@@ -189,7 +189,7 @@ export class ConfigService {
      *
      * @typeparam T Base provider type
      */
-    enabledServices<T extends object> (services: T[]): T[] {
+    enabledServices<T extends object> (services: T[]): T[] { // eslint-disable-line @typescript-eslint/ban-types
         if (!this.servicesCache) {
             this.servicesCache = {}
             const ngModule = window['rootModule'].ɵinj

+ 6 - 5
terminus-core/src/services/docking.service.ts

@@ -1,3 +1,4 @@
+import type { Display } from 'electron'
 import { Injectable } from '@angular/core'
 import { ConfigService } from '../services/config.service'
 import { ElectronService } from '../services/electron.service'
@@ -11,8 +12,8 @@ export class DockingService {
         private config: ConfigService,
         private hostApp: HostAppService,
     ) {
-        electron.screen.on('display-removed', () => this.repositionWindow())
-        electron.screen.on('display-metrics-changed', () => this.repositionWindow())
+        hostApp.displaysChanged$.subscribe(() => this.repositionWindow())
+        hostApp.displayMetricsChanged$.subscribe(() => this.repositionWindow())
     }
 
     dock (): void {
@@ -61,11 +62,11 @@ export class DockingService {
         })
     }
 
-    getCurrentScreen (): Electron.Display {
+    getCurrentScreen (): Display {
         return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint())
     }
 
-    getScreens (): Electron.Display[] {
+    getScreens (): Display[] {
         const primaryDisplayID = this.electron.screen.getPrimaryDisplay().id
         return this.electron.screen.getAllDisplays().sort((a, b) =>
             a.bounds.x === b.bounds.x ? a.bounds.y - b.bounds.y : a.bounds.x - b.bounds.x
@@ -73,7 +74,7 @@ export class DockingService {
             return {
                 ...display,
                 id: display.id,
-                name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index +1}`,
+                name: display.id === primaryDisplayID ? 'Primary Display' : `Display ${index + 1}`,
             }
         })
     }

+ 12 - 12
terminus-core/src/services/electron.service.ts

@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
-import { TouchBar, BrowserWindow, Menu, MenuItem, NativeImage } from 'electron'
+import { App, IpcRenderer, Shell, Dialog, Clipboard, GlobalShortcut, Screen, Remote, AutoUpdater, TouchBar, BrowserWindow, Menu, MenuItem, NativeImage, MessageBoxOptions } from 'electron'
 
 export interface MessageBoxResponse {
     response: number
@@ -8,16 +8,16 @@ export interface MessageBoxResponse {
 
 @Injectable({ providedIn: 'root' })
 export class ElectronService {
-    app: Electron.App
-    ipcRenderer: Electron.IpcRenderer
-    shell: Electron.Shell
-    dialog: Electron.Dialog
-    clipboard: Electron.Clipboard
-    globalShortcut: Electron.GlobalShortcut
+    app: App
+    ipcRenderer: IpcRenderer
+    shell: Shell
+    dialog: Dialog
+    clipboard: Clipboard
+    globalShortcut: GlobalShortcut
     nativeImage: typeof NativeImage
-    screen: Electron.Screen
-    remote: Electron.Remote
-    autoUpdater: Electron.AutoUpdater
+    screen: Screen
+    remote: Remote
+    autoUpdater: AutoUpdater
     TouchBar: typeof TouchBar
     BrowserWindow: typeof BrowserWindow
     Menu: typeof Menu
@@ -44,8 +44,8 @@ export class ElectronService {
     }
 
     async showMessageBox (
-        browserWindow: Electron.BrowserWindow,
-        options: Electron.MessageBoxOptions
+        browserWindow: BrowserWindow,
+        options: MessageBoxOptions
     ): Promise<MessageBoxResponse> {
         return this.dialog.showMessageBox(browserWindow, options)
     }

+ 1 - 1
terminus-core/src/services/homeBase.service.ts

@@ -58,7 +58,7 @@ export class HomeBaseService {
 
     getAnalyticsProperties (): Record<string, string> {
         return {
-            distinct_id: window.localStorage.analyticsUserID, // eslint-disable-line @typescript-eslint/camelcase
+            distinct_id: window.localStorage.analyticsUserID,
             platform: process.platform,
             os: os.release(),
             version: this.appVersion,

+ 12 - 4
terminus-core/src/services/hostApp.service.ts

@@ -1,3 +1,4 @@
+import type { BrowserWindow, TouchBar, MenuItemConstructorOptions } from 'electron'
 import * as path from 'path'
 import shellEscape from 'shell-escape'
 import { Observable, Subject } from 'rxjs'
@@ -42,6 +43,7 @@ export class HostAppService {
     private windowMoved = new Subject<void>()
     private windowFocused = new Subject<void>()
     private displayMetricsChanged = new Subject<void>()
+    private displaysChanged = new Subject<void>()
     private logger: Logger
     private windowId: number
 
@@ -91,6 +93,8 @@ export class HostAppService {
 
     get displayMetricsChanged$ (): Observable<void> { return this.displayMetricsChanged }
 
+    get displaysChanged$ (): Observable<void> { return this.displaysChanged }
+
     private constructor (
         private zone: NgZone,
         private electron: ElectronService,
@@ -140,6 +144,10 @@ export class HostAppService {
             this.zone.run(() => this.displayMetricsChanged.next())
         })
 
+        electron.ipcRenderer.on('host:displays-changed', () => {
+            this.zone.run(() => this.displaysChanged.next())
+        })
+
         electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
             this.logger.info('Second instance', argv)
             const op = argv._[0]
@@ -177,8 +185,8 @@ export class HostAppService {
     /**
      * Returns the current remote [[BrowserWindow]]
      */
-    getWindow (): Electron.BrowserWindow {
-        return this.electron.BrowserWindow.fromId(this.windowId)
+    getWindow (): BrowserWindow {
+        return this.electron.BrowserWindow.fromId(this.windowId)!
     }
 
     newWindow (): void {
@@ -239,11 +247,11 @@ export class HostAppService {
         this.electron.ipcRenderer.send('window-set-title', title)
     }
 
-    setTouchBar (touchBar: Electron.TouchBar): void {
+    setTouchBar (touchBar: TouchBar): void {
         this.getWindow().setTouchBar(touchBar)
     }
 
-    popupContextMenu (menuDefinition: Electron.MenuItemConstructorOptions[]): void {
+    popupContextMenu (menuDefinition: MenuItemConstructorOptions[]): void {
         this.electron.Menu.buildFromTemplate(menuDefinition).popup({})
     }
 

+ 2 - 2
terminus-core/src/services/log.service.ts

@@ -28,7 +28,7 @@ const initializeWinston = (electron: ElectronService) => {
 
 export class Logger {
     constructor (
-        private winstonLogger: any,
+        private winstonLogger: winston.Logger,
         private name: string,
     ) {}
 
@@ -62,7 +62,7 @@ export class Logger {
 
 @Injectable({ providedIn: 'root' })
 export class LogService {
-    private log: any
+    private log: winston.Logger
 
     /** @hidden */
     private constructor (electron: ElectronService) {

+ 2 - 2
terminus-core/src/services/shellIntegration.service.ts

@@ -73,10 +73,10 @@ export class ShellIntegrationService {
                 wnr.setRegistryValue(wnr.HK.CU, registryKey.path + '\\command', '', wnr.REG.SZ, exe + ' ' + registryKey.command)
             }
 
-            if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')) {
+            if (wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')) {
                 wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\Directory\\Background\\shell\\Open Terminus here')
             }
-            if(wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')) {
+            if (wnr.getRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')) {
                 wnr.deleteRegistryKey(wnr.HK.CU, 'Software\\Classes\\*\\shell\\Paste path into Terminus')
             }
         }

+ 3 - 3
terminus-core/src/services/touchbar.service.ts

@@ -1,5 +1,5 @@
+import { NativeImage, SegmentedControlSegment, TouchBarSegmentedControl } from 'electron'
 import { Injectable, Inject, NgZone } from '@angular/core'
-import { TouchBarSegmentedControl, SegmentedControlSegment } from 'electron'
 import { AppService } from './app.service'
 import { ConfigService } from './config.service'
 import { ElectronService } from './electron.service'
@@ -12,7 +12,7 @@ export class TouchbarService {
     private tabsSegmentedControl: TouchBarSegmentedControl
     private buttonsSegmentedControl: TouchBarSegmentedControl
     private tabSegments: SegmentedControlSegment[] = []
-    private nsImageCache: {[id: string]: Electron.NativeImage} = {}
+    private nsImageCache: {[id: string]: NativeImage} = {}
 
     private constructor (
         private app: AppService,
@@ -100,7 +100,7 @@ export class TouchbarService {
         this.hostApp.setTouchBar(touchBar)
     }
 
-    private getButton (button: ToolbarButton): Electron.SegmentedControlSegment {
+    private getButton (button: ToolbarButton): SegmentedControlSegment {
         return {
             label: button.touchBarNSImage ? undefined : this.shortenTitle(button.touchBarTitle || button.title),
             icon: button.touchBarNSImage ? this.getCachedNSImage(button.touchBarNSImage) : undefined,

+ 9 - 8
terminus-core/src/tabContextMenu.ts

@@ -1,4 +1,5 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import type { MenuItemConstructorOptions } from 'electron'
 import { Injectable, NgZone } from '@angular/core'
 import { Subscription } from 'rxjs'
 import { AppService } from './services/app.service'
@@ -19,8 +20,8 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
-        let items: Electron.MenuItemConstructorOptions[] = [
+    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
+        let items: MenuItemConstructorOptions[] = [
             {
                 label: 'Close',
                 click: () => this.zone.run(() => {
@@ -75,7 +76,7 @@ export class TabManagementContextMenu extends TabContextMenuItemProvider {
                         click: () => this.zone.run(() => {
                             (tab.parent as SplitTabComponent).splitTab(tab, dir)
                         }),
-                    })) as Electron.MenuItemConstructorOptions[],
+                    })) as MenuItemConstructorOptions[],
                 })
             }
         }
@@ -105,8 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
-        let items: Electron.MenuItemConstructorOptions[] = []
+    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
+        let items: MenuItemConstructorOptions[] = []
         if (tabHeader) {
             items = [
                 ...items,
@@ -128,7 +129,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
                         click: () => this.zone.run(() => {
                             tab.color = color.value
                         }),
-                    })) as Electron.MenuItemConstructorOptions[],
+                    })) as MenuItemConstructorOptions[],
                 },
             ]
         }
@@ -146,9 +147,9 @@ export class TaskCompletionContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent): Promise<MenuItemConstructorOptions[]> {
         const process = await tab.getCurrentProcess()
-        let items: Electron.MenuItemConstructorOptions[] = []
+        const items: MenuItemConstructorOptions[] = []
 
         const extTab: (BaseTabComponent & { __completionNotificationEnabled?: boolean, __outputNotificationSubscription?: Subscription|null }) = tab
 

+ 1 - 9
terminus-core/yarn.lock

@@ -349,7 +349,7 @@ process-nextick-args@~2.0.0:
   resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
   integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
 
-readable-stream@^2.3.7:
+[email protected], readable-stream@^2.3.7:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -470,14 +470,6 @@ winston-transport@^4.4.0:
     readable-stream "^2.3.7"
     triple-beam "^1.2.0"
 
-winston-transport@winstonjs/winston-transport:
-  version "4.4.0"
-  resolved "https://codeload.github.com/winstonjs/winston-transport/tar.gz/6a3bf79175288328d37c6cf4121d6b39eb68f19f"
-  dependencies:
-    logform "^2.2.0"
-    readable-stream "^3.4.0"
-    triple-beam "^1.2.0"
-
 winston@*, winston@^3.3.3:
   version "3.3.3"
   resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170"

+ 4 - 4
terminus-plugin-manager/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-plugin-manager",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Terminus' plugin manager",
   "keywords": [
     "terminus-builtin-plugin"
@@ -15,17 +15,17 @@
     "dist"
   ],
   "author": "Eugene Pankov",
-  "license": "MIT", 
+  "license": "MIT",
   "devDependencies": {
     "@types/semver": "^7.1.0",
     "axios": "^0.19.0",
     "mz": "^2.6.0",
     "semver": "^7.1.1"
   },
-  "peerDependencies": {    
+  "peerDependencies": {
     "@angular/common": "^9.1.11",
     "@angular/core": "^9.1.9",
-    "@angular/forms": "^9.1.11",    
+    "@angular/forms": "^9.1.11",
     "@angular/platform-browser": "^9.1.11",
     "@ng-bootstrap/ng-bootstrap": "^6.1.0",
     "rxjs": "^6.5.5",

+ 2 - 2
terminus-serial/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-serial",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Serial connection manager for Terminus",
   "keywords": [
     "terminus-builtin-plugin"
@@ -15,7 +15,7 @@
     "dist"
   ],
   "author": "Eugene Pankov",
-  "license": "MIT", 
+  "license": "MIT",
   "devDependencies": {
     "@types/node": "14.14.14",
     "@types/ssh2": "^0.5.35",

+ 2 - 2
terminus-serial/src/components/serialTab.component.ts

@@ -11,7 +11,7 @@ import { Subscription } from 'rxjs'
 /** @hidden */
 @Component({
     selector: 'serial-tab',
-    template: BaseTerminalTabComponent.template + require<string>('./serialTab.component.pug'),
+    template: BaseTerminalTabComponent.template + (require('./serialTab.component.pug') as string),
     styles: [require('./serialTab.component.scss'), ...BaseTerminalTabComponent.styles],
     animations: BaseTerminalTabComponent.animations,
 })
@@ -64,7 +64,7 @@ export class SerialTabComponent extends BaseTerminalTabComponent {
 
         this.session = this.injector.get(SerialService).createSession(this.connection)
         this.session.serviceMessage$.subscribe(msg => {
-            this.write('\r\n' + colors.black.bgWhite(' serial ') + ' ' + msg + '\r\n')
+            this.write(`\r\n${colors.black.bgWhite(' serial ')} ${msg}\r\n`)
             this.session.resize(this.size.columns, this.size.rows)
         })
         this.attachSessionHandlers()

+ 1 - 1
terminus-settings/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-settings",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Terminus terminal settings page",
   "keywords": [
     "terminus-builtin-plugin"

+ 2 - 6
terminus-settings/src/components/settingsTab.component.ts

@@ -71,13 +71,9 @@ export class SettingsTabComponent extends BaseTabComponent {
         this.configSubscription = config.changed$.subscribe(onConfigChange)
         onConfigChange()
 
-        const onScreenChange = () => {
+        hostApp.displaysChanged$.subscribe(() => {
             this.zone.run(() => this.screens = this.docking.getScreens())
-        }
-
-        electron.screen.on('display-added', onScreenChange)
-        electron.screen.on('display-removed', onScreenChange)
-        electron.screen.on('display-metrics-changed', onScreenChange)
+        })
 
         hotkeys.getHotkeyDescriptions().then(descriptions => {
             this.hotkeyDescriptions = descriptions

+ 1 - 1
terminus-ssh/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-ssh",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "SSH connection manager for Terminus",
   "keywords": [
     "terminus-builtin-plugin"

+ 6 - 6
terminus-ssh/src/api.ts

@@ -18,7 +18,7 @@ export enum SSHAlgorithmType {
     HMAC = 'hmac',
     KEX = 'kex',
     CIPHER = 'cipher',
-    HOSTKEY = 'serverHostKey'
+    HOSTKEY = 'serverHostKey',
 }
 
 export interface SSHConnection {
@@ -45,7 +45,7 @@ export interface SSHConnection {
 }
 
 export enum PortForwardType {
-    Local, Remote, Dynamic
+    Local, Remote, Dynamic,
 }
 
 export class ForwardedPort {
@@ -230,11 +230,11 @@ export class SSHSession extends BaseSession {
 
         this.ssh.on('x11', (details, accept, reject) => {
             this.logger.info(`Incoming X11 connection from ${details.srcIP}:${details.srcPort}`)
-            let displaySpec = process.env.DISPLAY || ':0'
+            const displaySpec = process.env.DISPLAY || ':0'
             this.logger.debug(`Trying display ${displaySpec}`)
-            let xHost = displaySpec.split(':')[0]
-            let xDisplay = parseInt(displaySpec.split(':')[1].split('.')[0] || '0')
-            let xPort = xDisplay < 100 ? xDisplay + 6000 : xDisplay
+            const xHost = displaySpec.split(':')[0]
+            const xDisplay = parseInt(displaySpec.split(':')[1].split('.')[0] || '0')
+            const xPort = xDisplay < 100 ? xDisplay + 6000 : xDisplay
 
             const socket = displaySpec.startsWith('/') ? createConnection(displaySpec) : new Socket()
             if (!displaySpec.startsWith('/')) {

+ 2 - 2
terminus-ssh/src/components/sshTab.component.ts

@@ -14,7 +14,7 @@ import { Subscription } from 'rxjs'
 /** @hidden */
 @Component({
     selector: 'ssh-tab',
-    template: BaseTerminalTabComponent.template + require<string>('./sshTab.component.pug'),
+    template: BaseTerminalTabComponent.template + (require('./sshTab.component.pug') as string),
     styles: [require('./sshTab.component.scss'), ...BaseTerminalTabComponent.styles],
     animations: BaseTerminalTabComponent.animations,
 })
@@ -91,7 +91,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
 
 
         session.serviceMessage$.subscribe(msg => {
-            this.write('\r\n' + colors.black.bgWhite(' SSH ') + ' ' + msg + '\r\n')
+            this.write(`\r\n${colors.black.bgWhite(' SSH ')} ${msg}\r\n`)
             session.resize(this.size.columns, this.size.rows)
         })
 

+ 4 - 4
terminus-ssh/src/services/ssh.service.ts

@@ -87,7 +87,7 @@ export class SSHService {
                         try {
                             const result  = await modal.result
                             passphrase = result?.value
-                        } catch (e) { }
+                        } catch { }
                         parsedKey = sshpk.parsePrivateKey(
                             privateKey,
                             'auto',
@@ -269,7 +269,7 @@ export class SSHService {
                     sock: session.jumpStream,
                     authHandler: methodsLeft => {
                         while (true) {
-                            let method = authMethodsLeft.shift()
+                            const method = authMethodsLeft.shift()
                             if (!method) {
                                 return false
                             }
@@ -348,8 +348,8 @@ export class SSHService {
             })
         }
 
-        let groups: { name: string, connections: SSHConnection[] }[] = []
-        let connections = this.config.store.ssh.connections
+        const groups: { name: string, connections: SSHConnection[] }[] = []
+        const connections = this.config.store.ssh.connections
         for (const connection of connections) {
             connection.group = connection.group || null
             let group = groups.find(x => x.name === connection.group)

+ 3 - 2
terminus-ssh/src/winSCPIntegration.ts

@@ -1,3 +1,4 @@
+import type { MenuItemConstructorOptions } from 'electron'
 import { execFile } from 'child_process'
 import { Injectable } from '@angular/core'
 import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, HostAppService, Platform } from 'terminus-core'
@@ -36,7 +37,7 @@ export class WinSCPContextMenu extends TabContextMenuItemProvider {
         }
     }
 
-    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
         if (this.hostApp.platform !== Platform.Windows || tabHeader) {
             return []
         }
@@ -75,7 +76,7 @@ export class WinSCPContextMenu extends TabContextMenuItemProvider {
         if (!path) {
             return
         }
-        let args = [await this.getURI(connection)]
+        const args = [await this.getURI(connection)]
         if ((!connection.auth || connection.auth === 'publicKey') && connection.privateKey) {
             args.push('/privatekey')
             args.push(connection.privateKey)

+ 1 - 1
terminus-terminal/package.json

@@ -1,6 +1,6 @@
 {
   "name": "terminus-terminal",
-  "version": "1.0.104-nightly.0",
+  "version": "1.0.123-nightly.0",
   "description": "Terminus' terminal emulation core",
   "keywords": [
     "terminus-builtin-plugin"

+ 8 - 7
terminus-terminal/src/api/baseTerminalTab.component.ts

@@ -1,3 +1,4 @@
+import type { MenuItemConstructorOptions } from 'electron'
 import { Observable, Subject, Subscription } from 'rxjs'
 import { first } from 'rxjs/operators'
 import { ToastrService } from 'ngx-toastr'
@@ -16,14 +17,14 @@ import { TerminalDecorator } from './decorator'
 
 /** @hidden */
 export interface ToastrServiceProxy {
-    info (_: string)
+    info: (_: string) => void
 }
 /**
  * A class to base your custom terminal tabs on
  */
 export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit, OnDestroy {
-    static template = require<string>('../components/baseTerminalTab.component.pug')
-    static styles = [require<string>('../components/terminalTab.component.scss')]
+    static template: string = require<string>('../components/baseTerminalTab.component.pug')
+    static styles: string[] = [require<string>('../components/terminalTab.component.scss')]
     static animations: AnimationTriggerMetadata[] = [trigger('slideInOut', [
         transition(':enter', [
             style({ transform: 'translateY(-25%)' }),
@@ -277,8 +278,8 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
         })
     }
 
-    async buildContextMenu (): Promise<Electron.MenuItemConstructorOptions[]> {
-        let items: Electron.MenuItemConstructorOptions[] = []
+    async buildContextMenu (): Promise<MenuItemConstructorOptions[]> {
+        let items: MenuItemConstructorOptions[] = []
         for (const section of await Promise.all(this.contextMenuProviders.map(x => x.getItems(this)))) {
             items = items.concat(section)
             items.push({ type: 'separator' })
@@ -435,7 +436,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
 
     async destroy (): Promise<void> {
         super.destroy()
-        if (this.session && this.session.open) {
+        if (this.session?.open) {
             await this.session.destroy()
         }
     }
@@ -512,7 +513,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
                 this.logger.debug(`Resizing to ${columns}x${rows}`)
                 this.size = { columns, rows }
                 this.zone.run(() => {
-                    if (this.session && this.session.open) {
+                    if (this.session?.open) {
                         this.session.resize(columns, rows)
                     }
                 })

+ 2 - 1
terminus-terminal/src/api/contextMenuProvider.ts

@@ -1,3 +1,4 @@
+import type { MenuItemConstructorOptions } from 'electron'
 import { BaseTerminalTabComponent } from './baseTerminalTab.component'
 
 /**
@@ -7,5 +8,5 @@ import { BaseTerminalTabComponent } from './baseTerminalTab.component'
 export abstract class TerminalContextMenuItemProvider {
     weight: number
 
-    abstract async getItems (tab: BaseTerminalTabComponent): Promise<Electron.MenuItemConstructorOptions[]>
+    abstract async getItems (tab: BaseTerminalTabComponent): Promise<MenuItemConstructorOptions[]>
 }

+ 1 - 1
terminus-terminal/src/api/interfaces.ts

@@ -8,7 +8,7 @@ export interface SessionOptions {
     command: string
     args: string[]
     cwd?: string
-    env?: {[id: string]: string}
+    env?: Record<string, string>
     width?: number
     height?: number
     pauseAfterExit?: boolean

+ 2 - 2
terminus-terminal/src/colorSchemes.ts

@@ -20,10 +20,10 @@ export class HyperColorSchemes extends TerminalColorSchemeProvider {
             try {
                 const module = (global as any).require(path.join(pluginsPath, plugin))
                 if (module.decorateConfig) {
-                    let config: any
+                    let config: any = {}
                     try {
                         config = module.decorateConfig({})
-                    } catch (error) {
+                    } catch {
                         console.warn('Could not load Hyper theme:', plugin)
                         return
                     }

+ 0 - 1
terminus-terminal/src/features/zmodem.ts

@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/camelcase */
 import colors from 'ansi-colors'
 import * as ZModem from 'zmodem.js'
 import * as fs from 'fs'

+ 10 - 10
terminus-terminal/src/frontends/xtermFrontend.ts

@@ -99,16 +99,16 @@ export class XTermFrontend extends Frontend {
         this.resizeHandler = () => {
             try {
                 if (this.xterm.element && getComputedStyle(this.xterm.element).getPropertyValue('height') !== 'auto') {
-                    let t = window.getComputedStyle(this.xterm.element.parentElement!)
-                    let r = parseInt(t.getPropertyValue('height'))
-                    let n = Math.max(0, parseInt(t.getPropertyValue('width')))
-                    let o = window.getComputedStyle(this.xterm.element)
-                    let i = r - (parseInt(o.getPropertyValue('padding-top')) + parseInt(o.getPropertyValue('padding-bottom')))
-                    let l = n - (parseInt(o.getPropertyValue('padding-right')) + parseInt(o.getPropertyValue('padding-left'))) - this.xtermCore.viewport.scrollBarWidth
-                    let actualCellWidth = this.xtermCore._renderService.dimensions.actualCellWidth || 9
-                    let actualCellHeight = this.xtermCore._renderService.dimensions.actualCellHeight || 17
-                    let cols = Math.floor(l / actualCellWidth)
-                    let rows = Math.floor(i / actualCellHeight)
+                    const t = window.getComputedStyle(this.xterm.element.parentElement!)
+                    const r = parseInt(t.getPropertyValue('height'))
+                    const n = Math.max(0, parseInt(t.getPropertyValue('width')))
+                    const o = window.getComputedStyle(this.xterm.element)
+                    const i = r - (parseInt(o.getPropertyValue('padding-top')) + parseInt(o.getPropertyValue('padding-bottom')))
+                    const l = n - (parseInt(o.getPropertyValue('padding-right')) + parseInt(o.getPropertyValue('padding-left'))) - this.xtermCore.viewport.scrollBarWidth
+                    const actualCellWidth = this.xtermCore._renderService.dimensions.actualCellWidth || 9
+                    const actualCellHeight = this.xtermCore._renderService.dimensions.actualCellHeight || 17
+                    const cols = Math.floor(l / actualCellWidth)
+                    const rows = Math.floor(i / actualCellHeight)
 
                     if (!isNaN(cols) && !isNaN(rows)) {
                         this.xterm.resize(cols, rows)

+ 1 - 1
terminus-terminal/src/index.ts

@@ -171,7 +171,7 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
                 argv = argv.slice(1)
             }
 
-            if(require('yargs').parse(argv.slice(1))._[0] !== 'open'){
+            if (require('yargs').parse(argv.slice(1))._[0] !== 'open'){
                 app.ready$.subscribe(() => {
                     terminal.openTab()
                 })

+ 2 - 2
terminus-terminal/src/services/sessions.service.ts

@@ -267,7 +267,7 @@ export class Session extends BaseSession {
             return null
         }
         if (process.platform === 'darwin') {
-            let lines: string[]
+            let lines: string[] = []
             try {
                 lines = (await exec(`lsof -p ${this.truePID} -Fn`))[0].toString().split('\n')
             } catch (e) {
@@ -312,7 +312,7 @@ export class Session extends BaseSession {
     private processOSC1337 (data: Buffer) {
         if (data.includes(OSC1337Prefix)) {
             const preData = data.subarray(0, data.indexOf(OSC1337Prefix))
-            let params = data.subarray(data.indexOf(OSC1337Prefix) + OSC1337Prefix.length)
+            const params = data.subarray(data.indexOf(OSC1337Prefix) + OSC1337Prefix.length)
             const postData = params.subarray(params.indexOf(OSC1337Suffix) + OSC1337Suffix.length)
             const paramString = params.subarray(0, params.indexOf(OSC1337Suffix)).toString()
 

+ 8 - 7
terminus-terminal/src/tabContextMenu.ts

@@ -1,3 +1,4 @@
+import { MenuItemConstructorOptions } from 'electron'
 import { Injectable, NgZone, Optional, Inject } from '@angular/core'
 import { ToastrService } from 'ngx-toastr'
 import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, TabHeaderComponent, SplitTabComponent } from 'terminus-core'
@@ -18,11 +19,11 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
         if (!(tab instanceof TerminalTabComponent)) {
             return []
         }
-        const items: Electron.MenuItemConstructorOptions[] = [
+        const items: MenuItemConstructorOptions[] = [
             {
                 label: 'Save as profile',
                 click: () => this.zone.run(async () => {
@@ -61,10 +62,10 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
         const profiles = await this.terminalService.getProfiles()
 
-        const items: Electron.MenuItemConstructorOptions[] = [
+        const items: MenuItemConstructorOptions[] = [
             {
                 label: 'New terminal',
                 click: () => this.zone.run(() => {
@@ -138,7 +139,7 @@ export class CopyPasteContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent, tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
         if (tabHeader) {
             return []
         }
@@ -178,12 +179,12 @@ export class LegacyContextMenu extends TabContextMenuItemProvider {
         super()
     }
 
-    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<Electron.MenuItemConstructorOptions[]> {
+    async getItems (tab: BaseTabComponent, _tabHeader?: TabHeaderComponent): Promise<MenuItemConstructorOptions[]> {
         if (!this.contextMenuProviders) {
             return []
         }
         if (tab instanceof BaseTerminalTabComponent) {
-            let items: Electron.MenuItemConstructorOptions[] = []
+            let items: MenuItemConstructorOptions[] = []
             for (const p of this.contextMenuProviders) {
                 items = items.concat(await p.getItems(tab))
             }

binární
tmp/assets/activity.png


+ 55 - 0
tmp/assets/logo.svg

@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{opacity:0.16;fill:url(#SVGID_2_);}
+	.st2{fill:url(#SVGID_3_);}
+	.st3{opacity:0.16;fill:url(#SVGID_4_);}
+	.st4{fill:url(#SVGID_5_);}
+	.st5{opacity:0.15;fill:url(#SVGID_6_);}
+	.st6{fill:url(#SVGID_7_);}
+</style>
+<g>
+	<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="260.9675" y1="871.1813" x2="919.1845" y2="491.1596">
+		<stop  offset="0" style="stop-color:#669ABD"/>
+		<stop  offset="1" style="stop-color:#77DBDB"/>
+	</linearGradient>
+	<polygon class="st0" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 	"/>
+	<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="553.5051" y1="617.8278" x2="626.647" y2="744.5132">
+		<stop  offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
+		<stop  offset="1" style="stop-color:#000000"/>
+	</linearGradient>
+	<polygon class="st1" points="297.54,934.52 882.6,596.72 882.61,427.82 297.54,765.65 	"/>
+</g>
+<g>
+	<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="114.6631" y1="744.5275" x2="334.0905" y2="871.2141">
+		<stop  offset="0" style="stop-color:#6A8FAD"/>
+		<stop  offset="1" style="stop-color:#669ABD"/>
+	</linearGradient>
+	<polygon class="st2" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 	"/>
+	<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="260.9478" y1="744.5281" x2="187.8059" y2="871.2135">
+		<stop  offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
+		<stop  offset="1" style="stop-color:#000000"/>
+	</linearGradient>
+	<polygon class="st3" points="151.23,681.18 151.22,850.09 297.54,934.52 297.54,765.65 	"/>
+</g>
+<g>
+	<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="114.663" y1="237.793" x2="553.5026" y2="491.1571">
+		<stop  offset="0" style="stop-color:#6A8FAD"/>
+		<stop  offset="1" style="stop-color:#669ABD"/>
+	</linearGradient>
+	<polygon class="st4" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 	"/>
+	<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="370.6562" y1="301.1281" x2="297.5094" y2="427.8221">
+		<stop  offset="0.5588" style="stop-color:#000000;stop-opacity:0"/>
+		<stop  offset="1" style="stop-color:#000000"/>
+	</linearGradient>
+	<polygon class="st5" points="151.23,174.45 151.21,343.36 443.79,512.27 590.08,427.81 	"/>
+</g>
+<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="78.0912" y1="554.4979" x2="736.3375" y2="174.4593">
+	<stop  offset="0" style="stop-color:#CCECFF"/>
+	<stop  offset="1" style="stop-color:#9FECED"/>
+</linearGradient>
+<polygon class="st6" points="297.51,765.64 151.23,681.18 590.08,427.81 151.23,174.45 297.5,90 882.61,427.82 "/>
+</svg>

binární
tmp/assets/tray-darwinHighlightTemplate.png


binární
tmp/assets/[email protected]


binární
tmp/assets/tray-darwinTemplate.png


binární
tmp/assets/[email protected]


binární
tmp/assets/tray.png


+ 4 - 0
tmp/dev-app-update.yml

@@ -0,0 +1,4 @@
+owner: eugeny
+repo: terminus
+provider: github
+updaterCacheDirName: terminus-updater

+ 22 - 0
tmp/index.pug

@@ -0,0 +1,22 @@
+doctype html
+html
+    head
+        meta(charset='UTF-8')
+        base(href='index.html')
+        script.
+            console.timeStamp('index')
+            window.nodeRequire = require
+        script(src='./preload.js')
+        script(src='./bundle.js', defer)
+        style.
+            body { transition: 0.5s background; }
+    body
+        style#custom-css
+        app-root
+            .preload-logo
+                div
+                    .terminus-logo
+                    h1.terminus-title Terminus
+                        sup α
+                    .progress
+                        .bar(style='width: 0%')

+ 230 - 0
tmp/lib/app.ts

@@ -0,0 +1,230 @@
+import { app, ipcMain, Menu, Tray, shell, screen, globalShortcut, MenuItemConstructorOptions } from 'electron'
+import { loadConfig } from './config'
+import { Window, WindowOptions } from './window'
+
+export class Application {
+    private tray: Tray
+    private windows: Window[] = []
+
+    constructor () {
+        ipcMain.on('app:config-change', (_event, config) => {
+            this.broadcast('host:config-change', config)
+        })
+
+        ipcMain.on('app:register-global-hotkey', (_event, specs) => {
+            globalShortcut.unregisterAll()
+            for (const spec of specs) {
+                globalShortcut.register(spec, () => {
+                    this.onGlobalHotkey()
+                })
+            }
+        })
+
+        const configData = loadConfig()
+        if (process.platform === 'linux') {
+            app.commandLine.appendSwitch('no-sandbox')
+            if (((configData.appearance || {}).opacity || 1) !== 1) {
+                app.commandLine.appendSwitch('enable-transparent-visuals')
+                app.disableHardwareAcceleration()
+            }
+        }
+
+        app.commandLine.appendSwitch('disable-http-cache')
+        app.commandLine.appendSwitch('lang', 'EN')
+        app.allowRendererProcessReuse = false
+
+        for (const flag of configData.flags || [['force_discrete_gpu', '0']]) {
+            app.commandLine.appendSwitch(flag[0], flag[1])
+        }
+    }
+
+    init (): void {
+        screen.on('display-metrics-changed', () => this.broadcast('host:display-metrics-changed'))
+        screen.on('display-added', () => this.broadcast('host:displays-changed'))
+        screen.on('display-removed', () => this.broadcast('host:displays-changed'))
+    }
+
+    async newWindow (options?: WindowOptions): Promise<Window> {
+        const window = new Window(options)
+        this.windows.push(window)
+        window.visible$.subscribe(visible => {
+            if (visible) {
+                this.disableTray()
+            } else {
+                this.enableTray()
+            }
+        })
+        window.closed$.subscribe(() => {
+            this.windows = this.windows.filter(x => x !== window)
+        })
+        if (process.platform === 'darwin') {
+            this.setupMenu()
+        }
+        await window.ready
+        return window
+    }
+
+    onGlobalHotkey (): void {
+        if (this.windows.some(x => x.isFocused())) {
+            for (const window of this.windows) {
+                window.hide()
+            }
+        } else {
+            for (const window of this.windows) {
+                window.present()
+            }
+        }
+    }
+
+    presentAllWindows (): void {
+        for (const window of this.windows) {
+            window.present()
+        }
+    }
+
+    broadcast (event: string, ...args: any[]): void {
+        for (const window of this.windows) {
+            window.send(event, ...args)
+        }
+    }
+
+    async send (event: string, ...args: any[]): Promise<void> {
+        if (!this.hasWindows()) {
+            await this.newWindow()
+        }
+        this.windows.filter(w => !w.isDestroyed())[0].send(event, ...args)
+    }
+
+    enableTray (): void {
+        if (this.tray) {
+            return
+        }
+        if (process.platform === 'darwin') {
+            this.tray = new Tray(`${app.getAppPath()}/assets/tray-darwinTemplate.png`)
+            this.tray.setPressedImage(`${app.getAppPath()}/assets/tray-darwinHighlightTemplate.png`)
+        } else {
+            this.tray = new Tray(`${app.getAppPath()}/assets/tray.png`)
+        }
+
+        this.tray.on('click', () => setTimeout(() => this.focus()))
+
+        const contextMenu = Menu.buildFromTemplate([{
+            label: 'Show',
+            click: () => this.focus(),
+        }])
+
+        if (process.platform !== 'darwin') {
+            this.tray.setContextMenu(contextMenu)
+        }
+
+        this.tray.setToolTip(`Terminus ${app.getVersion()}`)
+    }
+
+    disableTray (): void {
+        if (this.tray) {
+            this.tray.destroy()
+            this.tray = null
+        }
+    }
+
+    hasWindows (): boolean {
+        return !!this.windows.length
+    }
+
+    focus (): void {
+        for (const window of this.windows) {
+            window.show()
+        }
+    }
+
+    handleSecondInstance (argv: string[], cwd: string): void {
+        this.presentAllWindows()
+        this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd)
+    }
+
+    private setupMenu () {
+        const template: MenuItemConstructorOptions[] = [
+            {
+                label: 'Application',
+                submenu: [
+                    { role: 'about', label: 'About Terminus' },
+                    { type: 'separator' },
+                    {
+                        label: 'Preferences',
+                        accelerator: 'Cmd+,',
+                        click: async () => {
+                            if (!this.hasWindows()) {
+                                await this.newWindow()
+                            }
+                            this.windows[0].send('host:preferences-menu')
+                        },
+                    },
+                    { type: 'separator' },
+                    { role: 'services', submenu: [] },
+                    { type: 'separator' },
+                    { role: 'hide' },
+                    { role: 'hideOthers' },
+                    { role: 'unhide' },
+                    { type: 'separator' },
+                    {
+                        label: 'Quit',
+                        accelerator: 'Cmd+Q',
+                        click () {
+                            app.quit()
+                        },
+                    },
+                ],
+            },
+            {
+                label: 'Edit',
+                submenu: [
+                    { role: 'undo' },
+                    { role: 'redo' },
+                    { type: 'separator' },
+                    { role: 'cut' },
+                    { role: 'copy' },
+                    { role: 'paste' },
+                    { role: 'pasteAndMatchStyle' },
+                    { role: 'delete' },
+                    { role: 'selectAll' },
+                ],
+            },
+            {
+                label: 'View',
+                submenu: [
+                    { role: 'reload' },
+                    { role: 'forceReload' },
+                    { role: 'toggleDevTools' },
+                    { type: 'separator' },
+                    { role: 'resetZoom' },
+                    { role: 'zoomIn' },
+                    { role: 'zoomOut' },
+                    { type: 'separator' },
+                    { role: 'togglefullscreen' },
+                ],
+            },
+            {
+                role: 'window',
+                submenu: [
+                    { role: 'minimize' },
+                    { role: 'zoom' },
+                    { type: 'separator' },
+                    { role: 'front' },
+                ],
+            },
+            {
+                role: 'help',
+                submenu: [
+                    {
+                        label: 'Website',
+                        click () {
+                            shell.openExternal('https://eugeny.github.io/terminus')
+                        },
+                    },
+                ],
+            },
+        ]
+
+        Menu.setApplicationMenu(Menu.buildFromTemplate(template))
+    }
+}

+ 45 - 0
tmp/lib/cli.ts

@@ -0,0 +1,45 @@
+import { app } from 'electron'
+
+export function parseArgs (argv: string[], cwd: string): any {
+    if (argv[0].includes('node')) {
+        argv = argv.slice(1)
+    }
+
+    return require('yargs')
+        .usage('terminus [command] [arguments]')
+        .command('open [directory]', 'open a shell in a directory', {
+            directory: { type: 'string', 'default': cwd },
+        })
+        .command('run [command...]', 'run a command in the terminal', {
+            command: { type: 'string' },
+        })
+        .command('profile [profileName]', 'open a tab with specified profile', {
+            profileName: { type: 'string' },
+        })
+        .command('paste [text]', 'paste stdin into the active tab', yargs => {
+            return yargs.option('escape', {
+                alias: 'e',
+                type: 'boolean',
+                describe: 'Perform shell escaping',
+            }).positional('text', {
+                type: 'string',
+            })
+        })
+        .version('version', '', app.getVersion())
+        .option('debug', {
+            alias: 'd',
+            describe: 'Show DevTools on start',
+            type: 'boolean',
+        })
+        .option('hidden', {
+            describe: 'Start minimized',
+            type: 'boolean',
+        })
+        .option('version', {
+            alias: 'v',
+            describe: 'Show version and exit',
+            type: 'boolean',
+        })
+        .help('help')
+        .parse(argv.slice(1))
+}

+ 13 - 0
tmp/lib/config.ts

@@ -0,0 +1,13 @@
+import * as fs from 'fs'
+import * as path from 'path'
+import * as yaml from 'js-yaml'
+import { app } from 'electron'
+
+export function loadConfig (): any {
+    const configPath = path.join(app.getPath('userData'), 'config.yaml')
+    if (fs.existsSync(configPath)) {
+        return yaml.safeLoad(fs.readFileSync(configPath, 'utf8'))
+    } else {
+        return {}
+    }
+}

+ 68 - 0
tmp/lib/index.ts

@@ -0,0 +1,68 @@
+import './portable'
+import './sentry'
+import './lru'
+import { app, ipcMain, Menu } from 'electron'
+import { parseArgs } from './cli'
+import { Application } from './app'
+import electronDebug = require('electron-debug')
+
+if (!process.env.TERMINUS_PLUGINS) {
+    process.env.TERMINUS_PLUGINS = ''
+}
+
+const application = new Application()
+
+ipcMain.on('app:new-window', () => {
+    application.newWindow()
+})
+
+app.on('activate', () => {
+    if (!application.hasWindows()) {
+        application.newWindow()
+    } else {
+        application.focus()
+    }
+})
+
+app.on('window-all-closed', () => {
+    app.quit()
+})
+
+process.on('uncaughtException' as any, err => {
+    console.log(err)
+    application.broadcast('uncaughtException', err)
+})
+
+app.on('second-instance', (_event, argv, cwd) => {
+    application.handleSecondInstance(argv, cwd)
+})
+
+const argv = parseArgs(process.argv, process.cwd())
+
+if (!app.requestSingleInstanceLock()) {
+    app.quit()
+    app.exit(0)
+}
+
+if (argv.d) {
+    electronDebug({
+        isEnabled: true,
+        showDevTools: true,
+        devToolsMode: 'undocked',
+    })
+}
+
+app.on('ready', () => {
+    if (process.platform === 'darwin') {
+        app.dock.setMenu(Menu.buildFromTemplate([
+            {
+                label: 'New window',
+                click () {
+                    this.app.newWindow()
+                },
+            },
+        ]))
+    }
+    application.init()
+    application.newWindow({ hidden: argv.hidden })
+})

+ 17 - 0
tmp/lib/lru.ts

@@ -0,0 +1,17 @@
+import * as LRU from 'lru-cache'
+import * as fs from 'fs'
+const lru = new LRU({ max: 256, maxAge: 250 })
+const origLstat = fs.realpathSync.bind(fs)
+
+// NB: The biggest offender of thrashing realpathSync is the node module system
+// itself, which we can't get into via any sane means.
+require('fs').realpathSync = function (p) {
+    let r = lru.get(p)
+    if (r) {
+        return r
+    }
+
+    r = origLstat(p)
+    lru.set(p, r)
+    return r
+}

+ 24 - 0
tmp/lib/portable.ts

@@ -0,0 +1,24 @@
+import * as path from 'path'
+import * as fs from 'fs'
+
+let appPath: string | null = null
+try {
+    appPath = path.dirname(require('electron').app.getPath('exe'))
+} catch {
+    appPath = path.dirname(require('electron').remote.app.getPath('exe'))
+}
+
+if (null != appPath) {
+    if (fs.existsSync(path.join(appPath, 'terminus-data'))) {
+        fs.renameSync(path.join(appPath, 'terminus-data'), path.join(appPath, 'data'))
+    }
+    const portableData = path.join(appPath, 'data')
+    if (fs.existsSync(portableData)) {
+        console.log('reset user data to ' + portableData)
+        try {
+            require('electron').app.setPath('userData', portableData)
+        } catch {
+            require('electron').remote.app.setPath('userData', portableData)
+        }
+    }
+}

+ 21 - 0
tmp/lib/sentry.ts

@@ -0,0 +1,21 @@
+const { init } = String(process.type) === 'main' ? require('@sentry/electron/dist/main') : require('@sentry/electron/dist/renderer')
+import * as isDev from 'electron-is-dev'
+
+
+const SENTRY_DSN = 'https://[email protected]/181876'
+let release = null
+try {
+    release = require('electron').app.getVersion()
+} catch {
+    release = require('electron').remote.app.getVersion()
+}
+
+if (!isDev) {
+    init({
+        dsn: SENTRY_DSN,
+        release,
+        integrations (integrations) {
+            return integrations.filter(integration => integration.name !== 'Breadcrumbs')
+        },
+    })
+}

+ 389 - 0
tmp/lib/window.ts

@@ -0,0 +1,389 @@
+import * as glasstron from 'glasstron'
+if (process.platform === 'win32' || process.platform === 'linux') {
+    glasstron.init()
+}
+
+import { Subject, Observable } from 'rxjs'
+import { debounceTime } from 'rxjs/operators'
+import { BrowserWindow, app, ipcMain, Rectangle, Menu, screen, BrowserWindowConstructorOptions } from 'electron'
+import ElectronConfig = require('electron-config')
+import * as os from 'os'
+import * as path from 'path'
+
+import { parseArgs } from './cli'
+import { loadConfig } from './config'
+
+let DwmEnableBlurBehindWindow: any = null
+if (process.platform === 'win32') {
+    DwmEnableBlurBehindWindow = require('windows-blurbehind').DwmEnableBlurBehindWindow
+}
+
+export interface WindowOptions {
+    hidden?: boolean
+}
+
+export class Window {
+    ready: Promise<void>
+    private visible = new Subject<boolean>()
+    private closed = new Subject<void>()
+    private window: BrowserWindow
+    private windowConfig: ElectronConfig
+    private windowBounds: Rectangle
+    private closing = false
+    private lastVibrancy: {enabled: boolean, type?: string} | null = null
+    private disableVibrancyWhileDragging = false
+    private configStore: any
+
+    get visible$ (): Observable<boolean> { return this.visible }
+    get closed$ (): Observable<void> { return this.closed }
+
+    constructor (options?: WindowOptions) {
+        this.configStore = loadConfig()
+
+        options = options || {}
+
+        this.windowConfig = new ElectronConfig({ name: 'window' })
+        this.windowBounds = this.windowConfig.get('windowBoundaries')
+
+        const maximized = this.windowConfig.get('maximized')
+        const bwOptions: BrowserWindowConstructorOptions = {
+            width: 800,
+            height: 600,
+            title: 'Terminus',
+            minWidth: 400,
+            minHeight: 300,
+            webPreferences: {
+                nodeIntegration: true,
+                preload: path.join(__dirname, 'sentry.js'),
+                backgroundThrottling: false,
+                enableRemoteModule: true,
+            },
+            frame: false,
+            show: false,
+            backgroundColor: '#00000000',
+        }
+
+        if (this.windowBounds) {
+            Object.assign(bwOptions, this.windowBounds)
+            const closestDisplay = screen.getDisplayNearestPoint( { x: this.windowBounds.x, y: this.windowBounds.y } )
+
+            const [left1, top1, right1, bottom1] = [this.windowBounds.x, this.windowBounds.y, this.windowBounds.x + this.windowBounds.width, this.windowBounds.y + this.windowBounds.height]
+            const [left2, top2, right2, bottom2] = [closestDisplay.bounds.x, closestDisplay.bounds.y, closestDisplay.bounds.x + closestDisplay.bounds.width, closestDisplay.bounds.y + closestDisplay.bounds.height]
+
+            if ((left2 > right1 || right2 < left1 || top2 > bottom1 || bottom2 < top1) && !maximized) {
+                bwOptions.x = closestDisplay.bounds.width / 2 - bwOptions.width / 2
+                bwOptions.y = closestDisplay.bounds.height / 2 - bwOptions.height / 2
+            }
+        }
+
+        if ((this.configStore.appearance || {}).frame === 'native') {
+            bwOptions.frame = true
+        } else {
+            if (process.platform === 'darwin') {
+                bwOptions.titleBarStyle = 'hiddenInset'
+            }
+        }
+
+        this.window = new BrowserWindow(bwOptions)
+
+        this.window.once('ready-to-show', () => {
+            if (process.platform === 'darwin') {
+                this.window.setVibrancy('window')
+            } else if (process.platform === 'win32' && (this.configStore.appearance || {}).vibrancy) {
+                this.setVibrancy(true)
+            }
+
+            if (!options.hidden) {
+                if (maximized) {
+                    this.window.maximize()
+                } else {
+                    this.window.show()
+                }
+                this.window.focus()
+                this.window.moveTop()
+            }
+        })
+
+        this.window.on('blur', () => {
+            if (this.configStore.appearance?.dockHideOnBlur) {
+                this.hide()
+            }
+        })
+
+        this.window.loadURL(`file://${app.getAppPath()}/dist/index.html?${this.window.id}`, { extraHeaders: 'pragma: no-cache\n' })
+
+        if (process.platform !== 'darwin') {
+            this.window.setMenu(null)
+        }
+
+        this.setupWindowManagement()
+
+        this.ready = new Promise(resolve => {
+            const listener = event => {
+                if (event.sender === this.window.webContents) {
+                    ipcMain.removeListener('app:ready', listener as any)
+                    resolve()
+                }
+            }
+            ipcMain.on('app:ready', listener)
+        })
+    }
+
+    setVibrancy (enabled: boolean, type?: string): void {
+        this.lastVibrancy = { enabled, type }
+        if (process.platform === 'win32') {
+            if (parseFloat(os.release()) >= 10) {
+                glasstron.update(this.window, {
+                    windows: { blurType: enabled ? type === 'fluent' ? 'acrylic' : 'blurbehind' : null },
+                })
+            } else {
+                DwmEnableBlurBehindWindow(this.window, enabled)
+            }
+        } else if (process.platform === 'linux') {
+            glasstron.update(this.window, {
+                linux: { requestBlur: enabled },
+            })
+            this.window.setBackgroundColor(enabled ? '#00000000' : '#131d27')
+        } else {
+            this.window.setVibrancy(enabled ? 'dark' : null as any) // electron issue 20269
+        }
+    }
+
+    show (): void {
+        this.window.show()
+        this.window.moveTop()
+    }
+
+    focus (): void {
+        this.window.focus()
+    }
+
+    send (event: string, ...args: any[]): void {
+        if (!this.window) {
+            return
+        }
+        this.window.webContents.send(event, ...args)
+        if (event === 'host:config-change') {
+            this.configStore = args[0]
+        }
+    }
+
+    isDestroyed (): boolean {
+        return !this.window || this.window.isDestroyed()
+    }
+
+    isFocused (): boolean {
+        return this.window.isFocused()
+    }
+
+    hide (): void {
+        if (process.platform === 'darwin') {
+            // Lose focus
+            Menu.sendActionToFirstResponder('hide:')
+        }
+        this.window.blur()
+        if (process.platform !== 'darwin') {
+            this.window.hide()
+        }
+    }
+
+    present (): void {
+        if (!this.window.isVisible()) {
+            // unfocused, invisible
+            this.window.show()
+            this.window.focus()
+        } else {
+            if (!this.configStore.appearance?.dock || this.configStore.appearance?.dock === 'off') {
+                // not docked, visible
+                setTimeout(() => {
+                    this.window.show()
+                    this.window.focus()
+                })
+            } else {
+                if (this.configStore.appearance?.dockAlwaysOnTop) {
+                    // docked, visible, on top
+                    this.window.hide()
+                } else {
+                    // docked, visible, not on top
+                    this.window.focus()
+                }
+            }
+        }
+    }
+
+    handleSecondInstance (argv: string[], cwd: string): void {
+        this.send('host:second-instance', parseArgs(argv, cwd), cwd)
+    }
+
+    private setupWindowManagement () {
+        this.window.on('show', () => {
+            this.visible.next(true)
+            this.send('host:window-shown')
+        })
+
+        this.window.on('hide', () => {
+            this.visible.next(false)
+        })
+
+        const moveSubscription = new Observable<void>(observer => {
+            this.window.on('move', () => observer.next())
+        }).pipe(debounceTime(250)).subscribe(() => {
+            this.send('host:window-moved')
+        })
+
+        this.window.on('closed', () => {
+            moveSubscription.unsubscribe()
+        })
+
+        this.window.on('enter-full-screen', () => this.send('host:window-enter-full-screen'))
+        this.window.on('leave-full-screen', () => this.send('host:window-leave-full-screen'))
+
+        this.window.on('close', event => {
+            if (!this.closing) {
+                event.preventDefault()
+                this.send('host:window-close-request')
+                return
+            }
+            this.windowConfig.set('windowBoundaries', this.windowBounds)
+            this.windowConfig.set('maximized', this.window.isMaximized())
+        })
+
+        this.window.on('closed', () => {
+            this.destroy()
+        })
+
+        this.window.on('resize', () => {
+            if (!this.window.isMaximized()) {
+                this.windowBounds = this.window.getBounds()
+            }
+        })
+
+        this.window.on('move', () => {
+            if (!this.window.isMaximized()) {
+                this.windowBounds = this.window.getBounds()
+            }
+        })
+
+        this.window.on('focus', () => {
+            this.send('host:window-focused')
+        })
+
+        ipcMain.on('window-focus', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.focus()
+        })
+
+        ipcMain.on('window-maximize', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.maximize()
+        })
+
+        ipcMain.on('window-unmaximize', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.unmaximize()
+        })
+
+        ipcMain.on('window-toggle-maximize', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            if (this.window.isMaximized()) {
+                this.window.unmaximize()
+            } else {
+                this.window.maximize()
+            }
+        })
+
+        ipcMain.on('window-minimize', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.minimize()
+        })
+
+        ipcMain.on('window-set-bounds', (event, bounds) => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.setBounds(bounds)
+        })
+
+        ipcMain.on('window-set-always-on-top', (event, flag) => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.setAlwaysOnTop(flag)
+        })
+
+        ipcMain.on('window-set-vibrancy', (event, enabled, type) => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.setVibrancy(enabled, type)
+        })
+
+        ipcMain.on('window-set-title', (event, title) => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.window.setTitle(title)
+        })
+
+        ipcMain.on('window-bring-to-front', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            if (this.window.isMinimized()) {
+                this.window.restore()
+            }
+            this.window.show()
+            this.window.moveTop()
+        })
+
+        ipcMain.on('window-close', event => {
+            if (!this.window || event.sender !== this.window.webContents) {
+                return
+            }
+            this.closing = true
+            this.window.close()
+        })
+
+        this.window.webContents.on('new-window', event => event.preventDefault())
+
+        ipcMain.on('window-set-disable-vibrancy-while-dragging', (_event, value) => {
+            this.disableVibrancyWhileDragging = value
+        })
+
+        this.window.on('will-move', () => {
+            if (!this.lastVibrancy?.enabled || !this.disableVibrancyWhileDragging) {
+                return
+            }
+            let timeout: number|null = null
+            const oldVibrancy = this.lastVibrancy
+            this.setVibrancy(false)
+            const onMove = () => {
+                if (timeout) {
+                    clearTimeout(timeout)
+                }
+                timeout = setTimeout(() => {
+                    this.window.off('move', onMove)
+                    this.setVibrancy(oldVibrancy.enabled, oldVibrancy.type)
+                }, 500)
+            }
+            this.window.on('move', onMove)
+        })
+    }
+
+    private destroy () {
+        this.window = null
+        this.closed.next()
+        this.visible.complete()
+        this.closed.complete()
+    }
+}

+ 54 - 0
tmp/package.json

@@ -0,0 +1,54 @@
+{
+  "name": "terminus",
+  "description": "A terminal for a modern age",
+  "repository": "https://github.com/eugeny/terminus",
+  "author": {
+    "name": "Eugene Pankov",
+    "email": "[email protected]"
+  },
+  "main": "dist/main.js",
+  "version": "1.0.123-nightly.0",
+  "dependencies": {
+    "@angular/animations": "^9.1.9",
+    "@angular/common": "^9.1.11",
+    "@angular/compiler": "^9.1.9",
+    "@angular/core": "^9.1.9",
+    "@angular/forms": "^9.1.11",
+    "@angular/platform-browser": "^9.1.9",
+    "@angular/platform-browser-dynamic": "^9.1.9",
+    "@ng-bootstrap/ng-bootstrap": "^6.1.0",
+    "@terminus-term/node-pty": "0.10.0-beta10",
+    "devtron": "1.4.0",
+    "electron-config": "2.0.0",
+    "electron-debug": "^3.0.1",
+    "electron-is-dev": "1.1.0",
+    "fontmanager-redux": "1.0.0",
+    "glasstron": "AryToNeX/Glasstron#dependabot/npm_and_yarn/electron-11.1.0",
+    "js-yaml": "3.14.0",
+    "keytar": "^7.2.0",
+    "mz": "^2.7.0",
+    "ngx-toastr": "^12.0.1",
+    "npm": "7.0.15",
+    "path": "0.12.7",
+    "rxjs": "^6.5.5",
+    "rxjs-compat": "^6.6.0",
+    "yargs": "^15.4.1",
+    "zone.js": "^0.11.3"
+  },
+  "optionalDependencies": {
+    "macos-native-processlist": "^2.0.0",
+    "serialport": "^9.0.4",
+    "windows-blurbehind": "^1.0.1",
+    "windows-native-registry": "^3.0.0",
+    "windows-process-tree": "^0.2.4"
+  },
+  "peerDependencies": {
+    "terminus-core": "*",
+    "terminus-settings": "*",
+    "terminus-serial": "*",
+    "terminus-plugin-manager": "*",
+    "terminus-community-color-schemes": "*",
+    "terminus-ssh": "*",
+    "terminus-terminal": "*"
+  }
+}

+ 33 - 0
tmp/src/app.module.ts

@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
+import { NgModule } from '@angular/core'
+import { BrowserModule } from '@angular/platform-browser'
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
+import { ToastrModule } from 'ngx-toastr'
+
+export function getRootModule (plugins: any[]) {
+    const imports = [
+        BrowserModule,
+        ...plugins,
+        NgbModule,
+        ToastrModule.forRoot({
+            positionClass: 'toast-bottom-center',
+            toastClass: 'toast',
+            preventDuplicates: true,
+            extendedTimeOut: 5000,
+        }),
+    ]
+    const bootstrap = [
+        ...plugins.filter(x => x.bootstrap).map(x => x.bootstrap),
+    ]
+
+    if (bootstrap.length === 0) {
+        throw new Error('Did not find any bootstrap components. Are there any plugins installed?')
+    }
+
+    @NgModule({
+        imports,
+        bootstrap,
+    }) class RootModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
+
+    return RootModule
+}

+ 9 - 0
tmp/src/entry.preload.ts

@@ -0,0 +1,9 @@
+import '../lib/lru'
+import 'source-sans-pro/source-sans-pro.css'
+import 'source-code-pro/source-code-pro.css'
+import '@fortawesome/fontawesome-free/css/solid.css'
+import '@fortawesome/fontawesome-free/css/brands.css'
+import '@fortawesome/fontawesome-free/css/regular.css'
+import '@fortawesome/fontawesome-free/css/fontawesome.css'
+import 'ngx-toastr/toastr.css'
+import './preload.scss'

+ 65 - 0
tmp/src/entry.ts

@@ -0,0 +1,65 @@
+import 'zone.js'
+import 'core-js/proposals/reflect-metadata'
+import 'rxjs'
+
+import * as isDev from 'electron-is-dev'
+
+import './global.scss'
+import './toastr.scss'
+
+import { enableProdMode, NgModuleRef, ApplicationRef } from '@angular/core'
+import { enableDebugTools } from '@angular/platform-browser'
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
+
+import { getRootModule } from './app.module'
+import { findPlugins, loadPlugins, PluginInfo } from './plugins'
+
+// Always land on the start view
+location.hash = ''
+
+;(process as any).enablePromiseAPI = true
+
+if (process.platform === 'win32' && !('HOME' in process.env)) {
+    process.env.HOME = `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`
+}
+
+if (isDev) {
+    console.warn('Running in debug mode')
+} else {
+    enableProdMode()
+}
+
+async function bootstrap (plugins: PluginInfo[], safeMode = false): Promise<NgModuleRef<any>> {
+    if (safeMode) {
+        plugins = plugins.filter(x => x.isBuiltin)
+    }
+    const pluginsModules = await loadPlugins(plugins, (current, total) => {
+        (document.querySelector('.progress .bar') as HTMLElement).style.width = `${100 * current / total}%` // eslint-disable-line
+    })
+    const module = getRootModule(pluginsModules)
+    window['rootModule'] = module
+    return platformBrowserDynamic().bootstrapModule(module).then(moduleRef => {
+        if (isDev) {
+            const applicationRef = moduleRef.injector.get(ApplicationRef)
+            const componentRef = applicationRef.components[0]
+            enableDebugTools(componentRef)
+        }
+        return moduleRef
+    })
+}
+
+findPlugins().then(async plugins => {
+    console.log('Starting with plugins:', plugins)
+    try {
+        await bootstrap(plugins)
+    } catch (error) {
+        console.error('Angular bootstrapping error:', error)
+        console.warn('Trying safe mode')
+        window['safeModeReason'] = error
+        try {
+            await bootstrap(plugins, true)
+        } catch (error) {
+            console.error('Bootstrap failed:', error)
+        }
+    }
+})

+ 97 - 0
tmp/src/global.scss

@@ -0,0 +1,97 @@
+body {
+    min-height: 100vh;
+    overflow: hidden;
+    background: #1D272D;
+}
+
+.modal-dialog, .modal-backdrop, .no-drag {
+    -webkit-app-region: no-drag;
+}
+
+.selectable {
+    user-select: text;
+}
+
+[ngbradiogroup] input[type="radio"] {
+    display: none;
+}
+
+.btn {
+    & > svg {
+        pointer-events: none;
+    }
+}
+
+.form-line {
+    display: flex;
+    border-top: 1px solid rgba(0, 0, 0, 0.2);
+    align-items: center;
+    padding: 10px 0;
+    margin: 0;
+    min-height: 64px;
+
+    .header {
+        margin-right: auto;
+
+        .title {
+        }
+
+        .description {
+            font-size: 13px;
+            opacity: .5;
+        }
+    }
+
+    &>.form-control, &>.input-group {
+        width: 33%;
+    }
+}
+
+input[type=range] {
+    -webkit-appearance: none;
+    background: transparent;
+    outline: none;
+    padding: 0;
+
+    &:focus {
+        border-color: transparent;
+    }
+
+    @mixin thumb() {
+        -webkit-appearance: none;
+        display: block;
+        height: 12px;
+        width: 12px;
+        background: #aaa;
+        border-radius: 6px;
+        cursor: pointer;
+        margin-top: -4px;
+        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
+        transition: 0.25s background;
+
+        &:hover {
+            background: #777;
+        }
+
+        &:active {
+            background: #666;
+        }
+    }
+
+    &::-webkit-slider-thumb { @include thumb(); }
+    &::-moz-range-thumb  { @include thumb(); }
+    &::-ms-thumb  { @include thumb(); }
+    &::thumb { @include thumb(); }
+
+    @mixin track() {
+        height: 4px;
+        background: #111;
+        margin: 3px 0 0;
+        box-sizing: border-box;
+    }
+
+    &::-webkit-slider-runnable-track { @include track(); }
+    &:focus::-webkit-slider-runnable-track { @include track(); }
+    &::-moz-range-track { @include track(); }
+    &::-ms-track { @include track(); }
+}

+ 188 - 0
tmp/src/plugins.ts

@@ -0,0 +1,188 @@
+import * as fs from 'mz/fs'
+import * as path from 'path'
+const nodeModule = require('module') // eslint-disable-line @typescript-eslint/no-var-requires
+const nodeRequire = (global as any).require
+
+function normalizePath (path: string): string {
+    const cygwinPrefix = '/cygdrive/'
+    if (path.startsWith(cygwinPrefix)) {
+        path = path.substring(cygwinPrefix.length).replace('/', '\\')
+        path = path[0] + ':' + path.substring(1)
+    }
+    return path
+}
+
+global['module'].paths.map((x: string) => nodeModule.globalPaths.push(normalizePath(x)))
+
+if (process.env.TERMINUS_DEV) {
+    nodeModule.globalPaths.unshift(path.dirname(require('electron').remote.app.getAppPath()))
+}
+
+const builtinPluginsPath = process.env.TERMINUS_DEV ? path.dirname(require('electron').remote.app.getAppPath()) : path.join((process as any).resourcesPath, 'builtin-plugins')
+
+const userPluginsPath = path.join(
+    require('electron').remote.app.getPath('userData'),
+    'plugins',
+)
+
+if (!fs.existsSync(userPluginsPath)) {
+    fs.mkdir(userPluginsPath)
+}
+
+Object.assign(window, { builtinPluginsPath, userPluginsPath })
+nodeModule.globalPaths.unshift(builtinPluginsPath)
+nodeModule.globalPaths.unshift(path.join(userPluginsPath, 'node_modules'))
+// nodeModule.globalPaths.unshift(path.join((process as any).resourcesPath, 'app.asar', 'node_modules'))
+if (process.env.TERMINUS_PLUGINS) {
+    process.env.TERMINUS_PLUGINS.split(':').map(x => nodeModule.globalPaths.push(normalizePath(x)))
+}
+
+export type ProgressCallback = (current: number, total: number) => void // eslint-disable-line @typescript-eslint/no-type-alias
+
+export interface PluginInfo {
+    name: string
+    description: string
+    packageName: string
+    isBuiltin: boolean
+    version: string
+    author: string
+    homepage?: string
+    path?: string
+    info?: any
+}
+
+const builtinModules = [
+    '@angular/animations',
+    '@angular/common',
+    '@angular/compiler',
+    '@angular/core',
+    '@angular/forms',
+    '@angular/platform-browser',
+    '@angular/platform-browser-dynamic',
+    '@ng-bootstrap/ng-bootstrap',
+    'ngx-toastr',
+    'rxjs',
+    'rxjs/operators',
+    'rxjs-compat/Subject',
+    'terminus-core',
+    'terminus-settings',
+    'terminus-terminal',
+    'zone.js/dist/zone.js',
+]
+
+const cachedBuiltinModules = {}
+builtinModules.forEach(m => {
+    const label = 'Caching ' + m
+    console.time(label)
+    cachedBuiltinModules[m] = nodeRequire(m)
+    console.timeEnd(label)
+})
+
+const originalRequire = (global as any).require
+;(global as any).require = function (query: string) {
+    if (cachedBuiltinModules[query]) {
+        return cachedBuiltinModules[query]
+    }
+    return originalRequire.apply(this, [query])
+}
+
+const originalModuleRequire = nodeModule.prototype.require
+nodeModule.prototype.require = function (query: string) {
+    if (cachedBuiltinModules[query]) {
+        return cachedBuiltinModules[query]
+    }
+    return originalModuleRequire.call(this, query)
+}
+
+export async function findPlugins (): Promise<PluginInfo[]> {
+    const paths = nodeModule.globalPaths
+    let foundPlugins: PluginInfo[] = []
+    const candidateLocations: { pluginDir: string, packageName: string }[] = []
+    const PREFIX = 'terminus-'
+
+    for (let pluginDir of paths) {
+        pluginDir = normalizePath(pluginDir)
+        if (!await fs.exists(pluginDir)) {
+            continue
+        }
+        const pluginNames = await fs.readdir(pluginDir)
+        if (await fs.exists(path.join(pluginDir, 'package.json'))) {
+            candidateLocations.push({
+                pluginDir: path.dirname(pluginDir),
+                packageName: path.basename(pluginDir),
+            })
+        }
+        for (const packageName of pluginNames) {
+            if (packageName.startsWith(PREFIX)) {
+                candidateLocations.push({ pluginDir, packageName })
+            }
+        }
+    }
+
+    for (const { pluginDir, packageName } of candidateLocations) {
+        const pluginPath = path.join(pluginDir, packageName)
+        const infoPath = path.join(pluginPath, 'package.json')
+        if (!await fs.exists(infoPath)) {
+            continue
+        }
+
+        const name = packageName.substring(PREFIX.length)
+
+        if (foundPlugins.some(x => x.name === name)) {
+            console.info(`Plugin ${packageName} already exists, overriding`)
+            foundPlugins = foundPlugins.filter(x => x.name !== name)
+        }
+
+        try {
+            const info = JSON.parse(await fs.readFile(infoPath, { encoding: 'utf-8' }))
+            if (!info.keywords || !(info.keywords.includes('terminus-plugin') || info.keywords.includes('terminus-builtin-plugin'))) {
+                continue
+            }
+            let author = info.author
+            author = author.name || author
+            foundPlugins.push({
+                name: name,
+                packageName: packageName,
+                isBuiltin: pluginDir === builtinPluginsPath,
+                version: info.version,
+                description: info.description,
+                author,
+                path: pluginPath,
+                info,
+            })
+        } catch (error) {
+            console.error('Cannot load package info for', packageName)
+        }
+    }
+
+    foundPlugins.sort((a, b) => a.name > b.name ? 1 : -1)
+
+    ;(window as any).installedPlugins = foundPlugins
+    return foundPlugins
+}
+
+export async function loadPlugins (foundPlugins: PluginInfo[], progress: ProgressCallback): Promise<any[]> {
+    const plugins: any[] = []
+    progress(0, 1)
+    let index = 0
+    for (const foundPlugin of foundPlugins) {
+        console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`)
+        progress(index, foundPlugins.length)
+        try {
+            const label = 'Loading ' + foundPlugin.name
+            console.time(label)
+            const packageModule = nodeRequire(foundPlugin.path)
+            const pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default
+            pluginModule['pluginName'] = foundPlugin.name
+            pluginModule['bootstrap'] = packageModule.bootstrap
+            plugins.push(pluginModule)
+            console.timeEnd(label)
+            await new Promise(x => setTimeout(x, 50))
+        } catch (error) {
+            console.error(`Could not load ${foundPlugin.name}:`, error)
+        }
+        index++
+    }
+    progress(1, 1)
+    return plugins
+}

+ 60 - 0
tmp/src/preload.scss

@@ -0,0 +1,60 @@
+.preload-logo {
+  -webkit-app-region: drag;
+  position: fixed;
+  left: 0;
+  top: 0;
+  width: 100vw;
+  height: 100vh;
+  display: flex;
+  animation: 0.5s ease-out fadeIn;
+
+  &>div {
+    width: 200px;
+    height: 200px;
+    margin: auto;
+    flex: none;
+
+    .progress {
+      background: rgba(0,0,0,.25);
+      height: 3px;
+      margin: 10px 50px;
+
+      .bar {
+        transition: 1s ease-out width;
+        background: #a1c5e4;
+        height: 3px;
+      }
+    }
+  }
+}
+
+
+@keyframes fadeIn {
+  0% { opacity: 0; }
+  100% { opacity: 1; }
+}
+
+
+
+.terminus-logo {
+  width: 160px;
+  height: 160px;
+  background: url('../assets/logo.svg');
+  background-repeat: none;
+  background-size: contain;
+  margin: auto;
+}
+
+
+.terminus-title {
+  color: #a1c5e4;
+  font-family: 'Source Sans Pro';
+  text-align: center;
+  font-weight: normal;
+  font-size: 42px;
+  margin: 0;
+
+  sup {
+      color: #842fe0;
+  }
+}

+ 6 - 0
tmp/src/root.component.ts

@@ -0,0 +1,6 @@
+import { Component } from '@angular/core'
+
+@Component({
+    template: '<app-root></app-root>',
+})
+export class RootComponent { } // eslint-disable-line @typescript-eslint/no-extraneous-class

+ 21 - 0
tmp/src/toastr.scss

@@ -0,0 +1,21 @@
+#toast-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20px;
+
+  .toast {
+    box-shadow: 0 1px 0 rgba(0,0,0,.25);
+    padding: 10px;
+    background-image: none;
+    width: auto;
+
+    &.toast-error {
+      background-color: #BD362F;
+    }
+
+    &.toast-info {
+      background-color: #555;
+    }
+  }
+}

+ 32 - 0
tmp/tsconfig.json

@@ -0,0 +1,32 @@
+{
+    "compilerOptions": {
+        "baseUrl": "./src",
+        "module": "commonjs",
+        "target": "es2015",
+        "declaration": false,
+        "noImplicitAny": false,
+        "removeComments": false,
+        "emitDecoratorMetadata": true,
+        "experimentalDecorators": true,
+        "sourceMap": true,
+        "noImplicitReturns": true,
+        "noFallthroughCasesInSwitch": true,
+        "noUnusedParameters": true,
+        "noUnusedLocals": true,
+        "lib": [
+           "dom",
+           "es2015",
+           "es2015.iterable",
+           "es2017",
+           "es7"
+        ]
+    },
+    "compileOnSave": false,
+    "exclude": [
+        "dist",
+        "node_modules",
+        "*/node_modules",
+        "terminus*",
+        "platforms"
+    ]
+}

+ 30 - 0
tmp/tsconfig.main.json

@@ -0,0 +1,30 @@
+{
+    "compilerOptions": {
+        "baseUrl": "./lib",
+        "module": "commonjs",
+        "target": "es2017",
+        "declaration": false,
+        "noImplicitAny": false,
+        "removeComments": false,
+        "emitDecoratorMetadata": true,
+        "experimentalDecorators": true,
+        "sourceMap": true,
+        "noUnusedParameters": true,
+        "noImplicitReturns": true,
+        "noFallthroughCasesInSwitch": true,
+        "noUnusedLocals": true,
+        "lib": [
+           "dom",
+           "es2015",
+           "es2015.iterable",
+           "es2017",
+           "es7"
+        ]
+    },
+    "compileOnSave": false,
+    "exclude": [
+        "dist",
+        "node_modules",
+        "*/node_modules"
+    ]
+}

+ 86 - 0
tmp/webpack.config.js

@@ -0,0 +1,86 @@
+const path = require('path')
+const webpack = require('webpack')
+
+module.exports = {
+  name: 'terminus',
+  target: 'node',
+  entry: {
+    'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './index.pug'),
+    sentry: path.resolve(__dirname, 'lib/sentry.ts'),
+    preload: path.resolve(__dirname, 'src/entry.preload.ts'),
+    bundle: path.resolve(__dirname, 'src/entry.ts'),
+  },
+  mode: process.env.TERMINUS_DEV ? 'development' : 'production',
+  optimization:{
+     minimize: false,
+  },
+  context: __dirname,
+  devtool: 'source-map',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    pathinfo: true,
+    filename: '[name].js',
+  },
+  resolve: {
+    modules: ['src/', 'node_modules', '../node_modules', 'assets/'].map(x => path.join(__dirname, x)),
+    extensions: ['.ts', '.js'],
+  },
+  module: {
+    rules: [
+      {
+        test: /\.ts$/,
+        use: {
+          loader: 'awesome-typescript-loader',
+          options: {
+            configFileName: path.resolve(__dirname, 'tsconfig.json'),
+          },
+        },
+      },
+      { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
+      { test: /\.css$/, use: ['style-loader', 'css-loader', 'sass-loader'] },
+      {
+        test: /\.(png|svg)$/,
+        use: {
+          loader: 'file-loader',
+          options: {
+            name: 'images/[name].[ext]',
+          },
+        },
+      },
+      {
+        test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
+        use: {
+          loader: 'file-loader',
+          options: {
+            name: 'fonts/[name].[ext]',
+          },
+        },
+      },
+    ],
+  },
+  externals: {
+    '@angular/core': 'commonjs @angular/core',
+    '@angular/compiler': 'commonjs @angular/compiler',
+    '@angular/platform-browser': 'commonjs @angular/platform-browser',
+    '@angular/platform-browser-dynamic': 'commonjs @angular/platform-browser-dynamic',
+    '@angular/forms': 'commonjs @angular/forms',
+    '@angular/common': 'commonjs @angular/common',
+    '@ng-bootstrap/ng-bootstrap': 'commonjs @ng-bootstrap/ng-bootstrap',
+    child_process: 'commonjs child_process',
+    electron: 'commonjs electron',
+    'electron-is-dev': 'commonjs electron-is-dev',
+    fs: 'commonjs fs',
+    'ngx-toastr': 'commonjs ngx-toastr',
+    module: 'commonjs module',
+    mz: 'commonjs mz',
+    path: 'commonjs path',
+    rxjs: 'commonjs rxjs',
+    'zone.js': 'commonjs zone.js/dist/zone.js',
+  },
+  plugins: [
+    new webpack.optimize.ModuleConcatenationPlugin(),
+    new webpack.DefinePlugin({
+      'process.type': '"renderer"'
+    }),
+  ],
+}

+ 53 - 0
tmp/webpack.main.config.js

@@ -0,0 +1,53 @@
+const path = require('path')
+const webpack = require('webpack')
+
+module.exports = {
+  name: 'terminus-main',
+  target: 'node',
+  entry: {
+    main: path.resolve(__dirname, 'lib/index.ts'),
+  },
+  mode: process.env.TERMINUS_DEV ? 'development' : 'production',
+  context: __dirname,
+  devtool: 'source-map',
+  output: {
+    path: path.join(__dirname, 'dist'),
+    pathinfo: true,
+    filename: '[name].js',
+  },
+  resolve: {
+    modules: ['lib/', 'node_modules', '../node_modules'].map(x => path.join(__dirname, x)),
+    extensions: ['.ts', '.js'],
+  },
+  module: {
+    rules: [
+      {
+        test: /\.ts$/,
+        use: {
+          loader: 'awesome-typescript-loader',
+          options: {
+            configFileName: path.resolve(__dirname, 'tsconfig.main.json'),
+          },
+        },
+      },
+    ],
+  },
+  externals: {
+    electron: 'commonjs electron',
+    'electron-config': 'commonjs electron-config',
+    'electron-vibrancy': 'commonjs electron-vibrancy',
+    fs: 'commonjs fs',
+    glasstron: 'commonjs glasstron',
+    mz: 'commonjs mz',
+    path: 'commonjs path',
+    yargs: 'commonjs yargs',
+    'windows-swca': 'commonjs windows-swca',
+    'windows-blurbehind': 'commonjs windows-blurbehind',
+  },
+  plugins: [
+    new webpack.optimize.ModuleConcatenationPlugin(),
+    new webpack.DefinePlugin({
+      'process.type': '"main"',
+    }),
+  ],
+}

+ 1 - 1
tsconfig.json

@@ -16,7 +16,7 @@
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
     "importHelpers": true,
-    "strictNullChecks": false,
+    "strictNullChecks": true,
     "lib": [
       "dom",
       "es5",

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 7879 - 8451
yarn.lock


Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů