mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-15 05:08:41 -09:00
Compare commits
No commits in common. "canary" and "3.1.0-canary.3" have entirely different histories.
canary
...
3.1.0-cana
178 changed files with 12196 additions and 164170 deletions
98
.circleci/config.yml
Normal file
98
.circleci/config.yml
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
version: 2
|
||||||
|
jobs:
|
||||||
|
install:
|
||||||
|
macos:
|
||||||
|
xcode: "11.2.1"
|
||||||
|
working_directory: ~/repo
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: cache-{{ checksum "yarn.lock" }}
|
||||||
|
- run:
|
||||||
|
name: Installing Dependencies
|
||||||
|
command: yarn --ignore-engines
|
||||||
|
- save_cache:
|
||||||
|
key: cache-{{ checksum "yarn.lock" }}
|
||||||
|
paths:
|
||||||
|
- node_modules
|
||||||
|
- run:
|
||||||
|
name: Getting build icon
|
||||||
|
command: if [[ $CIRCLE_BRANCH == canary ]]; then cp build/canary.icns build/icon.icns; fi
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- node_modules
|
||||||
|
- app/node_modules
|
||||||
|
|
||||||
|
test:
|
||||||
|
macos:
|
||||||
|
xcode: "11.2.1"
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
- run:
|
||||||
|
name: Testing
|
||||||
|
command: yarn test
|
||||||
|
|
||||||
|
build:
|
||||||
|
macos:
|
||||||
|
xcode: "11.2.1"
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
- run:
|
||||||
|
name: Building
|
||||||
|
command: yarn dist --publish 'never'
|
||||||
|
- store_artifacts:
|
||||||
|
path: dist
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- dist
|
||||||
|
|
||||||
|
release:
|
||||||
|
macos:
|
||||||
|
xcode: "11.2.1"
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- attach_workspace:
|
||||||
|
at: .
|
||||||
|
- run:
|
||||||
|
name: Deploying to GitHub
|
||||||
|
command: yarn dist
|
||||||
|
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
version: 2
|
||||||
|
build:
|
||||||
|
jobs:
|
||||||
|
- install:
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
- test:
|
||||||
|
requires:
|
||||||
|
- install
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
- build:
|
||||||
|
requires:
|
||||||
|
- test
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- canary
|
||||||
|
tags:
|
||||||
|
ignore: /.*/
|
||||||
|
- release:
|
||||||
|
requires:
|
||||||
|
- test
|
||||||
|
filters:
|
||||||
|
tags:
|
||||||
|
only: /.*/
|
||||||
|
branches:
|
||||||
|
ignore: /.*/
|
||||||
|
|
@ -4,11 +4,8 @@ app/static
|
||||||
app/bin
|
app/bin
|
||||||
app/dist
|
app/dist
|
||||||
app/node_modules
|
app/node_modules
|
||||||
app/typings
|
|
||||||
assets
|
assets
|
||||||
website
|
website
|
||||||
bin
|
bin
|
||||||
dist
|
dist
|
||||||
target
|
target
|
||||||
cache
|
|
||||||
schema.json
|
|
||||||
123
.eslintrc.json
123
.eslintrc.json
|
|
@ -2,29 +2,23 @@
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"react",
|
"react",
|
||||||
"prettier",
|
"prettier",
|
||||||
"@typescript-eslint",
|
"@typescript-eslint"
|
||||||
"eslint-comments",
|
|
||||||
"lodash",
|
|
||||||
"import"
|
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended"
|
||||||
"plugin:eslint-comments/recommended"
|
|
||||||
],
|
],
|
||||||
"parser": "@typescript-eslint/parser",
|
"parser": "@typescript-eslint/parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 8,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true,
|
"jsx": true,
|
||||||
"impliedStrict": true,
|
"impliedStrict": true,
|
||||||
"experimentalObjectRestSpread": true
|
"experimentalObjectRestSpread": true
|
||||||
},
|
},
|
||||||
"allowImportExportEverywhere": true,
|
"allowImportExportEverywhere": true
|
||||||
"project": [
|
|
||||||
"./tsconfig.eslint.json"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
|
|
@ -34,11 +28,7 @@
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
"version": "detect"
|
"version": "detect"
|
||||||
},
|
}
|
||||||
"import/resolver": {
|
|
||||||
"typescript": {}
|
|
||||||
},
|
|
||||||
"import/internal-regex": "^(electron|react)$"
|
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"func-names": [
|
"func-names": [
|
||||||
|
|
@ -62,21 +52,33 @@
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"bracketSameLine": false
|
"jsxBracketSameLine": false
|
||||||
}
|
|
||||||
],
|
|
||||||
"eslint-comments/no-unused-disable": "error",
|
|
||||||
"react/no-unknown-property":[
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"ignore": [
|
|
||||||
"jsx",
|
|
||||||
"global"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"app/config/config-default.js",
|
||||||
|
".hyper.js"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"semi": true,
|
||||||
|
"useTabs": false,
|
||||||
|
"parser": "babel",
|
||||||
|
"jsxBracketSameLine": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"**.ts",
|
"**.ts",
|
||||||
|
|
@ -84,77 +86,12 @@
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
"plugin:@typescript-eslint/recommended",
|
"plugin:@typescript-eslint/recommended",
|
||||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
"prettier/@typescript-eslint"
|
||||||
"prettier"
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
"@typescript-eslint/prefer-optional-chain": "error",
|
|
||||||
"@typescript-eslint/ban-types": "off",
|
|
||||||
"no-shadow": "off",
|
|
||||||
"@typescript-eslint/no-shadow": ["error"],
|
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [ "error", { "disallowTypeAnnotations": false } ],
|
|
||||||
"lodash/prop-shorthand": [ "error", "always" ],
|
|
||||||
"lodash/import-scope": [ "error", "method" ],
|
|
||||||
"lodash/collection-return": "error",
|
|
||||||
"lodash/collection-method-value": "error",
|
|
||||||
"import/no-extraneous-dependencies": "error",
|
|
||||||
"import/no-anonymous-default-export": "error",
|
|
||||||
"import/order": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"internal",
|
|
||||||
"external",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index"
|
|
||||||
],
|
|
||||||
"newlines-between": "always",
|
|
||||||
"alphabetize": {
|
|
||||||
"order": "asc",
|
|
||||||
"orderImportKind": "desc",
|
|
||||||
"caseInsensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"extends": [
|
|
||||||
"plugin:jsonc/recommended-with-json",
|
|
||||||
"plugin:json-schema-validator/recommended"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
"*.json"
|
|
||||||
],
|
|
||||||
"parser": "jsonc-eslint-parser",
|
|
||||||
"plugins": [
|
|
||||||
"jsonc",
|
|
||||||
"json-schema-validator"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"jsonc/array-element-newline": [
|
|
||||||
"error",
|
|
||||||
"consistent"
|
|
||||||
],
|
|
||||||
"jsonc/array-bracket-newline": [
|
|
||||||
"error",
|
|
||||||
"consistent"
|
|
||||||
],
|
|
||||||
"jsonc/indent": [
|
|
||||||
"error",
|
|
||||||
2
|
|
||||||
],
|
|
||||||
"prettier/prettier": "off",
|
|
||||||
"json-schema-validator/no-invalid": "error"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea/feature for Hyper
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
137
.github/actions/build-linux-arm/action.yml
vendored
137
.github/actions/build-linux-arm/action.yml
vendored
|
|
@ -1,137 +0,0 @@
|
||||||
name: 'Build Linux ARM'
|
|
||||||
description: 'Cross-compiles Hyper app for ARMv7l and ARM64 using arm-runner'
|
|
||||||
inputs:
|
|
||||||
node-version:
|
|
||||||
description: 'Node.js version to use'
|
|
||||||
required: true
|
|
||||||
matrix-name:
|
|
||||||
description: 'Matrix name (arch)'
|
|
||||||
required: true
|
|
||||||
matrix-cpu:
|
|
||||||
description: 'CPU architecture'
|
|
||||||
required: true
|
|
||||||
matrix-image:
|
|
||||||
description: 'Base OS image for ARM emulation'
|
|
||||||
required: true
|
|
||||||
upload-artifact:
|
|
||||||
description: 'Whether to upload artifacts'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ inputs.node-version }}
|
|
||||||
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Fix node-gyp and Python
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python3 -m pip install packaging setuptools
|
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
|
||||||
shell: bash
|
|
||||||
id: yarn-cache-dir-path
|
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Delete old electron headers
|
|
||||||
# These can sometimes be present on a freshly-provisioned github runner, so remove them
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -rf ~/.electron-gyp || true
|
|
||||||
|
|
||||||
- name: Install dependencies and tools
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
yarn install
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install libarchive-tools
|
|
||||||
|
|
||||||
- name: Rebuild node-pty and all native modules for ARM
|
|
||||||
uses: pguyot/arm-runner-action@v2.6.5
|
|
||||||
with:
|
|
||||||
image_additional_mb: 2000
|
|
||||||
base_image: ${{ inputs.matrix-image }}
|
|
||||||
cpu: ${{ inputs.matrix-cpu }}
|
|
||||||
shell: bash
|
|
||||||
copy_artifact_path: target
|
|
||||||
copy_artifact_dest: target
|
|
||||||
commands: |
|
|
||||||
# Install Python distutils in the ARM container
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y python3-pip python3-setuptools python3-dev
|
|
||||||
pip3 install setuptools --break-system-packages || pip3 install setuptools
|
|
||||||
|
|
||||||
cd target
|
|
||||||
# TODO upgrade node to 20.11.0 to match NODE_VERSION
|
|
||||||
wget https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
tar -xJf node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
sudo cp node-v18.16.0-linux-${{ inputs.matrix-name }}/* /usr/local/ -R
|
|
||||||
rm node-v18.16.0-linux-${{ inputs.matrix-name }}.tar.xz
|
|
||||||
|
|
||||||
cd ..
|
|
||||||
npm run rebuild-node-pty
|
|
||||||
|
|
||||||
cd target
|
|
||||||
npx electron-rebuild
|
|
||||||
|
|
||||||
- name: Compile
|
|
||||||
shell: bash
|
|
||||||
run: yarn run build
|
|
||||||
|
|
||||||
- name: Chown rebuilt node_modules
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo chown -R $USER:$USER target/node_modules
|
|
||||||
|
|
||||||
- name: Prepare v8 snapshot (only armv7l)
|
|
||||||
if: ${{ inputs.matrix-name == 'armv7l' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo dpkg --add-architecture i386
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y libglib2.0-0:i386 libexpat1:i386 libgcc-s1:i386
|
|
||||||
npm_config_arch=armv7l yarn run v8-snapshot:arch
|
|
||||||
|
|
||||||
- name: Build final Electron App for ARM
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
yarn run electron-builder -p never -l deb rpm AppImage pacman --${{ inputs.matrix-name }} -c electron-builder-linux-ci.json
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ env.GH_TOKEN }}
|
|
||||||
|
|
||||||
- name: Archive Build Artifacts
|
|
||||||
if: ${{ inputs.upload-artifact == 'true' }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: hyper-linux-${{ inputs.matrix-name }}
|
|
||||||
path: |
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.rpm
|
|
||||||
dist/*.pacman
|
|
||||||
|
|
||||||
# - name: Run E2E Tests on Linux
|
|
||||||
# if: runner.os == 'Linux'
|
|
||||||
# uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
# with:
|
|
||||||
# run: |
|
|
||||||
# yarn run test
|
|
||||||
#
|
|
||||||
172
.github/actions/build/action.yml
vendored
172
.github/actions/build/action.yml
vendored
|
|
@ -1,172 +0,0 @@
|
||||||
name: 'Build'
|
|
||||||
description: 'Builds, tests, and packages the app for release'
|
|
||||||
inputs:
|
|
||||||
node-version:
|
|
||||||
description: 'Node.js version to use'
|
|
||||||
required: true
|
|
||||||
matrix-name:
|
|
||||||
description: 'Matrix Name'
|
|
||||||
required: true
|
|
||||||
matrix-os:
|
|
||||||
description: 'Matrix OS'
|
|
||||||
required: true
|
|
||||||
upload-artifact:
|
|
||||||
description: 'Whether to upload artifacts'
|
|
||||||
required: false
|
|
||||||
default: 'true'
|
|
||||||
runs:
|
|
||||||
using: 'composite'
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
|
|
||||||
- name: Set up Python 3.10
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Fix node-gyp and Python
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [[ "$RUNNER_OS" == "macOS" ]]; then
|
|
||||||
brew install python-setuptools python-packaging
|
|
||||||
else
|
|
||||||
python3 -m pip install $EXTRA_ARGS packaging setuptools
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Get yarn cache directory path
|
|
||||||
shell: bash
|
|
||||||
id: yarn-cache-dir-path
|
|
||||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-yarn-
|
|
||||||
|
|
||||||
- name: Delete old electron headers
|
|
||||||
# These can sometimes be present on a freshly-provisioned github runner, so remove them
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
rm -rf ~/.electron-gyp || true
|
|
||||||
|
|
||||||
- name: Install
|
|
||||||
shell: bash
|
|
||||||
run: yarn install
|
|
||||||
env:
|
|
||||||
npm_config_node_gyp: ${{ github.workspace }}${{ runner.os == 'Windows' && '\node_modules\node-gyp\bin\node-gyp.js' || '/node_modules/node-gyp/bin/node-gyp.js' }}
|
|
||||||
|
|
||||||
- name: Install libarchive-tools
|
|
||||||
shell: bash
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install libarchive-tools
|
|
||||||
|
|
||||||
- name: Rebuild native modules for Electron
|
|
||||||
shell: bash
|
|
||||||
run: npx electron-rebuild
|
|
||||||
|
|
||||||
- name: Lint and Run Unit Tests
|
|
||||||
shell: bash
|
|
||||||
run: yarn run test
|
|
||||||
|
|
||||||
- name: Getting Build Icon
|
|
||||||
shell: bash
|
|
||||||
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
|
||||||
run: |
|
|
||||||
cp build/canary.ico build/icon.ico
|
|
||||||
cp build/canary.icns build/icon.icns
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ -z "$CSC_LINK" ] ; then unset CSC_LINK ; fi
|
|
||||||
if [ -z "$CSC_KEY_PASSWORD" ] ; then unset CSC_KEY_PASSWORD ; fi
|
|
||||||
if [ -z "$WIN_CSC_LINK" ] ; then unset WIN_CSC_LINK ; fi
|
|
||||||
if [ -z "$WIN_CSC_KEY_PASSWORD" ] ; then unset WIN_CSC_KEY_PASSWORD ; fi
|
|
||||||
if [ -z "$APPLE_ID" ] ; then unset APPLE_ID ; fi
|
|
||||||
if [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ] ; then unset APPLE_APP_SPECIFIC_PASSWORD ; fi
|
|
||||||
|
|
||||||
yarn run dist
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ env.GH_TOKEN }}
|
|
||||||
CSC_LINK: ${{ env.MAC_CERT_P12_BASE64 }}
|
|
||||||
CSC_KEY_PASSWORD: ${{ env.MAC_CERT_P12_PASSWORD }}
|
|
||||||
WIN_CSC_LINK: ${{ env.WIN_CERT_P12_BASE64 }}
|
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ env.WIN_CERT_P12_PASSWORD }}
|
|
||||||
APPLE_ID: ${{ env.APPLE_ID }}
|
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ env.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Archive Build Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: hyper-${{ runner.os }}-${{ inputs.matrix-name }}
|
|
||||||
path: |
|
|
||||||
dist/*.dmg
|
|
||||||
dist/*.snap
|
|
||||||
dist/*.AppImage
|
|
||||||
dist/*.deb
|
|
||||||
dist/*.pacman
|
|
||||||
dist/*.exe
|
|
||||||
|
|
||||||
- name: Run E2E Tests
|
|
||||||
shell: bash
|
|
||||||
if: runner.os != 'Linux'
|
|
||||||
run: yarn run test:e2e --verbose
|
|
||||||
|
|
||||||
- name: Run E2E Tests on Linux
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
with:
|
|
||||||
run: |
|
|
||||||
yarn run test:e2e
|
|
||||||
env:
|
|
||||||
SHELL: /bin/bash
|
|
||||||
DEBUG: "pw:browser*"
|
|
||||||
|
|
||||||
- name: Archive E2E test screenshot
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: e2e-${{ inputs.matrix-os }}-${{ strategy.job-index }}
|
|
||||||
path: dist/tmp/*.png
|
|
||||||
|
|
||||||
- name: Save the pr number in an artifact
|
|
||||||
shell: bash
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
env:
|
|
||||||
PR_NUM: ${{ github.event.number }}
|
|
||||||
run: echo $PR_NUM > pr_num.txt
|
|
||||||
|
|
||||||
- name: Upload the pr num
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
with:
|
|
||||||
name: pr_num
|
|
||||||
path: ./pr_num.txt
|
|
||||||
|
|
||||||
- uses: actions/cache/save@v4
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock', 'app/yarn.lock') }}
|
|
||||||
|
|
||||||
# - name: Run E2E Tests (non-Linux)
|
|
||||||
# if: runner.os != 'Linux'
|
|
||||||
# shell: bash
|
|
||||||
# run: yarn run test:e2e --verbose
|
|
||||||
#
|
|
||||||
# - name: Run E2E Tests on Linux
|
|
||||||
# if: runner.os == 'Linux'
|
|
||||||
# uses: GabrielBB/xvfb-action@v1.7
|
|
||||||
# with:
|
|
||||||
# run: |
|
|
||||||
# yarn run test
|
|
||||||
#
|
|
||||||
39
.github/dependabot.yml
vendored
39
.github/dependabot.yml
vendored
|
|
@ -1,39 +0,0 @@
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: '11:00'
|
|
||||||
target-branch: canary
|
|
||||||
versioning-strategy: increase
|
|
||||||
commit-message:
|
|
||||||
prefix: "chore(deps-dev):"
|
|
||||||
groups:
|
|
||||||
minorAndPatch:
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
open-pull-requests-limit: 100
|
|
||||||
- package-ecosystem: npm
|
|
||||||
directory: "/app"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: '11:00'
|
|
||||||
target-branch: canary
|
|
||||||
versioning-strategy: increase
|
|
||||||
commit-message:
|
|
||||||
prefix: "chore(deps):"
|
|
||||||
groups:
|
|
||||||
minorAndPatch:
|
|
||||||
update-types:
|
|
||||||
- "minor"
|
|
||||||
- "patch"
|
|
||||||
open-pull-requests-limit: 100
|
|
||||||
- package-ecosystem: github-actions
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: '11:00'
|
|
||||||
open-pull-requests-limit: 30
|
|
||||||
target-branch: canary
|
|
||||||
|
|
@ -1,12 +1,3 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help Hyper improve
|
|
||||||
title: ''
|
|
||||||
labels: ''
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Hi there! Thank you for discovering and submitting an issue.
|
Hi there! Thank you for discovering and submitting an issue.
|
||||||
|
|
||||||
|
|
@ -16,8 +7,8 @@ assignees: ''
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Checked checkbox should look like this: [x] -->
|
<!-- Checked checkbox should look like this: [x] -->
|
||||||
- [ ] I am on the [latest](https://github.com/vercel/hyper/releases/latest) Hyper.app version
|
- [ ] I am on the [latest](https://github.com/zeit/hyper/releases/latest) Hyper.app version
|
||||||
- [ ] I have searched the [issues](https://github.com/vercel/hyper/issues) of this repo and believe that this is not a duplicate
|
- [ ] I have searched the [issues](https://github.com/zeit/hyper/issues) of this repo and believe that this is not a duplicate
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Once those are done, if you're able to fill in the following list with your information,
|
Once those are done, if you're able to fill in the following list with your information,
|
||||||
|
|
@ -26,7 +17,7 @@ assignees: ''
|
||||||
|
|
||||||
- **OS version and name**: <!-- Replace with version + name -->
|
- **OS version and name**: <!-- Replace with version + name -->
|
||||||
- **Hyper.app version**: <!-- Replace with version -->
|
- **Hyper.app version**: <!-- Replace with version -->
|
||||||
- **Link of a [Gist](https://gist.github.com/) with the contents of your hyper.json**: <!-- Gist Link Here -->
|
- **Link of a [Gist](https://gist.github.com/) with the contents of your .hyper.js**: <!-- Gist Link Here -->
|
||||||
- **Relevant information from devtools** _(CMD+ALT+I on macOS, CTRL+SHIFT+I elsewhere)_: <!-- Replace with info if applicable, or N/A -->
|
- **Relevant information from devtools** _(CMD+ALT+I on macOS, CTRL+SHIFT+I elsewhere)_: <!-- Replace with info if applicable, or N/A -->
|
||||||
- **The issue is reproducible in vanilla Hyper.app**: <!-- Replace with info if applicable, or `Is Vanilla`. (Vanilla means Hyper.app without any add-ons or extras. Straight out of the box.) -->
|
- **The issue is reproducible in vanilla Hyper.app**: <!-- Replace with info if applicable, or `Is Vanilla`. (Vanilla means Hyper.app without any add-ons or extras. Straight out of the box.) -->
|
||||||
|
|
||||||
9
.github/pull_request_template.md
vendored
9
.github/pull_request_template.md
vendored
|
|
@ -1 +1,8 @@
|
||||||
<!-- Please check `Allow edits from maintainers`. Thanks! -->
|
<!-- Hi there! Thanks for submitting a PR! We're excited to see what you've got for us.
|
||||||
|
|
||||||
|
- To help whoever reviews your PR, it'd be extremely helpful for you to list whether your PR is ready to be merged,
|
||||||
|
If there's anything left to do and if there are any related PRs
|
||||||
|
- It'd also be extremely helpful to enable us to update your PR incase we need to rebase or what-not by checking `Allow edits from maintainers`
|
||||||
|
- If your PR changes some API, please make a PR for hyper website too: https://github.com/zeit/hyper-site.
|
||||||
|
|
||||||
|
Thanks, again! -->
|
||||||
|
|
|
||||||
154
.github/workflows/ci.yml
vendored
154
.github/workflows/ci.yml
vendored
|
|
@ -1,154 +0,0 @@
|
||||||
name: Node CI
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
workflow_dispatch:
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ci-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 20.11.0
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
# CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
|
|
||||||
# CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
|
|
||||||
# WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
|
|
||||||
# WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
|
|
||||||
# APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
# APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-ubuntu:
|
|
||||||
name: Build Ubuntu
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-ubuntu-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: ubuntu-latest
|
|
||||||
matrix-name: ubuntu
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-macos:
|
|
||||||
name: Build macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-macos-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: macos-latest
|
|
||||||
matrix-name: macos
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-win-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: windows-latest
|
|
||||||
matrix-name: win
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
# ARM Linux:
|
|
||||||
|
|
||||||
build-linux-armv7l:
|
|
||||||
name: Build Linux ARMv7l
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-linux-armv7l-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU and binfmt
|
|
||||||
run: |
|
|
||||||
# Install qemu-user-static
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
|
|
||||||
|
|
||||||
# Install distutils for node-gyp
|
|
||||||
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
|
|
||||||
|
|
||||||
|
|
||||||
# Download and install binfmt configurations
|
|
||||||
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
|
|
||||||
chmod +x qemu-binfmt-conf.sh
|
|
||||||
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
|
|
||||||
|
|
||||||
# Import the binfmt configurations
|
|
||||||
sudo update-binfmts --import qemu-arm
|
|
||||||
sudo update-binfmts --import qemu-aarch64
|
|
||||||
|
|
||||||
# Verify they exist
|
|
||||||
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
|
|
||||||
|
|
||||||
- name: Build ARMv7l
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: armv7l
|
|
||||||
matrix-cpu: cortex-a8
|
|
||||||
matrix-image: raspios_lite:latest
|
|
||||||
upload-artifact: false
|
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
name: Build Linux ARM64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: build-linux-arm64-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build ARM64
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: arm64
|
|
||||||
matrix-cpu: cortex-a53
|
|
||||||
matrix-image: raspios_lite_arm64:latest
|
|
||||||
upload-artifact: false
|
|
||||||
67
.github/workflows/codeql-analysis.yml
vendored
67
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -1,67 +0,0 @@
|
||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ canary ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ canary ]
|
|
||||||
schedule:
|
|
||||||
- cron: '37 6 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
|
||||||
# Learn more:
|
|
||||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v3
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
63
.github/workflows/e2e_comment.yml
vendored
63
.github/workflows/e2e_comment.yml
vendored
|
|
@ -1,63 +0,0 @@
|
||||||
name: Comment e2e test screenshots on PR
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: ['Node CI']
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
jobs:
|
|
||||||
e2e_comment:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event.workflow_run.event == 'pull_request'
|
|
||||||
steps:
|
|
||||||
- name: Dump Workflow run info from GitHub context
|
|
||||||
env:
|
|
||||||
WORKFLOW_RUN_INFO: ${{ toJSON(github.event.workflow_run) }}
|
|
||||||
run: echo "$WORKFLOW_RUN_INFO"
|
|
||||||
- name: Download Artifacts
|
|
||||||
uses: dawidd6/action-download-artifact@v6
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
workflow: nodejs.yml
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: e2e
|
|
||||||
- name: Get PR number
|
|
||||||
uses: dawidd6/action-download-artifact@v6
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
workflow: nodejs.yml
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
name: pr_num
|
|
||||||
- name: Read the pr_num file
|
|
||||||
id: pr_num_reader
|
|
||||||
uses: juliangruber/read-file-action@v1.1.7
|
|
||||||
with:
|
|
||||||
path: ./pr_num.txt
|
|
||||||
- name: List images
|
|
||||||
run: ls -al
|
|
||||||
- name: Upload images to imgur
|
|
||||||
id: upload_screenshots
|
|
||||||
uses: devicons/public-upload-to-imgur@v2.2.2
|
|
||||||
with:
|
|
||||||
path: ./*.png
|
|
||||||
client_id: ${{ secrets.IMGUR_CLIENT_ID }}
|
|
||||||
- name: Comment on the PR
|
|
||||||
uses: jungwinter/comment@v1
|
|
||||||
env:
|
|
||||||
IMG_MARKDOWN: ${{ join(fromJSON(steps.upload_screenshots.outputs.markdown_urls), '') }}
|
|
||||||
MESSAGE: |
|
|
||||||
Hi there,
|
|
||||||
Thank you for contributing to Hyper!
|
|
||||||
You can get the build artifacts from [here](https://nightly.link/{1}/actions/runs/{2}).
|
|
||||||
Here are screenshots of Hyper built from this pr.
|
|
||||||
{0}
|
|
||||||
with:
|
|
||||||
type: create
|
|
||||||
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
body: ${{ format(env.MESSAGE, env.IMG_MARKDOWN, github.repository, github.event.workflow_run.id) }}
|
|
||||||
- name: Hide older comments
|
|
||||||
uses: kanga333/comment-hider@v0.4.0
|
|
||||||
with:
|
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
leave_visible: 1
|
|
||||||
issue_number: ${{ steps.pr_num_reader.outputs.content }}
|
|
||||||
138
.github/workflows/nodejs.yml
vendored
Normal file
138
.github/workflows/nodejs.yml
vendored
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
name: Node CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- canary
|
||||||
|
pull_request:
|
||||||
|
jobs:
|
||||||
|
ci_macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Install
|
||||||
|
run: yarn install
|
||||||
|
- name: Test
|
||||||
|
run: yarn run test
|
||||||
|
- name: Getting Build Icon
|
||||||
|
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
||||||
|
run: |
|
||||||
|
cp build/canary.ico build/icon.ico
|
||||||
|
cp build/canary.icns build/icon.icns
|
||||||
|
- name: Build
|
||||||
|
run: yarn run dist --publish=never
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
- name: Get macOS Artifact Names
|
||||||
|
id: getmacosfilename
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=dmgName::$(ls dist/*.dmg | cut -d'/' -f2)"
|
||||||
|
echo "::set-output name=dmgPath::$(ls dist/*.dmg)"
|
||||||
|
- name: Archive macOS Build Artifacts
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getmacosfilename.outputs.dmgName }}
|
||||||
|
path: ${{ steps.getmacosfilename.outputs.dmgPath }}
|
||||||
|
ci_linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Install
|
||||||
|
run: yarn install
|
||||||
|
- name: Test
|
||||||
|
run: yarn run test
|
||||||
|
- name: Getting Build Icon
|
||||||
|
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
||||||
|
run: |
|
||||||
|
cp build/canary.ico build/icon.ico
|
||||||
|
cp build/canary.icns build/icon.icns
|
||||||
|
- name: Build
|
||||||
|
run: yarn run dist --publish=never
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
- name: Get Linux Artifact Names
|
||||||
|
id: getlinuxfilename
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=snapName::$(ls dist/*.snap | cut -d'/' -f2)"
|
||||||
|
echo "::set-output name=snapPath::$(ls dist/*.snap)"
|
||||||
|
echo "::set-output name=AppImageName::$(ls dist/*.AppImage | cut -d'/' -f2)"
|
||||||
|
echo "::set-output name=AppImagePath::$(ls dist/*.AppImage)"
|
||||||
|
echo "::set-output name=debName::$(ls dist/*.deb | cut -d'/' -f2)"
|
||||||
|
echo "::set-output name=debPath::$(ls dist/*.deb)"
|
||||||
|
echo "::set-output name=rpmName::$(ls dist/*.rpm | cut -d'/' -f2)"
|
||||||
|
echo "::set-output name=rpmPath::$(ls dist/*.rpm)"
|
||||||
|
- name: Archive Linux Build Artifacts (Snap)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getlinuxfilename.outputs.snapName }}
|
||||||
|
path: ${{ steps.getlinuxfilename.outputs.snapPath }}
|
||||||
|
- name: Archive Linux Build Artifacts (AppImage)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getlinuxfilename.outputs.AppImageName }}
|
||||||
|
path: ${{ steps.getlinuxfilename.outputs.AppImagePath }}
|
||||||
|
- name: Archive Linux Build Artifacts (Deb)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getlinuxfilename.outputs.debName }}
|
||||||
|
path: ${{ steps.getlinuxfilename.outputs.debPath }}
|
||||||
|
- name: Archive Linux Build Artifacts (RPM)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getlinuxfilename.outputs.rpmName }}
|
||||||
|
path: ${{ steps.getlinuxfilename.outputs.rpmPath }}
|
||||||
|
ci_windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [12.x]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- name: Install
|
||||||
|
run: yarn install
|
||||||
|
- name: Test
|
||||||
|
run: yarn run test
|
||||||
|
- name: Getting Build Icon
|
||||||
|
if: github.ref == 'refs/heads/canary' || github.base_ref == 'canary'
|
||||||
|
run: |
|
||||||
|
Copy-Item .\build\canary.ico .\build\icon.ico
|
||||||
|
Copy-Item .\build\canary.icns .\build\icon.icns
|
||||||
|
- name: Build
|
||||||
|
run: yarn run dist --publish=never
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
- name: Get Windows Artifact Names
|
||||||
|
id: getwindowsfilename
|
||||||
|
run: |
|
||||||
|
Write-Host "::set-output name=exeName::$(Get-ChildItem -Name .\dist\squirrel-windows\ | Select-String exe)"
|
||||||
|
Write-Host "::set-output name=exePath::dist/squirrel-windows/$(Get-ChildItem -Name .\dist\squirrel-windows\ | Select-String exe)"
|
||||||
|
Write-Host "::set-output name=nupkgName::$(Get-ChildItem -Name .\dist\squirrel-windows\ | Select-String nupkg)"
|
||||||
|
Write-Host "::set-output name=nupkgPath::dist/squirrel-windows/$(Get-ChildItem -Name .\dist\squirrel-windows\ | Select-String nupkg)"
|
||||||
|
- name: Archive Windows Build Artifacts (exe)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getwindowsfilename.outputs.exeName }}
|
||||||
|
path: ${{ steps.getwindowsfilename.outputs.exePath }}
|
||||||
|
- name: Archive Windows Build Artifacts (nupkg)
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: ${{ steps.getwindowsfilename.outputs.nupkgName }}
|
||||||
|
path: ${{ steps.getwindowsfilename.outputs.nupkgPath }}
|
||||||
194
.github/workflows/release.yml
vendored
194
.github/workflows/release.yml
vendored
|
|
@ -1,194 +0,0 @@
|
||||||
name: Create Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: release-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 20.11.0
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CSC_LINK: ${{ secrets.MAC_CERT_P12_BASE64 }}
|
|
||||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_P12_PASSWORD }}
|
|
||||||
WIN_CSC_LINK: ${{ secrets.WIN_CERT_P12_BASE64 }}
|
|
||||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CERT_P12_PASSWORD }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-ubuntu:
|
|
||||||
name: Build Ubuntu
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: ubuntu-latest
|
|
||||||
matrix-name: ubuntu
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-macos:
|
|
||||||
name: Build macOS
|
|
||||||
runs-on: macos-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: macos-latest
|
|
||||||
matrix-name: macos
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-windows:
|
|
||||||
name: Build Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/build
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-os: windows-latest
|
|
||||||
matrix-name: win
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
# ARM Linux:
|
|
||||||
|
|
||||||
build-linux-armv7l:
|
|
||||||
name: Build Linux ARMv7l
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU and binfmt
|
|
||||||
run: |
|
|
||||||
# Install qemu-user-static
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y qemu-user-static binfmt-support python3-setuptools
|
|
||||||
|
|
||||||
# Install distutils for node-gyp
|
|
||||||
sudo apt-get install -y python3-distutils || sudo apt-get install -y python3-dev python-is-python3
|
|
||||||
|
|
||||||
# Download and install binfmt configurations
|
|
||||||
wget https://github.com/qemu/qemu/raw/master/scripts/qemu-binfmt-conf.sh
|
|
||||||
chmod +x qemu-binfmt-conf.sh
|
|
||||||
sudo ./qemu-binfmt-conf.sh --qemu-path /usr/bin --qemu-suffix -static --debian
|
|
||||||
|
|
||||||
# Import the binfmt configurations
|
|
||||||
sudo update-binfmts --import qemu-arm
|
|
||||||
sudo update-binfmts --import qemu-aarch64
|
|
||||||
|
|
||||||
# Verify they exist
|
|
||||||
ls -la /proc/sys/fs/binfmt_misc/qemu-arm* || true
|
|
||||||
|
|
||||||
- name: Build ARMv7l
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: armv7l
|
|
||||||
matrix-cpu: cortex-a8
|
|
||||||
matrix-image: raspios_lite:latest
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
build-linux-arm64:
|
|
||||||
name: Build Linux ARM64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build ARM64
|
|
||||||
uses: ./.github/actions/build-linux-arm
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
matrix-name: arm64
|
|
||||||
matrix-cpu: cortex-a53
|
|
||||||
matrix-image: raspios_lite_arm64:latest
|
|
||||||
upload-artifact: true
|
|
||||||
|
|
||||||
###
|
|
||||||
|
|
||||||
upload-release:
|
|
||||||
name: Upload and Create Release
|
|
||||||
needs:
|
|
||||||
- build-ubuntu
|
|
||||||
- build-macos
|
|
||||||
- build-windows
|
|
||||||
- build-linux-armv7l
|
|
||||||
- build-linux-arm64
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
path: ./artifacts
|
|
||||||
|
|
||||||
- name: List downloaded artifacts
|
|
||||||
run: ls -R ./artifacts
|
|
||||||
|
|
||||||
- name: Ensure non-empty artifacts
|
|
||||||
run: |
|
|
||||||
echo "Checking for zero-byte files in artifacts/..."
|
|
||||||
find artifacts/ -type f -empty -print -delete
|
|
||||||
echo "Cleaned up any zero-byte files."
|
|
||||||
echo "Remaining files:"
|
|
||||||
ls -R artifacts/
|
|
||||||
|
|
||||||
- name: Dump release assets for debugging
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GitHub CLI needs this
|
|
||||||
run: |
|
|
||||||
echo "Fetching release assets for tag: ${{ github.ref_name }}"
|
|
||||||
gh release view "${{ github.ref_name }}" --repo "${{ github.repository }}" --json assets --jq '.assets[] | "\(.name) - \(.url)"' || true
|
|
||||||
|
|
||||||
- name: Create GitHub Release
|
|
||||||
# Pinned to work around https://github.com/softprops/action-gh-release/issues/445
|
|
||||||
uses: softprops/action-gh-release@v2.0.5
|
|
||||||
env:
|
|
||||||
NODE_VERSION: ${{ env.NODE_VERSION }}
|
|
||||||
ACTIONS_STEP_DEBUG: true
|
|
||||||
with:
|
|
||||||
preserve_order: true
|
|
||||||
overwrite: true
|
|
||||||
name: Release ${{ github.ref_name }}
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
draft: true
|
|
||||||
files: |
|
|
||||||
artifacts/**/*.dmg
|
|
||||||
artifacts/**/*.snap
|
|
||||||
artifacts/**/*.AppImage
|
|
||||||
artifacts/**/*.deb
|
|
||||||
artifacts/**/*.pacman
|
|
||||||
artifacts/**/*.exe
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -3,7 +3,6 @@ dist
|
||||||
app/renderer
|
app/renderer
|
||||||
target
|
target
|
||||||
bin/cli.*
|
bin/cli.*
|
||||||
cache
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
|
@ -13,11 +12,8 @@ npm-debug.log
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
# optional dev config file and plugins directory
|
# optional dev config file and plugins directory
|
||||||
hyper.json
|
.hyper.js
|
||||||
schema.json
|
.hyper_plugins
|
||||||
plugins
|
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/*
|
.vscode/settings.json
|
||||||
!.vscode/launch.json
|
|
||||||
.idea
|
|
||||||
|
|
|
||||||
1
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
||||||
_
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
yarn test
|
|
||||||
6
.huskyrc.json
Normal file
6
.huskyrc.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/huskyrc",
|
||||||
|
"hooks": {
|
||||||
|
"pre-push": "yarn test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
20.11.0
|
|
||||||
1
.nvmrc
1
.nvmrc
|
|
@ -1 +0,0 @@
|
||||||
20.11.0
|
|
||||||
41
.travis.yml
Normal file
41
.travis.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
sudo: required
|
||||||
|
dist: xenial
|
||||||
|
|
||||||
|
language: node_js
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: linux
|
||||||
|
node_js: 12
|
||||||
|
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||||
|
compiler: clang
|
||||||
|
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- gcc-multilib
|
||||||
|
- g++-multilib
|
||||||
|
- libgnome-keyring-dev
|
||||||
|
- icnsutils
|
||||||
|
- graphicsmagick
|
||||||
|
- xz-utils
|
||||||
|
- rpm
|
||||||
|
- bsdtar
|
||||||
|
- snapd
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo snap install snapcraft --classic; fi
|
||||||
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start; sleep 3; fi
|
||||||
|
|
||||||
|
cache: yarn
|
||||||
|
|
||||||
|
install:
|
||||||
|
- yarn
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- (git branch --contains $TRAVIS_COMMIT | grep canary > /dev/null || [[ "$TRAVIS_BRANCH" == "canary" ]] ) && (cd build; cp canary.icns icon.icns; cp canary.ico icon.ico)
|
||||||
|
- yarn run dist
|
||||||
|
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- "/^v\\d+\\.\\d+\\.\\d+$/"
|
||||||
148049
.yarn/releases/yarn-classic.cjs
vendored
148049
.yarn/releases/yarn-classic.cjs
vendored
File diff suppressed because one or more lines are too long
8
.yarnrc
8
.yarnrc
|
|
@ -1 +1,7 @@
|
||||||
registry "https://registry.npmjs.org/"
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
child-concurrency "1"
|
||||||
|
lastUpdateCheck 1570388773781
|
||||||
|
save-exact true
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
yarnPath: .yarn/releases/yarn-classic.cjs
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
# MIT License
|
# MIT License
|
||||||
|
|
||||||
Copyright (c) 2018 Vercel, Inc.
|
Copyright (c) 2018 ZEIT, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
||||||
12
PLUGINS.md
12
PLUGINS.md
|
|
@ -3,19 +3,19 @@
|
||||||
## Workflow
|
## Workflow
|
||||||
|
|
||||||
### Run Hyper in dev mode
|
### Run Hyper in dev mode
|
||||||
Hyper can be run in dev mode by cloning this repository and following the ["Contributing" section of our README](https://github.com/vercel/hyper#contribute).
|
Hyper can be run in dev mode by cloning this repository and following the ["Contributing" section of our README](https://github.com/zeit/hyper#contribute).
|
||||||
|
|
||||||
In dev mode you'll get more ouput and access to React/Redux dev-tools in Electron.
|
In dev mode you'll get more ouput and access to React/Redux dev-tools in Electron.
|
||||||
|
|
||||||
Prerequisites and steps are described in the ["Contributing" section of our README](https://github.com/vercel/hyper#contribute).
|
Prerequisites and steps are described in the ["Contributing" section of our README](https://github.com/zeit/hyper#contribute).
|
||||||
Be sure to use the `canary` branch.
|
Be sure to use the `canary` branch.
|
||||||
|
|
||||||
### Create a dev config file
|
### Create a dev config file
|
||||||
Copy your config file `hyper.json` to the root of your cloned repository. Hyper, in dev mode, will use this copied config file. That means that you can continue to use your main installation of Hyper with your day-to-day configuration.
|
Copy your config file `.hyper.js` to the root of your cloned repository. Hyper, in dev mode, will use this copied config file. That means that you can continue to use your main installation of Hyper with your day-to-day configuration.
|
||||||
After the first run, Hyper, in dev mode, will have created a new `plugins` directory in your repository directory.
|
After the first run, Hyper, in dev mode, will have created a new `.hyper_plugins` directory in your repository directory.
|
||||||
|
|
||||||
### Setup your plugin
|
### Setup your plugin
|
||||||
Go to your recently created `<repository_root>/plugins/local` directory and create/clone your plugin repo. An even better method on macOS/Linux is to add a symlink to your plugin directory.
|
Go to your recently created `<repository_root>/.hyper_plugins/local` directory and create/clone your plugin repo. An even better method on macOS/Linux is to add a symlink to your plugin directory.
|
||||||
|
|
||||||
Edit your dev config file, and add your plugin name (directory name in your `local` directory) in the `localPlugins` array.
|
Edit your dev config file, and add your plugin name (directory name in your `local` directory) in the `localPlugins` array.
|
||||||
```js
|
```js
|
||||||
|
|
@ -30,7 +30,7 @@ module.exports = {
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running your plugin
|
### Running your plugin
|
||||||
To load, your plugin should expose at least one API method. All possible methods are listed [here](https://github.com/vercel/hyper/blob/canary/app/plugins/extensions.ts).
|
To load, your plugin should expose at least one API method. All possible methods are listed [here](https://github.com/zeit/hyper/blob/canary/app/plugins/extensions.js).
|
||||||
|
|
||||||
After launching Hyper in dev mode, run `yarn run app`, it should log that your plugin has been correcty loaded: `Plugin hyper-awesome-plugin (0.1.0) loaded.`. Name and version printed are the ones in your plugins `package.json` file.
|
After launching Hyper in dev mode, run `yarn run app`, it should log that your plugin has been correcty loaded: `Plugin hyper-awesome-plugin (0.1.0) loaded.`. Name and version printed are the ones in your plugins `package.json` file.
|
||||||
|
|
||||||
|
|
|
||||||
46
README.md
46
README.md
|
|
@ -1,44 +1,23 @@
|
||||||
<p align="center">
|

|
||||||
<img alt="hyper - modern web-based terminal" height=150 src="https://github.com/user-attachments/assets/3096f20a-8116-45ce-8c5e-0f1106107484">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a aria-label="Vercel logo" href="https://vercel.com"><img
|
|
||||||
src="https://img.shields.io/badge/MADE%20BY%20Vercel-000000.svg?style=for-the-badge&logo=vercel&labelColor=000000&logoWidth=20"
|
|
||||||
/></a> <a aria-label="Quine logo" href="https://quineglobal.com"><img
|
|
||||||
width="143" height="28" alt="forked-by-quine" src="https://github.com/user-attachments/assets/57decaa2-7d8c-4d13-ada7-ff6b964346f7"
|
|
||||||
/></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/quine-global/hyper/actions/workflows/ci.yml)
|
|
||||||
|
|
||||||
|
[](https://circleci.com/gh/zeit/hyper)
|
||||||
|
[](https://ci.appveyor.com/project/zeit/hyper)
|
||||||
|
[](https://travis-ci.org/zeit/hyper)
|
||||||
[](https://changelog.com/213)
|
[](https://changelog.com/213)
|
||||||
|
[](https://spectrum.chat/zeit/hyper)
|
||||||
|
|
||||||
For more details, head to: https://hyper.is
|
For more details, head to: https://hyper.is
|
||||||
|
|
||||||
## Project goals
|
|
||||||
|
|
||||||
The goal of the project is to create a beautiful and customizable experience for command-line interface users, built on open web standards. We have picked up where Vercel left off, and intend to first offer stability, followed by a more tailored experience. We will still support customizability, but stability of theming APIs is not a goal.
|
|
||||||
|
|
||||||
In the future, we anticipate the community will come up with innovative additions to enhance what could be the simplest, most powerful and well-tested interface for productivity.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
[Download the latest release!](https://hyper.is/#installation)
|
[Download the latest release!](https://hyper.is/#installation)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
#### Arch and derivatives
|
#### Arch and derivatives
|
||||||
Hyper is available in the [AUR](https://aur.archlinux.org/packages/hyper/). Use an AUR [package manager](https://wiki.archlinux.org/index.php/AUR_helpers) e.g. [paru](https://github.com/Morganamilo/paru)
|
Hyper is available in the [AUR](https://aur.archlinux.org/packages/hyper/). Use an AUR package manager like [aurman](https://github.com/polygamma/aurman)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
paru -S hyper
|
aurman -S hyper
|
||||||
```
|
|
||||||
|
|
||||||
#### NixOS
|
|
||||||
Hyper is available as [Nix package](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/hyper/default.nix), to install the app run this command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
nix-env -i hyper
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
|
|
@ -47,7 +26,7 @@ Use [Homebrew Cask](https://brew.sh) to download the app by running these comman
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew update
|
brew update
|
||||||
brew install --cask hyper
|
brew cask install hyper
|
||||||
```
|
```
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
@ -104,7 +83,7 @@ make sure its build process is working correctly by running `yarn run rebuild-no
|
||||||
If you are on macOS, this typically is related to Xcode issues (like not having agreed
|
If you are on macOS, this typically is related to Xcode issues (like not having agreed
|
||||||
to the Terms of Service by running `sudo xcodebuild` after a fresh Xcode installation).
|
to the Terms of Service by running `sudo xcodebuild` after a fresh Xcode installation).
|
||||||
|
|
||||||
##### Error with `C++` on macOS when running `yarn`
|
##### Error with `c++` on macOS when running `yarn`
|
||||||
|
|
||||||
If you are getting compiler errors when running `yarn` add the environment variable `export CXX=clang++`
|
If you are getting compiler errors when running `yarn` add the environment variable `export CXX=clang++`
|
||||||
|
|
||||||
|
|
@ -115,7 +94,8 @@ If you have issues in the `codesign` step when running `yarn run dist` on macOS,
|
||||||
|
|
||||||
## Related Repositories
|
## Related Repositories
|
||||||
|
|
||||||
- [Website](https://github.com/vercel/hyper-site)
|
- [Art](https://github.com/zeit/art/tree/master/hyper)
|
||||||
- [Sample Extension](https://github.com/vercel/hyperpower)
|
- [Website](https://github.com/zeit/hyper-site)
|
||||||
- [Sample Theme](https://github.com/vercel/hyperyellow)
|
- [Sample Extension](https://github.com/zeit/hyperpower)
|
||||||
|
- [Sample Theme](https://github.com/zeit/hyperyellow)
|
||||||
- [Awesome Hyper](https://github.com/bnb/awesome-hyper)
|
- [Awesome Hyper](https://github.com/bnb/awesome-hyper)
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
registry "https://registry.npmjs.org/"
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
|
import fetch from 'electron-fetch';
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
|
||||||
import fetch from 'electron-fetch';
|
class AutoUpdater extends EventEmitter {
|
||||||
|
|
||||||
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|
||||||
updateURL!: string;
|
|
||||||
quitAndInstall() {
|
quitAndInstall() {
|
||||||
this.emitError('QuitAndInstall unimplemented');
|
this.emitError('QuitAndInstall unimplemented');
|
||||||
}
|
}
|
||||||
|
|
@ -11,8 +9,8 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||||
return this.updateURL;
|
return this.updateURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeedURL(options: Electron.FeedURLOptions) {
|
setFeedURL(updateURL) {
|
||||||
this.updateURL = options.url;
|
this.updateURL = updateURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkForUpdates() {
|
checkForUpdates() {
|
||||||
|
|
@ -22,31 +20,29 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||||
this.emit('checking-for-update');
|
this.emit('checking-for-update');
|
||||||
|
|
||||||
fetch(this.updateURL)
|
fetch(this.updateURL)
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
if (res.status === 204) {
|
if (res.status === 204) {
|
||||||
this.emit('update-not-available');
|
return this.emit('update-not-available');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
return res.json().then(({name, notes, pub_date}: {name: string; notes: string; pub_date: string}) => {
|
return res.json().then(({name, notes, pub_date}) => {
|
||||||
// Only name is mandatory, needed to construct release URL.
|
// Only name is mandatory, needed to construct release URL.
|
||||||
if (!name) {
|
if (!name) {
|
||||||
throw new Error('Malformed server response: release name is missing.');
|
throw new Error('Malformed server response: release name is missing.');
|
||||||
}
|
}
|
||||||
const date = pub_date ? new Date(pub_date) : new Date();
|
// If `null` is passed to Date constructor, current time will be used. This doesn't work with `undefined`
|
||||||
|
const date = new Date(pub_date || null);
|
||||||
this.emit('update-available', {}, notes, name, date);
|
this.emit('update-available', {}, notes, name, date);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(this.emitError.bind(this));
|
.catch(this.emitError.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
emitError(error: string | Error) {
|
emitError(error) {
|
||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
error = new Error(error);
|
error = new Error(error);
|
||||||
}
|
}
|
||||||
this.emit('error', error);
|
this.emit('error', error, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoUpdaterLinux = new AutoUpdater();
|
export default new AutoUpdater();
|
||||||
|
|
||||||
export default autoUpdaterLinux;
|
|
||||||
157
app/commands.ts
157
app/commands.ts
|
|
@ -1,51 +1,48 @@
|
||||||
import {app, Menu} from 'electron';
|
import {app, Menu, BrowserWindow} from 'electron';
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import {openConfig, getConfig} from './config';
|
import {openConfig, getConfig} from './config';
|
||||||
import {updatePlugins} from './plugins';
|
import {updatePlugins} from './plugins';
|
||||||
import {installCLI} from './utils/cli-install';
|
import {installCLI} from './utils/cli-install';
|
||||||
import * as systemContextMenu from './utils/system-context-menu';
|
|
||||||
|
|
||||||
const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = {
|
const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = {
|
||||||
'window:new': () => {
|
'window:new': () => {
|
||||||
// If window is created on the same tick, it will consume event too
|
// If window is created on the same tick, it will consume event too
|
||||||
setTimeout(app.createWindow, 0);
|
setTimeout(app.createWindow, 0);
|
||||||
},
|
},
|
||||||
'tab:new': (focusedWindow) => {
|
'tab:new': focusedWindow => {
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
focusedWindow.rpc.emit('termgroup add req', {});
|
focusedWindow.rpc.emit('termgroup add req', {});
|
||||||
} else {
|
} else {
|
||||||
setTimeout(app.createWindow, 0);
|
setTimeout(app.createWindow, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'pane:splitRight': (focusedWindow) => {
|
'pane:splitRight': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('split request vertical', {});
|
focusedWindow && focusedWindow.rpc.emit('split request vertical', {});
|
||||||
},
|
},
|
||||||
'pane:splitDown': (focusedWindow) => {
|
'pane:splitDown': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('split request horizontal', {});
|
focusedWindow && focusedWindow.rpc.emit('split request horizontal', {});
|
||||||
},
|
},
|
||||||
'pane:close': (focusedWindow) => {
|
'pane:close': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('termgroup close req');
|
focusedWindow && focusedWindow.rpc.emit('termgroup close req');
|
||||||
},
|
},
|
||||||
'window:preferences': () => {
|
'window:preferences': () => {
|
||||||
void openConfig();
|
openConfig();
|
||||||
},
|
},
|
||||||
'editor:clearBuffer': (focusedWindow) => {
|
'editor:clearBuffer': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session clear req');
|
focusedWindow && focusedWindow.rpc.emit('session clear req');
|
||||||
},
|
},
|
||||||
'editor:selectAll': (focusedWindow) => {
|
'editor:selectAll': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('term selectAll');
|
focusedWindow && focusedWindow.rpc.emit('term selectAll');
|
||||||
},
|
},
|
||||||
'plugins:update': () => {
|
'plugins:update': () => {
|
||||||
updatePlugins();
|
updatePlugins();
|
||||||
},
|
},
|
||||||
'window:reload': (focusedWindow) => {
|
'window:reload': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('reload');
|
focusedWindow && focusedWindow.rpc.emit('reload');
|
||||||
},
|
},
|
||||||
'window:reloadFull': (focusedWindow) => {
|
'window:reloadFull': focusedWindow => {
|
||||||
focusedWindow?.reload();
|
focusedWindow && focusedWindow.reload();
|
||||||
},
|
},
|
||||||
'window:devtools': (focusedWindow) => {
|
'window:devtools': focusedWindow => {
|
||||||
if (!focusedWindow) {
|
if (!focusedWindow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -56,109 +53,75 @@ const commands: Record<string, (focusedWindow?: BrowserWindow) => void> = {
|
||||||
webContents.openDevTools({mode: 'detach'});
|
webContents.openDevTools({mode: 'detach'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'zoom:reset': (focusedWindow) => {
|
'zoom:reset': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('reset fontSize req');
|
focusedWindow && focusedWindow.rpc.emit('reset fontSize req');
|
||||||
},
|
},
|
||||||
'zoom:in': (focusedWindow) => {
|
'zoom:in': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('increase fontSize req');
|
focusedWindow && focusedWindow.rpc.emit('increase fontSize req');
|
||||||
},
|
},
|
||||||
'zoom:out': (focusedWindow) => {
|
'zoom:out': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('decrease fontSize req');
|
focusedWindow && focusedWindow.rpc.emit('decrease fontSize req');
|
||||||
},
|
},
|
||||||
'tab:prev': (focusedWindow) => {
|
'tab:prev': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('move left req');
|
focusedWindow && focusedWindow.rpc.emit('move left req');
|
||||||
},
|
},
|
||||||
'tab:next': (focusedWindow) => {
|
'tab:next': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('move right req');
|
focusedWindow && focusedWindow.rpc.emit('move right req');
|
||||||
},
|
},
|
||||||
'pane:prev': (focusedWindow) => {
|
'pane:prev': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('prev pane req');
|
focusedWindow && focusedWindow.rpc.emit('prev pane req');
|
||||||
},
|
},
|
||||||
'pane:next': (focusedWindow) => {
|
'pane:next': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('next pane req');
|
focusedWindow && focusedWindow.rpc.emit('next pane req');
|
||||||
},
|
},
|
||||||
'editor:movePreviousWord': (focusedWindow) => {
|
'editor:movePreviousWord': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session move word left req');
|
focusedWindow && focusedWindow.rpc.emit('session move word left req');
|
||||||
},
|
},
|
||||||
'editor:moveNextWord': (focusedWindow) => {
|
'editor:moveNextWord': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session move word right req');
|
focusedWindow && focusedWindow.rpc.emit('session move word right req');
|
||||||
},
|
},
|
||||||
'editor:moveBeginningLine': (focusedWindow) => {
|
'editor:moveBeginningLine': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session move line beginning req');
|
focusedWindow && focusedWindow.rpc.emit('session move line beginning req');
|
||||||
},
|
},
|
||||||
'editor:moveEndLine': (focusedWindow) => {
|
'editor:moveEndLine': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session move line end req');
|
focusedWindow && focusedWindow.rpc.emit('session move line end req');
|
||||||
},
|
},
|
||||||
'editor:deletePreviousWord': (focusedWindow) => {
|
'editor:deletePreviousWord': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session del word left req');
|
focusedWindow && focusedWindow.rpc.emit('session del word left req');
|
||||||
},
|
},
|
||||||
'editor:deleteNextWord': (focusedWindow) => {
|
'editor:deleteNextWord': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session del word right req');
|
focusedWindow && focusedWindow.rpc.emit('session del word right req');
|
||||||
},
|
},
|
||||||
'editor:deleteBeginningLine': (focusedWindow) => {
|
'editor:deleteBeginningLine': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session del line beginning req');
|
focusedWindow && focusedWindow.rpc.emit('session del line beginning req');
|
||||||
},
|
},
|
||||||
'editor:deleteEndLine': (focusedWindow) => {
|
'editor:deleteEndLine': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session del line end req');
|
focusedWindow && focusedWindow.rpc.emit('session del line end req');
|
||||||
},
|
},
|
||||||
'editor:break': (focusedWindow) => {
|
'editor:break': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session break req');
|
focusedWindow && focusedWindow.rpc.emit('session break req');
|
||||||
},
|
},
|
||||||
'editor:stop': (focusedWindow) => {
|
'editor:search': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session stop req');
|
focusedWindow && focusedWindow.rpc.emit('session search');
|
||||||
},
|
},
|
||||||
'editor:quit': (focusedWindow) => {
|
'editor:search-close': focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('session quit req');
|
focusedWindow && focusedWindow.rpc.emit('session search close');
|
||||||
},
|
|
||||||
'editor:tmux': (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('session tmux req');
|
|
||||||
},
|
|
||||||
'editor:search': (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('session search');
|
|
||||||
},
|
|
||||||
'editor:search-close': (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('session search close');
|
|
||||||
},
|
},
|
||||||
'cli:install': () => {
|
'cli:install': () => {
|
||||||
void installCLI(true);
|
installCLI(true);
|
||||||
},
|
},
|
||||||
'window:hamburgerMenu': () => {
|
'window:hamburgerMenu': () => {
|
||||||
if (process.platform !== 'darwin' && ['', true].includes(getConfig().showHamburgerMenu)) {
|
if (getConfig().showHamburgerMenu) {
|
||||||
Menu.getApplicationMenu()!.popup({x: 25, y: 22});
|
Menu.getApplicationMenu()!.popup({x: 15, y: 15});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
'systemContextMenu:add': () => {
|
|
||||||
systemContextMenu.add();
|
|
||||||
},
|
|
||||||
'systemContextMenu:remove': () => {
|
|
||||||
systemContextMenu.remove();
|
|
||||||
},
|
|
||||||
'window:toggleKeepOnTop': (focusedWindow) => {
|
|
||||||
focusedWindow?.setAlwaysOnTop(!focusedWindow.isAlwaysOnTop());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Special numeric command
|
//Special numeric command
|
||||||
([1, 2, 3, 4, 5, 6, 7, 8, 'last'] as const).forEach((cmdIndex) => {
|
([1, 2, 3, 4, 5, 6, 7, 8, 'last'] as const).forEach(cmdIndex => {
|
||||||
const index = cmdIndex === 'last' ? cmdIndex : cmdIndex - 1;
|
const index = cmdIndex === 'last' ? cmdIndex : cmdIndex - 1;
|
||||||
commands[`tab:jump:${cmdIndex}`] = (focusedWindow) => {
|
commands[`tab:jump:${cmdIndex}`] = focusedWindow => {
|
||||||
focusedWindow?.rpc.emit('move jump req', index);
|
focusedWindow && focusedWindow.rpc.emit('move jump req', index);
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//Profile specific commands
|
|
||||||
getConfig().profiles.forEach((profile) => {
|
|
||||||
commands[`window:new:${profile.name}`] = () => {
|
|
||||||
setTimeout(() => app.createWindow(undefined, undefined, profile.name), 0);
|
|
||||||
};
|
|
||||||
commands[`tab:new:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('termgroup add req', {profile: profile.name});
|
|
||||||
};
|
|
||||||
commands[`pane:splitRight:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('split request vertical', {profile: profile.name});
|
|
||||||
};
|
|
||||||
commands[`pane:splitDown:${profile.name}`] = (focusedWindow) => {
|
|
||||||
focusedWindow?.rpc.emit('split request horizontal', {profile: profile.name});
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
115
app/config.ts
115
app/config.ts
|
|
@ -1,24 +1,20 @@
|
||||||
import {app} from 'electron';
|
import fs from 'fs';
|
||||||
|
import notify from './notify';
|
||||||
import chokidar from 'chokidar';
|
|
||||||
|
|
||||||
import type {parsedConfig, configOptions} from '../typings/config';
|
|
||||||
|
|
||||||
import {_import, getDefaultConfig} from './config/import';
|
import {_import, getDefaultConfig} from './config/import';
|
||||||
import _openConfig from './config/open';
|
import _openConfig from './config/open';
|
||||||
|
import win from './config/windows';
|
||||||
import {cfgPath, cfgDir} from './config/paths';
|
import {cfgPath, cfgDir} from './config/paths';
|
||||||
import notify from './notify';
|
|
||||||
import {getColorMap} from './utils/colors';
|
import {getColorMap} from './utils/colors';
|
||||||
|
|
||||||
const watchers: Function[] = [];
|
const watchers: any[] = [];
|
||||||
let cfg: parsedConfig = {} as any;
|
let cfg: Record<string, any> = {};
|
||||||
let _watcher: chokidar.FSWatcher;
|
let _watcher: fs.FSWatcher;
|
||||||
|
|
||||||
export const getDeprecatedCSS = (config: configOptions) => {
|
export const getDeprecatedCSS = (config: Record<string, any>) => {
|
||||||
const deprecated: string[] = [];
|
const deprecated: string[] = [];
|
||||||
const deprecatedCSS = ['x-screen', 'x-row', 'cursor-node', '::selection'];
|
const deprecatedCSS = ['x-screen', 'x-row', 'cursor-node', '::selection'];
|
||||||
deprecatedCSS.forEach((css) => {
|
deprecatedCSS.forEach(css => {
|
||||||
if (config.css?.includes(css) || config.termCSS?.includes(css)) {
|
if ((config.css && config.css.includes(css)) || (config.termCSS && config.termCSS.includes(css))) {
|
||||||
deprecated.push(css);
|
deprecated.push(css);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -39,7 +35,7 @@ const checkDeprecatedConfig = () => {
|
||||||
|
|
||||||
const _watch = () => {
|
const _watch = () => {
|
||||||
if (_watcher) {
|
if (_watcher) {
|
||||||
return;
|
return _watcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
|
|
@ -47,26 +43,47 @@ const _watch = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cfg = _import();
|
cfg = _import();
|
||||||
notify('Configuration updated', 'Hyper configuration reloaded!');
|
notify('Configuration updated', 'Hyper configuration reloaded!');
|
||||||
watchers.forEach((fn) => {
|
watchers.forEach(fn => fn());
|
||||||
fn();
|
|
||||||
});
|
|
||||||
checkDeprecatedConfig();
|
checkDeprecatedConfig();
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
_watcher = chokidar.watch(cfgPath);
|
// Windows
|
||||||
_watcher.on('change', onChange);
|
if (process.platform === 'win32') {
|
||||||
_watcher.on('error', (error) => {
|
// watch for changes on config every 2s on Windows
|
||||||
console.error('error watching config', error);
|
// https://github.com/zeit/hyper/pull/1772
|
||||||
});
|
_watcher = fs.watchFile(cfgPath, {interval: 2000}, (curr, prev) => {
|
||||||
|
if (!curr.mtime || curr.mtime.getTime() === 0) {
|
||||||
app.on('before-quit', () => {
|
//eslint-disable-next-line no-console
|
||||||
if (Object.keys(_watcher.getWatched()).length > 0) {
|
console.error('error watching config');
|
||||||
_watcher.close().catch((err) => {
|
} else if (curr.mtime.getTime() !== prev.mtime.getTime()) {
|
||||||
console.warn(err);
|
onChange();
|
||||||
|
}
|
||||||
|
}) as any;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// macOS/Linux
|
||||||
|
function setWatcher() {
|
||||||
|
try {
|
||||||
|
_watcher = fs.watch(cfgPath, eventType => {
|
||||||
|
if (eventType === 'rename') {
|
||||||
|
_watcher.close();
|
||||||
|
// Ensure that new file has been written
|
||||||
|
setTimeout(() => setWatcher(), 500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error('Failed to watch config file:', cfgPath, e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
_watcher.on('change', onChange);
|
||||||
|
_watcher.on('error', error => {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error('error watching config', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setWatcher();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const subscribe = (fn: Function) => {
|
export const subscribe = (fn: Function) => {
|
||||||
|
|
@ -81,30 +98,8 @@ export const getConfigDir = () => {
|
||||||
return cfgDir;
|
return cfgDir;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultProfile = () => {
|
|
||||||
return cfg.config.defaultProfile || cfg.config.profiles[0]?.name || 'default';
|
|
||||||
};
|
|
||||||
|
|
||||||
// get config for the default profile, keeping it for backward compatibility
|
|
||||||
export const getConfig = () => {
|
export const getConfig = () => {
|
||||||
return getProfileConfig(getDefaultProfile());
|
return cfg.config;
|
||||||
};
|
|
||||||
|
|
||||||
export const getProfiles = () => {
|
|
||||||
return cfg.config.profiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getProfileConfig = (profileName: string): configOptions => {
|
|
||||||
const {profiles, defaultProfile, ...baseConfig} = cfg.config;
|
|
||||||
const profileConfig = profiles.find((p) => p.name === profileName)?.config || {};
|
|
||||||
for (const key in profileConfig) {
|
|
||||||
if (typeof baseConfig[key] === 'object' && !Array.isArray(baseConfig[key])) {
|
|
||||||
baseConfig[key] = {...baseConfig[key], ...profileConfig[key]};
|
|
||||||
} else {
|
|
||||||
baseConfig[key] = profileConfig[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {...baseConfig, defaultProfile, profiles};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openConfig = () => {
|
export const openConfig = () => {
|
||||||
|
|
@ -128,17 +123,19 @@ export const setup = () => {
|
||||||
checkDeprecatedConfig();
|
checkDeprecatedConfig();
|
||||||
};
|
};
|
||||||
|
|
||||||
export {get as getWin, recordState as winRecord, defaults as windowDefaults} from './config/windows';
|
export const getWin = win.get;
|
||||||
|
export const winRecord = win.recordState;
|
||||||
|
export const windowDefaults = win.defaults;
|
||||||
|
|
||||||
export const fixConfigDefaults = (decoratedConfig: configOptions) => {
|
export const fixConfigDefaults = (decoratedConfig: any) => {
|
||||||
const defaultConfig = getDefaultConfig().config!;
|
const defaultConfig = getDefaultConfig()?.config;
|
||||||
decoratedConfig.colors = getColorMap(decoratedConfig.colors) || {};
|
decoratedConfig.colors = getColorMap(decoratedConfig.colors) || {};
|
||||||
// We must have default colors for xterm css.
|
// We must have default colors for xterm css.
|
||||||
decoratedConfig.colors = {...defaultConfig.colors, ...decoratedConfig.colors};
|
decoratedConfig.colors = Object.assign({}, defaultConfig.colors, decoratedConfig.colors);
|
||||||
return decoratedConfig;
|
return decoratedConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const htermConfigTranslate = (config: configOptions) => {
|
export const htermConfigTranslate = (config: Record<string, any>) => {
|
||||||
const cssReplacements: Record<string, string> = {
|
const cssReplacements: Record<string, string> = {
|
||||||
'x-screen x-row([ {.[])': '.xterm-rows > div$1',
|
'x-screen x-row([ {.[])': '.xterm-rows > div$1',
|
||||||
'.cursor-node([ {.[])': '.terminal-cursor$1',
|
'.cursor-node([ {.[])': '.terminal-cursor$1',
|
||||||
|
|
@ -146,11 +143,11 @@ export const htermConfigTranslate = (config: configOptions) => {
|
||||||
'x-screen a([ {.[])': '.terminal a$1',
|
'x-screen a([ {.[])': '.terminal a$1',
|
||||||
'x-row a([ {.[])': '.terminal a$1'
|
'x-row a([ {.[])': '.terminal a$1'
|
||||||
};
|
};
|
||||||
Object.keys(cssReplacements).forEach((pattern) => {
|
Object.keys(cssReplacements).forEach(pattern => {
|
||||||
const searchvalue = new RegExp(pattern, 'g');
|
const searchvalue = new RegExp(pattern, 'g');
|
||||||
const newvalue = cssReplacements[pattern];
|
const newvalue = cssReplacements[pattern];
|
||||||
config.css = config.css?.replace(searchvalue, newvalue);
|
config.css = config.css && config.css.replace(searchvalue, newvalue);
|
||||||
config.termCSS = config.termCSS?.replace(searchvalue, newvalue);
|
config.termCSS = config.termCSS && config.termCSS.replace(searchvalue, newvalue);
|
||||||
});
|
});
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
180
app/config/config-default.js
Normal file
180
app/config/config-default.js
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Future versions of Hyper may add additional config options,
|
||||||
|
// which will not automatically be merged into this file.
|
||||||
|
// See https://hyper.is#cfg for all currently supported options.
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
config: {
|
||||||
|
// choose either `'stable'` for receiving highly polished,
|
||||||
|
// or `'canary'` for less polished but more frequent updates
|
||||||
|
updateChannel: 'stable',
|
||||||
|
|
||||||
|
// default font size in pixels for all tabs
|
||||||
|
fontSize: 12,
|
||||||
|
|
||||||
|
// font family with optional fallbacks
|
||||||
|
fontFamily: 'Menlo, "DejaVu Sans Mono", Consolas, "Lucida Console", monospace',
|
||||||
|
|
||||||
|
// default font weight: 'normal' or 'bold'
|
||||||
|
fontWeight: 'normal',
|
||||||
|
|
||||||
|
// font weight for bold characters: 'normal' or 'bold'
|
||||||
|
fontWeightBold: 'bold',
|
||||||
|
|
||||||
|
// line height as a relative unit
|
||||||
|
lineHeight: 1,
|
||||||
|
|
||||||
|
// letter spacing as a relative unit
|
||||||
|
letterSpacing: 0,
|
||||||
|
|
||||||
|
// terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)
|
||||||
|
cursorColor: 'rgba(248,28,229,0.8)',
|
||||||
|
|
||||||
|
// terminal text color under BLOCK cursor
|
||||||
|
cursorAccentColor: '#000',
|
||||||
|
|
||||||
|
// `'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █
|
||||||
|
cursorShape: 'BLOCK',
|
||||||
|
|
||||||
|
// set to `true` (without backticks and without quotes) for blinking cursor
|
||||||
|
cursorBlink: false,
|
||||||
|
|
||||||
|
// color of the text
|
||||||
|
foregroundColor: '#fff',
|
||||||
|
|
||||||
|
// terminal background color
|
||||||
|
// opacity is only supported on macOS
|
||||||
|
backgroundColor: '#000',
|
||||||
|
|
||||||
|
// terminal selection color
|
||||||
|
selectionColor: 'rgba(248,28,229,0.3)',
|
||||||
|
|
||||||
|
// border color (window, tabs)
|
||||||
|
borderColor: '#333',
|
||||||
|
|
||||||
|
// custom CSS to embed in the main window
|
||||||
|
css: '',
|
||||||
|
|
||||||
|
// custom CSS to embed in the terminal window
|
||||||
|
termCSS: '',
|
||||||
|
|
||||||
|
// set custom startup directory (must be an absolute path)
|
||||||
|
workingDirectory: '',
|
||||||
|
|
||||||
|
// if you're using a Linux setup which show native menus, set to false
|
||||||
|
// default: `true` on Linux, `true` on Windows, ignored on macOS
|
||||||
|
showHamburgerMenu: '',
|
||||||
|
|
||||||
|
// set to `false` (without backticks and without quotes) if you want to hide the minimize, maximize and close buttons
|
||||||
|
// additionally, set to `'left'` if you want them on the left, like in Ubuntu
|
||||||
|
// default: `true` (without backticks and without quotes) on Windows and Linux, ignored on macOS
|
||||||
|
showWindowControls: '',
|
||||||
|
|
||||||
|
// custom padding (CSS format, i.e.: `top right bottom left`)
|
||||||
|
padding: '12px 14px',
|
||||||
|
|
||||||
|
// the full list. if you're going to provide the full color palette,
|
||||||
|
// including the 6 x 6 color cubes and the grayscale map, just provide
|
||||||
|
// an array here instead of a color map object
|
||||||
|
colors: {
|
||||||
|
black: '#000000',
|
||||||
|
red: '#C51E14',
|
||||||
|
green: '#1DC121',
|
||||||
|
yellow: '#C7C329',
|
||||||
|
blue: '#0A2FC4',
|
||||||
|
magenta: '#C839C5',
|
||||||
|
cyan: '#20C5C6',
|
||||||
|
white: '#C7C7C7',
|
||||||
|
lightBlack: '#686868',
|
||||||
|
lightRed: '#FD6F6B',
|
||||||
|
lightGreen: '#67F86F',
|
||||||
|
lightYellow: '#FFFA72',
|
||||||
|
lightBlue: '#6A76FB',
|
||||||
|
lightMagenta: '#FD7CFC',
|
||||||
|
lightCyan: '#68FDFE',
|
||||||
|
lightWhite: '#FFFFFF',
|
||||||
|
limeGreen: '#32CD32',
|
||||||
|
lightCoral: '#F08080',
|
||||||
|
},
|
||||||
|
|
||||||
|
// the shell to run when spawning a new session (i.e. /usr/local/bin/fish)
|
||||||
|
// if left empty, your system's login shell will be used by default
|
||||||
|
//
|
||||||
|
// Windows
|
||||||
|
// - Make sure to use a full path if the binary name doesn't work
|
||||||
|
// - Remove `--login` in shellArgs
|
||||||
|
//
|
||||||
|
// Windows Subsystem for Linux (WSL) - previously Bash on Windows
|
||||||
|
// - Example: `C:\\Windows\\System32\\wsl.exe`
|
||||||
|
//
|
||||||
|
// Git-bash on Windows
|
||||||
|
// - Example: `C:\\Program Files\\Git\\bin\\bash.exe`
|
||||||
|
//
|
||||||
|
// PowerShell on Windows
|
||||||
|
// - Example: `C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
|
||||||
|
//
|
||||||
|
// Cygwin
|
||||||
|
// - Example: `C:\\cygwin64\\bin\\bash.exe`
|
||||||
|
//
|
||||||
|
// Git Bash
|
||||||
|
// - Example: `C:\\Program Files\\Git\\git-cmd.exe`
|
||||||
|
// Then Add `--command=usr/bin/bash.exe` to shellArgs
|
||||||
|
shell: '',
|
||||||
|
|
||||||
|
// for setting shell arguments (i.e. for using interactive shellArgs: `['-i']`)
|
||||||
|
// by default `['--login']` will be used
|
||||||
|
shellArgs: ['--login'],
|
||||||
|
|
||||||
|
// for environment variables
|
||||||
|
env: {},
|
||||||
|
|
||||||
|
// Supported Options:
|
||||||
|
// 1. 'SOUND' -> Enables the bell as a sound
|
||||||
|
// 2. false: turns off the bell
|
||||||
|
bell: 'SOUND',
|
||||||
|
|
||||||
|
// An absolute file path to a sound file on the machine.
|
||||||
|
// bellSoundURL: '/path/to/sound/file',
|
||||||
|
|
||||||
|
// if `true` (without backticks and without quotes), selected text will automatically be copied to the clipboard
|
||||||
|
copyOnSelect: false,
|
||||||
|
|
||||||
|
// if `true` (without backticks and without quotes), hyper will be set as the default protocol client for SSH
|
||||||
|
defaultSSHApp: true,
|
||||||
|
|
||||||
|
// if `true` (without backticks and without quotes), on right click selected text will be copied or pasted if no
|
||||||
|
// selection is present (`true` by default on Windows and disables the context menu feature)
|
||||||
|
quickEdit: false,
|
||||||
|
|
||||||
|
// choose either `'vertical'`, if you want the column mode when Option key is hold during selection (Default)
|
||||||
|
// or `'force'`, if you want to force selection regardless of whether the terminal is in mouse events mode
|
||||||
|
// (inside tmux or vim with mouse mode enabled for example).
|
||||||
|
macOptionSelectionMode: 'vertical',
|
||||||
|
|
||||||
|
// Whether to use the WebGL renderer. Set it to false to use canvas-based
|
||||||
|
// rendering (slower, but supports transparent backgrounds)
|
||||||
|
webGLRenderer: true,
|
||||||
|
|
||||||
|
// if `true` (without backticks and without quotes), Hyper will ignore ligatures provided by some fonts
|
||||||
|
disableLigatures: false,
|
||||||
|
|
||||||
|
// for advanced config flags please refer to https://hyper.is/#cfg
|
||||||
|
},
|
||||||
|
|
||||||
|
// a list of plugins to fetch and install from npm
|
||||||
|
// format: [@org/]project[#version]
|
||||||
|
// examples:
|
||||||
|
// `hyperpower`
|
||||||
|
// `@company/project`
|
||||||
|
// `project#1.0.1`
|
||||||
|
plugins: [],
|
||||||
|
|
||||||
|
// in development, you can create a directory under
|
||||||
|
// `~/.hyper_plugins/local/` and include it here
|
||||||
|
// to load it and avoid it being `npm install`ed
|
||||||
|
localPlugins: [],
|
||||||
|
|
||||||
|
keymaps: {
|
||||||
|
// Example
|
||||||
|
// 'window:devtools': 'cmd+alt+o',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "./schema.json",
|
|
||||||
"config": {
|
|
||||||
"updateChannel": "stable",
|
|
||||||
"fontSize": 12,
|
|
||||||
"fontFamily": "Menlo, \"DejaVu Sans Mono\", Consolas, \"Lucida Console\", monospace",
|
|
||||||
"fontWeight": "normal",
|
|
||||||
"fontWeightBold": "bold",
|
|
||||||
"lineHeight": 1,
|
|
||||||
"letterSpacing": 0,
|
|
||||||
"scrollback": 1000,
|
|
||||||
"cursorColor": "rgba(248,28,229,0.8)",
|
|
||||||
"cursorAccentColor": "#000",
|
|
||||||
"cursorShape": "BLOCK",
|
|
||||||
"cursorBlink": false,
|
|
||||||
"foregroundColor": "#fff",
|
|
||||||
"backgroundColor": "#000",
|
|
||||||
"selectionColor": "rgba(248,28,229,0.3)",
|
|
||||||
"borderColor": "#333",
|
|
||||||
"css": "",
|
|
||||||
"termCSS": "",
|
|
||||||
"workingDirectory": "",
|
|
||||||
"showHamburgerMenu": "",
|
|
||||||
"showWindowControls": "",
|
|
||||||
"padding": "12px 14px",
|
|
||||||
"colors": {
|
|
||||||
"black": "#000000",
|
|
||||||
"red": "#C51E14",
|
|
||||||
"green": "#1DC121",
|
|
||||||
"yellow": "#C7C329",
|
|
||||||
"blue": "#0A2FC4",
|
|
||||||
"magenta": "#C839C5",
|
|
||||||
"cyan": "#20C5C6",
|
|
||||||
"white": "#C7C7C7",
|
|
||||||
"lightBlack": "#686868",
|
|
||||||
"lightRed": "#FD6F6B",
|
|
||||||
"lightGreen": "#67F86F",
|
|
||||||
"lightYellow": "#FFFA72",
|
|
||||||
"lightBlue": "#6A76FB",
|
|
||||||
"lightMagenta": "#FD7CFC",
|
|
||||||
"lightCyan": "#68FDFE",
|
|
||||||
"lightWhite": "#FFFFFF",
|
|
||||||
"limeGreen": "#32CD32",
|
|
||||||
"lightCoral": "#F08080"
|
|
||||||
},
|
|
||||||
"shell": "",
|
|
||||||
"shellArgs": [
|
|
||||||
"--login"
|
|
||||||
],
|
|
||||||
"env": {},
|
|
||||||
"bell": "SOUND",
|
|
||||||
"bellSound": null,
|
|
||||||
"bellSoundURL": null,
|
|
||||||
"copyOnSelect": false,
|
|
||||||
"defaultSSHApp": true,
|
|
||||||
"quickEdit": false,
|
|
||||||
"macOptionSelectionMode": "vertical",
|
|
||||||
"webGLRenderer": false,
|
|
||||||
"webLinksActivationKey": "",
|
|
||||||
"disableLigatures": true,
|
|
||||||
"disableAutoUpdates": false,
|
|
||||||
"autoUpdatePlugins": true,
|
|
||||||
"preserveCWD": true,
|
|
||||||
"screenReaderMode": false,
|
|
||||||
"imageSupport": true,
|
|
||||||
"defaultProfile": "default",
|
|
||||||
"profiles": [
|
|
||||||
{
|
|
||||||
"name": "default",
|
|
||||||
"config": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"plugins": [],
|
|
||||||
"localPlugins": [],
|
|
||||||
"keymaps": {}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +1,85 @@
|
||||||
import {readFileSync, mkdirpSync} from 'fs-extra';
|
import {moveSync, copySync, existsSync, writeFileSync, readFileSync, lstatSync} from 'fs-extra';
|
||||||
|
import {sync as mkdirpSync} from 'mkdirp';
|
||||||
import type {rawConfig} from '../../typings/config';
|
import {defaultCfg, cfgPath, legacyCfgPath, plugs, defaultPlatformKeyPath} from './paths';
|
||||||
|
import {_init, _extractDefault} from './init';
|
||||||
import notify from '../notify';
|
import notify from '../notify';
|
||||||
|
|
||||||
import {_init} from './init';
|
let defaultConfig: Record<string, any> | undefined;
|
||||||
import {migrateHyper3Config} from './migrate';
|
|
||||||
import {defaultCfg, cfgPath, plugs, defaultPlatformKeyPath} from './paths';
|
|
||||||
|
|
||||||
let defaultConfig: rawConfig;
|
const _write = (path: string, data: any) => {
|
||||||
|
// This method will take text formatted as Unix line endings and transform it
|
||||||
|
// to text formatted with DOS line endings. We do this because the default
|
||||||
|
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
|
||||||
|
const crlfify = (str: string) => {
|
||||||
|
return str.replace(/\r?\n/g, '\r\n');
|
||||||
|
};
|
||||||
|
const format = process.platform === 'win32' ? crlfify(data.toString()) : data;
|
||||||
|
writeFileSync(path, format, 'utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Saves a file as backup by appending '.backup' or '.backup2', '.backup3', etc.
|
||||||
|
// so as to not override any existing files
|
||||||
|
const saveAsBackup = (src: string) => {
|
||||||
|
let attempt = 1;
|
||||||
|
while (attempt < 100) {
|
||||||
|
try {
|
||||||
|
const backupPath = `${src}.backup${attempt === 1 ? '' : attempt}`;
|
||||||
|
moveSync(src, backupPath);
|
||||||
|
return backupPath;
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'EEXIST') {
|
||||||
|
attempt++;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Failed to create backup for config file. Too many backups');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Migrate Hyper2 config to Hyper3 but only if the user hasn't manually
|
||||||
|
// touched the new config and if the old config is not a symlink
|
||||||
|
const migrateHyper2Config = () => {
|
||||||
|
if (cfgPath === legacyCfgPath) {
|
||||||
|
// No need to migrate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!existsSync(legacyCfgPath)) {
|
||||||
|
// Already migrated or user never used Hyper 2
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const existsNew = existsSync(cfgPath);
|
||||||
|
if (lstatSync(legacyCfgPath).isSymbolicLink() || (existsNew && lstatSync(cfgPath).isSymbolicLink())) {
|
||||||
|
// One of the files is a symlink, there could be a number of complications
|
||||||
|
// in this case so let's avoid those and not do automatic migration
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsNew) {
|
||||||
|
const cfg1 = readFileSync(defaultCfg, 'utf8').replace(/\r|\n/g, '');
|
||||||
|
const cfg2 = readFileSync(cfgPath, 'utf8').replace(/\r|\n/g, '');
|
||||||
|
const hasNewConfigBeenTouched = cfg1 !== cfg2;
|
||||||
|
if (hasNewConfigBeenTouched) {
|
||||||
|
// Assume the user has migrated manually but rename old config to .backup so
|
||||||
|
// we don't keep trying to migrate on every launch
|
||||||
|
const backupPath = saveAsBackup(legacyCfgPath);
|
||||||
|
notify(
|
||||||
|
'Hyper 3',
|
||||||
|
`Settings location has changed to ${cfgPath}.\nWe've backed up your old Hyper config to ${backupPath}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate
|
||||||
|
copySync(legacyCfgPath, cfgPath);
|
||||||
|
saveAsBackup(legacyCfgPath);
|
||||||
|
|
||||||
|
notify(
|
||||||
|
'Hyper 3',
|
||||||
|
`Settings location has changed to ${cfgPath}.\nWe've automatically migrated your existing config!\nPlease restart Hyper now`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const _importConf = () => {
|
const _importConf = () => {
|
||||||
// init plugin directories if not present
|
// init plugin directories if not present
|
||||||
|
|
@ -15,51 +87,49 @@ const _importConf = () => {
|
||||||
mkdirpSync(plugs.local);
|
mkdirpSync(plugs.local);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
migrateHyper3Config();
|
migrateHyper2Config();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let defaultCfgRaw = '{}';
|
|
||||||
try {
|
try {
|
||||||
defaultCfgRaw = readFileSync(defaultCfg, 'utf8');
|
const defaultCfgRaw = readFileSync(defaultCfg, 'utf8');
|
||||||
|
const _defaultCfg = _extractDefault(defaultCfgRaw);
|
||||||
|
// Importing platform specific keymap
|
||||||
|
try {
|
||||||
|
const content = readFileSync(defaultPlatformKeyPath(), 'utf8');
|
||||||
|
const mapping = JSON.parse(content) as Record<string, string | string[]>;
|
||||||
|
_defaultCfg.keymaps = mapping;
|
||||||
|
} catch (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import user config
|
||||||
|
try {
|
||||||
|
const userCfg = readFileSync(cfgPath, 'utf8');
|
||||||
|
return {userCfg, defaultCfg: _defaultCfg};
|
||||||
|
} catch (err) {
|
||||||
|
_write(cfgPath, defaultCfgRaw);
|
||||||
|
return {userCfg: defaultCfgRaw, defaultCfg: _defaultCfg};
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
const _defaultCfg = JSON.parse(defaultCfgRaw) as rawConfig;
|
|
||||||
|
|
||||||
// Importing platform specific keymap
|
|
||||||
let content = '{}';
|
|
||||||
try {
|
|
||||||
content = readFileSync(defaultPlatformKeyPath(), 'utf8');
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
const mapping = JSON.parse(content) as Record<string, string | string[]>;
|
|
||||||
_defaultCfg.keymaps = mapping;
|
|
||||||
|
|
||||||
// Import user config
|
|
||||||
let userCfg: rawConfig;
|
|
||||||
try {
|
|
||||||
userCfg = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
||||||
} catch (err) {
|
|
||||||
notify("Couldn't parse config file. Using default config instead.");
|
|
||||||
userCfg = JSON.parse(defaultCfgRaw);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {userCfg, defaultCfg: _defaultCfg};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _import = () => {
|
export const _import = () => {
|
||||||
const imported = _importConf();
|
const imported = _importConf();
|
||||||
defaultConfig = imported.defaultCfg;
|
defaultConfig = imported?.defaultCfg;
|
||||||
const result = _init(imported.userCfg, imported.defaultCfg);
|
const result = _init(imported!);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDefaultConfig = () => {
|
export const getDefaultConfig = () => {
|
||||||
if (!defaultConfig) {
|
if (!defaultConfig) {
|
||||||
defaultConfig = _importConf().defaultCfg;
|
defaultConfig = _importConf()?.defaultCfg;
|
||||||
}
|
}
|
||||||
return defaultConfig;
|
return defaultConfig;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,21 @@
|
||||||
import vm from 'vm';
|
import vm from 'vm';
|
||||||
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
|
|
||||||
import type {parsedConfig, rawConfig, configOptions} from '../../typings/config';
|
|
||||||
import notify from '../notify';
|
import notify from '../notify';
|
||||||
import mapKeys from '../utils/map-keys';
|
import mapKeys from '../utils/map-keys';
|
||||||
|
|
||||||
const _extract = (script?: vm.Script): Record<string, any> => {
|
const _extract = (script?: vm.Script): Record<string, any> => {
|
||||||
const module: Record<string, any> = {};
|
const module: Record<string, any> = {};
|
||||||
script?.runInNewContext({module}, {displayErrors: true});
|
script?.runInNewContext({module});
|
||||||
if (!module.exports) {
|
if (!module.exports) {
|
||||||
throw new Error('Error reading configuration: `module.exports` not set');
|
throw new Error('Error reading configuration: `module.exports` not set');
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
||||||
return module.exports;
|
return module.exports;
|
||||||
};
|
};
|
||||||
|
|
||||||
const _syntaxValidation = (cfg: string) => {
|
const _syntaxValidation = (cfg: string) => {
|
||||||
try {
|
try {
|
||||||
return new vm.Script(cfg, {filename: '.hyper.js'});
|
return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true});
|
||||||
} catch (_err) {
|
} catch (err) {
|
||||||
const err = _err as {name: string};
|
notify('Error loading config:', `${err.name}, see DevTools for more info`, {error: err});
|
||||||
notify(`Error loading config: ${err.name}`, JSON.stringify(err), {error: err});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -30,34 +24,22 @@ const _extractDefault = (cfg: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// init config
|
// init config
|
||||||
const _init = (userCfg: rawConfig, defaultCfg: rawConfig): parsedConfig => {
|
const _init = (cfg: {userCfg: string; defaultCfg: Record<string, any>}) => {
|
||||||
return {
|
const script = _syntaxValidation(cfg.userCfg);
|
||||||
config: (() => {
|
if (script) {
|
||||||
if (userCfg?.config) {
|
const _cfg = _extract(script);
|
||||||
const conf = userCfg.config;
|
if (!_cfg.config) {
|
||||||
conf.defaultProfile = conf.defaultProfile || 'default';
|
notify('Error reading configuration: `config` key is missing');
|
||||||
conf.profiles = conf.profiles || [];
|
return cfg.defaultCfg;
|
||||||
conf.profiles = conf.profiles.length > 0 ? conf.profiles : [{name: 'default', config: {}}];
|
}
|
||||||
conf.profiles = conf.profiles.map((p, i) => ({
|
|
||||||
...p,
|
|
||||||
name: p.name || `profile-${i + 1}`,
|
|
||||||
config: p.config || {}
|
|
||||||
}));
|
|
||||||
if (!conf.profiles.map((p) => p.name).includes(conf.defaultProfile)) {
|
|
||||||
conf.defaultProfile = conf.profiles[0].name;
|
|
||||||
}
|
|
||||||
return merge({}, defaultCfg.config, conf);
|
|
||||||
} else {
|
|
||||||
notify('Error reading configuration: `config` key is missing');
|
|
||||||
return defaultCfg.config || ({} as configOptions);
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
// Merging platform specific keymaps with user defined keymaps
|
// Merging platform specific keymaps with user defined keymaps
|
||||||
keymaps: mapKeys({...defaultCfg.keymaps, ...userCfg?.keymaps}),
|
_cfg.keymaps = mapKeys(Object.assign({}, cfg.defaultCfg.keymaps, _cfg.keymaps));
|
||||||
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
||||||
plugins: userCfg?.plugins?.filter(Boolean) || [],
|
_cfg.plugins = (_cfg.plugins && _cfg.plugins.filter(Boolean)) || [];
|
||||||
localPlugins: userCfg?.localPlugins?.filter(Boolean) || []
|
_cfg.localPlugins = (_cfg.localPlugins && _cfg.localPlugins.filter(Boolean)) || [];
|
||||||
};
|
return _cfg;
|
||||||
|
}
|
||||||
|
return cfg.defaultCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
export {_init, _extractDefault};
|
export {_init, _extractDefault};
|
||||||
|
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
import {dirname, resolve} from 'path';
|
|
||||||
|
|
||||||
import {builders, namedTypes} from 'ast-types';
|
|
||||||
import type {ExpressionKind} from 'ast-types/lib/gen/kinds';
|
|
||||||
import {copy, copySync, existsSync, readFileSync, writeFileSync} from 'fs-extra';
|
|
||||||
import merge from 'lodash/merge';
|
|
||||||
import {parse, prettyPrint} from 'recast';
|
|
||||||
import * as babelParser from 'recast/parsers/babel';
|
|
||||||
|
|
||||||
import notify from '../notify';
|
|
||||||
|
|
||||||
import {_extractDefault} from './init';
|
|
||||||
import {cfgDir, cfgPath, defaultCfg, legacyCfgPath, plugs, schemaFile, schemaPath} from './paths';
|
|
||||||
|
|
||||||
// function to remove all json serializable entries from an array expression
|
|
||||||
function removeElements(node: namedTypes.ArrayExpression): namedTypes.ArrayExpression {
|
|
||||||
const newElements = node.elements.filter((element) => {
|
|
||||||
if (namedTypes.ObjectExpression.check(element)) {
|
|
||||||
const newElement = removeProperties(element);
|
|
||||||
if (newElement.properties.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (namedTypes.ArrayExpression.check(element)) {
|
|
||||||
const newElement = removeElements(element);
|
|
||||||
if (newElement.elements.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (namedTypes.Literal.check(element)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return {...node, elements: newElements};
|
|
||||||
}
|
|
||||||
|
|
||||||
// function to remove all json serializable properties from an object expression
|
|
||||||
function removeProperties(node: namedTypes.ObjectExpression): namedTypes.ObjectExpression {
|
|
||||||
const newProperties = node.properties.filter((property) => {
|
|
||||||
if (
|
|
||||||
namedTypes.ObjectProperty.check(property) &&
|
|
||||||
(namedTypes.Literal.check(property.key) || namedTypes.Identifier.check(property.key)) &&
|
|
||||||
!property.computed
|
|
||||||
) {
|
|
||||||
if (namedTypes.ObjectExpression.check(property.value)) {
|
|
||||||
const newValue = removeProperties(property.value);
|
|
||||||
if (newValue.properties.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (namedTypes.ArrayExpression.check(property.value)) {
|
|
||||||
const newValue = removeElements(property.value);
|
|
||||||
if (newValue.elements.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (namedTypes.Literal.check(property.value)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return {...node, properties: newProperties};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function configToPlugin(code: string): string {
|
|
||||||
const ast: namedTypes.File = parse(code, {
|
|
||||||
parser: babelParser
|
|
||||||
});
|
|
||||||
const statements = ast.program.body;
|
|
||||||
let moduleExportsNode: namedTypes.AssignmentExpression | null = null;
|
|
||||||
let configNode: ExpressionKind | null = null;
|
|
||||||
|
|
||||||
for (const statement of statements) {
|
|
||||||
if (namedTypes.ExpressionStatement.check(statement)) {
|
|
||||||
const expression = statement.expression;
|
|
||||||
if (
|
|
||||||
namedTypes.AssignmentExpression.check(expression) &&
|
|
||||||
expression.operator === '=' &&
|
|
||||||
namedTypes.MemberExpression.check(expression.left) &&
|
|
||||||
namedTypes.Identifier.check(expression.left.object) &&
|
|
||||||
expression.left.object.name === 'module' &&
|
|
||||||
namedTypes.Identifier.check(expression.left.property) &&
|
|
||||||
expression.left.property.name === 'exports'
|
|
||||||
) {
|
|
||||||
moduleExportsNode = expression;
|
|
||||||
if (namedTypes.ObjectExpression.check(expression.right)) {
|
|
||||||
const properties = expression.right.properties;
|
|
||||||
for (const property of properties) {
|
|
||||||
if (
|
|
||||||
namedTypes.ObjectProperty.check(property) &&
|
|
||||||
namedTypes.Identifier.check(property.key) &&
|
|
||||||
property.key.name === 'config'
|
|
||||||
) {
|
|
||||||
configNode = property.value as ExpressionKind;
|
|
||||||
if (namedTypes.ObjectExpression.check(property.value)) {
|
|
||||||
configNode = removeProperties(property.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configNode = builders.memberExpression(moduleExportsNode.right, builders.identifier('config'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!moduleExportsNode) {
|
|
||||||
console.log('No module.exports found in config');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (!configNode) {
|
|
||||||
console.log('No config field found in module.exports');
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (namedTypes.ObjectExpression.check(configNode) && configNode.properties.length === 0) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
moduleExportsNode.right = builders.objectExpression([
|
|
||||||
builders.property(
|
|
||||||
'init',
|
|
||||||
builders.identifier('decorateConfig'),
|
|
||||||
builders.arrowFunctionExpression(
|
|
||||||
[builders.identifier('_config')],
|
|
||||||
builders.callExpression(
|
|
||||||
builders.memberExpression(builders.identifier('Object'), builders.identifier('assign')),
|
|
||||||
[builders.objectExpression([]), builders.identifier('_config'), configNode]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
|
|
||||||
return prettyPrint(ast, {tabWidth: 2}).code;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const _write = (path: string, data: string) => {
|
|
||||||
// This method will take text formatted as Unix line endings and transform it
|
|
||||||
// to text formatted with DOS line endings. We do this because the default
|
|
||||||
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
|
|
||||||
const crlfify = (str: string) => {
|
|
||||||
return str.replace(/\r?\n/g, '\r\n');
|
|
||||||
};
|
|
||||||
const format = process.platform === 'win32' ? crlfify(data.toString()) : data;
|
|
||||||
writeFileSync(path, format, 'utf8');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Migrate Hyper3 config to Hyper4 but only if the user hasn't manually
|
|
||||||
// touched the new config and if the old config is not a symlink
|
|
||||||
export const migrateHyper3Config = () => {
|
|
||||||
copy(schemaPath, resolve(cfgDir, schemaFile), (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existsSync(cfgPath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existsSync(legacyCfgPath)) {
|
|
||||||
copySync(defaultCfg, cfgPath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate
|
|
||||||
copySync(resolve(dirname(legacyCfgPath), '.hyper_plugins', 'local'), plugs.local);
|
|
||||||
|
|
||||||
const defaultCfgData = JSON.parse(readFileSync(defaultCfg, 'utf8'));
|
|
||||||
let newCfgData;
|
|
||||||
try {
|
|
||||||
const legacyCfgRaw = readFileSync(legacyCfgPath, 'utf8');
|
|
||||||
const legacyCfgData = _extractDefault(legacyCfgRaw);
|
|
||||||
newCfgData = merge({}, defaultCfgData, legacyCfgData);
|
|
||||||
|
|
||||||
const pluginCode = configToPlugin(legacyCfgRaw);
|
|
||||||
if (pluginCode) {
|
|
||||||
const pluginPath = resolve(plugs.local, 'migrated-hyper3-config.js');
|
|
||||||
newCfgData.localPlugins = ['migrated-hyper3-config', ...(newCfgData.localPlugins || [])];
|
|
||||||
_write(pluginPath, pluginCode);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
notify(
|
|
||||||
'Hyper 4',
|
|
||||||
`Failed to migrate your config from Hyper 3.\nDefault config will be created instead at ${cfgPath}`
|
|
||||||
);
|
|
||||||
newCfgData = defaultCfgData;
|
|
||||||
}
|
|
||||||
_write(cfgPath, JSON.stringify(newCfgData, null, 2));
|
|
||||||
|
|
||||||
notify('Hyper 4', `Settings location and format has changed to ${cfgPath}`);
|
|
||||||
};
|
|
||||||
|
|
@ -1,80 +1,76 @@
|
||||||
import {exec} from 'child_process';
|
|
||||||
|
|
||||||
import {shell} from 'electron';
|
import {shell} from 'electron';
|
||||||
|
|
||||||
import * as Registry from 'native-reg';
|
|
||||||
|
|
||||||
import {cfgPath} from './paths';
|
import {cfgPath} from './paths';
|
||||||
|
export default () => Promise.resolve(shell.openItem(cfgPath));
|
||||||
|
|
||||||
const getUserChoiceKey = () => {
|
// Windows opens .js files with WScript.exe by default
|
||||||
try {
|
// If the user hasn't set up an editor for .js files, we fallback to notepad.
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const Registry = require('winreg') as typeof import('winreg');
|
||||||
|
const {exec} = require('child_process') as typeof import('child_process');
|
||||||
|
|
||||||
|
const getUserChoiceKey = async () => {
|
||||||
// Load FileExts keys for .js files
|
// Load FileExts keys for .js files
|
||||||
const fileExtsKeys = Registry.openKey(
|
const keys: Winreg.Registry[] = await new Promise((resolve, reject) => {
|
||||||
Registry.HKCU,
|
new Registry({
|
||||||
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js',
|
hive: Registry.HKCU,
|
||||||
Registry.Access.READ
|
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js'
|
||||||
);
|
}).keys((error, items) => {
|
||||||
const keys = fileExtsKeys ? Registry.enumKeyNames(fileExtsKeys) : [];
|
if (error) {
|
||||||
Registry.closeKey(fileExtsKeys);
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(items || []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Find UserChoice key
|
// Find UserChoice key
|
||||||
const userChoice = keys.find((k) => k.endsWith('UserChoice'));
|
const userChoice = keys.find(k => k.key.endsWith('UserChoice'));
|
||||||
return userChoice
|
return userChoice;
|
||||||
? `Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js\\${userChoice}`
|
};
|
||||||
: userChoice;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasDefaultSet = () => {
|
const hasDefaultSet = async () => {
|
||||||
const userChoice = getUserChoiceKey();
|
const userChoice = await getUserChoiceKey();
|
||||||
if (!userChoice) return false;
|
if (!userChoice) return false;
|
||||||
|
|
||||||
try {
|
|
||||||
// Load key values
|
// Load key values
|
||||||
const userChoiceKey = Registry.openKey(Registry.HKCU, userChoice, Registry.Access.READ)!;
|
const values: string[] = await new Promise((resolve, reject) => {
|
||||||
const values: string[] = Registry.enumValueNames(userChoiceKey).map(
|
userChoice.values((error, items) => {
|
||||||
(x) => (Registry.queryValue(userChoiceKey, x) as string) || ''
|
if (error) {
|
||||||
);
|
reject(error);
|
||||||
Registry.closeKey(userChoiceKey);
|
}
|
||||||
|
resolve(items.map(item => item.value || '') || []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Look for default program
|
// Look for default program
|
||||||
const hasDefaultProgramConfigured = values.every(
|
const hasDefaultProgramConfigured = values.every(
|
||||||
(value) => value && typeof value === 'string' && !value.includes('WScript.exe') && !value.includes('JSFile')
|
value => value && typeof value === 'string' && !value.includes('WScript.exe') && !value.includes('JSFile')
|
||||||
);
|
);
|
||||||
|
|
||||||
return hasDefaultProgramConfigured;
|
return hasDefaultProgramConfigured;
|
||||||
} catch (error) {
|
};
|
||||||
console.error(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This mimics shell.openItem, true if it worked, false if not.
|
// This mimics shell.openItem, true if it worked, false if not.
|
||||||
const openNotepad = (file: string) =>
|
const openNotepad = (file: string) =>
|
||||||
new Promise<boolean>((resolve) => {
|
new Promise(resolve => {
|
||||||
exec(`start notepad.exe ${file}`, (error) => {
|
exec(`start notepad.exe ${file}`, error => {
|
||||||
resolve(!error);
|
resolve(!error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const openConfig = () => {
|
module.exports = () =>
|
||||||
// Windows opens .js files with WScript.exe by default
|
hasDefaultSet()
|
||||||
// If the user hasn't set up an editor for .js files, we fallback to notepad.
|
.then(yes => {
|
||||||
if (process.platform === 'win32') {
|
if (yes) {
|
||||||
try {
|
return shell.openItem(cfgPath);
|
||||||
if (hasDefaultSet()) {
|
}
|
||||||
return shell.openPath(cfgPath).then((error) => error === '');
|
//eslint-disable-next-line no-console
|
||||||
}
|
console.warn('No default app set for .js files, using notepad.exe fallback');
|
||||||
console.warn('No default app set for .js files, using notepad.exe fallback');
|
return openNotepad(cfgPath);
|
||||||
} catch (err) {
|
})
|
||||||
console.error('Open config with default app error:', err);
|
.catch(err => {
|
||||||
}
|
//eslint-disable-next-line no-console
|
||||||
return openNotepad(cfgPath);
|
console.error('Open config with default app error:', err);
|
||||||
}
|
return openNotepad(cfgPath);
|
||||||
return shell.openPath(cfgPath).then((error) => error === '');
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
export default openConfig;
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,26 @@
|
||||||
// This module exports paths, names, and other metadata that is referenced
|
// This module exports paths, names, and other metadata that is referenced
|
||||||
import {statSync} from 'fs';
|
|
||||||
import {homedir} from 'os';
|
import {homedir} from 'os';
|
||||||
import {resolve, join} from 'path';
|
|
||||||
|
|
||||||
import {app} from 'electron';
|
import {app} from 'electron';
|
||||||
|
import {statSync} from 'fs';
|
||||||
|
import {resolve, join} from 'path';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
const cfgFile = 'hyper.json';
|
const cfgFile = '.hyper.js';
|
||||||
const defaultCfgFile = 'config-default.json';
|
const defaultCfgFile = 'config-default.js';
|
||||||
const schemaFile = 'schema.json';
|
|
||||||
const homeDirectory = homedir();
|
const homeDirectory = homedir();
|
||||||
|
|
||||||
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
||||||
// otherwise use the home directory in linux/mac and userdata in windows
|
// otherwise use the home directory in linux/mac and userdata in windows
|
||||||
let cfgDir = process.env.XDG_CONFIG_HOME
|
const applicationDirectory =
|
||||||
? join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
|
||||||
: process.platform === 'win32'
|
|
||||||
? app.getPath('userData')
|
|
||||||
: join(homeDirectory, '.config', 'Hyper');
|
|
||||||
|
|
||||||
const legacyCfgPath = join(
|
|
||||||
process.env.XDG_CONFIG_HOME !== undefined
|
process.env.XDG_CONFIG_HOME !== undefined
|
||||||
? join(process.env.XDG_CONFIG_HOME, 'hyper')
|
? join(process.env.XDG_CONFIG_HOME, 'hyper')
|
||||||
: process.platform == 'win32'
|
: process.platform == 'win32'
|
||||||
? app.getPath('userData')
|
? app.getPath('userData')
|
||||||
: homedir(),
|
: homedir();
|
||||||
'.hyper.js'
|
|
||||||
);
|
|
||||||
|
|
||||||
let cfgPath = join(cfgDir, cfgFile);
|
let cfgDir = applicationDirectory;
|
||||||
const schemaPath = resolve(__dirname, schemaFile);
|
let cfgPath = join(applicationDirectory, cfgFile);
|
||||||
|
const legacyCfgPath = join(homeDirectory, cfgFile); // Hyper 2 config location
|
||||||
|
|
||||||
const devDir = resolve(__dirname, '../..');
|
const devDir = resolve(__dirname, '../..');
|
||||||
const devCfg = join(devDir, cfgFile);
|
const devCfg = join(devDir, cfgFile);
|
||||||
|
|
@ -42,14 +32,17 @@ if (isDev) {
|
||||||
statSync(devCfg);
|
statSync(devCfg);
|
||||||
cfgPath = devCfg;
|
cfgPath = devCfg;
|
||||||
cfgDir = devDir;
|
cfgDir = devDir;
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('using config file:', cfgPath);
|
console.log('using config file:', cfgPath);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = resolve(cfgDir, 'plugins');
|
const plugins = resolve(cfgDir, '.hyper_plugins');
|
||||||
const plugs = {
|
const plugs = {
|
||||||
|
legacyBase: resolve(homeDirectory, '.hyper_plugins'),
|
||||||
|
legacyLocal: resolve(homeDirectory, '.hyper_plugins', 'local'),
|
||||||
base: plugins,
|
base: plugins,
|
||||||
local: resolve(plugins, 'local'),
|
local: resolve(plugins, 'local'),
|
||||||
cache: resolve(plugins, 'cache')
|
cache: resolve(plugins, 'cache')
|
||||||
|
|
@ -90,7 +83,5 @@ export {
|
||||||
yarn,
|
yarn,
|
||||||
cliScriptPath,
|
cliScriptPath,
|
||||||
cliLinkPath,
|
cliLinkPath,
|
||||||
homeDirectory,
|
homeDirectory
|
||||||
schemaFile,
|
|
||||||
schemaPath
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,756 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"definitions": {
|
|
||||||
"FontWeight": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"enum": [
|
|
||||||
"100",
|
|
||||||
"200",
|
|
||||||
"300",
|
|
||||||
"400",
|
|
||||||
"500",
|
|
||||||
"600",
|
|
||||||
"700",
|
|
||||||
"800",
|
|
||||||
"900",
|
|
||||||
"bold",
|
|
||||||
"normal"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "A string or number representing text font weight."
|
|
||||||
},
|
|
||||||
"Partial<profileConfigOptions>": {
|
|
||||||
"properties": {
|
|
||||||
"backgroundColor": {
|
|
||||||
"description": "terminal background color\n\nopacity is only supported on macOS",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bell": {
|
|
||||||
"description": "Supported Options:\n1. 'SOUND' -> Enables the bell as a sound\n2. false: turns off the bell",
|
|
||||||
"enum": [
|
|
||||||
"SOUND",
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSound": {
|
|
||||||
"description": "base64 encoded string of the sound file to use for the bell\nif null, the default bell will be used",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSoundURL": {
|
|
||||||
"description": "An absolute file path to a sound file on the machine.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"borderColor": {
|
|
||||||
"description": "border color (window, tabs)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"description": "the full list. if you're going to provide the full color palette,\nincluding the 6 x 6 color cubes and the grayscale map, just provide\nan array here instead of a color map object",
|
|
||||||
"properties": {
|
|
||||||
"black": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"blue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"green": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlack": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightCyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightGreen": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightMagenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightRed": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightWhite": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightYellow": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"magenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"red": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"white": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"yellow": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"black",
|
|
||||||
"blue",
|
|
||||||
"cyan",
|
|
||||||
"green",
|
|
||||||
"lightBlack",
|
|
||||||
"lightBlue",
|
|
||||||
"lightCyan",
|
|
||||||
"lightGreen",
|
|
||||||
"lightMagenta",
|
|
||||||
"lightRed",
|
|
||||||
"lightWhite",
|
|
||||||
"lightYellow",
|
|
||||||
"magenta",
|
|
||||||
"red",
|
|
||||||
"white",
|
|
||||||
"yellow"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"copyOnSelect": {
|
|
||||||
"description": "if `true` selected text will automatically be copied to the clipboard",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"css": {
|
|
||||||
"description": "custom CSS to embed in the main window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorAccentColor": {
|
|
||||||
"description": "terminal text color under BLOCK cursor",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorBlink": {
|
|
||||||
"description": "set to `true` for blinking cursor",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cursorColor": {
|
|
||||||
"description": "terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorShape": {
|
|
||||||
"description": "`'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █",
|
|
||||||
"enum": [
|
|
||||||
"BEAM",
|
|
||||||
"BLOCK",
|
|
||||||
"UNDERLINE"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"disableLigatures": {
|
|
||||||
"description": "if `false` Hyper will use ligatures provided by some fonts",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "for environment variables",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"fontFamily": {
|
|
||||||
"description": "font family with optional fallbacks",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fontSize": {
|
|
||||||
"description": "default font size in pixels for all tabs",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"fontWeight": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "default font weight eg:'normal', '400', 'bold'"
|
|
||||||
},
|
|
||||||
"fontWeightBold": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "font weight for bold characters eg:'normal', '600', 'bold'"
|
|
||||||
},
|
|
||||||
"foregroundColor": {
|
|
||||||
"description": "color of the text",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"imageSupport": {
|
|
||||||
"description": "Whether to enable Sixel and iTerm2 inline image protocol support or not.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"letterSpacing": {
|
|
||||||
"description": "letter spacing as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"lineHeight": {
|
|
||||||
"description": "line height as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"macOptionSelectionMode": {
|
|
||||||
"description": "choose either `'vertical'`, if you want the column mode when Option key is hold during selection (Default)\nor `'force'`, if you want to force selection regardless of whether the terminal is in mouse events mode\n(inside tmux or vim with mouse mode enabled for example).",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modifierKeys": {
|
|
||||||
"properties": {
|
|
||||||
"altIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cmdIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"altIsMeta",
|
|
||||||
"cmdIsMeta"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"description": "custom padding (CSS format, i.e.: `top right bottom left` or `top horizontal bottom` or `vertical horizontal` or `all`)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"preserveCWD": {
|
|
||||||
"description": "set to true to preserve working directory when creating splits or tabs",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"quickEdit": {
|
|
||||||
"description": "if `true` on right click selected text will be copied or pasted if no\nselection is present (`true` by default on Windows and disables the context menu feature)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"screenReaderMode": {
|
|
||||||
"description": "set to true to enable screen reading apps (like NVDA) to read the contents of the terminal",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"scrollback": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"selectionColor": {
|
|
||||||
"description": "terminal selection color",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shell": {
|
|
||||||
"description": "the shell to run when spawning a new session (e.g. /usr/local/bin/fish)\nif left empty, your system's login shell will be used by default\n\nWindows\n- Make sure to use a full path if the binary name doesn't work\n- Remove `--login` in shellArgs\n\nWindows Subsystem for Linux (WSL) - previously Bash on Windows\n- Example: `C:\\\\Windows\\\\System32\\\\wsl.exe`\n\nGit-bash on Windows\n- Example: `C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe`\n\nPowerShell on Windows\n- Example: `C:\\\\WINDOWS\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`\n\nCygwin\n- Example: `C:\\\\cygwin64\\\\bin\\\\bash.exe`\n\nGit Bash\n- Example: `C:\\\\Program Files\\\\Git\\\\git-cmd.exe`\nThen Add `--command=usr/bin/bash.exe` to shellArgs",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shellArgs": {
|
|
||||||
"description": "for setting shell arguments (e.g. for using interactive shellArgs: `['-i']`)\nby default `['--login']` will be used",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"showHamburgerMenu": {
|
|
||||||
"description": "if you're using a Linux setup which show native menus, set to false\n\ndefault: `true` on Linux, `true` on Windows, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"showWindowControls": {
|
|
||||||
"description": "set to `false` if you want to hide the minimize, maximize and close buttons\n\nadditionally, set to `'left'` if you want them on the left, like in Ubuntu\n\ndefault: `true` on Windows and Linux, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
"left",
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"termCSS": {
|
|
||||||
"description": "custom CSS to embed in the terminal window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"uiFontFamily": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"webGLRenderer": {
|
|
||||||
"description": "Whether to use the WebGL renderer. Set it to false to use canvas-based\nrendering (slower, but supports transparent backgrounds)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"webLinksActivationKey": {
|
|
||||||
"description": "keypress required for weblink activation: [ctrl | alt | meta | shift]",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
"alt",
|
|
||||||
"ctrl",
|
|
||||||
"meta",
|
|
||||||
"shift"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"windowSize": {
|
|
||||||
"description": "Initial window size in pixels",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2,
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"workingDirectory": {
|
|
||||||
"description": "set custom startup directory (must be an absolute path)",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"configOptions": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"autoUpdatePlugins": {
|
|
||||||
"description": "if `true` (default), Hyper will update plugins every 5 hours\nyou can also set it to a custom time e.g. `1d` or `2h`",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"boolean"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"defaultSSHApp": {
|
|
||||||
"description": "if `true` hyper will be set as the default protocol client for SSH",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"disableAutoUpdates": {
|
|
||||||
"description": "if `true` hyper will not check for updates",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"updateChannel": {
|
|
||||||
"description": "choose either `'stable'` for receiving highly polished, or `'canary'` for less polished but more frequent updates",
|
|
||||||
"enum": [
|
|
||||||
"canary",
|
|
||||||
"stable"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"useConpty": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"autoUpdatePlugins",
|
|
||||||
"defaultSSHApp",
|
|
||||||
"disableAutoUpdates",
|
|
||||||
"updateChannel"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"backgroundColor": {
|
|
||||||
"description": "terminal background color\n\nopacity is only supported on macOS",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bell": {
|
|
||||||
"description": "Supported Options:\n1. 'SOUND' -> Enables the bell as a sound\n2. false: turns off the bell",
|
|
||||||
"enum": [
|
|
||||||
"SOUND",
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSound": {
|
|
||||||
"description": "base64 encoded string of the sound file to use for the bell\nif null, the default bell will be used",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bellSoundURL": {
|
|
||||||
"description": "An absolute file path to a sound file on the machine.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"borderColor": {
|
|
||||||
"description": "border color (window, tabs)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"colors": {
|
|
||||||
"description": "the full list. if you're going to provide the full color palette,\nincluding the 6 x 6 color cubes and the grayscale map, just provide\nan array here instead of a color map object",
|
|
||||||
"properties": {
|
|
||||||
"black": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"blue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"green": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlack": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightBlue": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightCyan": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightGreen": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightMagenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightRed": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightWhite": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"lightYellow": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"magenta": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"red": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"white": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"yellow": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"black",
|
|
||||||
"blue",
|
|
||||||
"cyan",
|
|
||||||
"green",
|
|
||||||
"lightBlack",
|
|
||||||
"lightBlue",
|
|
||||||
"lightCyan",
|
|
||||||
"lightGreen",
|
|
||||||
"lightMagenta",
|
|
||||||
"lightRed",
|
|
||||||
"lightWhite",
|
|
||||||
"lightYellow",
|
|
||||||
"magenta",
|
|
||||||
"red",
|
|
||||||
"white",
|
|
||||||
"yellow"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"copyOnSelect": {
|
|
||||||
"description": "if `true` selected text will automatically be copied to the clipboard",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"css": {
|
|
||||||
"description": "custom CSS to embed in the main window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorAccentColor": {
|
|
||||||
"description": "terminal text color under BLOCK cursor",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorBlink": {
|
|
||||||
"description": "set to `true` for blinking cursor",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cursorColor": {
|
|
||||||
"description": "terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"cursorShape": {
|
|
||||||
"description": "`'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █",
|
|
||||||
"enum": [
|
|
||||||
"BEAM",
|
|
||||||
"BLOCK",
|
|
||||||
"UNDERLINE"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"disableLigatures": {
|
|
||||||
"description": "if `false` Hyper will use ligatures provided by some fonts",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "for environment variables",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"fontFamily": {
|
|
||||||
"description": "font family with optional fallbacks",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"fontSize": {
|
|
||||||
"description": "default font size in pixels for all tabs",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"fontWeight": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "default font weight eg:'normal', '400', 'bold'"
|
|
||||||
},
|
|
||||||
"fontWeightBold": {
|
|
||||||
"$ref": "#/definitions/FontWeight",
|
|
||||||
"description": "font weight for bold characters eg:'normal', '600', 'bold'"
|
|
||||||
},
|
|
||||||
"foregroundColor": {
|
|
||||||
"description": "color of the text",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"imageSupport": {
|
|
||||||
"description": "Whether to enable Sixel and iTerm2 inline image protocol support or not.",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"letterSpacing": {
|
|
||||||
"description": "letter spacing as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"lineHeight": {
|
|
||||||
"description": "line height as a relative unit",
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"macOptionSelectionMode": {
|
|
||||||
"description": "choose either `'vertical'`, if you want the column mode when Option key is hold during selection (Default)\nor `'force'`, if you want to force selection regardless of whether the terminal is in mouse events mode\n(inside tmux or vim with mouse mode enabled for example).",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"modifierKeys": {
|
|
||||||
"properties": {
|
|
||||||
"altIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"cmdIsMeta": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"altIsMeta",
|
|
||||||
"cmdIsMeta"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"padding": {
|
|
||||||
"description": "custom padding (CSS format, i.e.: `top right bottom left` or `top horizontal bottom` or `vertical horizontal` or `all`)",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"preserveCWD": {
|
|
||||||
"description": "set to true to preserve working directory when creating splits or tabs",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"quickEdit": {
|
|
||||||
"description": "if `true` on right click selected text will be copied or pasted if no\nselection is present (`true` by default on Windows and disables the context menu feature)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"screenReaderMode": {
|
|
||||||
"description": "set to true to enable screen reading apps (like NVDA) to read the contents of the terminal",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"scrollback": {
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
"selectionColor": {
|
|
||||||
"description": "terminal selection color",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shell": {
|
|
||||||
"description": "the shell to run when spawning a new session (e.g. /usr/local/bin/fish)\nif left empty, your system's login shell will be used by default\n\nWindows\n- Make sure to use a full path if the binary name doesn't work\n- Remove `--login` in shellArgs\n\nWindows Subsystem for Linux (WSL) - previously Bash on Windows\n- Example: `C:\\\\Windows\\\\System32\\\\wsl.exe`\n\nGit-bash on Windows\n- Example: `C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe`\n\nPowerShell on Windows\n- Example: `C:\\\\WINDOWS\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe`\n\nCygwin\n- Example: `C:\\\\cygwin64\\\\bin\\\\bash.exe`\n\nGit Bash\n- Example: `C:\\\\Program Files\\\\Git\\\\git-cmd.exe`\nThen Add `--command=usr/bin/bash.exe` to shellArgs",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"shellArgs": {
|
|
||||||
"description": "for setting shell arguments (e.g. for using interactive shellArgs: `['-i']`)\nby default `['--login']` will be used",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"showHamburgerMenu": {
|
|
||||||
"description": "if you're using a Linux setup which show native menus, set to false\n\ndefault: `true` on Linux, `true` on Windows, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"showWindowControls": {
|
|
||||||
"description": "set to `false` if you want to hide the minimize, maximize and close buttons\n\nadditionally, set to `'left'` if you want them on the left, like in Ubuntu\n\ndefault: `true` on Windows and Linux, ignored on macOS",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
false,
|
|
||||||
"left",
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"termCSS": {
|
|
||||||
"description": "custom CSS to embed in the terminal window",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"uiFontFamily": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"webGLRenderer": {
|
|
||||||
"description": "Whether to use the WebGL renderer. Set it to false to use canvas-based\nrendering (slower, but supports transparent backgrounds)",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"webLinksActivationKey": {
|
|
||||||
"description": "keypress required for weblink activation: [ctrl | alt | meta | shift]",
|
|
||||||
"enum": [
|
|
||||||
"",
|
|
||||||
"alt",
|
|
||||||
"ctrl",
|
|
||||||
"meta",
|
|
||||||
"shift"
|
|
||||||
],
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"windowSize": {
|
|
||||||
"description": "Initial window size in pixels",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "number"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"maxItems": 2,
|
|
||||||
"minItems": 2,
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"workingDirectory": {
|
|
||||||
"description": "set custom startup directory (must be an absolute path)",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"backgroundColor",
|
|
||||||
"bell",
|
|
||||||
"bellSound",
|
|
||||||
"bellSoundURL",
|
|
||||||
"borderColor",
|
|
||||||
"colors",
|
|
||||||
"copyOnSelect",
|
|
||||||
"css",
|
|
||||||
"cursorAccentColor",
|
|
||||||
"cursorBlink",
|
|
||||||
"cursorColor",
|
|
||||||
"cursorShape",
|
|
||||||
"disableLigatures",
|
|
||||||
"env",
|
|
||||||
"fontFamily",
|
|
||||||
"fontSize",
|
|
||||||
"fontWeight",
|
|
||||||
"fontWeightBold",
|
|
||||||
"foregroundColor",
|
|
||||||
"imageSupport",
|
|
||||||
"letterSpacing",
|
|
||||||
"lineHeight",
|
|
||||||
"macOptionSelectionMode",
|
|
||||||
"padding",
|
|
||||||
"preserveCWD",
|
|
||||||
"quickEdit",
|
|
||||||
"screenReaderMode",
|
|
||||||
"scrollback",
|
|
||||||
"selectionColor",
|
|
||||||
"shell",
|
|
||||||
"shellArgs",
|
|
||||||
"showHamburgerMenu",
|
|
||||||
"showWindowControls",
|
|
||||||
"termCSS",
|
|
||||||
"webGLRenderer",
|
|
||||||
"webLinksActivationKey",
|
|
||||||
"workingDirectory"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"defaultProfile": {
|
|
||||||
"description": "The default profile name to use when launching a new session",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"profiles": {
|
|
||||||
"description": "A list of profiles to use",
|
|
||||||
"items": {
|
|
||||||
"properties": {
|
|
||||||
"config": {
|
|
||||||
"$ref": "#/definitions/Partial<profileConfigOptions>",
|
|
||||||
"description": "Specify all the options you want to override for each profile.\nOptions set here override the defaults set in the root."
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"config",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"defaultProfile",
|
|
||||||
"profiles"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"properties": {
|
|
||||||
"config": {
|
|
||||||
"$ref": "#/definitions/configOptions"
|
|
||||||
},
|
|
||||||
"keymaps": {
|
|
||||||
"additionalProperties": {
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"description": "Example\n'window:devtools': 'cmd+alt+o',",
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"localPlugins": {
|
|
||||||
"description": "in development, you can create a directory under\n`plugins/local/` and include it here\nto load it and avoid it being `npm install`ed",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"plugins": {
|
|
||||||
"description": "a list of plugins to fetch and install from npm\nformat: [@org/]project[#version]\nexamples:\n `hyperpower`\n `@company/project`\n `project#1.0.1`",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,21 +1,23 @@
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import Config from 'electron-store';
|
import Config from 'electron-store';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
|
||||||
export const defaults = {
|
const defaults = {
|
||||||
windowPosition: [50, 50] as [number, number],
|
windowPosition: [50, 50],
|
||||||
windowSize: [540, 380] as [number, number]
|
windowSize: [540, 380]
|
||||||
};
|
};
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
const cfg = new Config({defaults});
|
const cfg = new Config({defaults});
|
||||||
|
|
||||||
export function get() {
|
export default {
|
||||||
const position = cfg.get('windowPosition', defaults.windowPosition);
|
defaults,
|
||||||
const size = cfg.get('windowSize', defaults.windowSize);
|
get() {
|
||||||
return {position, size};
|
const position = cfg.get('windowPosition');
|
||||||
}
|
const size = cfg.get('windowSize');
|
||||||
export function recordState(win: BrowserWindow) {
|
return {position, size};
|
||||||
cfg.set('windowPosition', win.getPosition());
|
},
|
||||||
cfg.set('windowSize', win.getSize());
|
recordState(win: BrowserWindow) {
|
||||||
}
|
cfg.set('windowPosition', win.getPosition());
|
||||||
|
cfg.set('windowSize', win.getSize());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
8
app/ext-modules.d.ts
vendored
Normal file
8
app/ext-modules.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
declare module 'git-describe' {
|
||||||
|
export function gitDescribe(...args: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'default-shell' {
|
||||||
|
const val: string;
|
||||||
|
export default val;
|
||||||
|
}
|
||||||
1
app/index.d.ts
vendored
Normal file
1
app/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// Dummy file, required by tsc
|
||||||
173
app/index.ts
173
app/index.ts
|
|
@ -1,40 +1,89 @@
|
||||||
// eslint-disable-next-line import/order
|
|
||||||
import {cfgPath} from './config/paths';
|
|
||||||
|
|
||||||
// Print diagnostic information for a few arguments instead of running Hyper.
|
// Print diagnostic information for a few arguments instead of running Hyper.
|
||||||
if (['--help', '-v', '--version'].includes(process.argv[1])) {
|
if (['--help', '-v', '--version'].includes(process.argv[1])) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const {version} = require('./package');
|
const {version} = require('./package');
|
||||||
|
const configLocation = process.platform === 'win32' ? `${process.env.userprofile}\\.hyper.js` : '~/.hyper.js';
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log(`Hyper version ${version}`);
|
console.log(`Hyper version ${version}`);
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Hyper does not accept any command line arguments. Please modify the config file instead.');
|
console.log('Hyper does not accept any command line arguments. Please modify the config file instead.');
|
||||||
console.log(`Hyper configuration file located at: ${cfgPath}`);
|
//eslint-disable-next-line no-console
|
||||||
|
console.log(`Hyper configuration file located at: ${configLocation}`);
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable remote module
|
const checkSquirrel = () => {
|
||||||
// eslint-disable-next-line import/order
|
let squirrel;
|
||||||
import {initialize as remoteInitialize} from '@electron/remote/main';
|
|
||||||
remoteInitialize();
|
|
||||||
|
|
||||||
// set up config
|
try {
|
||||||
// eslint-disable-next-line import/order
|
squirrel = require('electron-squirrel-startup');
|
||||||
import * as config from './config';
|
//eslint-disable-next-line no-empty
|
||||||
config.setup();
|
} catch (err) {}
|
||||||
|
if (squirrel) {
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle startup squirrel events
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const systemContextMenu = require('./system-context-menu');
|
||||||
|
|
||||||
|
switch (process.argv[1]) {
|
||||||
|
case '--squirrel-install':
|
||||||
|
case '--squirrel-updated':
|
||||||
|
systemContextMenu.add(() => {
|
||||||
|
checkSquirrel();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case '--squirrel-uninstall':
|
||||||
|
systemContextMenu.remove(() => {
|
||||||
|
checkSquirrel();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
checkSquirrel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Native
|
// Native
|
||||||
import {resolve} from 'path';
|
import {resolve} from 'path';
|
||||||
|
|
||||||
// Packages
|
// Packages
|
||||||
import {app, BrowserWindow, Menu, screen} from 'electron';
|
import {app, BrowserWindow, Menu} from 'electron';
|
||||||
|
|
||||||
import isDev from 'electron-is-dev';
|
|
||||||
import {gitDescribe} from 'git-describe';
|
import {gitDescribe} from 'git-describe';
|
||||||
import parseUrl from 'parse-url';
|
import isDev from 'electron-is-dev';
|
||||||
|
import * as config from './config';
|
||||||
|
|
||||||
|
// Hack - this declararion doesn't work when put into ./ext-modules.d.ts for some reason so it's in this file for the time being
|
||||||
|
declare module 'electron' {
|
||||||
|
interface App {
|
||||||
|
config: typeof import('./config');
|
||||||
|
plugins: typeof import('./plugins');
|
||||||
|
getWindows: () => Set<BrowserWindow>;
|
||||||
|
getLastFocusedWindow: () => BrowserWindow | null;
|
||||||
|
windowCallback: (win: BrowserWindow) => void;
|
||||||
|
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow;
|
||||||
|
setVersion: (version: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server = import('./rpc').Server;
|
||||||
|
interface BrowserWindow {
|
||||||
|
uid: string;
|
||||||
|
sessions: Map<any, any>;
|
||||||
|
focusTime: number;
|
||||||
|
clean: () => void;
|
||||||
|
rpc: Server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up config
|
||||||
|
config.setup();
|
||||||
|
|
||||||
import * as AppMenu from './menus/menu';
|
|
||||||
import * as plugins from './plugins';
|
import * as plugins from './plugins';
|
||||||
import {newWindow} from './ui/window';
|
|
||||||
import {installCLI} from './utils/cli-install';
|
import {installCLI} from './utils/cli-install';
|
||||||
|
import * as AppMenu from './menus/menu';
|
||||||
|
import {newWindow} from './ui/window';
|
||||||
import * as windowUtils from './utils/window-utils';
|
import * as windowUtils from './utils/window-utils';
|
||||||
|
|
||||||
const windowSet = new Set<BrowserWindow>([]);
|
const windowSet = new Set<BrowserWindow>([]);
|
||||||
|
|
@ -55,54 +104,54 @@ app.getLastFocusedWindow = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Disabling Chromium GPU blacklist');
|
console.log('Disabling Chromium GPU blacklist');
|
||||||
app.commandLine.appendSwitch('ignore-gpu-blacklist');
|
app.commandLine.appendSwitch('ignore-gpu-blacklist');
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('running in dev mode');
|
console.log('running in dev mode');
|
||||||
|
|
||||||
// Override default appVersion which is set from package.json
|
// Override default appVersion which is set from package.json
|
||||||
gitDescribe({customArguments: ['--tags']}, (error: any, gitInfo: {raw: string}) => {
|
gitDescribe({customArguments: ['--tags']}, (error: any, gitInfo: any) => {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
app.setVersion(gitInfo.raw);
|
app.setVersion(gitInfo.raw);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('running in prod mode');
|
console.log('running in prod mode');
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `file://${resolve(isDev ? __dirname : app.getAppPath(), 'index.html')}`;
|
const url = `file://${resolve(isDev ? __dirname : app.getAppPath(), 'index.html')}`;
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('electron will open', url);
|
console.log('electron will open', url);
|
||||||
|
|
||||||
async function installDevExtensions(isDev_: boolean) {
|
function installDevExtensions(isDev_: boolean) {
|
||||||
if (!isDev_) {
|
if (!isDev_) {
|
||||||
return [];
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
const {default: installer, REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS} = await import('electron-devtools-installer');
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const installer = require('electron-devtools-installer') as typeof import('electron-devtools-installer');
|
||||||
|
|
||||||
const extensions = [REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS];
|
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'] as const;
|
||||||
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
|
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload)));
|
||||||
extensions.map((extension) => installer(extension, {forceDownload, loadExtensionOptions: {allowFileAccess: true}}))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
app.on('ready', () =>
|
app.on('ready', () =>
|
||||||
installDevExtensions(isDev)
|
installDevExtensions(isDev)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
function createWindow(
|
function createWindow(fn?: (win: BrowserWindow) => void, options: Record<string, any> = {}) {
|
||||||
fn?: (win: BrowserWindow) => void,
|
const cfg = plugins.getDecoratedConfig();
|
||||||
options: {size?: [number, number]; position?: [number, number]} = {},
|
|
||||||
profileName: string = config.getDefaultProfile()
|
|
||||||
) {
|
|
||||||
const cfg = plugins.getDecoratedConfig(profileName);
|
|
||||||
|
|
||||||
const winSet = config.getWin();
|
const winSet = config.getWin();
|
||||||
let [startX, startY] = winSet.position;
|
let [startX, startY] = winSet.position;
|
||||||
|
|
||||||
const [width, height] = options.size ? options.size : cfg.windowSize || winSet.size;
|
const [width, height] = options.size ? options.size : cfg.windowSize || winSet.size;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const {screen} = require('electron');
|
||||||
|
|
||||||
const winPos = options.position;
|
const winPos = options.position;
|
||||||
|
|
||||||
|
|
@ -139,13 +188,9 @@ app.on('ready', () =>
|
||||||
[startX, startY] = config.windowDefaults.windowPosition;
|
[startX, startY] = config.windowDefaults.windowPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hwin = newWindow({width, height, x: startX, y: startY}, cfg, fn, profileName);
|
const hwin = newWindow({width, height, x: startX, y: startY}, cfg, fn);
|
||||||
windowSet.add(hwin);
|
windowSet.add(hwin);
|
||||||
void hwin.loadURL(url);
|
hwin.loadURL(url);
|
||||||
|
|
||||||
hwin.once('ready-to-show', () => {
|
|
||||||
hwin.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
// the window can be closed by the browser process itself
|
// the window can be closed by the browser process itself
|
||||||
hwin.on('close', () => {
|
hwin.on('close', () => {
|
||||||
|
|
@ -153,6 +198,12 @@ app.on('ready', () =>
|
||||||
windowSet.delete(hwin);
|
windowSet.delete(hwin);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
hwin.on('closed', () => {
|
||||||
|
if (process.platform !== 'darwin' && windowSet.size === 0) {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return hwin;
|
return hwin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,12 +222,6 @@ app.on('ready', () =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const makeMenu = () => {
|
const makeMenu = () => {
|
||||||
const menu = plugins.decorateMenu(AppMenu.createMenu(createWindow, plugins.getLoadedPluginVersions));
|
const menu = plugins.decorateMenu(AppMenu.createMenu(createWindow, plugins.getLoadedPluginVersions));
|
||||||
|
|
||||||
|
|
@ -190,7 +235,7 @@ app.on('ready', () =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
app.dock?.setMenu(dockMenu);
|
app.dock.setMenu(dockMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.setApplicationMenu(AppMenu.buildMenu(menu));
|
Menu.setApplicationMenu(AppMenu.buildMenu(menu));
|
||||||
|
|
@ -203,26 +248,26 @@ app.on('ready', () =>
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
// check if should be set/removed as default ssh protocol client
|
// check if should be set/removed as default ssh protocol client
|
||||||
if (config.getConfig().defaultSSHApp && !app.isDefaultProtocolClient('ssh')) {
|
if (config.getConfig().defaultSSHApp && !app.isDefaultProtocolClient('ssh')) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Setting Hyper as default client for ssh:// protocol');
|
console.log('Setting Hyper as default client for ssh:// protocol');
|
||||||
app.setAsDefaultProtocolClient('ssh');
|
app.setAsDefaultProtocolClient('ssh');
|
||||||
} else if (!config.getConfig().defaultSSHApp && app.isDefaultProtocolClient('ssh')) {
|
} else if (!config.getConfig().defaultSSHApp && app.isDefaultProtocolClient('ssh')) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Removing Hyper from default client for ssh:// protocol');
|
console.log('Removing Hyper from default client for ssh:// protocol');
|
||||||
app.removeAsDefaultProtocolClient('ssh');
|
app.removeAsDefaultProtocolClient('ssh');
|
||||||
}
|
}
|
||||||
void installCLI(false);
|
installCLI(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.error('Error while loading devtools extensions', err);
|
console.error('Error while loading devtools extensions', err);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
app.on('open-file', (event, path) => {
|
||||||
* Get last focused BrowserWindow or create new if none and callback
|
|
||||||
* @param callback Function to call with the BrowserWindow
|
|
||||||
*/
|
|
||||||
function GetWindow(callback: (win: BrowserWindow) => void) {
|
|
||||||
const lastWindow = app.getLastFocusedWindow();
|
const lastWindow = app.getLastFocusedWindow();
|
||||||
|
const callback = (win: BrowserWindow) => win.rpc.emit('open file', {path});
|
||||||
if (lastWindow) {
|
if (lastWindow) {
|
||||||
callback(lastWindow);
|
callback(lastWindow);
|
||||||
} else if (!lastWindow && {}.hasOwnProperty.call(app, 'createWindow')) {
|
} else if (!lastWindow && {}.hasOwnProperty.call(app, 'createWindow')) {
|
||||||
|
|
@ -232,16 +277,18 @@ function GetWindow(callback: (win: BrowserWindow) => void) {
|
||||||
// sets his callback to an app.windowCallback property.
|
// sets his callback to an app.windowCallback property.
|
||||||
app.windowCallback = callback;
|
app.windowCallback = callback;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
app.on('open-file', (_event, path) => {
|
|
||||||
GetWindow((win: BrowserWindow) => {
|
|
||||||
win.rpc.emit('open file', {path});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('open-url', (_event, sshUrl) => {
|
app.on('open-url', (event, sshUrl) => {
|
||||||
GetWindow((win: BrowserWindow) => {
|
const lastWindow = app.getLastFocusedWindow();
|
||||||
win.rpc.emit('open ssh', parseUrl(sshUrl));
|
const callback = (win: BrowserWindow) => win.rpc.emit('open ssh', sshUrl);
|
||||||
});
|
if (lastWindow) {
|
||||||
|
callback(lastWindow);
|
||||||
|
} else if (!lastWindow && {}.hasOwnProperty.call(app, 'createWindow')) {
|
||||||
|
app.createWindow(callback);
|
||||||
|
} else {
|
||||||
|
// If createWindow doesn't exist yet ('ready' event was not fired),
|
||||||
|
// sets his callback to an app.windowCallback property.
|
||||||
|
app.windowCallback = callback;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
"window:reload": "ctrl+shift+r",
|
"window:reload": "ctrl+shift+r",
|
||||||
"window:reloadFull": "ctrl+shift+f5",
|
"window:reloadFull": "ctrl+shift+f5",
|
||||||
"window:preferences": "ctrl+,",
|
"window:preferences": "ctrl+,",
|
||||||
"window:hamburgerMenu": "alt+f",
|
|
||||||
"zoom:reset": "ctrl+0",
|
"zoom:reset": "ctrl+0",
|
||||||
"zoom:in": "ctrl+=",
|
"zoom:in": "ctrl+=",
|
||||||
"zoom:out": "ctrl+-",
|
"zoom:out": "ctrl+-",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"window:reload": "ctrl+shift+r",
|
"window:reload": "ctrl+shift+r",
|
||||||
"window:reloadFull": "ctrl+shift+f5",
|
"window:reloadFull": "ctrl+shift+f5",
|
||||||
"window:preferences": "ctrl+,",
|
"window:preferences": "ctrl+,",
|
||||||
"window:hamburgerMenu": "alt+f",
|
"window:hamburgerMenu": "alt",
|
||||||
"zoom:reset": "ctrl+0",
|
"zoom:reset": "ctrl+0",
|
||||||
"zoom:in": "ctrl+=",
|
"zoom:in": "ctrl+=",
|
||||||
"zoom:out": "ctrl+-",
|
"zoom:out": "ctrl+-",
|
||||||
|
|
@ -16,12 +16,6 @@
|
||||||
"alt+f4"
|
"alt+f4"
|
||||||
],
|
],
|
||||||
"tab:new": "ctrl+shift+t",
|
"tab:new": "ctrl+shift+t",
|
||||||
"tab:next": [
|
|
||||||
"ctrl+tab"
|
|
||||||
],
|
|
||||||
"tab:prev": [
|
|
||||||
"ctrl+shift+tab"
|
|
||||||
],
|
|
||||||
"tab:jump:prefix": "ctrl",
|
"tab:jump:prefix": "ctrl",
|
||||||
"pane:next": "ctrl+pageup",
|
"pane:next": "ctrl+pageup",
|
||||||
"pane:prev": "ctrl+pagedown",
|
"pane:prev": "ctrl+pagedown",
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
// Packages
|
// Packages
|
||||||
import {app, dialog, Menu} from 'electron';
|
import {app, dialog, Menu, BrowserWindow} from 'electron';
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
// Utilities
|
// Utilities
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getConfig} from '../config';
|
import {getConfig} from '../config';
|
||||||
import {icon} from '../config/paths';
|
import {icon} from '../config/paths';
|
||||||
|
import viewMenu from './menus/view';
|
||||||
|
import shellMenu from './menus/shell';
|
||||||
|
import editMenu from './menus/edit';
|
||||||
|
import pluginsMenu from './menus/plugins';
|
||||||
|
import windowMenu from './menus/window';
|
||||||
|
import helpMenu from './menus/help';
|
||||||
|
import darwinMenu from './menus/darwin';
|
||||||
import {getDecoratedKeymaps} from '../plugins';
|
import {getDecoratedKeymaps} from '../plugins';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
import {getRendererTypes} from '../utils/renderer-utils';
|
import {getRendererTypes} from '../utils/renderer-utils';
|
||||||
|
|
||||||
import darwinMenu from './menus/darwin';
|
const appName = app.getName();
|
||||||
import editMenu from './menus/edit';
|
|
||||||
import helpMenu from './menus/help';
|
|
||||||
import shellMenu from './menus/shell';
|
|
||||||
import toolsMenu from './menus/tools';
|
|
||||||
import viewMenu from './menus/view';
|
|
||||||
import windowMenu from './menus/window';
|
|
||||||
|
|
||||||
const appName = app.name;
|
|
||||||
const appVersion = app.getVersion();
|
const appVersion = app.getVersion();
|
||||||
|
|
||||||
let menu_: Menu;
|
let menu_: Menu;
|
||||||
|
|
@ -36,14 +34,14 @@ export const createMenu = (
|
||||||
|
|
||||||
let updateChannel = 'stable';
|
let updateChannel = 'stable';
|
||||||
|
|
||||||
if (config?.updateChannel && config.updateChannel === 'canary') {
|
if (config && config.updateChannel && config.updateChannel === 'canary') {
|
||||||
updateChannel = 'canary';
|
updateChannel = 'canary';
|
||||||
}
|
}
|
||||||
|
|
||||||
const showAbout = () => {
|
const showAbout = () => {
|
||||||
const loadedPlugins = getLoadedPluginVersions();
|
const loadedPlugins = getLoadedPluginVersions();
|
||||||
const pluginList =
|
const pluginList =
|
||||||
loadedPlugins.length === 0 ? 'none' : loadedPlugins.map((plugin) => `\n ${plugin.name} (${plugin.version})`);
|
loadedPlugins.length === 0 ? 'none' : loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
|
||||||
|
|
||||||
const rendererCounts = Object.values(getRendererTypes()).reduce((acc: Record<string, number>, type) => {
|
const rendererCounts = Object.values(getRendererTypes()).reduce((acc: Record<string, number>, type) => {
|
||||||
acc[type] = acc[type] ? acc[type] + 1 : 1;
|
acc[type] = acc[type] ? acc[type] + 1 : 1;
|
||||||
|
|
@ -53,36 +51,20 @@ export const createMenu = (
|
||||||
.map(([type, count]) => type + (count > 1 ? ` (${count})` : ''))
|
.map(([type, count]) => type + (count > 1 ? ` (${count})` : ''))
|
||||||
.join(', ');
|
.join(', ');
|
||||||
|
|
||||||
void dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
title: `About ${appName}`,
|
title: `About ${appName}`,
|
||||||
message: `${appName} ${appVersion} (${updateChannel})`,
|
message: `${appName} ${appVersion} (${updateChannel})`,
|
||||||
detail: `
|
detail: `Renderers: ${renderers}\nPlugins: ${pluginList}\n\nCreated by Guillermo Rauch\nCopyright © 2020 ZEIT, Inc.`,
|
||||||
Renderers: ${renderers}
|
|
||||||
Plugins: ${pluginList}
|
|
||||||
|
|
||||||
Maintained by QUINE Global
|
|
||||||
Copyright © 2025
|
|
||||||
|
|
||||||
Created by Guillermo Rauch
|
|
||||||
Copyright © 2022 Vercel, Inc.
|
|
||||||
`
|
|
||||||
.split('\n')
|
|
||||||
.map((z) => z.trim())
|
|
||||||
.join('\n'),
|
|
||||||
buttons: [],
|
buttons: [],
|
||||||
icon: icon as any
|
icon: icon as any
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const menu = [
|
const menu = [
|
||||||
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
|
...(process.platform === 'darwin' ? [darwinMenu(commandKeys, execCommand, showAbout)] : []),
|
||||||
shellMenu(
|
shellMenu(commandKeys, execCommand),
|
||||||
commandKeys,
|
|
||||||
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
|
|
||||||
getConfig().profiles.map((p) => p.name)
|
|
||||||
),
|
|
||||||
editMenu(commandKeys, execCommand),
|
editMenu(commandKeys, execCommand),
|
||||||
viewMenu(commandKeys, execCommand),
|
viewMenu(commandKeys, execCommand),
|
||||||
toolsMenu(commandKeys, execCommand),
|
pluginsMenu(commandKeys, execCommand),
|
||||||
windowMenu(commandKeys, execCommand),
|
windowMenu(commandKeys, execCommand),
|
||||||
helpMenu(commandKeys, showAbout)
|
helpMenu(commandKeys, showAbout)
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
// This menu label is overrided by OSX to be the appName
|
// This menu label is overrided by OSX to be the appName
|
||||||
// The label is set to appName here so it matches actual behavior
|
// The label is set to appName here so it matches actual behavior
|
||||||
import {app} from 'electron';
|
import {app, BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
const darwinMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void,
|
||||||
showAbout: () => void
|
showAbout: () => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
return {
|
return {
|
||||||
label: `${app.name}`,
|
label: `${app.getName()}`,
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'About Hyper',
|
label: 'About Hyper',
|
||||||
|
|
@ -55,5 +54,3 @@ const darwinMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default darwinMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const editMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -31,14 +31,13 @@ const editMenu = (
|
||||||
} as any,
|
} as any,
|
||||||
{
|
{
|
||||||
role: 'paste',
|
role: 'paste',
|
||||||
accelerator: commandKeys['editor:paste'],
|
accelerator: commandKeys['editor:paste']
|
||||||
registerAccelerator: true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Select All',
|
label: 'Select All',
|
||||||
accelerator: commandKeys['editor:selectAll'],
|
accelerator: commandKeys['editor:selectAll'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:selectAll', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:selectAll', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -51,28 +50,28 @@ const editMenu = (
|
||||||
label: 'Previous word',
|
label: 'Previous word',
|
||||||
accelerator: commandKeys['editor:movePreviousWord'],
|
accelerator: commandKeys['editor:movePreviousWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:movePreviousWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:movePreviousWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next word',
|
label: 'Next word',
|
||||||
accelerator: commandKeys['editor:moveNextWord'],
|
accelerator: commandKeys['editor:moveNextWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveNextWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveNextWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line beginning',
|
label: 'Line beginning',
|
||||||
accelerator: commandKeys['editor:moveBeginningLine'],
|
accelerator: commandKeys['editor:moveBeginningLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveBeginningLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveBeginningLine', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line end',
|
label: 'Line end',
|
||||||
accelerator: commandKeys['editor:moveEndLine'],
|
accelerator: commandKeys['editor:moveEndLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:moveEndLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:moveEndLine', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -84,28 +83,28 @@ const editMenu = (
|
||||||
label: 'Previous word',
|
label: 'Previous word',
|
||||||
accelerator: commandKeys['editor:deletePreviousWord'],
|
accelerator: commandKeys['editor:deletePreviousWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deletePreviousWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deletePreviousWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next word',
|
label: 'Next word',
|
||||||
accelerator: commandKeys['editor:deleteNextWord'],
|
accelerator: commandKeys['editor:deleteNextWord'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteNextWord', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteNextWord', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line beginning',
|
label: 'Line beginning',
|
||||||
accelerator: commandKeys['editor:deleteBeginningLine'],
|
accelerator: commandKeys['editor:deleteBeginningLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteBeginningLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteBeginningLine', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Line end',
|
label: 'Line end',
|
||||||
accelerator: commandKeys['editor:deleteEndLine'],
|
accelerator: commandKeys['editor:deleteEndLine'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:deleteEndLine', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:deleteEndLine', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -117,14 +116,14 @@ const editMenu = (
|
||||||
label: 'Clear Buffer',
|
label: 'Clear Buffer',
|
||||||
accelerator: commandKeys['editor:clearBuffer'],
|
accelerator: commandKeys['editor:clearBuffer'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:clearBuffer', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:clearBuffer', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Search',
|
label: 'Search',
|
||||||
accelerator: commandKeys['editor:search'],
|
accelerator: commandKeys['editor:search'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('editor:search', focusedWindow as BrowserWindow | undefined);
|
execCommand('editor:search', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -147,5 +146,3 @@ const editMenu = (
|
||||||
submenu
|
submenu
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default editMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,36 @@
|
||||||
import {release} from 'os';
|
import {release} from 'os';
|
||||||
|
import {app, shell, MenuItemConstructorOptions} from 'electron';
|
||||||
import {app, shell, dialog, clipboard} from 'electron';
|
|
||||||
import type {MenuItemConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
import {getConfig, getPlugins} from '../../config';
|
import {getConfig, getPlugins} from '../../config';
|
||||||
|
const {arch, env, platform, versions} = process;
|
||||||
import {version} from '../../package.json';
|
import {version} from '../../package.json';
|
||||||
|
|
||||||
const {arch, env, platform, versions} = process;
|
export default (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
|
||||||
|
|
||||||
const helpMenu = (commands: Record<string, string>, showAbout: () => void): MenuItemConstructorOptions => {
|
|
||||||
const submenu: MenuItemConstructorOptions[] = [
|
const submenu: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label: `${app.name} Website`,
|
label: `${app.getName()} Website`,
|
||||||
click() {
|
click() {
|
||||||
void shell.openExternal('https://hyper.is');
|
shell.openExternal('https://hyper.is');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report Issue',
|
label: 'Report Issue',
|
||||||
click(menuItem, focusedWindow) {
|
click() {
|
||||||
const body = `<!--
|
const body = `
|
||||||
|
<!--
|
||||||
Hi there! Thank you for discovering and submitting an issue.
|
Hi there! Thank you for discovering and submitting an issue.
|
||||||
Before you submit this; let's make sure of a few things.
|
Before you submit this; let's make sure of a few things.
|
||||||
Please make sure the following boxes are ✅ if they are correct.
|
Please make sure the following boxes are ✅ if they are correct.
|
||||||
If not, please try and fulfil these first.
|
If not, please try and fulfil these first.
|
||||||
-->
|
-->
|
||||||
<!-- 👉 Checked checkbox should look like this: [x] -->
|
<!-- 👉 Checked checkbox should look like this: [x] -->
|
||||||
- [ ] Your Hyper.app version is **${version}**. Please verify you're using the [latest](https://github.com/vercel/hyper/releases/latest) Hyper.app version
|
- [ ] Your Hyper.app version is **${version}**. Please verify your using the [latest](https://github.com/zeit/hyper/releases/latest) Hyper.app version
|
||||||
- [ ] I have searched the [issues](https://github.com/vercel/hyper/issues) of this repo and believe that this is not a duplicate
|
- [ ] I have searched the [issues](https://github.com/zeit/hyper/issues) of this repo and believe that this is not a duplicate
|
||||||
---
|
|
||||||
- **Any relevant information from devtools?** _(CMD+OPTION+I on macOS, CTRL+SHIFT+I elsewhere)_:
|
---
|
||||||
|
- **Any relevant information from devtools?** _(CMD+ALT+I on macOS, CTRL+SHIFT+I elsewhere)_:
|
||||||
<!-- 👉 Replace with info if applicable, or N/A -->
|
<!-- 👉 Replace with info if applicable, or N/A -->
|
||||||
|
|
||||||
- **Is the issue reproducible in vanilla Hyper.app?**
|
- **Is the issue reproducible in vanilla Hyper.app?**
|
||||||
<!-- 👉 Replace with info if applicable, or Is Vanilla. (Vanilla means Hyper.app without any add-ons or extras. Straight out of the box.) -->
|
<!-- 👉 Replace with info if applicable, or Is Vanilla. (Vanilla means Hyper.app without any add-ons or extras. Straight out of the box.) -->
|
||||||
|
|
||||||
## Issue
|
## Issue
|
||||||
|
|
@ -42,54 +40,26 @@ const helpMenu = (commands: Record<string, string>, showAbout: () => void): Menu
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
<!-- hyper.json config -->
|
|
||||||
- **${app.name} version**: ${env.TERM_PROGRAM_VERSION} "${app.getVersion()}"
|
|
||||||
- **OS ARCH VERSION:** ${platform} ${arch} ${release()}
|
|
||||||
- **Electron:** ${versions.electron} **LANG:** ${env.LANG}
|
|
||||||
- **SHELL:** ${env.SHELL} **TERM:** ${env.TERM}
|
|
||||||
<details><summary><strong>hyper.json contents</strong></summary>
|
|
||||||
|
|
||||||
\`\`\`json
|
<!-- ~/.hyper.js config -->
|
||||||
${JSON.stringify(getConfig(), null, 2)}
|
- **${app.getName()} version**: ${env.TERM_PROGRAM_VERSION} "${app.getVersion()}"
|
||||||
\`\`\`
|
|
||||||
</details>
|
|
||||||
<details><summary><strong>plugins</strong></summary>
|
|
||||||
|
|
||||||
\`\`\`json
|
- **OS ARCH VERSION:** ${platform} ${arch} ${release()}
|
||||||
${JSON.stringify(getPlugins(), null, 2)}
|
- **Electron:** ${versions.electron} **LANG:** ${env.LANG}
|
||||||
\`\`\`
|
- **SHELL:** ${env.SHELL} **TERM:** ${env.TERM}
|
||||||
</details>`;
|
|
||||||
|
|
||||||
const issueURL = `https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(body)}`;
|
<details>
|
||||||
const copyAndSend = () => {
|
<summary><strong> ~/.hyper.js contents</strong></summary>
|
||||||
clipboard.writeText(body);
|
<pre>
|
||||||
void shell.openExternal(
|
<code>
|
||||||
`https://github.com/quine-global/hyper/issues/new?body=${encodeURIComponent(
|
${JSON.stringify(getConfig(), null, 2)}
|
||||||
'<!-- We have written the needed data into your clipboard because it was too large to send. ' +
|
|
||||||
'Please paste. -->\n'
|
${JSON.stringify(getPlugins(), null, 2)}
|
||||||
)}`
|
</code>
|
||||||
);
|
</pre>
|
||||||
};
|
</details>`;
|
||||||
if (!focusedWindow) {
|
|
||||||
copyAndSend();
|
shell.openExternal(`https://github.com/zeit/hyper/issues/new?body=${encodeURIComponent(body)}`);
|
||||||
} else if (issueURL.length > 6144) {
|
|
||||||
void dialog
|
|
||||||
.showMessageBox(focusedWindow, {
|
|
||||||
message:
|
|
||||||
'There is too much data to send to GitHub directly. The data will be copied to the clipboard, ' +
|
|
||||||
'please paste it into the GitHub issue page that will open.',
|
|
||||||
type: 'warning',
|
|
||||||
buttons: ['OK', 'Cancel']
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
if (result.response === 0) {
|
|
||||||
copyAndSend();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
void shell.openExternal(issueURL);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -98,7 +68,7 @@ ${JSON.stringify(getPlugins(), null, 2)}
|
||||||
submenu.push(
|
submenu.push(
|
||||||
{type: 'separator'},
|
{type: 'separator'},
|
||||||
{
|
{
|
||||||
label: 'About Hyper',
|
role: 'about',
|
||||||
click() {
|
click() {
|
||||||
showAbout();
|
showAbout();
|
||||||
}
|
}
|
||||||
|
|
@ -110,5 +80,3 @@ ${JSON.stringify(getPlugins(), null, 2)}
|
||||||
submenu
|
submenu
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default helpMenu;
|
|
||||||
|
|
|
||||||
28
app/menus/menus/plugins.ts
Normal file
28
app/menus/menus/plugins.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
|
export default (
|
||||||
|
commands: Record<string, string>,
|
||||||
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
|
): MenuItemConstructorOptions => {
|
||||||
|
return {
|
||||||
|
label: 'Plugins',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
accelerator: commands['plugins:update'],
|
||||||
|
click() {
|
||||||
|
execCommand('plugins:update');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Install Hyper CLI command in PATH',
|
||||||
|
click() {
|
||||||
|
execCommand('cli:install');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import type {BaseWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const shellMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BaseWindow) => void,
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
profiles: string[]
|
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
|
|
||||||
|
|
@ -44,47 +43,6 @@ const shellMenu = (
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
...profiles.map(
|
|
||||||
(profile): MenuItemConstructorOptions => ({
|
|
||||||
label: profile,
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'New Tab',
|
|
||||||
accelerator: commandKeys[`tab:new:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`tab:new:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'New Window',
|
|
||||||
accelerator: commandKeys[`window:new:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`window:new:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Split Down',
|
|
||||||
accelerator: commandKeys[`pane:splitDown:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`pane:splitDown:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Split Right',
|
|
||||||
accelerator: commandKeys[`pane:splitRight:${profile}`],
|
|
||||||
click(item, focusedWindow) {
|
|
||||||
execCommand(`pane:splitRight:${profile}`, focusedWindow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
),
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Close',
|
label: 'Close',
|
||||||
accelerator: commandKeys['pane:close'],
|
accelerator: commandKeys['pane:close'],
|
||||||
|
|
@ -100,5 +58,3 @@ const shellMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default shellMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
const toolsMenu = (
|
|
||||||
commands: Record<string, string>,
|
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
|
||||||
): MenuItemConstructorOptions => {
|
|
||||||
return {
|
|
||||||
label: 'Tools',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Update plugins',
|
|
||||||
accelerator: commands['plugins:update'],
|
|
||||||
click() {
|
|
||||||
execCommand('plugins:update');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Install Hyper CLI command in PATH',
|
|
||||||
click() {
|
|
||||||
execCommand('cli:install');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
...(process.platform === 'win32'
|
|
||||||
? <MenuItemConstructorOptions[]>[
|
|
||||||
{
|
|
||||||
label: 'Add Hyper to system context menu',
|
|
||||||
click() {
|
|
||||||
execCommand('systemContextMenu:add');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Remove Hyper from system context menu',
|
|
||||||
click() {
|
|
||||||
execCommand('systemContextMenu:remove');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
: [])
|
|
||||||
]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default toolsMenu;
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const viewMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
|
|
@ -11,21 +11,21 @@ const viewMenu = (
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
accelerator: commandKeys['window:reload'],
|
accelerator: commandKeys['window:reload'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('window:reload', focusedWindow as BrowserWindow);
|
execCommand('window:reload', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Full Reload',
|
label: 'Full Reload',
|
||||||
accelerator: commandKeys['window:reloadFull'],
|
accelerator: commandKeys['window:reloadFull'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('window:reloadFull', focusedWindow as BrowserWindow);
|
execCommand('window:reloadFull', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Developer Tools',
|
label: 'Developer Tools',
|
||||||
accelerator: commandKeys['window:devtools'],
|
accelerator: commandKeys['window:devtools'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('window:devtools', focusedWindow as BrowserWindow);
|
execCommand('window:devtools', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -35,25 +35,23 @@ const viewMenu = (
|
||||||
label: 'Reset Zoom Level',
|
label: 'Reset Zoom Level',
|
||||||
accelerator: commandKeys['zoom:reset'],
|
accelerator: commandKeys['zoom:reset'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:reset', focusedWindow as BrowserWindow);
|
execCommand('zoom:reset', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom In',
|
label: 'Zoom In',
|
||||||
accelerator: commandKeys['zoom:in'],
|
accelerator: commandKeys['zoom:in'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:in', focusedWindow as BrowserWindow);
|
execCommand('zoom:in', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom Out',
|
label: 'Zoom Out',
|
||||||
accelerator: commandKeys['zoom:out'],
|
accelerator: commandKeys['zoom:out'],
|
||||||
click(item, focusedWindow) {
|
click(item, focusedWindow) {
|
||||||
execCommand('zoom:out', focusedWindow as BrowserWindow);
|
execCommand('zoom:out', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default viewMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import type {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
import {BrowserWindow, MenuItemConstructorOptions} from 'electron';
|
||||||
|
|
||||||
const windowMenu = (
|
export default (
|
||||||
commandKeys: Record<string, string>,
|
commandKeys: Record<string, string>,
|
||||||
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
execCommand: (command: string, focusedWindow?: BrowserWindow) => void
|
||||||
): MenuItemConstructorOptions => {
|
): MenuItemConstructorOptions => {
|
||||||
// Generating tab:jump array
|
// Generating tab:jump array
|
||||||
const tabJump: MenuItemConstructorOptions[] = [];
|
const tabJump = [];
|
||||||
for (let i = 1; i <= 9; i++) {
|
for (let i = 1; i <= 9; i++) {
|
||||||
// 9 is a special number because it means 'last'
|
// 9 is a special number because it means 'last'
|
||||||
const label = i === 9 ? 'Last' : `${i}`;
|
const label = i === 9 ? 'Last' : `${i}`;
|
||||||
|
|
@ -37,14 +37,14 @@ const windowMenu = (
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commandKeys['tab:prev'],
|
accelerator: commandKeys['tab:prev'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('tab:prev', focusedWindow as BrowserWindow);
|
execCommand('tab:prev', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commandKeys['tab:next'],
|
accelerator: commandKeys['tab:next'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('tab:next', focusedWindow as BrowserWindow);
|
execCommand('tab:next', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -63,14 +63,14 @@ const windowMenu = (
|
||||||
label: 'Previous',
|
label: 'Previous',
|
||||||
accelerator: commandKeys['pane:prev'],
|
accelerator: commandKeys['pane:prev'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('pane:prev', focusedWindow as BrowserWindow);
|
execCommand('pane:prev', focusedWindow);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Next',
|
label: 'Next',
|
||||||
accelerator: commandKeys['pane:next'],
|
accelerator: commandKeys['pane:next'],
|
||||||
click: (item, focusedWindow) => {
|
click: (item, focusedWindow) => {
|
||||||
execCommand('pane:next', focusedWindow as BrowserWindow);
|
execCommand('pane:next', focusedWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -81,12 +81,6 @@ const windowMenu = (
|
||||||
{
|
{
|
||||||
role: 'front'
|
role: 'front'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Toggle Always on Top',
|
|
||||||
click: (item, focusedWindow) => {
|
|
||||||
execCommand('window:toggleKeepOnTop', focusedWindow as BrowserWindow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: 'togglefullscreen',
|
role: 'togglefullscreen',
|
||||||
accelerator: commandKeys['window:toggleFullScreen']
|
accelerator: commandKeys['window:toggleFullScreen']
|
||||||
|
|
@ -94,5 +88,3 @@ const windowMenu = (
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default windowMenu;
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import type {BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import fetch from 'electron-fetch';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import fetch from 'electron-fetch';
|
||||||
import {version} from './package.json';
|
import {version} from './package.json';
|
||||||
|
import {BrowserWindow} from 'electron';
|
||||||
|
|
||||||
const NEWS_URL = 'https://hyper-news.now.sh';
|
const NEWS_URL = 'https://hyper-news.now.sh';
|
||||||
|
|
||||||
export default function fetchNotifications(win: BrowserWindow) {
|
export default function fetchNotifications(win: BrowserWindow) {
|
||||||
const {rpc} = win;
|
const {rpc} = win;
|
||||||
const retry = (err?: Error) => {
|
const retry = (err?: any) => {
|
||||||
setTimeout(() => fetchNotifications(win), ms('30m'));
|
setTimeout(() => fetchNotifications(win), ms('30m'));
|
||||||
if (err) {
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.error('Notification messages fetch error', err.stack);
|
console.error('Notification messages fetch error', err.stack);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Checking for notification messages');
|
console.log('Checking for notification messages');
|
||||||
fetch(NEWS_URL, {
|
fetch(NEWS_URL, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -22,13 +22,14 @@ export default function fetchNotifications(win: BrowserWindow) {
|
||||||
'X-Hyper-Platform': process.platform
|
'X-Hyper-Platform': process.platform
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then(res => res.json())
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
const message: {text: string; url: string; dismissable: boolean} | '' = data.message || '';
|
const {message} = data || {};
|
||||||
if (typeof message !== 'object' && message !== '') {
|
if (typeof message !== 'object' && message !== '') {
|
||||||
throw new Error('Bad response');
|
throw new Error('Bad response');
|
||||||
}
|
}
|
||||||
if (message === '') {
|
if (message === '') {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('No matching notification messages');
|
console.log('No matching notification messages');
|
||||||
} else {
|
} else {
|
||||||
rpc.emit('add notification', message);
|
rpc.emit('add notification', message);
|
||||||
|
|
|
||||||
5
app/notify.html
Normal file
5
app/notify.html
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<script>
|
||||||
|
require('electron').ipcRenderer.on('notification', (ev, { title, body }) => {
|
||||||
|
new Notification(title, { body });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,21 +1,46 @@
|
||||||
import {app, Notification} from 'electron';
|
import {resolve} from 'path';
|
||||||
|
import {app, BrowserWindow} from 'electron';
|
||||||
|
import isDev from 'electron-is-dev';
|
||||||
|
|
||||||
import {icon} from './config/paths';
|
let win: BrowserWindow;
|
||||||
|
|
||||||
export default function notify(title: string, body = '', details: {error?: any} = {}) {
|
// the hack of all hacks
|
||||||
|
// electron doesn't have a built in notification thing,
|
||||||
|
// so we launch a window on which we can use the
|
||||||
|
// HTML5 `Notification` API :'(
|
||||||
|
|
||||||
|
let buffer: string[][] = [];
|
||||||
|
|
||||||
|
function notify(title: string, body = '', details: any = {}) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log(`[Notification] ${title}: ${body}`);
|
console.log(`[Notification] ${title}: ${body}`);
|
||||||
if (details.error) {
|
if (details.error) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.error(details.error);
|
console.error(details.error);
|
||||||
}
|
}
|
||||||
if (app.isReady()) {
|
if (win) {
|
||||||
_createNotification(title, body);
|
win.webContents.send('notification', {title, body});
|
||||||
} else {
|
} else {
|
||||||
app.on('ready', () => {
|
buffer.push([title, body]);
|
||||||
_createNotification(title, body);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _createNotification = (title: string, body: string) => {
|
app.on('ready', () => {
|
||||||
new Notification({title, body, ...(process.platform === 'linux' && {icon})}).show();
|
const win_ = new BrowserWindow({
|
||||||
};
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const url = `file://${resolve(isDev ? __dirname : app.getAppPath(), 'notify.html')}`;
|
||||||
|
win_.loadURL(url);
|
||||||
|
win_.webContents.on('dom-ready', () => {
|
||||||
|
win = win_;
|
||||||
|
buffer.forEach(([title, body]) => {
|
||||||
|
notify(title, body);
|
||||||
|
});
|
||||||
|
buffer = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default notify;
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,38 @@
|
||||||
"name": "hyper",
|
"name": "hyper",
|
||||||
"productName": "Hyper",
|
"productName": "Hyper",
|
||||||
"description": "A terminal built on web technologies",
|
"description": "A terminal built on web technologies",
|
||||||
"version": "4.0.0-q-canary.8",
|
"version": "3.1.0-canary.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "ZEIT, Inc.",
|
"name": "ZEIT, Inc.",
|
||||||
"email": "team@zeit.co"
|
"email": "team@zeit.co"
|
||||||
},
|
},
|
||||||
"repository": "quine-global/hyper",
|
"repository": "zeit/hyper",
|
||||||
"scripts": {
|
|
||||||
"postinstall": "npx patch-package"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "7.27.0",
|
"async-retry": "1.3.1",
|
||||||
"@electron/remote": "2.1.2",
|
"color": "3.1.2",
|
||||||
"ast-types": "^0.16.1",
|
"convert-css-color-name-to-hex": "0.1.1",
|
||||||
"async-retry": "1.3.3",
|
|
||||||
"chokidar": "^3.6.0",
|
|
||||||
"color": "4.2.3",
|
|
||||||
"default-shell": "1.0.1",
|
"default-shell": "1.0.1",
|
||||||
"electron-devtools-installer": "3.2.1",
|
"electron-store": "5.1.0",
|
||||||
"electron-fetch": "1.9.1",
|
"electron-fetch": "1.4.0",
|
||||||
"electron-is-dev": "2.0.0",
|
"electron-is-dev": "1.1.0",
|
||||||
"electron-store": "8.2.0",
|
"electron-squirrel-startup": "1.0.0",
|
||||||
"fs-extra": "11.3.0",
|
"file-uri-to-path": "2.0.0",
|
||||||
"git-describe": "4.1.1",
|
"fs-extra": "8.1.0",
|
||||||
"lodash": "4.17.21",
|
"git-describe": "4.0.4",
|
||||||
"ms": "2.1.3",
|
"lodash": "4.17.15",
|
||||||
"native-process-working-directory": "^1.0.2",
|
"mkdirp": "0.5.1",
|
||||||
"node-pty": "1.1.0-beta33",
|
"ms": "2.1.2",
|
||||||
"os-locale": "5.0.0",
|
"node-pty": "0.9.0",
|
||||||
"parse-url": "9.2.0",
|
"os-locale": "4.0.0",
|
||||||
"queue": "6.0.2",
|
"parse-url": "5.0.1",
|
||||||
"quine-electron-drag-click": "2.0.0",
|
"pify": "4.0.1",
|
||||||
"react": "18.3.1",
|
"queue": "6.0.1",
|
||||||
"react-dom": "18.3.1",
|
"react": "16.12.0",
|
||||||
"recast": "0.23.11",
|
"react-dom": "16.12.0",
|
||||||
"semver": "7.7.1",
|
"semver": "7.1.1",
|
||||||
"shell-env": "3.0.1",
|
"shell-env": "3.0.0",
|
||||||
"sudo-prompt": "^9.2.1",
|
"uuid": "3.4.0",
|
||||||
"uuid": "10.0.0"
|
"winreg": "1.2.4"
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"native-reg": "1.1.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"node-gyp": "^10.2.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
120
app/plugins.ts
120
app/plugins.ts
|
|
@ -1,27 +1,16 @@
|
||||||
/* eslint-disable eslint-comments/disable-enable-pair */
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
import {app, dialog, BrowserWindow, App} from 'electron';
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
import {exec, execFile} from 'child_process';
|
|
||||||
import {writeFileSync} from 'fs';
|
|
||||||
import {resolve, basename} from 'path';
|
import {resolve, basename} from 'path';
|
||||||
import {promisify} from 'util';
|
import {writeFileSync} from 'fs';
|
||||||
|
|
||||||
import {app, dialog, ipcMain as _ipcMain} from 'electron';
|
|
||||||
import type {BrowserWindow, App, MenuItemConstructorOptions} from 'electron';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Config from 'electron-store';
|
import Config from 'electron-store';
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import React from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDom from 'react-dom';
|
||||||
|
|
||||||
import type {IpcMainWithCommands} from '../typings/common';
|
|
||||||
import type {configOptions} from '../typings/config';
|
|
||||||
|
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import {plugs} from './config/paths';
|
|
||||||
import notify from './notify';
|
import notify from './notify';
|
||||||
import {availableExtensions} from './plugins/extensions';
|
import {availableExtensions} from './plugins/extensions';
|
||||||
import {install} from './plugins/install';
|
import {install} from './plugins/install';
|
||||||
|
import {plugs} from './config/paths';
|
||||||
import mapKeys from './utils/map-keys';
|
import mapKeys from './utils/map-keys';
|
||||||
|
|
||||||
// local storage
|
// local storage
|
||||||
|
|
@ -60,7 +49,7 @@ config.subscribe(() => {
|
||||||
|
|
||||||
// patching Module._load
|
// patching Module._load
|
||||||
// so plugins can `require` them without needing their own version
|
// so plugins can `require` them without needing their own version
|
||||||
// https://github.com/vercel/hyper/issues/619
|
// https://github.com/zeit/hyper/issues/619
|
||||||
function patchModuleLoad() {
|
function patchModuleLoad() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const Module = require('module');
|
const Module = require('module');
|
||||||
|
|
@ -92,7 +81,7 @@ function patchModuleLoad() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDeprecatedExtendKeymaps() {
|
function checkDeprecatedExtendKeymaps() {
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (plugin.extendKeymaps) {
|
if (plugin.extendKeymaps) {
|
||||||
notify('Plugin warning!', `"${plugin._name}" use deprecated "extendKeymaps" handler`);
|
notify('Plugin warning!', `"${plugin._name}" use deprecated "extendKeymaps" handler`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -109,10 +98,11 @@ function updatePlugins({force = false} = {}) {
|
||||||
updating = true;
|
updating = true;
|
||||||
syncPackageJSON();
|
syncPackageJSON();
|
||||||
const id_ = id;
|
const id_ = id;
|
||||||
install((err) => {
|
install((err: any) => {
|
||||||
updating = false;
|
updating = false;
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
notify('Error updating plugins.', err, {error: err});
|
notify('Error updating plugins.', err, {error: err});
|
||||||
} else {
|
} else {
|
||||||
// flag successful plugin update
|
// flag successful plugin update
|
||||||
|
|
@ -134,9 +124,7 @@ function updatePlugins({force = false} = {}) {
|
||||||
cache.set('hyper.plugin-versions', pluginVersions);
|
cache.set('hyper.plugin-versions', pluginVersions);
|
||||||
|
|
||||||
// notify watchers
|
// notify watchers
|
||||||
watchers.forEach((fn) => {
|
watchers.forEach(fn => fn(err, {force}));
|
||||||
fn(err, {force});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (force || changed) {
|
if (force || changed) {
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
|
@ -152,10 +140,9 @@ function updatePlugins({force = false} = {}) {
|
||||||
|
|
||||||
function getPluginVersions() {
|
function getPluginVersions() {
|
||||||
const paths_ = paths.plugins.concat(paths.localPlugins);
|
const paths_ = paths.plugins.concat(paths.localPlugins);
|
||||||
return paths_.map((path_) => {
|
return paths_.map(path_ => {
|
||||||
let version: string | null = null;
|
let version = null;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
version = require(resolve(path_, 'package.json')).version;
|
version = require(resolve(path_, 'package.json')).version;
|
||||||
//eslint-disable-next-line no-empty
|
//eslint-disable-next-line no-empty
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
@ -165,7 +152,7 @@ function getPluginVersions() {
|
||||||
|
|
||||||
function clearCache() {
|
function clearCache() {
|
||||||
// trigger unload hooks
|
// trigger unload hooks
|
||||||
modules.forEach((mod) => {
|
modules.forEach(mod => {
|
||||||
if (mod.onUnload) {
|
if (mod.onUnload) {
|
||||||
mod.onUnload(app);
|
mod.onUnload(app);
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +169,7 @@ function clearCache() {
|
||||||
export {updatePlugins};
|
export {updatePlugins};
|
||||||
|
|
||||||
export const getLoadedPluginVersions = () => {
|
export const getLoadedPluginVersions = () => {
|
||||||
return modules.map((mod) => ({name: mod._name, version: mod._version}));
|
return modules.map(mod => ({name: mod._name, version: mod._version}));
|
||||||
};
|
};
|
||||||
|
|
||||||
// we schedule the initial plugins update
|
// we schedule the initial plugins update
|
||||||
|
|
@ -190,6 +177,7 @@ export const getLoadedPluginVersions = () => {
|
||||||
// to prevent slowness
|
// to prevent slowness
|
||||||
if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) {
|
if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) {
|
||||||
// install immediately if the user changed plugins
|
// install immediately if the user changed plugins
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('plugins have changed / not init, scheduling plugins installation');
|
console.log('plugins have changed / not init, scheduling plugins installation');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
updatePlugins();
|
updatePlugins();
|
||||||
|
|
@ -200,7 +188,10 @@ if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) {
|
||||||
const baseConfig = config.getConfig();
|
const baseConfig = config.getConfig();
|
||||||
if (baseConfig['autoUpdatePlugins']) {
|
if (baseConfig['autoUpdatePlugins']) {
|
||||||
// otherwise update plugins every 5 hours
|
// otherwise update plugins every 5 hours
|
||||||
setInterval(updatePlugins, ms(baseConfig['autoUpdatePlugins'] === true ? '5h' : baseConfig['autoUpdatePlugins']));
|
setInterval(
|
||||||
|
updatePlugins,
|
||||||
|
ms(baseConfig['autoUpdatePlugins'] === true ? '5h' : (baseConfig['autoUpdatePlugins'] as string))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
@ -208,10 +199,10 @@ function syncPackageJSON() {
|
||||||
const dependencies = toDependencies(plugins);
|
const dependencies = toDependencies(plugins);
|
||||||
const pkg = {
|
const pkg = {
|
||||||
name: 'hyper-plugins',
|
name: 'hyper-plugins',
|
||||||
description: 'Auto-generated from `hyper.json`!',
|
description: 'Auto-generated from `~/.hyper.js`!',
|
||||||
private: true,
|
private: true,
|
||||||
version: '0.0.1',
|
version: '0.0.1',
|
||||||
repository: 'quine-global/hyper',
|
repository: 'zeit/hyper',
|
||||||
license: 'MIT',
|
license: 'MIT',
|
||||||
homepage: 'https://hyper.is',
|
homepage: 'https://hyper.is',
|
||||||
dependencies
|
dependencies
|
||||||
|
|
@ -226,7 +217,7 @@ function syncPackageJSON() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function alert(message: string) {
|
function alert(message: string) {
|
||||||
void dialog.showMessageBox({
|
dialog.showMessageBox({
|
||||||
message,
|
message,
|
||||||
buttons: ['Ok']
|
buttons: ['Ok']
|
||||||
});
|
});
|
||||||
|
|
@ -234,13 +225,13 @@ function alert(message: string) {
|
||||||
|
|
||||||
function toDependencies(plugins_: {plugins: string[]}) {
|
function toDependencies(plugins_: {plugins: string[]}) {
|
||||||
const obj: Record<string, string> = {};
|
const obj: Record<string, string> = {};
|
||||||
plugins_.plugins.forEach((plugin) => {
|
plugins_.plugins.forEach(plugin => {
|
||||||
const regex = /.(@|#)/;
|
const regex = /.(@|#)/;
|
||||||
const match = regex.exec(plugin);
|
const match = regex.exec(plugin);
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const index = match.index + 1;
|
const index = match.index + 1;
|
||||||
const pieces: string[] = [];
|
const pieces = [];
|
||||||
|
|
||||||
pieces[0] = plugin.substring(0, index);
|
pieces[0] = plugin.substring(0, index);
|
||||||
pieces[1] = plugin.substring(index + 1, plugin.length);
|
pieces[1] = plugin.substring(index + 1, plugin.length);
|
||||||
|
|
@ -261,10 +252,10 @@ export const subscribe = (fn: Function) => {
|
||||||
|
|
||||||
function getPaths() {
|
function getPaths() {
|
||||||
return {
|
return {
|
||||||
plugins: plugins.plugins.map((name) => {
|
plugins: plugins.plugins.map(name => {
|
||||||
return resolve(path, 'node_modules', name.split('#')[0]);
|
return resolve(path, 'node_modules', name.split('#')[0]);
|
||||||
}),
|
}),
|
||||||
localPlugins: plugins.localPlugins.map((name) => {
|
localPlugins: plugins.localPlugins.map(name => {
|
||||||
return resolve(localPath, name);
|
return resolve(localPath, name);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
@ -282,10 +273,10 @@ function requirePlugins(): any[] {
|
||||||
const {plugins: plugins_, localPlugins} = paths;
|
const {plugins: plugins_, localPlugins} = paths;
|
||||||
|
|
||||||
const load = (path_: string) => {
|
const load = (path_: string) => {
|
||||||
let mod: Record<string, any>;
|
let mod: any;
|
||||||
try {
|
try {
|
||||||
mod = require(path_);
|
mod = require(path_);
|
||||||
const exposed = mod && Object.keys(mod).some((key) => availableExtensions.has(key));
|
const exposed = mod && Object.keys(mod).some(key => availableExtensions.has(key));
|
||||||
if (!exposed) {
|
if (!exposed) {
|
||||||
notify('Plugin error!', `${`Plugin "${basename(path_)}" does not expose any `}Hyper extension API methods`);
|
notify('Plugin error!', `${`Plugin "${basename(path_)}" does not expose any `}Hyper extension API methods`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -294,17 +285,18 @@ function requirePlugins(): any[] {
|
||||||
// populate the name for internal errors here
|
// populate the name for internal errors here
|
||||||
mod._name = basename(path_);
|
mod._name = basename(path_);
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
mod._version = require(resolve(path_, 'package.json')).version;
|
mod._version = require(resolve(path_, 'package.json')).version;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn(`No package.json found in ${path_}`);
|
console.warn(`No package.json found in ${path_}`);
|
||||||
}
|
}
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
|
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
|
||||||
|
|
||||||
return mod;
|
return mod;
|
||||||
} catch (_err) {
|
} catch (err) {
|
||||||
const err = _err as {code: string; message: string};
|
|
||||||
if (err.code === 'MODULE_NOT_FOUND') {
|
if (err.code === 'MODULE_NOT_FOUND') {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn(`Plugin error while loading "${basename(path_)}" (${path_}): ${err.message}`);
|
console.warn(`Plugin error while loading "${basename(path_)}" (${path_}): ${err.message}`);
|
||||||
} else {
|
} else {
|
||||||
notify('Plugin error!', `Plugin "${basename(path_)}" failed to load (${err.message})`, {error: err});
|
notify('Plugin error!', `Plugin "${basename(path_)}" failed to load (${err.message})`, {error: err});
|
||||||
|
|
@ -312,17 +304,14 @@ function requirePlugins(): any[] {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return plugins_
|
||||||
...localPlugins.filter((p) => basename(p) === 'migrated-hyper3-config'),
|
|
||||||
...plugins_,
|
|
||||||
...localPlugins.filter((p) => basename(p) !== 'migrated-hyper3-config')
|
|
||||||
]
|
|
||||||
.map(load)
|
.map(load)
|
||||||
.filter((v): v is Record<string, any> => Boolean(v));
|
.concat(localPlugins.map(load))
|
||||||
|
.filter(v => Boolean(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onApp = (app_: App) => {
|
export const onApp = (app_: App) => {
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (plugin.onApp) {
|
if (plugin.onApp) {
|
||||||
try {
|
try {
|
||||||
plugin.onApp(app_);
|
plugin.onApp(app_);
|
||||||
|
|
@ -336,7 +325,7 @@ export const onApp = (app_: App) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onWindowClass = (win: BrowserWindow) => {
|
export const onWindowClass = (win: BrowserWindow) => {
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (plugin.onWindowClass) {
|
if (plugin.onWindowClass) {
|
||||||
try {
|
try {
|
||||||
plugin.onWindowClass(win);
|
plugin.onWindowClass(win);
|
||||||
|
|
@ -350,7 +339,7 @@ export const onWindowClass = (win: BrowserWindow) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onWindow = (win: BrowserWindow) => {
|
export const onWindow = (win: BrowserWindow) => {
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (plugin.onWindow) {
|
if (plugin.onWindow) {
|
||||||
try {
|
try {
|
||||||
plugin.onWindow(win);
|
plugin.onWindow(win);
|
||||||
|
|
@ -367,7 +356,7 @@ export const onWindow = (win: BrowserWindow) => {
|
||||||
// for all the available plugins
|
// for all the available plugins
|
||||||
function decorateEntity(base: any, key: string, type: 'object' | 'function') {
|
function decorateEntity(base: any, key: string, type: 'object' | 'function') {
|
||||||
let decorated = base;
|
let decorated = base;
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (plugin[key]) {
|
if (plugin[key]) {
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
|
|
@ -387,7 +376,7 @@ function decorateEntity(base: any, key: string, type: 'object' | 'function') {
|
||||||
return decorated;
|
return decorated;
|
||||||
}
|
}
|
||||||
|
|
||||||
function decorateObject<T>(base: T, key: string): T {
|
function decorateObject(base: any, key: string) {
|
||||||
return decorateEntity(base, key, 'object');
|
return decorateEntity(base, key, 'object');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,14 +385,14 @@ function decorateClass(base: any, key: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDeprecatedConfig = () => {
|
export const getDeprecatedConfig = () => {
|
||||||
const deprecated: Record<string, {css: string[]}> = {};
|
const deprecated: Record<string, any> = {};
|
||||||
const baseConfig = config.getConfig();
|
const baseConfig = config.getConfig();
|
||||||
modules.forEach((plugin) => {
|
modules.forEach(plugin => {
|
||||||
if (!plugin.decorateConfig) {
|
if (!plugin.decorateConfig) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We need to clone config in case of plugin modifies config directly.
|
// We need to clone config in case of plugin modifies config directly.
|
||||||
let configTmp: configOptions;
|
let configTmp;
|
||||||
try {
|
try {
|
||||||
configTmp = plugin.decorateConfig(JSON.parse(JSON.stringify(baseConfig)));
|
configTmp = plugin.decorateConfig(JSON.parse(JSON.stringify(baseConfig)));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -421,7 +410,7 @@ export const getDeprecatedConfig = () => {
|
||||||
return deprecated;
|
return deprecated;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const decorateMenu = (tpl: MenuItemConstructorOptions[]) => {
|
export const decorateMenu = (tpl: any) => {
|
||||||
return decorateObject(tpl, 'decorateMenu');
|
return decorateObject(tpl, 'decorateMenu');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -429,8 +418,8 @@ export const getDecoratedEnv = (baseEnv: Record<string, string>) => {
|
||||||
return decorateObject(baseEnv, 'decorateEnv');
|
return decorateObject(baseEnv, 'decorateEnv');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDecoratedConfig = (profile: string) => {
|
export const getDecoratedConfig = () => {
|
||||||
const baseConfig = config.getProfileConfig(profile);
|
const baseConfig = config.getConfig();
|
||||||
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
|
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
|
||||||
const fixedConfig = config.fixConfigDefaults(decoratedConfig);
|
const fixedConfig = config.fixConfigDefaults(decoratedConfig);
|
||||||
const translatedConfig = config.htermConfigTranslate(fixedConfig);
|
const translatedConfig = config.htermConfigTranslate(fixedConfig);
|
||||||
|
|
@ -461,20 +450,3 @@ export const decorateSessionClass = <T>(Session: T): T => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export {toDependencies as _toDependencies};
|
export {toDependencies as _toDependencies};
|
||||||
|
|
||||||
const ipcMain = _ipcMain as IpcMainWithCommands;
|
|
||||||
|
|
||||||
ipcMain.handle('child_process.exec', (event, command, options) => {
|
|
||||||
return promisify(exec)(command, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('child_process.execFile', (event, file, args, options) => {
|
|
||||||
return promisify(execFile)(file, args, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('getLoadedPluginVersions', () => getLoadedPluginVersions());
|
|
||||||
ipcMain.handle('getPaths', () => getPaths());
|
|
||||||
ipcMain.handle('getBasePaths', () => getBasePaths());
|
|
||||||
ipcMain.handle('getDeprecatedConfig', () => getDeprecatedConfig());
|
|
||||||
ipcMain.handle('getDecoratedConfig', (e, profile) => getDecoratedConfig(profile));
|
|
||||||
ipcMain.handle('getDecoratedKeymaps', () => getDecoratedKeymaps());
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,18 @@
|
||||||
import cp from 'child_process';
|
import cp from 'child_process';
|
||||||
|
|
||||||
import ms from 'ms';
|
|
||||||
import queue from 'queue';
|
import queue from 'queue';
|
||||||
|
import ms from 'ms';
|
||||||
import {yarn, plugs} from '../config/paths';
|
import {yarn, plugs} from '../config/paths';
|
||||||
|
|
||||||
export const install = (fn: (err: string | null) => void) => {
|
export const install = (fn: Function) => {
|
||||||
const spawnQueue = queue({concurrency: 1});
|
const spawnQueue = queue({concurrency: 1});
|
||||||
function yarnFn(args: string[], cb: (err: string | null) => void) {
|
function yarnFn(args: string[], cb: Function) {
|
||||||
const env = {
|
const env = {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
ELECTRON_RUN_AS_NODE: 'true'
|
ELECTRON_RUN_AS_NODE: 'true'
|
||||||
};
|
};
|
||||||
spawnQueue.push((end) => {
|
spawnQueue.push(end => {
|
||||||
const cmd = [process.execPath, yarn].concat(args).join(' ');
|
const cmd = [process.execPath, yarn].concat(args).join(' ');
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Launching yarn:', cmd);
|
console.log('Launching yarn:', cmd);
|
||||||
|
|
||||||
cp.execFile(
|
cp.execFile(
|
||||||
|
|
@ -40,7 +39,7 @@ export const install = (fn: (err: string | null) => void) => {
|
||||||
spawnQueue.start();
|
spawnQueue.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], (err) => {
|
yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], (err: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return fn(err);
|
return fn(err);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
48
app/rpc.ts
48
app/rpc.ts
|
|
@ -1,28 +1,21 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
|
import {ipcMain, BrowserWindow} from 'electron';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import {ipcMain} from 'electron';
|
export class Server extends EventEmitter {
|
||||||
import type {BrowserWindow, IpcMainEvent} from 'electron';
|
|
||||||
|
|
||||||
import {v4 as uuidv4} from 'uuid';
|
|
||||||
|
|
||||||
import type {TypedEmitter, MainEvents, RendererEvents, FilterNever} from '../typings/common';
|
|
||||||
|
|
||||||
export class Server {
|
|
||||||
emitter: TypedEmitter<MainEvents>;
|
|
||||||
destroyed = false;
|
destroyed = false;
|
||||||
win: BrowserWindow;
|
win: BrowserWindow;
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
constructor(win: BrowserWindow) {
|
constructor(win: BrowserWindow) {
|
||||||
this.emitter = new EventEmitter();
|
super();
|
||||||
this.win = win;
|
this.win = win;
|
||||||
this.emit = this.emit.bind(this);
|
this.ipcListener = this.ipcListener.bind(this);
|
||||||
|
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uid = uuidv4();
|
const uid = uuid.v4();
|
||||||
this.id = uid;
|
this.id = uid;
|
||||||
|
|
||||||
ipcMain.on(uid, this.ipcListener);
|
ipcMain.on(uid, this.ipcListener);
|
||||||
|
|
@ -31,7 +24,7 @@ export class Server {
|
||||||
// to support reloading the window and re-initializing
|
// to support reloading the window and re-initializing
|
||||||
// the channel
|
// the channel
|
||||||
this.wc.on('did-finish-load', () => {
|
this.wc.on('did-finish-load', () => {
|
||||||
this.wc.send('init', uid, win.profileName);
|
this.wc.send('init', uid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,33 +32,20 @@ export class Server {
|
||||||
return this.win.webContents;
|
return this.win.webContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcListener = <U extends keyof MainEvents>(event: IpcMainEvent, {ev, data}: {ev: U; data: MainEvents[U]}) =>
|
ipcListener(event: any, {ev, data}: {ev: string; data: any}) {
|
||||||
this.emitter.emit(ev, data);
|
super.emit(ev, data);
|
||||||
|
}
|
||||||
|
|
||||||
on = <U extends keyof MainEvents>(ev: U, fn: (arg0: MainEvents[U]) => void) => {
|
emit(ch: string, data: any = {}): any {
|
||||||
this.emitter.on(ev, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
once = <U extends keyof MainEvents>(ev: U, fn: (arg0: MainEvents[U]) => void) => {
|
|
||||||
this.emitter.once(ev, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
emit<U extends Exclude<keyof RendererEvents, FilterNever<RendererEvents>>>(ch: U): boolean;
|
|
||||||
emit<U extends FilterNever<RendererEvents>>(ch: U, data: RendererEvents[U]): boolean;
|
|
||||||
emit<U extends keyof RendererEvents>(ch: U, data?: RendererEvents[U]) {
|
|
||||||
// This check is needed because data-batching can cause extra data to be
|
// This check is needed because data-batching can cause extra data to be
|
||||||
// emitted after the window has already closed
|
// emitted after the window has already closed
|
||||||
if (!this.win.isDestroyed()) {
|
if (!this.win.isDestroyed()) {
|
||||||
this.wc.send(this.id, {ch, data});
|
this.wc.send(this.id, {ch, data});
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.emitter.removeAllListeners();
|
this.removeAllListeners();
|
||||||
this.wc.removeAllListeners();
|
this.wc.removeAllListeners();
|
||||||
if (this.id) {
|
if (this.id) {
|
||||||
ipcMain.removeListener(this.id, this.ipcListener);
|
ipcMain.removeListener(this.id, this.ipcListener);
|
||||||
|
|
@ -76,8 +56,6 @@ export class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRPC = (win: BrowserWindow) => {
|
export default (win: BrowserWindow) => {
|
||||||
return new Server(win);
|
return new Server(win);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createRPC;
|
|
||||||
|
|
|
||||||
142
app/session.ts
142
app/session.ts
|
|
@ -1,17 +1,10 @@
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {dirname} from 'path';
|
|
||||||
import {StringDecoder} from 'string_decoder';
|
import {StringDecoder} from 'string_decoder';
|
||||||
|
|
||||||
import defaultShell from 'default-shell';
|
import defaultShell from 'default-shell';
|
||||||
import type {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
|
|
||||||
import osLocale from 'os-locale';
|
|
||||||
import shellEnv from 'shell-env';
|
|
||||||
|
|
||||||
import * as config from './config';
|
|
||||||
import {cliScriptPath} from './config/paths';
|
|
||||||
import {productName, version} from './package.json';
|
|
||||||
import {getDecoratedEnv} from './plugins';
|
import {getDecoratedEnv} from './plugins';
|
||||||
import {getFallBackShellConfig} from './utils/shell-fallback';
|
import {productName, version} from './package.json';
|
||||||
|
import * as config from './config';
|
||||||
|
import {IPty, IWindowsPtyForkOptions, spawn as npSpawn} from 'node-pty';
|
||||||
|
|
||||||
const createNodePtyError = () =>
|
const createNodePtyError = () =>
|
||||||
new Error(
|
new Error(
|
||||||
|
|
@ -20,12 +13,12 @@ const createNodePtyError = () =>
|
||||||
|
|
||||||
let spawn: typeof npSpawn;
|
let spawn: typeof npSpawn;
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
spawn = require('node-pty').spawn;
|
spawn = require('node-pty').spawn;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw createNodePtyError();
|
throw createNodePtyError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const envFromConfig = config.getConfig().env || {};
|
||||||
const useConpty = config.getConfig().useConpty;
|
const useConpty = config.getConfig().useConpty;
|
||||||
|
|
||||||
// Max duration to batch session data before sending it to the renderer process.
|
// Max duration to batch session data before sending it to the renderer process.
|
||||||
|
|
@ -58,7 +51,7 @@ class DataBatcher extends EventEmitter {
|
||||||
this.timeout = null;
|
this.timeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
write(chunk: Buffer | string) {
|
write(chunk: Buffer) {
|
||||||
if (this.data.length + chunk.length >= BATCH_MAX_SIZE) {
|
if (this.data.length + chunk.length >= BATCH_MAX_SIZE) {
|
||||||
// We've reached the max batch size. Flush it and start another one
|
// We've reached the max batch size. Flush it and start another one
|
||||||
if (this.timeout) {
|
if (this.timeout) {
|
||||||
|
|
@ -68,7 +61,7 @@ class DataBatcher extends EventEmitter {
|
||||||
this.flush();
|
this.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data += typeof chunk === 'string' ? chunk : this.decoder.write(chunk);
|
this.data += this.decoder.write(chunk);
|
||||||
|
|
||||||
if (!this.timeout) {
|
if (!this.timeout) {
|
||||||
this.timeout = setTimeout(() => this.flush(), BATCH_DURATION_MS);
|
this.timeout = setTimeout(() => this.flush(), BATCH_DURATION_MS);
|
||||||
|
|
@ -86,66 +79,53 @@ class DataBatcher extends EventEmitter {
|
||||||
|
|
||||||
interface SessionOptions {
|
interface SessionOptions {
|
||||||
uid: string;
|
uid: string;
|
||||||
rows?: number;
|
rows: number;
|
||||||
cols?: number;
|
cols: number;
|
||||||
cwd?: string;
|
cwd: string;
|
||||||
shell?: string;
|
shell: string;
|
||||||
shellArgs?: string[];
|
shellArgs: string[];
|
||||||
profile: string;
|
|
||||||
}
|
}
|
||||||
export default class Session extends EventEmitter {
|
export default class Session extends EventEmitter {
|
||||||
pty: IPty | null;
|
pty: IPty | null;
|
||||||
batcher: DataBatcher | null;
|
batcher: DataBatcher | null;
|
||||||
shell: string | null;
|
shell: string | null;
|
||||||
ended: boolean;
|
ended: boolean;
|
||||||
initTimestamp: number;
|
|
||||||
profile!: string;
|
|
||||||
constructor(options: SessionOptions) {
|
constructor(options: SessionOptions) {
|
||||||
super();
|
super();
|
||||||
this.pty = null;
|
this.pty = null;
|
||||||
this.batcher = null;
|
this.batcher = null;
|
||||||
this.shell = null;
|
this.shell = null;
|
||||||
this.ended = false;
|
this.ended = false;
|
||||||
this.initTimestamp = new Date().getTime();
|
|
||||||
this.init(options);
|
this.init(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
init({uid, rows, cols, cwd, shell: _shell, shellArgs: _shellArgs, profile}: SessionOptions) {
|
init({uid, rows, cols: columns, cwd, shell, shellArgs}: SessionOptions) {
|
||||||
this.profile = profile;
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const envFromConfig = config.getProfileConfig(profile).env || {};
|
const osLocale = require('os-locale') as typeof import('os-locale');
|
||||||
const defaultShellArgs = ['--login'];
|
const baseEnv = Object.assign(
|
||||||
|
{},
|
||||||
const shell = _shell || defaultShell;
|
process.env,
|
||||||
const shellArgs = _shellArgs || defaultShellArgs;
|
{
|
||||||
|
LANG: `${osLocale.sync().replace(/-/, '_')}.UTF-8`,
|
||||||
const cleanEnv =
|
TERM: 'xterm-256color',
|
||||||
process.env['APPIMAGE'] && process.env['APPDIR'] ? shellEnv.sync(_shell || defaultShell) : process.env;
|
COLORTERM: 'truecolor',
|
||||||
const baseEnv: Record<string, string> = {
|
TERM_PROGRAM: productName,
|
||||||
...cleanEnv,
|
TERM_PROGRAM_VERSION: version
|
||||||
LANG: `${osLocale.sync().replace(/-/, '_')}.UTF-8`,
|
},
|
||||||
TERM: 'xterm-256color',
|
envFromConfig
|
||||||
COLORTERM: 'truecolor',
|
);
|
||||||
TERM_PROGRAM: productName,
|
|
||||||
TERM_PROGRAM_VERSION: version,
|
|
||||||
...envFromConfig
|
|
||||||
};
|
|
||||||
// path to AppImage mount point is added to PATH environment variable automatically
|
|
||||||
// which conflicts with the cli
|
|
||||||
if (baseEnv['APPIMAGE'] && baseEnv['APPDIR']) {
|
|
||||||
baseEnv['PATH'] = [dirname(cliScriptPath)]
|
|
||||||
.concat((baseEnv['PATH'] || '').split(':').filter((val) => !val.startsWith(baseEnv['APPDIR'])))
|
|
||||||
.join(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Electron has a default value for process.env.GOOGLE_API_KEY
|
// Electron has a default value for process.env.GOOGLE_API_KEY
|
||||||
// We don't want to leak this to the shell
|
// We don't want to leak this to the shell
|
||||||
// See https://github.com/vercel/hyper/issues/696
|
// See https://github.com/zeit/hyper/issues/696
|
||||||
if (baseEnv.GOOGLE_API_KEY && process.env.GOOGLE_API_KEY === baseEnv.GOOGLE_API_KEY) {
|
if (baseEnv.GOOGLE_API_KEY && process.env.GOOGLE_API_KEY === baseEnv.GOOGLE_API_KEY) {
|
||||||
delete baseEnv.GOOGLE_API_KEY;
|
delete baseEnv.GOOGLE_API_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultShellArgs = ['--login'];
|
||||||
|
|
||||||
const options: IWindowsPtyForkOptions = {
|
const options: IWindowsPtyForkOptions = {
|
||||||
cols,
|
cols: columns,
|
||||||
rows,
|
rows,
|
||||||
cwd,
|
cwd,
|
||||||
env: getDecoratedEnv(baseEnv)
|
env: getDecoratedEnv(baseEnv)
|
||||||
|
|
@ -157,9 +137,8 @@ export default class Session extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.pty = spawn(shell, shellArgs, options);
|
this.pty = spawn(shell || defaultShell, shellArgs || defaultShellArgs, options);
|
||||||
} catch (_err) {
|
} catch (err) {
|
||||||
const err = _err as {message: string};
|
|
||||||
if (/is not a function/.test(err.message)) {
|
if (/is not a function/.test(err.message)) {
|
||||||
throw createNodePtyError();
|
throw createNodePtyError();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -168,57 +147,25 @@ export default class Session extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.batcher = new DataBatcher(uid);
|
this.batcher = new DataBatcher(uid);
|
||||||
this.pty.onData((chunk) => {
|
this.pty.onData(chunk => {
|
||||||
if (this.ended) {
|
if (this.ended) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.batcher?.write(chunk);
|
this.batcher?.write(chunk as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.batcher.on('flush', (data: string) => {
|
this.batcher.on('flush', data => {
|
||||||
this.emit('data', data);
|
this.emit('data', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pty.onExit((e) => {
|
this.pty.onExit(() => {
|
||||||
if (!this.ended) {
|
if (!this.ended) {
|
||||||
// fall back to default shell config if the shell exits within 1 sec with non zero exit code
|
this.ended = true;
|
||||||
// this will inform users in case there are errors in the config instead of instant exit
|
this.emit('exit');
|
||||||
const runDuration = new Date().getTime() - this.initTimestamp;
|
|
||||||
if (e.exitCode > 0 && runDuration < 1000) {
|
|
||||||
const fallBackShellConfig = getFallBackShellConfig(shell, shellArgs, defaultShell, defaultShellArgs);
|
|
||||||
if (fallBackShellConfig) {
|
|
||||||
const msg = `
|
|
||||||
shell exited in ${runDuration} ms with exit code ${e.exitCode}
|
|
||||||
please check the shell config: ${JSON.stringify({shell, shellArgs}, undefined, 2)}
|
|
||||||
using fallback shell config: ${JSON.stringify(fallBackShellConfig, undefined, 2)}
|
|
||||||
`;
|
|
||||||
console.warn(msg);
|
|
||||||
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
|
|
||||||
this.init({
|
|
||||||
uid,
|
|
||||||
rows,
|
|
||||||
cols,
|
|
||||||
cwd,
|
|
||||||
shell: fallBackShellConfig.shell,
|
|
||||||
shellArgs: fallBackShellConfig.shellArgs,
|
|
||||||
profile
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const msg = `
|
|
||||||
shell exited in ${runDuration} ms with exit code ${e.exitCode}
|
|
||||||
No fallback available, please check the shell config.
|
|
||||||
`;
|
|
||||||
console.warn(msg);
|
|
||||||
this.batcher?.write(msg.replace(/\n/g, '\r\n'));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.ended = true;
|
|
||||||
this.emit('exit');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.shell = shell;
|
this.shell = shell || defaultShell;
|
||||||
}
|
}
|
||||||
|
|
||||||
exit() {
|
exit() {
|
||||||
|
|
@ -229,6 +176,7 @@ No fallback available, please check the shell config.
|
||||||
if (this.pty) {
|
if (this.pty) {
|
||||||
this.pty.write(data);
|
this.pty.write(data);
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn('Warning: Attempted to write to a session with no pty');
|
console.warn('Warning: Attempted to write to a session with no pty');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,11 +185,12 @@ No fallback available, please check the shell config.
|
||||||
if (this.pty) {
|
if (this.pty) {
|
||||||
try {
|
try {
|
||||||
this.pty.resize(cols, rows);
|
this.pty.resize(cols, rows);
|
||||||
} catch (_err) {
|
} catch (err) {
|
||||||
const err = _err as {stack: any};
|
//eslint-disable-next-line no-console
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn('Warning: Attempted to resize a session with no pty');
|
console.warn('Warning: Attempted to resize a session with no pty');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -250,11 +199,12 @@ No fallback available, please check the shell config.
|
||||||
if (this.pty) {
|
if (this.pty) {
|
||||||
try {
|
try {
|
||||||
this.pty.kill();
|
this.pty.kill();
|
||||||
} catch (_err) {
|
} catch (err) {
|
||||||
const err = _err as {stack: any};
|
//eslint-disable-next-line no-console
|
||||||
console.error('exit error', err.stack);
|
console.error('exit error', err.stack);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn('Warning: Attempted to destroy a session with no pty');
|
console.warn('Warning: Attempted to destroy a session with no pty');
|
||||||
}
|
}
|
||||||
this.emit('exit');
|
this.emit('exit');
|
||||||
|
|
|
||||||
89
app/system-context-menu.ts
Normal file
89
app/system-context-menu.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
import Registry from 'winreg';
|
||||||
|
|
||||||
|
const appPath = `"${process.execPath}"`;
|
||||||
|
const regKey = `\\Software\\Classes\\Directory\\background\\shell\\Hyper`;
|
||||||
|
const regParts = [
|
||||||
|
{key: 'command', name: '', value: `${appPath} "%V"`},
|
||||||
|
{name: '', value: 'Open Hyper here'},
|
||||||
|
{name: 'Icon', value: `${appPath}`}
|
||||||
|
];
|
||||||
|
|
||||||
|
function addValues(hyperKey: Registry.Registry, commandKey: Registry.Registry, callback: Function) {
|
||||||
|
hyperKey.set(regParts[1].name, Registry.REG_SZ, regParts[1].value, error => {
|
||||||
|
if (error) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
hyperKey.set(regParts[2].name, Registry.REG_SZ, regParts[2].value, err => {
|
||||||
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
commandKey.set(regParts[0].name, Registry.REG_SZ, regParts[0].value, err_ => {
|
||||||
|
if (err_) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err_.message);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const add = (callback: Function) => {
|
||||||
|
const hyperKey = new Registry({hive: 'HKCU', key: regKey});
|
||||||
|
const commandKey = new Registry({
|
||||||
|
hive: 'HKCU',
|
||||||
|
key: `${regKey}\\${regParts[0].key}`
|
||||||
|
});
|
||||||
|
|
||||||
|
hyperKey.keyExists((error, exists) => {
|
||||||
|
if (error) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(error.message);
|
||||||
|
}
|
||||||
|
if (exists) {
|
||||||
|
commandKey.keyExists((err_, exists_) => {
|
||||||
|
if (err_) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err_.message);
|
||||||
|
}
|
||||||
|
if (exists_) {
|
||||||
|
addValues(hyperKey, commandKey, callback);
|
||||||
|
} else {
|
||||||
|
commandKey.create(err => {
|
||||||
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
addValues(hyperKey, commandKey, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
hyperKey.create(err => {
|
||||||
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
commandKey.create(err_ => {
|
||||||
|
if (err_) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err_.message);
|
||||||
|
}
|
||||||
|
addValues(hyperKey, commandKey, callback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const remove = (callback: Function) => {
|
||||||
|
new Registry({hive: 'HKCU', key: regKey}).destroy(err => {
|
||||||
|
if (err) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error(err.message);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
{
|
{
|
||||||
"extends": "../tsconfig.base.json",
|
"extends": "../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "../dist/tmp/appdts/",
|
"declarationDir": "../dist/tmp/appdts/",
|
||||||
"outDir": "../target/",
|
"outDir": "../target/"
|
||||||
"composite": true,
|
|
||||||
"noImplicitAny": false
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./**/*",
|
"./**/*",
|
||||||
"./package.json",
|
"./package.json"
|
||||||
"../typings/extend-electron.d.ts",
|
|
||||||
"../typings/ext-modules.d.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"../dist/**/*",
|
|
||||||
"../target/**/*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
import type {MenuItemConstructorOptions, BrowserWindow} from 'electron';
|
|
||||||
|
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getProfiles} from '../config';
|
|
||||||
import editMenu from '../menus/menus/edit';
|
import editMenu from '../menus/menus/edit';
|
||||||
import shellMenu from '../menus/menus/shell';
|
import shellMenu from '../menus/menus/shell';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
import {getDecoratedKeymaps} from '../plugins';
|
import {getDecoratedKeymaps} from '../plugins';
|
||||||
|
import {MenuItemConstructorOptions, BrowserWindow} from 'electron';
|
||||||
const separator: MenuItemConstructorOptions = {type: 'separator'};
|
const separator: MenuItemConstructorOptions = {type: 'separator'};
|
||||||
|
|
||||||
const getCommandKeys = (keymaps: Record<string, string[]>): Record<string, string> =>
|
const getCommandKeys = (keymaps: Record<string, string[]>): Record<string, string> =>
|
||||||
|
|
@ -23,20 +20,14 @@ const filterCutCopy = (selection: string, menuItem: MenuItemConstructorOptions)
|
||||||
return menuItem;
|
return menuItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextMenuTemplate = (
|
export default (
|
||||||
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow,
|
createWindow: (fn?: (win: BrowserWindow) => void, options?: Record<string, any>) => BrowserWindow,
|
||||||
selection: string
|
selection: string
|
||||||
) => {
|
) => {
|
||||||
const commandKeys = getCommandKeys(getDecoratedKeymaps());
|
const commandKeys = getCommandKeys(getDecoratedKeymaps());
|
||||||
const _shell = shellMenu(
|
const _shell = shellMenu(commandKeys, execCommand).submenu as MenuItemConstructorOptions[];
|
||||||
commandKeys,
|
|
||||||
(command, focusedWindow) => execCommand(command, focusedWindow as BrowserWindow | undefined),
|
|
||||||
getProfiles().map((p) => p.name)
|
|
||||||
).submenu as MenuItemConstructorOptions[];
|
|
||||||
const _edit = editMenu(commandKeys, execCommand).submenu.filter(filterCutCopy.bind(null, selection));
|
const _edit = editMenu(commandKeys, execCommand).submenu.filter(filterCutCopy.bind(null, selection));
|
||||||
return _edit
|
return _edit
|
||||||
.concat(separator, _shell)
|
.concat(separator, _shell)
|
||||||
.filter((menuItem) => !Object.prototype.hasOwnProperty.call(menuItem, 'enabled') || menuItem.enabled);
|
.filter(menuItem => !Object.prototype.hasOwnProperty.call(menuItem, 'enabled') || menuItem.enabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default contextMenuTemplate;
|
|
||||||
|
|
|
||||||
238
app/ui/window.ts
238
app/ui/window.ts
|
|
@ -1,87 +1,75 @@
|
||||||
import {existsSync} from 'fs';
|
import {app, BrowserWindow, shell, Menu, BrowserWindowConstructorOptions} from 'electron';
|
||||||
import {isAbsolute, normalize, sep} from 'path';
|
import {isAbsolute} from 'path';
|
||||||
import {URL, fileURLToPath} from 'url';
|
import {parse as parseUrl} from 'url';
|
||||||
|
import uuid from 'uuid';
|
||||||
import {app, BrowserWindow, shell, Menu} from 'electron';
|
import fileUriToPath from 'file-uri-to-path';
|
||||||
import type {BrowserWindowConstructorOptions} from 'electron';
|
|
||||||
|
|
||||||
import {enable as remoteEnable} from '@electron/remote/main';
|
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
import {getWorkingDirectoryFromPID} from 'native-process-working-directory';
|
|
||||||
import electronDragClick from 'quine-electron-drag-click';
|
|
||||||
import {v4 as uuidv4} from 'uuid';
|
|
||||||
|
|
||||||
import type {sessionExtraOptions} from '../../typings/common';
|
|
||||||
import type {configOptions} from '../../typings/config';
|
|
||||||
import {execCommand} from '../commands';
|
|
||||||
import {getDefaultProfile} from '../config';
|
|
||||||
import {icon, homeDirectory} from '../config/paths';
|
|
||||||
import fetchNotifications from '../notifications';
|
|
||||||
import notify from '../notify';
|
|
||||||
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
|
|
||||||
import createRPC from '../rpc';
|
|
||||||
import Session from '../session';
|
|
||||||
import updater from '../updater';
|
import updater from '../updater';
|
||||||
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
|
|
||||||
import toElectronBackgroundColor from '../utils/to-electron-background-color';
|
import toElectronBackgroundColor from '../utils/to-electron-background-color';
|
||||||
|
import {icon, cfgDir} from '../config/paths';
|
||||||
|
import createRPC from '../rpc';
|
||||||
|
import notify from '../notify';
|
||||||
|
import fetchNotifications from '../notifications';
|
||||||
|
import Session from '../session';
|
||||||
import contextMenuTemplate from './contextmenu';
|
import contextMenuTemplate from './contextmenu';
|
||||||
|
import {execCommand} from '../commands';
|
||||||
if (process.platform === 'darwin') {
|
import {setRendererType, unsetRendererType} from '../utils/renderer-utils';
|
||||||
electronDragClick();
|
import {decorateSessionOptions, decorateSessionClass} from '../plugins';
|
||||||
}
|
|
||||||
|
|
||||||
export function newWindow(
|
export function newWindow(
|
||||||
options_: BrowserWindowConstructorOptions,
|
options_: BrowserWindowConstructorOptions,
|
||||||
cfg: configOptions,
|
cfg: any,
|
||||||
fn?: (win: BrowserWindow) => void,
|
fn?: (win: BrowserWindow) => void
|
||||||
profileName: string = getDefaultProfile()
|
|
||||||
): BrowserWindow {
|
): BrowserWindow {
|
||||||
const classOpts = Object.assign({uid: uuidv4()});
|
const classOpts = Object.assign({uid: uuid.v4()});
|
||||||
app.plugins.decorateWindowClass(classOpts);
|
app.plugins.decorateWindowClass(classOpts);
|
||||||
|
|
||||||
const winOpts: BrowserWindowConstructorOptions = {
|
const winOpts = Object.assign(
|
||||||
minWidth: 370,
|
{
|
||||||
minHeight: 190,
|
minWidth: 370,
|
||||||
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
|
minHeight: 190,
|
||||||
titleBarStyle: 'hiddenInset',
|
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
|
||||||
title: 'Hyper.app',
|
titleBarStyle: 'hiddenInset',
|
||||||
// we want to go frameless on Windows and Linux
|
title: 'Hyper.app',
|
||||||
frame: process.platform === 'darwin',
|
// we want to go frameless on Windows and Linux
|
||||||
transparent: process.platform === 'darwin',
|
frame: process.platform === 'darwin',
|
||||||
icon,
|
transparent: process.platform === 'darwin',
|
||||||
show: Boolean(process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev),
|
icon,
|
||||||
acceptFirstMouse: true,
|
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
|
||||||
webPreferences: {
|
acceptFirstMouse: true,
|
||||||
nodeIntegration: true,
|
webPreferences: {
|
||||||
navigateOnDragDrop: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false
|
navigateOnDragDrop: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
...options_
|
options_
|
||||||
};
|
);
|
||||||
|
|
||||||
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
|
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
|
||||||
|
|
||||||
window.profileName = profileName;
|
|
||||||
|
|
||||||
// Enable remote module on this window
|
|
||||||
remoteEnable(window.webContents);
|
|
||||||
|
|
||||||
window.uid = classOpts.uid;
|
window.uid = classOpts.uid;
|
||||||
|
|
||||||
app.plugins.onWindowClass(window);
|
app.plugins.onWindowClass(window);
|
||||||
window.uid = classOpts.uid;
|
window.uid = classOpts.uid;
|
||||||
|
|
||||||
const rpc = createRPC(window);
|
const rpc = createRPC(window);
|
||||||
const sessions = new Map<string, Session>();
|
const sessions = new Map();
|
||||||
|
|
||||||
const updateBackgroundColor = () => {
|
const updateBackgroundColor = () => {
|
||||||
const cfg_ = app.plugins.getDecoratedConfig(profileName);
|
const cfg_ = app.plugins.getDecoratedConfig();
|
||||||
window.setBackgroundColor(toElectronBackgroundColor(cfg_.backgroundColor || '#000'));
|
window.setBackgroundColor(toElectronBackgroundColor(cfg_.backgroundColor || '#000'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// set working directory
|
||||||
|
let workingDirectory = cfgDir;
|
||||||
|
if (process.argv[1] && isAbsolute(process.argv[1])) {
|
||||||
|
workingDirectory = process.argv[1];
|
||||||
|
} else if (cfg.workingDirectory && isAbsolute(cfg.workingDirectory)) {
|
||||||
|
workingDirectory = cfg.workingDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
// config changes
|
// config changes
|
||||||
const cfgUnsubscribe = app.config.subscribe(() => {
|
const cfgUnsubscribe = app.config.subscribe(() => {
|
||||||
const cfg_ = app.plugins.getDecoratedConfig(profileName);
|
const cfg_ = app.plugins.getDecoratedConfig();
|
||||||
|
|
||||||
// notify renderer
|
// notify renderer
|
||||||
window.webContents.send('config change');
|
window.webContents.send('config change');
|
||||||
|
|
@ -104,9 +92,7 @@ export function newWindow(
|
||||||
// If no callback is passed to createWindow,
|
// If no callback is passed to createWindow,
|
||||||
// a new session will be created by default.
|
// a new session will be created by default.
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
fn = (win: BrowserWindow) => {
|
fn = (win: BrowserWindow) => win.rpc.emit('termgroup add req', {});
|
||||||
win.rpc.emit('termgroup add req', {});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// app.windowCallback is the createWindow callback
|
// app.windowCallback is the createWindow callback
|
||||||
|
|
@ -114,68 +100,30 @@ export function newWindow(
|
||||||
// and createWindow definition. It's executed in place of
|
// and createWindow definition. It's executed in place of
|
||||||
// the callback passed as parameter, and deleted right after.
|
// the callback passed as parameter, and deleted right after.
|
||||||
(app.windowCallback || fn)(window);
|
(app.windowCallback || fn)(window);
|
||||||
app.windowCallback = undefined;
|
delete app.windowCallback;
|
||||||
fetchNotifications(window);
|
fetchNotifications(window);
|
||||||
// auto updates
|
// auto updates
|
||||||
if (!isDev) {
|
if (!isDev) {
|
||||||
updater(window);
|
updater(window);
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring auto updates during dev');
|
console.log('ignoring auto updates during dev');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function createSession(extraOptions: sessionExtraOptions = {}) {
|
function createSession(extraOptions: any = {}) {
|
||||||
const uid = uuidv4();
|
const uid = uuid.v4();
|
||||||
const extraOptionsFiltered: sessionExtraOptions = {};
|
|
||||||
Object.keys(extraOptions).forEach((key) => {
|
|
||||||
if (extraOptions[key] !== undefined) extraOptionsFiltered[key] = extraOptions[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
const profile = extraOptionsFiltered.profile || profileName;
|
|
||||||
const activeSession = extraOptionsFiltered.activeUid ? sessions.get(extraOptionsFiltered.activeUid) : undefined;
|
|
||||||
let cwd = '';
|
|
||||||
if (cfg.preserveCWD !== false && activeSession && activeSession.profile === profile) {
|
|
||||||
const activePID = activeSession.pty?.pid;
|
|
||||||
if (activePID !== undefined) {
|
|
||||||
try {
|
|
||||||
cwd = getWorkingDirectoryFromPID(activePID) || '';
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cwd = cwd && isAbsolute(cwd) && existsSync(cwd) ? cwd : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const profileCfg = app.plugins.getDecoratedConfig(profile);
|
|
||||||
|
|
||||||
// set working directory
|
|
||||||
let argPath = process.argv[1];
|
|
||||||
if (argPath && process.platform === 'win32') {
|
|
||||||
if (/[a-zA-Z]:"/.test(argPath)) {
|
|
||||||
argPath = argPath.replace('"', sep);
|
|
||||||
}
|
|
||||||
argPath = normalize(argPath + sep);
|
|
||||||
}
|
|
||||||
let workingDirectory = homeDirectory;
|
|
||||||
if (argPath && isAbsolute(argPath)) {
|
|
||||||
workingDirectory = argPath;
|
|
||||||
} else if (profileCfg.workingDirectory && isAbsolute(profileCfg.workingDirectory)) {
|
|
||||||
workingDirectory = profileCfg.workingDirectory;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the rows and cols, the wrong value of them will break layout when init create
|
// remove the rows and cols, the wrong value of them will break layout when init create
|
||||||
const defaultOptions = Object.assign(
|
const defaultOptions = Object.assign(
|
||||||
{
|
{
|
||||||
cwd: cwd || workingDirectory,
|
cwd: workingDirectory,
|
||||||
splitDirection: undefined,
|
splitDirection: undefined,
|
||||||
shell: profileCfg.shell,
|
shell: cfg.shell,
|
||||||
shellArgs: profileCfg.shellArgs && Array.from(profileCfg.shellArgs)
|
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
|
||||||
},
|
},
|
||||||
extraOptionsFiltered,
|
extraOptions,
|
||||||
{
|
{uid}
|
||||||
profile: extraOptionsFiltered.profile || profileName,
|
|
||||||
uid
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
const options = decorateSessionOptions(defaultOptions);
|
const options = decorateSessionOptions(defaultOptions);
|
||||||
const DecoratedSession = decorateSessionClass(Session);
|
const DecoratedSession = decorateSessionClass(Session);
|
||||||
|
|
@ -184,7 +132,7 @@ export function newWindow(
|
||||||
return {session, options};
|
return {session, options};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc.on('new', (extraOptions) => {
|
rpc.on('new', extraOptions => {
|
||||||
const {session, options} = createSession(extraOptions);
|
const {session, options} = createSession(extraOptions);
|
||||||
|
|
||||||
sessions.set(options.uid, session);
|
sessions.set(options.uid, session);
|
||||||
|
|
@ -195,11 +143,10 @@ export function newWindow(
|
||||||
splitDirection: options.splitDirection,
|
splitDirection: options.splitDirection,
|
||||||
shell: session.shell,
|
shell: session.shell,
|
||||||
pid: session.pty ? session.pty.pid : null,
|
pid: session.pty ? session.pty.pid : null,
|
||||||
activeUid: options.activeUid ?? undefined,
|
activeUid: options.activeUid
|
||||||
profile: options.profile
|
|
||||||
});
|
});
|
||||||
|
|
||||||
session.on('data', (data: string) => {
|
session.on('data', (data: any) => {
|
||||||
rpc.emit('session data', data);
|
rpc.emit('session data', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -232,10 +179,10 @@ export function newWindow(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
rpc.on('data', ({uid, data, escaped}) => {
|
rpc.on('data', ({uid, data, escaped}) => {
|
||||||
const session = uid && sessions.get(uid);
|
const session = sessions.get(uid);
|
||||||
if (session) {
|
if (session) {
|
||||||
if (escaped) {
|
if (escaped) {
|
||||||
const escapedData = session.shell?.endsWith('cmd.exe')
|
const escapedData = session.shell.endsWith('cmd.exe')
|
||||||
? `"${data}"` // This is how cmd.exe does it
|
? `"${data}"` // This is how cmd.exe does it
|
||||||
: `'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
|
: `'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
|
||||||
|
|
||||||
|
|
@ -250,11 +197,12 @@ export function newWindow(
|
||||||
setRendererType(uid, type);
|
setRendererType(uid, type);
|
||||||
});
|
});
|
||||||
rpc.on('open external', ({url}) => {
|
rpc.on('open external', ({url}) => {
|
||||||
void shell.openExternal(url);
|
shell.openExternal(url);
|
||||||
});
|
});
|
||||||
rpc.on('open context menu', (selection) => {
|
rpc.on('open context menu', selection => {
|
||||||
const {createWindow} = app;
|
const {createWindow} = app;
|
||||||
Menu.buildFromTemplate(contextMenuTemplate(createWindow, selection)).popup({window});
|
const {buildFromTemplate} = Menu;
|
||||||
|
buildFromTemplate(contextMenuTemplate(createWindow, selection)).popup({window});
|
||||||
});
|
});
|
||||||
rpc.on('open hamburger menu', ({x, y}) => {
|
rpc.on('open hamburger menu', ({x, y}) => {
|
||||||
Menu.getApplicationMenu()!.popup({x: Math.ceil(x), y: Math.ceil(y)});
|
Menu.getApplicationMenu()!.popup({x: Math.ceil(x), y: Math.ceil(y)});
|
||||||
|
|
@ -262,12 +210,9 @@ export function newWindow(
|
||||||
// Same deal as above, grabbing the window titlebar when the window
|
// Same deal as above, grabbing the window titlebar when the window
|
||||||
// is maximized on Windows results in unmaximize, without hitting any
|
// is maximized on Windows results in unmaximize, without hitting any
|
||||||
// app buttons
|
// app buttons
|
||||||
const onGeometryChange = () => rpc.emit('windowGeometry change', {isMaximized: window.isMaximized()});
|
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore'] as any) {
|
||||||
window.on('maximize', onGeometryChange);
|
window.on(ev, () => rpc.emit('windowGeometry change', {}));
|
||||||
window.on('unmaximize', onGeometryChange);
|
}
|
||||||
window.on('minimize', onGeometryChange);
|
|
||||||
window.on('restore', onGeometryChange);
|
|
||||||
|
|
||||||
window.on('move', () => {
|
window.on('move', () => {
|
||||||
const position = window.getPosition();
|
const position = window.getPosition();
|
||||||
rpc.emit('move', {bounds: {x: position[0], y: position[1]}});
|
rpc.emit('move', {bounds: {x: position[0], y: position[1]}});
|
||||||
|
|
@ -275,16 +220,16 @@ export function newWindow(
|
||||||
rpc.on('close', () => {
|
rpc.on('close', () => {
|
||||||
window.close();
|
window.close();
|
||||||
});
|
});
|
||||||
rpc.on('command', (command) => {
|
rpc.on('command', command => {
|
||||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
execCommand(command, focusedWindow!);
|
execCommand(command, focusedWindow!);
|
||||||
});
|
});
|
||||||
// pass on the full screen events from the window to react
|
// pass on the full screen events from the window to react
|
||||||
rpc.win.on('enter-full-screen', () => {
|
rpc.win.on('enter-full-screen', () => {
|
||||||
rpc.emit('enter full screen');
|
rpc.emit('enter full screen', {});
|
||||||
});
|
});
|
||||||
rpc.win.on('leave-full-screen', () => {
|
rpc.win.on('leave-full-screen', () => {
|
||||||
rpc.emit('leave full screen');
|
rpc.emit('leave full screen', {});
|
||||||
});
|
});
|
||||||
const deleteSessions = () => {
|
const deleteSessions = () => {
|
||||||
sessions.forEach((session, key) => {
|
sessions.forEach((session, key) => {
|
||||||
|
|
@ -302,32 +247,29 @@ export function newWindow(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDroppedURL = (url: string) => {
|
// If file is dropped onto the terminal window, navigate event is prevented
|
||||||
const protocol = typeof url === 'string' && new URL(url).protocol;
|
// and his path is added to active session.
|
||||||
if (protocol === 'file:') {
|
|
||||||
const path = fileURLToPath(url);
|
|
||||||
return {uid: null, data: path, escaped: true};
|
|
||||||
} else if (protocol === 'http:' || protocol === 'https:') {
|
|
||||||
return {uid: null, data: url};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// If file is dropped onto the terminal window, navigate and new-window events are prevented
|
|
||||||
// and it's path is added to active session.
|
|
||||||
window.webContents.on('will-navigate', (event, url) => {
|
window.webContents.on('will-navigate', (event, url) => {
|
||||||
const data = handleDroppedURL(url);
|
const protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||||
if (data) {
|
if (protocol === 'file:') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
rpc.emit('session data send', data);
|
|
||||||
|
const path = fileUriToPath(url);
|
||||||
|
|
||||||
|
rpc.emit('session data send', {data: path, escaped: true});
|
||||||
|
} else if (protocol === 'http:' || protocol === 'https:') {
|
||||||
|
event.preventDefault();
|
||||||
|
rpc.emit('session data send', {data: url});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.webContents.setWindowOpenHandler(({url}) => {
|
|
||||||
const data = handleDroppedURL(url);
|
// xterm makes link clickable
|
||||||
if (data) {
|
window.webContents.on('new-window', (event, url) => {
|
||||||
rpc.emit('session data send', data);
|
const protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||||
return {action: 'deny'};
|
if (protocol === 'http:' || protocol === 'https:') {
|
||||||
|
event.preventDefault();
|
||||||
|
shell.openExternal(url);
|
||||||
}
|
}
|
||||||
return {action: 'allow'};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// expose internals to extension authors
|
// expose internals to extension authors
|
||||||
|
|
|
||||||
101
app/updater.js
Normal file
101
app/updater.js
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Packages
|
||||||
|
import electron from 'electron';
|
||||||
|
const {app} = electron;
|
||||||
|
import ms from 'ms';
|
||||||
|
import retry from 'async-retry';
|
||||||
|
|
||||||
|
// Utilities
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import {version} from './package';
|
||||||
|
import {getDecoratedConfig} from './plugins';
|
||||||
|
|
||||||
|
const {platform} = process;
|
||||||
|
const isLinux = platform === 'linux';
|
||||||
|
|
||||||
|
const autoUpdater = isLinux ? require('./auto-updater-linux').default : electron.autoUpdater;
|
||||||
|
|
||||||
|
let isInit = false;
|
||||||
|
// Default to the "stable" update channel
|
||||||
|
let canaryUpdates = false;
|
||||||
|
|
||||||
|
const buildFeedUrl = (canary, currentVersion) => {
|
||||||
|
const updatePrefix = canary ? 'releases-canary' : 'releases';
|
||||||
|
return `https://${updatePrefix}.hyper.is/update/${isLinux ? 'deb' : platform}/${currentVersion}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isCanary = updateChannel => updateChannel === 'canary';
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
autoUpdater.on('error', (err, msg) => {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
|
console.error('Error fetching updates', `${msg} (${err.stack})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await retry(async () => {
|
||||||
|
const content = await getDecoratedConfig();
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
throw new Error('No config content loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If defined in the config, switch to the "canary" channel
|
||||||
|
if (config.updateChannel && isCanary(config.updateChannel)) {
|
||||||
|
canaryUpdates = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feedURL = buildFeedUrl(canaryUpdates, version);
|
||||||
|
|
||||||
|
autoUpdater.setFeedURL(feedURL);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}, ms('10s'));
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}, ms('30m'));
|
||||||
|
|
||||||
|
isInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default win => {
|
||||||
|
if (!isInit) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
const {rpc} = win;
|
||||||
|
|
||||||
|
const onupdate = (ev, releaseNotes, releaseName, date, updateUrl, onQuitAndInstall) => {
|
||||||
|
const releaseUrl = updateUrl || `https://github.com/zeit/hyper/releases/tag/${releaseName}`;
|
||||||
|
rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !!onQuitAndInstall});
|
||||||
|
};
|
||||||
|
|
||||||
|
const eventName = isLinux ? 'update-available' : 'update-downloaded';
|
||||||
|
|
||||||
|
autoUpdater.on(eventName, onupdate);
|
||||||
|
|
||||||
|
rpc.once('quit and install', () => {
|
||||||
|
autoUpdater.quitAndInstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.config.subscribe(() => {
|
||||||
|
const {updateChannel} = app.plugins.getDecoratedConfig();
|
||||||
|
const newUpdateIsCanary = isCanary(updateChannel);
|
||||||
|
|
||||||
|
if (newUpdateIsCanary !== canaryUpdates) {
|
||||||
|
const feedURL = buildFeedUrl(newUpdateIsCanary, version);
|
||||||
|
|
||||||
|
autoUpdater.setFeedURL(feedURL);
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
|
||||||
|
canaryUpdates = newUpdateIsCanary;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
win.on('close', () => {
|
||||||
|
autoUpdater.removeListener(eventName, onupdate);
|
||||||
|
});
|
||||||
|
};
|
||||||
134
app/updater.ts
134
app/updater.ts
|
|
@ -1,134 +0,0 @@
|
||||||
// Packages
|
|
||||||
import electron, {app} from 'electron';
|
|
||||||
import type {BrowserWindow, AutoUpdater as OriginalAutoUpdater} from 'electron';
|
|
||||||
|
|
||||||
import retry from 'async-retry';
|
|
||||||
import ms from 'ms';
|
|
||||||
|
|
||||||
// Utilities
|
|
||||||
import autoUpdaterLinux from './auto-updater-linux';
|
|
||||||
import {getDefaultProfile} from './config';
|
|
||||||
import {version} from './package.json';
|
|
||||||
import {getDecoratedConfig} from './plugins';
|
|
||||||
|
|
||||||
// Necessary due to typescript not handling overloads well
|
|
||||||
type AutoUpdaterEvent =
|
|
||||||
| 'error'
|
|
||||||
| 'checking-for-update'
|
|
||||||
| 'before-quit-for-update'
|
|
||||||
| 'update-downloaded'
|
|
||||||
| 'update-available'
|
|
||||||
| 'update-not-available';
|
|
||||||
|
|
||||||
interface AutoUpdater extends OriginalAutoUpdater {
|
|
||||||
on(event: AutoUpdaterEvent, listener: Function): this;
|
|
||||||
removeListener(event: AutoUpdaterEvent, listener: Function): this;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {platform} = process;
|
|
||||||
const isLinux = platform === 'linux';
|
|
||||||
|
|
||||||
const autoUpdater: AutoUpdater = isLinux ? autoUpdaterLinux : electron.autoUpdater;
|
|
||||||
|
|
||||||
const getDecoratedConfigWithRetry = async () => {
|
|
||||||
return await retry(() => {
|
|
||||||
const content = getDecoratedConfig(getDefaultProfile());
|
|
||||||
if (!content) {
|
|
||||||
throw new Error('No config content loaded');
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const checkForUpdates = async () => {
|
|
||||||
const config = await getDecoratedConfigWithRetry();
|
|
||||||
if (!config.disableAutoUpdates) {
|
|
||||||
autoUpdater.checkForUpdates();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let isInit = false;
|
|
||||||
// Default to the "stable" update channel
|
|
||||||
let canaryUpdates = false;
|
|
||||||
|
|
||||||
const buildFeedUrl = (canary: boolean, currentVersion: string) => {
|
|
||||||
const updatePrefix = canary ? 'releases-canary' : 'releases';
|
|
||||||
const archSuffix = process.arch === 'arm64' || app.runningUnderARM64Translation ? '_arm64' : '';
|
|
||||||
return `https://${updatePrefix}.hyper.is/update/${isLinux ? 'deb' : platform}${archSuffix}/${currentVersion}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isCanary = (updateChannel: string) => updateChannel === 'canary';
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
autoUpdater.on('error', (err) => {
|
|
||||||
console.error('Error fetching updates', `${err.message} (${err.stack})`);
|
|
||||||
});
|
|
||||||
|
|
||||||
const config = await getDecoratedConfigWithRetry();
|
|
||||||
|
|
||||||
// If defined in the config, switch to the "canary" channel
|
|
||||||
if (config.updateChannel && isCanary(config.updateChannel)) {
|
|
||||||
canaryUpdates = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedURL = buildFeedUrl(canaryUpdates, version);
|
|
||||||
|
|
||||||
autoUpdater.setFeedURL({url: feedURL});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
void checkForUpdates();
|
|
||||||
}, ms('10s'));
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
void checkForUpdates();
|
|
||||||
}, ms('30m'));
|
|
||||||
|
|
||||||
isInit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const updater = (win: BrowserWindow) => {
|
|
||||||
if (!isInit) {
|
|
||||||
void init();
|
|
||||||
}
|
|
||||||
|
|
||||||
const {rpc} = win;
|
|
||||||
|
|
||||||
const onupdate = (ev: Event, releaseNotes: string, releaseName: string, date: Date, updateUrl: string) => {
|
|
||||||
const releaseUrl = updateUrl || `https://github.com/quine-global/hyper/releases/tag/${releaseName}`;
|
|
||||||
rpc.emit('update available', {releaseNotes, releaseName, releaseUrl, canInstall: !isLinux});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLinux) {
|
|
||||||
autoUpdater.on('update-available', onupdate);
|
|
||||||
} else {
|
|
||||||
autoUpdater.on('update-downloaded', onupdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc.once('quit and install', () => {
|
|
||||||
autoUpdater.quitAndInstall();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.config.subscribe(async () => {
|
|
||||||
const {updateChannel} = await getDecoratedConfigWithRetry();
|
|
||||||
const newUpdateIsCanary = isCanary(updateChannel);
|
|
||||||
|
|
||||||
if (newUpdateIsCanary !== canaryUpdates) {
|
|
||||||
const feedURL = buildFeedUrl(newUpdateIsCanary, version);
|
|
||||||
|
|
||||||
autoUpdater.setFeedURL({url: feedURL});
|
|
||||||
void checkForUpdates();
|
|
||||||
|
|
||||||
canaryUpdates = newUpdateIsCanary;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
win.on('close', () => {
|
|
||||||
if (isLinux) {
|
|
||||||
autoUpdater.removeListener('update-available', onupdate);
|
|
||||||
} else {
|
|
||||||
autoUpdater.removeListener('update-downloaded', onupdate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updater;
|
|
||||||
|
|
@ -1,25 +1,17 @@
|
||||||
import {existsSync, readlink, symlink} from 'fs';
|
import pify from 'pify';
|
||||||
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {promisify} from 'util';
|
import Registry from 'winreg';
|
||||||
|
|
||||||
import {clipboard, dialog} from 'electron';
|
|
||||||
|
|
||||||
import {mkdirpSync} from 'fs-extra';
|
|
||||||
import * as Registry from 'native-reg';
|
|
||||||
import type {ValueType} from 'native-reg';
|
|
||||||
import sudoPrompt from 'sudo-prompt';
|
|
||||||
|
|
||||||
import {cliScriptPath, cliLinkPath} from '../config/paths';
|
|
||||||
import notify from '../notify';
|
import notify from '../notify';
|
||||||
|
import {cliScriptPath, cliLinkPath} from '../config/paths';
|
||||||
|
|
||||||
const readLink = promisify(readlink);
|
const readlink = pify(fs.readlink);
|
||||||
const symLink = promisify(symlink);
|
const symlink = pify(fs.symlink);
|
||||||
const sudoExec = promisify(sudoPrompt.exec);
|
|
||||||
|
|
||||||
const checkInstall = () => {
|
const checkInstall = () => {
|
||||||
return readLink(cliLinkPath)
|
return readlink(cliLinkPath)
|
||||||
.then((link) => link === cliScriptPath)
|
.then(link => link === cliScriptPath)
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -27,133 +19,101 @@ const checkInstall = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addSymlink = async (silent: boolean) => {
|
const addSymlink = () => {
|
||||||
try {
|
return checkInstall().then(isInstalled => {
|
||||||
const isInstalled = await checkInstall();
|
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Hyper CLI already in PATH');
|
console.log('Hyper CLI already in PATH');
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Linking HyperCLI');
|
console.log('Linking HyperCLI');
|
||||||
if (!existsSync(path.dirname(cliLinkPath))) {
|
return symlink(cliScriptPath, cliLinkPath);
|
||||||
try {
|
});
|
||||||
mkdirpSync(path.dirname(cliLinkPath));
|
|
||||||
} catch (err) {
|
|
||||||
throw `Failed to create directory ${path.dirname(cliLinkPath)} - ${err}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await symLink(cliScriptPath, cliLinkPath);
|
|
||||||
} catch (_err) {
|
|
||||||
const err = _err as {code: string};
|
|
||||||
// 'EINVAL' is returned by readlink,
|
|
||||||
// 'EEXIST' is returned by symlink
|
|
||||||
let error =
|
|
||||||
err.code === 'EEXIST' || err.code === 'EINVAL'
|
|
||||||
? `File already exists: ${cliLinkPath}`
|
|
||||||
: `Symlink creation failed: ${err.code}`;
|
|
||||||
// Need sudo access to create symlink
|
|
||||||
if (err.code === 'EACCES' && !silent) {
|
|
||||||
const result = await dialog.showMessageBox({
|
|
||||||
message: `You need to grant elevated privileges to add Hyper CLI to PATH
|
|
||||||
Or you can run
|
|
||||||
sudo ln -sf "${cliScriptPath}" "${cliLinkPath}"`,
|
|
||||||
type: 'info',
|
|
||||||
buttons: ['OK', 'Copy Command', 'Cancel']
|
|
||||||
});
|
|
||||||
if (result.response === 0) {
|
|
||||||
try {
|
|
||||||
await sudoExec(`ln -sf "${cliScriptPath}" "${cliLinkPath}"`, {name: 'Hyper'});
|
|
||||||
return;
|
|
||||||
} catch (_error) {
|
|
||||||
error = (_error as any[])[0];
|
|
||||||
}
|
|
||||||
} else if (result.response === 1) {
|
|
||||||
clipboard.writeText(`sudo ln -sf "${cliScriptPath}" "${cliLinkPath}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addBinToUserPath = () => {
|
const addBinToUserPath = () => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
// Can't use pify because of param order of Registry.values callback
|
||||||
try {
|
return new Promise((resolve, reject) => {
|
||||||
const envKey = Registry.openKey(Registry.HKCU, 'Environment', Registry.Access.ALL_ACCESS)!;
|
const envKey = new Registry({hive: 'HKCU', key: '\\Environment'});
|
||||||
|
envKey.values((err, items) => {
|
||||||
// C:\Users\<user>\AppData\Local\Programs\hyper\resources\bin
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// C:\Users\<user>\AppData\Local\hyper\app-<version>\resources\bin
|
||||||
const binPath = path.dirname(cliScriptPath);
|
const binPath = path.dirname(cliScriptPath);
|
||||||
// C:\Users\<user>\AppData\Local\hyper
|
// C:\Users\<user>\AppData\Local\hyper
|
||||||
const oldPath = path.resolve(process.env.LOCALAPPDATA!, 'hyper');
|
const basePath = path.resolve(binPath, '../../..');
|
||||||
|
|
||||||
const items = Registry.enumValueNames(envKey);
|
const pathItem = items.find(item => item.name.toUpperCase() === 'PATH');
|
||||||
const pathItem = items.find((item) => item.toUpperCase() === 'PATH');
|
|
||||||
const pathItemName = pathItem || 'PATH';
|
|
||||||
|
|
||||||
let newPathValue = binPath;
|
let newPathValue = binPath;
|
||||||
let type: ValueType = Registry.ValueType.SZ;
|
const pathItemName = pathItem ? pathItem.name : 'PATH';
|
||||||
if (pathItem) {
|
if (pathItem) {
|
||||||
type = Registry.queryValueRaw(envKey, pathItem)!.type;
|
const pathParts = pathItem.value.split(';');
|
||||||
if (type !== Registry.ValueType.SZ && type !== Registry.ValueType.EXPAND_SZ) {
|
const existingPath = pathParts.find(pathPart => pathPart === binPath);
|
||||||
reject(`Registry key type is ${type}`);
|
if (existingPath) {
|
||||||
return;
|
//eslint-disable-next-line no-console
|
||||||
}
|
|
||||||
const value = Registry.queryValue(envKey, pathItem) as string;
|
|
||||||
let pathParts = value.split(';');
|
|
||||||
const existingPath = pathParts.includes(binPath);
|
|
||||||
const existingOldPath = pathParts.some((pathPart) => pathPart.startsWith(oldPath));
|
|
||||||
if (existingPath && !existingOldPath) {
|
|
||||||
console.log('Hyper CLI already in PATH');
|
console.log('Hyper CLI already in PATH');
|
||||||
Registry.closeKey(envKey);
|
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because nsis install path is different from squirrel we need to remove old path if present
|
// Because version is in path we need to remove old path if present and add current path
|
||||||
// and add current path if absent
|
newPathValue = pathParts
|
||||||
if (existingOldPath) pathParts = pathParts.filter((pathPart) => !pathPart.startsWith(oldPath));
|
.filter(pathPart => !pathPart.startsWith(basePath))
|
||||||
if (!pathParts.includes(binPath)) pathParts.push(binPath);
|
.concat([binPath])
|
||||||
newPathValue = pathParts.join(';');
|
.join(';');
|
||||||
}
|
}
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('Adding HyperCLI path (registry)');
|
console.log('Adding HyperCLI path (registry)');
|
||||||
Registry.setValueRaw(envKey, pathItemName, type, Registry.formatString(newPathValue));
|
envKey.set(pathItemName, Registry.REG_SZ, newPathValue, error => {
|
||||||
Registry.closeKey(envKey);
|
if (error) {
|
||||||
resolve();
|
reject(error);
|
||||||
} catch (error) {
|
return;
|
||||||
reject(error);
|
}
|
||||||
}
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const logNotify = (withNotification: boolean, title: string, body: string, details?: {error?: any}) => {
|
const logNotify = (withNotification: boolean, title: string, body: string, details?: any) => {
|
||||||
console.log(title, body, details);
|
console.log(title, body, details);
|
||||||
withNotification && notify(title, body, details);
|
withNotification && notify(title, body, details);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const installCLI = async (withNotification: boolean) => {
|
export const installCLI = (withNotification: boolean) => {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
try {
|
addBinToUserPath()
|
||||||
await addBinToUserPath();
|
.then(() =>
|
||||||
logNotify(
|
logNotify(
|
||||||
withNotification,
|
withNotification,
|
||||||
'Hyper CLI installed',
|
'Hyper CLI installed',
|
||||||
'You may need to restart your computer to complete this installation process.'
|
'You may need to restart your computer to complete this installation process.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.catch(err =>
|
||||||
|
logNotify(withNotification, 'Hyper CLI installation failed', `Failed to add Hyper CLI path to user PATH ${err}`)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} else if (process.platform === 'darwin') {
|
||||||
logNotify(withNotification, 'Hyper CLI installation failed', `Failed to add Hyper CLI path to user PATH ${err}`);
|
addSymlink()
|
||||||
}
|
.then(() => logNotify(withNotification, 'Hyper CLI installed', `Symlink created at ${cliLinkPath}`))
|
||||||
} else if (process.platform === 'darwin' || process.platform === 'linux') {
|
.catch(err => {
|
||||||
// AppImages are mounted on run at a temporary path, don't create symlink
|
// 'EINVAL' is returned by readlink,
|
||||||
if (process.env['APPIMAGE']) {
|
// 'EEXIST' is returned by symlink
|
||||||
console.log('Skipping CLI symlink creation as it is an AppImage install');
|
const error =
|
||||||
return;
|
err.code === 'EEXIST' || err.code === 'EINVAL'
|
||||||
}
|
? `File already exists: ${cliLinkPath}`
|
||||||
try {
|
: `Symlink creation failed: ${err.code}`;
|
||||||
await addSymlink(!withNotification);
|
|
||||||
logNotify(withNotification, 'Hyper CLI installed', `Symlink created at ${cliLinkPath}`);
|
//eslint-disable-next-line no-console
|
||||||
} catch (error) {
|
console.error(err);
|
||||||
logNotify(withNotification, 'Hyper CLI installation failed', `${error}`);
|
logNotify(withNotification, 'Hyper CLI installation failed', error);
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
logNotify(withNotification, 'Hyper CLI installation failed', `Unsupported platform ${process.platform}`);
|
withNotification &&
|
||||||
|
notify('Hyper CLI installation', 'Command is added in PATH only at package installation. Please reinstall.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,14 @@ const colorList = [
|
||||||
|
|
||||||
export const getColorMap: {
|
export const getColorMap: {
|
||||||
<T>(colors: T): T extends (infer U)[] ? {[k: string]: U} : T;
|
<T>(colors: T): T extends (infer U)[] ? {[k: string]: U} : T;
|
||||||
} = (colors) => {
|
} = colors => {
|
||||||
if (!Array.isArray(colors)) {
|
if (!Array.isArray(colors)) {
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
||||||
return colors.reduce((result, color, index) => {
|
return colors.reduce((result, color, index) => {
|
||||||
if (index < colorList.length) {
|
if (index < colorList.length) {
|
||||||
result[colorList[index]] = color;
|
result[colorList[index]] = color;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ const generatePrefixedCommand = (command: string, shortcuts: string[]) => {
|
||||||
for (let i = 1; i <= 9; i++) {
|
for (let i = 1; i <= 9; i++) {
|
||||||
// 9 is a special number because it means 'last'
|
// 9 is a special number because it means 'last'
|
||||||
const index = i === 9 ? 'last' : i;
|
const index = i === 9 ? 'last' : i;
|
||||||
const prefixedShortcuts = shortcuts.map((shortcut) => `${shortcut}+${i}`);
|
const prefixedShortcuts = shortcuts.map(shortcut => `${shortcut}+${i}`);
|
||||||
result[`${baseCmd}:${index}`] = prefixedShortcuts;
|
result[`${baseCmd}:${index}`] = prefixedShortcuts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapKeys = (config: Record<string, string[] | string>) => {
|
export default (config: Record<string, string[] | string>) => {
|
||||||
return Object.keys(config).reduce((keymap: Record<string, string[]>, command: string) => {
|
return Object.keys(config).reduce((keymap: Record<string, string[]>, command: string) => {
|
||||||
if (!command) {
|
if (!command) {
|
||||||
return keymap;
|
return keymap;
|
||||||
|
|
@ -20,10 +20,11 @@ const mapKeys = (config: Record<string, string[] | string>) => {
|
||||||
const _shortcuts = config[command];
|
const _shortcuts = config[command];
|
||||||
const shortcuts = Array.isArray(_shortcuts) ? _shortcuts : [_shortcuts];
|
const shortcuts = Array.isArray(_shortcuts) ? _shortcuts : [_shortcuts];
|
||||||
const fixedShortcuts: string[] = [];
|
const fixedShortcuts: string[] = [];
|
||||||
shortcuts.forEach((shortcut) => {
|
shortcuts.forEach(shortcut => {
|
||||||
let newShortcut = shortcut;
|
let newShortcut = shortcut;
|
||||||
if (newShortcut.indexOf('cmd') !== -1) {
|
if (newShortcut.indexOf('cmd') !== -1) {
|
||||||
// Mousetrap use `command` and not `cmd`
|
// Mousetrap use `command` and not `cmd`
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.warn('Your config use deprecated `cmd` in key combination. Please use `command` instead.');
|
console.warn('Your config use deprecated `cmd` in key combination. Please use `command` instead.');
|
||||||
newShortcut = newShortcut.replace('cmd', 'command');
|
newShortcut = newShortcut.replace('cmd', 'command');
|
||||||
}
|
}
|
||||||
|
|
@ -39,5 +40,3 @@ const mapKeys = (config: Record<string, string[] | string>) => {
|
||||||
return keymap;
|
return keymap;
|
||||||
}, {});
|
}, {});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mapKeys;
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
export const getFallBackShellConfig = (
|
|
||||||
shell: string,
|
|
||||||
shellArgs: string[],
|
|
||||||
defaultShell: string,
|
|
||||||
defaultShellArgs: string[]
|
|
||||||
): {
|
|
||||||
shell: string;
|
|
||||||
shellArgs: string[];
|
|
||||||
} | null => {
|
|
||||||
if (shellArgs.length > 0) {
|
|
||||||
return {
|
|
||||||
shell,
|
|
||||||
shellArgs: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shell != defaultShell) {
|
|
||||||
return {
|
|
||||||
shell: defaultShell,
|
|
||||||
shellArgs: defaultShellArgs
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
import * as Registry from 'native-reg';
|
|
||||||
import type {HKEY} from 'native-reg';
|
|
||||||
|
|
||||||
const appPath = `"${process.execPath}"`;
|
|
||||||
const regKeys = [
|
|
||||||
`Software\\Classes\\Directory\\Background\\shell\\Hyper`,
|
|
||||||
`Software\\Classes\\Directory\\shell\\Hyper`,
|
|
||||||
`Software\\Classes\\Drive\\shell\\Hyper`
|
|
||||||
];
|
|
||||||
const regParts = [
|
|
||||||
{key: 'command', name: '', value: `${appPath} "%V"`},
|
|
||||||
{name: '', value: 'Open &Hyper here'},
|
|
||||||
{name: 'Icon', value: `${appPath}`}
|
|
||||||
];
|
|
||||||
|
|
||||||
function addValues(hyperKey: HKEY, commandKey: HKEY) {
|
|
||||||
try {
|
|
||||||
Registry.setValueSZ(hyperKey, regParts[1].name, regParts[1].value);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Registry.setValueSZ(hyperKey, regParts[2].name, regParts[2].value);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Registry.setValueSZ(commandKey, regParts[0].name, regParts[0].value);
|
|
||||||
} catch (err_) {
|
|
||||||
console.error(err_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const add = () => {
|
|
||||||
regKeys.forEach((regKey) => {
|
|
||||||
try {
|
|
||||||
const hyperKey =
|
|
||||||
Registry.openKey(Registry.HKCU, regKey, Registry.Access.ALL_ACCESS) ||
|
|
||||||
Registry.createKey(Registry.HKCU, regKey, Registry.Access.ALL_ACCESS);
|
|
||||||
const commandKey =
|
|
||||||
Registry.openKey(Registry.HKCU, `${regKey}\\${regParts[0].key}`, Registry.Access.ALL_ACCESS) ||
|
|
||||||
Registry.createKey(Registry.HKCU, `${regKey}\\${regParts[0].key}`, Registry.Access.ALL_ACCESS);
|
|
||||||
addValues(hyperKey, commandKey);
|
|
||||||
Registry.closeKey(hyperKey);
|
|
||||||
Registry.closeKey(commandKey);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const remove = () => {
|
|
||||||
regKeys.forEach((regKey) => {
|
|
||||||
try {
|
|
||||||
Registry.deleteTree(Registry.HKCU, regKey);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Color from 'color';
|
||||||
// returns a background color that's in hex
|
// returns a background color that's in hex
|
||||||
// format including the alpha channel (e.g.: `#00000050`)
|
// format including the alpha channel (e.g.: `#00000050`)
|
||||||
// input can be any css value (rgb, hsl, string…)
|
// input can be any css value (rgb, hsl, string…)
|
||||||
const toElectronBackgroundColor = (bgColor: string) => {
|
export default (bgColor: string) => {
|
||||||
const color = Color(bgColor);
|
const color = Color(bgColor);
|
||||||
|
|
||||||
if (color.alpha() === 1) {
|
if (color.alpha() === 1) {
|
||||||
|
|
@ -13,7 +13,8 @@ const toElectronBackgroundColor = (bgColor: string) => {
|
||||||
|
|
||||||
// http://stackoverflow.com/a/11019879/1202488
|
// http://stackoverflow.com/a/11019879/1202488
|
||||||
const alphaHex = Math.round(color.alpha() * 255).toString(16);
|
const alphaHex = Math.round(color.alpha() * 255).toString(16);
|
||||||
return `#${alphaHex}${color.hex().toString().slice(1)}`;
|
return `#${alphaHex}${color
|
||||||
|
.hex()
|
||||||
|
.toString()
|
||||||
|
.substr(1)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default toElectronBackgroundColor;
|
|
||||||
|
|
|
||||||
1683
app/yarn.lock
1683
app/yarn.lock
File diff suppressed because it is too large
Load diff
29
appveyor.yml
Normal file
29
appveyor.yml
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# https://github.com/sindresorhus/appveyor-node/blob/master/appveyor.yml
|
||||||
|
|
||||||
|
environment:
|
||||||
|
matrix:
|
||||||
|
- platform: x64
|
||||||
|
|
||||||
|
image: Visual Studio 2019
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: Install-Product node 12 x64
|
||||||
|
- set CI=true
|
||||||
|
- yarn
|
||||||
|
|
||||||
|
build: off
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
|
||||||
|
shallow_clone: true
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- node --version
|
||||||
|
- yarn --version
|
||||||
|
- yarn run test
|
||||||
|
|
||||||
|
on_success:
|
||||||
|
- IF %APPVEYOR_REPO_BRANCH%==canary cp build\canary.ico build\icon.ico
|
||||||
|
- yarn run dist
|
||||||
|
- ps: Get-ChildItem .\dist\squirrel-windows\*.exe | % { Push-AppveyorArtifact $_.FullName }
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
files: ['test/*'],
|
|
||||||
extensions: ['ts'],
|
|
||||||
require: ['ts-node/register/transpile-only'],
|
|
||||||
timeout: '2m',
|
|
||||||
verbose: true,
|
|
||||||
// Due to permissions issues, Windows needs cache turned off
|
|
||||||
cache: false
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
module.exports = {
|
export default {
|
||||||
files: ['test/unit/*'],
|
files: ['test/unit/*'],
|
||||||
|
babel: {
|
||||||
|
compileEnhancements: false,
|
||||||
|
compileAsTests: ['**/testUtils/**/*']
|
||||||
|
},
|
||||||
extensions: ['ts'],
|
extensions: ['ts'],
|
||||||
require: ['ts-node/register/transpile-only'],
|
require: ['ts-node/register/transpile-only']
|
||||||
verbose: true,
|
|
||||||
// Due to permissions issues, Windows needs cache turned off
|
|
||||||
cache: false
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
88
bin/cp-snapshot.js
vendored
88
bin/cp-snapshot.js
vendored
|
|
@ -1,88 +0,0 @@
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const fsPromises = require('fs/promises');
|
|
||||||
const {Arch} = require('electron-builder');
|
|
||||||
|
|
||||||
function copySnapshot(pathToElectron, archToCopy) {
|
|
||||||
const snapshotFileName = 'snapshot_blob.bin';
|
|
||||||
const v8ContextFileName = getV8ContextFileName(archToCopy);
|
|
||||||
const pathToBlob = path.resolve(__dirname, '..', 'cache', archToCopy, snapshotFileName);
|
|
||||||
const pathToBlobV8 = path.resolve(__dirname, '..', 'cache', archToCopy, v8ContextFileName);
|
|
||||||
|
|
||||||
console.log('Copying v8 snapshots from', pathToBlob, 'to', pathToElectron);
|
|
||||||
fs.mkdirSync(pathToElectron, { recursive: true });
|
|
||||||
fs.copyFileSync(pathToBlob, path.join(pathToElectron, snapshotFileName));
|
|
||||||
fs.copyFileSync(pathToBlobV8, path.join(pathToElectron, v8ContextFileName));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPathToElectron() {
|
|
||||||
const electronPath = require.resolve('electron');
|
|
||||||
|
|
||||||
switch (process.platform) {
|
|
||||||
case 'darwin':
|
|
||||||
return path.resolve(
|
|
||||||
electronPath,
|
|
||||||
'..',
|
|
||||||
'..',
|
|
||||||
'..',
|
|
||||||
'dist/Electron.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources'
|
|
||||||
);
|
|
||||||
case 'win32':
|
|
||||||
case 'linux':
|
|
||||||
return path.resolve(electronPath, '..', '..', '..', 'dist');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getV8ContextFileName(archToCopy) {
|
|
||||||
return `snapshot_blob.bin`;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.default = async (context) => {
|
|
||||||
const archToCopy = Arch[context.arch];
|
|
||||||
const pathToElectron =
|
|
||||||
process.platform === 'darwin'
|
|
||||||
? `${context.appOutDir}/Hyper.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources`
|
|
||||||
: context.appOutDir;
|
|
||||||
copySnapshot(pathToElectron, archToCopy);
|
|
||||||
useLoaderScriptFix(context);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
const archToCopy = process.env.npm_config_arch;
|
|
||||||
const pathToElectron = getPathToElectron();
|
|
||||||
if ((process.arch.startsWith('arm') ? 'arm64' : 'x64') === archToCopy) {
|
|
||||||
copySnapshot(pathToElectron, archToCopy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// copied and modified from https://github.com/gergof/electron-builder-sandbox-fix/blob/master/lib/index.js
|
|
||||||
// copied and modified from https://github.com/Adamant-im/adamant-im/blob/7b20272a717833ffb0b49b034ab9974118fc59ec/scripts/electron/sandboxFix.js
|
|
||||||
|
|
||||||
const useLoaderScriptFix = async (params) => {
|
|
||||||
if (params.electronPlatformName !== 'linux') {
|
|
||||||
// this fix is only required on linux
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const executable = path.join(params.appOutDir, params.packager.executableName)
|
|
||||||
|
|
||||||
const loaderScript = `#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "\${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
exec "$SCRIPT_DIR/${params.packager.executableName}.bin" "--no-sandbox" "$@"
|
|
||||||
`
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fsPromises.rename(executable, executable + '.bin')
|
|
||||||
await fsPromises.writeFile(executable, loaderScript)
|
|
||||||
await fsPromises.chmod(executable, 0o755)
|
|
||||||
} catch (e) {
|
|
||||||
console.error('failed to create loader for sandbox fix: ' + e.message)
|
|
||||||
throw new Error('Failed to create loader for sandbox fix')
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('sandbox fix successfully applied')
|
|
||||||
}
|
|
||||||
84
bin/mk-snapshot.js
vendored
84
bin/mk-snapshot.js
vendored
|
|
@ -1,84 +0,0 @@
|
||||||
const childProcess = require('child_process');
|
|
||||||
const vm = require('vm');
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const electronLink = require('electron-link');
|
|
||||||
const {mkdirp} = require('fs-extra');
|
|
||||||
|
|
||||||
const excludedModules = {};
|
|
||||||
|
|
||||||
const crossArchDirs = ['clang_x86_v8_arm', 'clang_x64_v8_arm64', 'win_clang_x64'];
|
|
||||||
|
|
||||||
const archMap = {
|
|
||||||
x64: 'x86_64',
|
|
||||||
arm64: 'arm64'
|
|
||||||
};
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const npmConfigArch = process.env.npm_config_arch;
|
|
||||||
if (!npmConfigArch) {
|
|
||||||
throw new Error('env var npm_config_arch is not specified')
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const baseDirPath = path.resolve(__dirname, '..');
|
|
||||||
|
|
||||||
console.log('Creating a linked script..');
|
|
||||||
const result = await electronLink({
|
|
||||||
baseDirPath: baseDirPath,
|
|
||||||
mainPath: `${__dirname}/snapshot-libs.js`,
|
|
||||||
cachePath: `${baseDirPath}/cache`,
|
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
|
||||||
shouldExcludeModule: (modulePath) => excludedModules.hasOwnProperty(modulePath)
|
|
||||||
});
|
|
||||||
|
|
||||||
const snapshotScriptPath = `${baseDirPath}/cache/snapshot-libs.js`;
|
|
||||||
fs.writeFileSync(snapshotScriptPath, result.snapshotScript);
|
|
||||||
|
|
||||||
// Verify if we will be able to use this in `mksnapshot`
|
|
||||||
vm.runInNewContext(result.snapshotScript, undefined, {filename: snapshotScriptPath, displayErrors: true});
|
|
||||||
|
|
||||||
const outputBlobPath = `${baseDirPath}/cache/${npmConfigArch}`;
|
|
||||||
await mkdirp(outputBlobPath);
|
|
||||||
|
|
||||||
let mksnapshotBinPath
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
mksnapshotBinPath =
|
|
||||||
require.resolve(
|
|
||||||
path.join("electron-mksnapshot", "bin", "mksnapshot.exe")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mksnapshotBinPath =
|
|
||||||
require.resolve(
|
|
||||||
path.join("electron-mksnapshot", "bin", "mksnapshot")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
mksnapshotBinPath = path.dirname(mksnapshotBinPath);
|
|
||||||
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
const matchingDirs = crossArchDirs.map((dir) => `${mksnapshotBinPath}/${dir}`).filter((dir) => fs.existsSync(dir));
|
|
||||||
for (const dir of matchingDirs) {
|
|
||||||
if (fs.existsSync(`${mksnapshotBinPath}/gen/v8/embedded.S`)) {
|
|
||||||
await mkdirp(`${dir}/gen/v8`);
|
|
||||||
fs.copyFileSync(`${mksnapshotBinPath}/gen/v8/embedded.S`, `${dir}/gen/v8/embedded.S`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startupBlobPath = path.join(outputBlobPath, 'snapshot_blob.bin');
|
|
||||||
|
|
||||||
console.log(`Generating startup blob in "${outputBlobPath}"`);
|
|
||||||
const res = childProcess.execFileSync(
|
|
||||||
require.resolve(`electron-mksnapshot/bin/mksnapshot${process.platform === 'win32' ? '.exe' : ''}`),
|
|
||||||
[
|
|
||||||
'--startup-src=' + snapshotScriptPath,
|
|
||||||
'--startup-blob=' + startupBlobPath,
|
|
||||||
`--target-arch=${archMap[process.env.npm_config_arch]}`,
|
|
||||||
//'--v8-context-snapshot=' + v8SnapshotPath
|
|
||||||
]
|
|
||||||
);
|
|
||||||
console.log('result:', res.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((err) => console.error(err));
|
|
||||||
17
bin/notarize.js
vendored
17
bin/notarize.js
vendored
|
|
@ -1,17 +0,0 @@
|
||||||
exports.default = async function notarizing(context) {
|
|
||||||
|
|
||||||
const { electronPlatformName, appOutDir } = context;
|
|
||||||
if (electronPlatformName !== "darwin" || !process.env.APPLE_ID || !process.env.APPLE_PASSWORD) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { notarize } = await import('@electron/notarize');
|
|
||||||
|
|
||||||
const appName = context.packager.appInfo.productFilename;
|
|
||||||
return await notarize({
|
|
||||||
appBundleId: "com.quineglobal.hyper",
|
|
||||||
appPath: `${appOutDir}/${appName}.app`,
|
|
||||||
appleId: process.env.APPLE_ID,
|
|
||||||
appleIdPassword: process.env.APPLE_PASSWORD
|
|
||||||
});
|
|
||||||
};
|
|
||||||
31
bin/snapshot-libs.js
vendored
31
bin/snapshot-libs.js
vendored
|
|
@ -1,31 +0,0 @@
|
||||||
require('color-convert');
|
|
||||||
require('color-string');
|
|
||||||
require('columnify');
|
|
||||||
require('lodash');
|
|
||||||
require('ms');
|
|
||||||
require('normalize-url');
|
|
||||||
require('parse-url');
|
|
||||||
require('php-escape-shell');
|
|
||||||
require('plist');
|
|
||||||
require('redux-thunk');
|
|
||||||
require('redux');
|
|
||||||
require('reselect');
|
|
||||||
require('seamless-immutable');
|
|
||||||
require('stylis');
|
|
||||||
require('@xterm/addon-unicode11');
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
if (false) {
|
|
||||||
require('args');
|
|
||||||
require('mousetrap');
|
|
||||||
require('open');
|
|
||||||
require('react-dom');
|
|
||||||
require('react-redux');
|
|
||||||
require('react');
|
|
||||||
require('@xterm/addon-fit');
|
|
||||||
require('@xterm/addon-image');
|
|
||||||
require('@xterm/addon-search');
|
|
||||||
require('@xterm/addon-web-links');
|
|
||||||
require('@xterm/addon-webgl');
|
|
||||||
require('@xterm/addon-canvas');
|
|
||||||
require('@xterm/xterm');
|
|
||||||
}
|
|
||||||
34
build/Info.plist
Normal file
34
build/Info.plist
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Folders</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.folder</string>
|
||||||
|
<string>com.apple.bundle</string>
|
||||||
|
<string>com.apple.package</string>
|
||||||
|
<string>com.apple.resolvable</string>
|
||||||
|
</array>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>UnixExecutables</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Shell</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>public.unix-executable</string>
|
||||||
|
</array>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Binary file not shown.
BIN
build/icon.fig
BIN
build/icon.fig
Binary file not shown.
BIN
build/icon.icns
BIN
build/icon.icns
Binary file not shown.
|
|
@ -1,26 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>com.apple.security.automation.apple-events</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.allow-jit</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.cs.disable-library-validation</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.device.audio-input</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.device.camera</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.personal-information.addressbook</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.personal-information.calendars</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.personal-information.location</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.security.personal-information.photos-library</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|
@ -1,27 +1,8 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Deeply inspired by https://github.com/Microsoft/vscode/blob/1.65.2/resources/darwin/bin/code.sh
|
# Deeply inspired by https://github.com/Microsoft/vscode/blob/1.17.0/resources/darwin/bin/code.sh
|
||||||
|
|
||||||
# TODO: bash is deprecated on macOS and will be removed.
|
function realpath() { /usr/bin/python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0"; }
|
||||||
# Port this to /bin/sh or /bin/zsh
|
CONTENTS="$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")"
|
||||||
|
|
||||||
function app_realpath() {
|
|
||||||
SOURCE=$1
|
|
||||||
while [ -h "$SOURCE" ]; do
|
|
||||||
DIR=$(dirname "$SOURCE")
|
|
||||||
SOURCE=$(readlink "$SOURCE")
|
|
||||||
[[ $SOURCE != /* ]] && SOURCE=$DIR/$SOURCE
|
|
||||||
done
|
|
||||||
SOURCE_DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )"
|
|
||||||
echo "${SOURCE_DIR%%"${SOURCE_DIR#*.app}"}"
|
|
||||||
}
|
|
||||||
|
|
||||||
APP_PATH="$(app_realpath "${BASH_SOURCE[0]}")"
|
|
||||||
if [ -z "$APP_PATH" ]; then
|
|
||||||
echo "Unable to determine app path from symlink : ${BASH_SOURCE[0]}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
CONTENTS="$APP_PATH/Contents"
|
|
||||||
ELECTRON="$CONTENTS/MacOS/Hyper"
|
ELECTRON="$CONTENTS/MacOS/Hyper"
|
||||||
CLI="$CONTENTS/Resources/bin/cli.js"
|
CLI="$CONTENTS/Resources/bin/cli.js"
|
||||||
ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@"
|
ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
!macro customInstall
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "" "Open &Hyper here"
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper" "Icon" `"$appExe"`
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\Hyper\command" "" `"$appExe" "%V"`
|
|
||||||
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "" "Open &Hyper here"
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper" "Icon" `"$appExe"`
|
|
||||||
WriteRegStr HKCU "Software\Classes\Directory\shell\Hyper\command" "" `"$appExe" "%V"`
|
|
||||||
|
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "" "Open &Hyper here"
|
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper" "Icon" `"$appExe"`
|
|
||||||
WriteRegStr HKCU "Software\Classes\Drive\shell\Hyper\command" "" `"$appExe" "%V"`
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro customUnInstall
|
|
||||||
DeleteRegKey HKCU "Software\Classes\Directory\Background\shell\Hyper"
|
|
||||||
DeleteRegKey HKCU "Software\Classes\Directory\shell\Hyper"
|
|
||||||
DeleteRegKey HKCU "Software\Classes\Drive\shell\Hyper"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro customInstallMode
|
|
||||||
StrCpy $isForceCurrentInstall "1"
|
|
||||||
!macroend
|
|
||||||
|
|
||||||
!macro customInit
|
|
||||||
IfFileExists $LOCALAPPDATA\Hyper\Update.exe 0 +2
|
|
||||||
nsExec::Exec '"$LOCALAPPDATA\Hyper\Update.exe" --uninstall -s'
|
|
||||||
!macroend
|
|
||||||
83
cli/api.ts
83
cli/api.ts
|
|
@ -1,28 +1,27 @@
|
||||||
// eslint-disable-next-line eslint-comments/disable-enable-pair
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import registryUrlModule from 'registry-url';
|
import registryUrlModule from 'registry-url';
|
||||||
|
|
||||||
const registryUrl = registryUrlModule();
|
const registryUrl = registryUrlModule();
|
||||||
|
import pify from 'pify';
|
||||||
|
import * as recast from 'recast';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
// If the user defines XDG_CONFIG_HOME they definitely want their config there,
|
||||||
// otherwise use the home directory in linux/mac and userdata in windows
|
// otherwise use the home directory in linux/mac and userdata in windows
|
||||||
const applicationDirectory = process.env.XDG_CONFIG_HOME
|
const applicationDirectory =
|
||||||
? path.join(process.env.XDG_CONFIG_HOME, 'Hyper')
|
process.env.XDG_CONFIG_HOME !== undefined
|
||||||
: process.platform === 'win32'
|
? path.join(process.env.XDG_CONFIG_HOME, 'hyper')
|
||||||
|
: process.platform == 'win32'
|
||||||
? path.join(process.env.APPDATA!, 'Hyper')
|
? path.join(process.env.APPDATA!, 'Hyper')
|
||||||
: path.join(os.homedir(), '.config', 'Hyper');
|
: os.homedir();
|
||||||
|
|
||||||
const devConfigFileName = path.join(__dirname, `../hyper.json`);
|
const devConfigFileName = path.join(__dirname, `../.hyper.js`);
|
||||||
|
|
||||||
const fileName =
|
const fileName =
|
||||||
process.env.NODE_ENV !== 'production' && fs.existsSync(devConfigFileName)
|
process.env.NODE_ENV !== 'production' && fs.existsSync(devConfigFileName)
|
||||||
? devConfigFileName
|
? devConfigFileName
|
||||||
: path.join(applicationDirectory, 'hyper.json');
|
: path.join(applicationDirectory, '.hyper.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need to make sure the file reading and parsing is lazy so that failure to
|
* We need to make sure the file reading and parsing is lazy so that failure to
|
||||||
|
|
@ -32,7 +31,7 @@ const fileName =
|
||||||
function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
||||||
let hasResult = false;
|
let hasResult = false;
|
||||||
let result: any;
|
let result: any;
|
||||||
return ((...args: Parameters<T>) => {
|
return ((...args: any[]) => {
|
||||||
if (!hasResult) {
|
if (!hasResult) {
|
||||||
result = fn(...args);
|
result = fn(...args);
|
||||||
hasResult = true;
|
hasResult = true;
|
||||||
|
|
@ -42,12 +41,33 @@ function memoize<T extends (...args: any[]) => any>(fn: T): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileContents = memoize(() => {
|
const getFileContents = memoize(() => {
|
||||||
return fs.readFileSync(fileName, 'utf8');
|
try {
|
||||||
|
return fs.readFileSync(fileName, 'utf8');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
// ENOENT === !exists()
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getParsedFile = memoize(() => JSON.parse(getFileContents()));
|
const getParsedFile = memoize(() => recast.parse(getFileContents()!));
|
||||||
|
|
||||||
const getPluginsByKey = (key: string): any[] => getParsedFile()[key] || [];
|
const getProperties = memoize(() => ((getParsedFile()?.program?.body as any[]) || []).map(obj => obj));
|
||||||
|
|
||||||
|
const getPluginsByKey = (key: string) => {
|
||||||
|
const properties = getProperties();
|
||||||
|
for (let i = 0; i < properties.length; i++) {
|
||||||
|
const rightProperties = Object.values<any>(properties[i]?.expression?.right?.properties || {});
|
||||||
|
for (let j = 0; j < rightProperties.length; j++) {
|
||||||
|
const plugin = rightProperties[j];
|
||||||
|
if (plugin?.key?.name === key) {
|
||||||
|
return (plugin?.value?.elements as any[]) || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getPlugins = memoize(() => {
|
const getPlugins = memoize(() => {
|
||||||
return getPluginsByKey('plugins');
|
return getPluginsByKey('plugins');
|
||||||
|
|
@ -62,15 +82,15 @@ function exists() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isInstalled(plugin: string, locally?: boolean) {
|
function isInstalled(plugin: string, locally?: boolean) {
|
||||||
const array = locally ? getLocalPlugins() : getPlugins();
|
const array = (locally ? getLocalPlugins() : getPlugins()) || [];
|
||||||
if (array && Array.isArray(array)) {
|
if (array && Array.isArray(array)) {
|
||||||
return array.includes(plugin);
|
return array.some(entry => entry.value === plugin);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save(config: any) {
|
function save() {
|
||||||
return fs.writeFileSync(fileName, JSON.stringify(config, null, 2), 'utf8');
|
return pify(fs.writeFile)(fileName, recast.print(getParsedFile()).code, 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPackageName(plugin: string) {
|
function getPackageName(plugin: string) {
|
||||||
|
|
@ -87,8 +107,8 @@ function getPackageName(plugin: string) {
|
||||||
function existsOnNpm(plugin: string) {
|
function existsOnNpm(plugin: string) {
|
||||||
const name = getPackageName(plugin);
|
const name = getPackageName(plugin);
|
||||||
return got
|
return got
|
||||||
.get<any>(registryUrl + name.toLowerCase(), {timeout: {request: 10000}, responseType: 'json'})
|
.get<any>(registryUrl + name.toLowerCase(), {timeout: 10000, responseType: 'json'})
|
||||||
.then((res) => {
|
.then(res => {
|
||||||
if (!res.body.versions) {
|
if (!res.body.versions) {
|
||||||
return Promise.reject(res);
|
return Promise.reject(res);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -98,7 +118,7 @@ function existsOnNpm(plugin: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function install(plugin: string, locally?: boolean) {
|
function install(plugin: string, locally?: boolean) {
|
||||||
const array = locally ? getLocalPlugins() : getPlugins();
|
const array = (locally ? getLocalPlugins() : getPlugins()) || [];
|
||||||
return existsOnNpm(plugin)
|
return existsOnNpm(plugin)
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
const {statusCode} = err;
|
const {statusCode} = err;
|
||||||
|
|
@ -112,25 +132,26 @@ function install(plugin: string, locally?: boolean) {
|
||||||
return Promise.reject(`${plugin} is already installed`);
|
return Promise.reject(`${plugin} is already installed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = getParsedFile();
|
array.push(recast.types.builders.literal(plugin));
|
||||||
config[locally ? 'localPlugins' : 'plugins'] = [...array, plugin];
|
return save();
|
||||||
save(config);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uninstall(plugin: string) {
|
function uninstall(plugin: string) {
|
||||||
if (!isInstalled(plugin)) {
|
if (!isInstalled(plugin)) {
|
||||||
return Promise.reject(`${plugin} is not installed`);
|
return Promise.reject(`${plugin} is not installed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = getParsedFile();
|
const index = getPlugins()!.findIndex(entry => entry.value === plugin);
|
||||||
config.plugins = getPlugins().filter((p) => p !== plugin);
|
getPlugins()!.splice(index, 1);
|
||||||
save(config);
|
return save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function list() {
|
function list() {
|
||||||
if (getPlugins().length > 0) {
|
if (Array.isArray(getPlugins())) {
|
||||||
return getPlugins().join('\n');
|
return getPlugins()!
|
||||||
|
.map(plugin => plugin.value)
|
||||||
|
.join('\n');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
97
cli/index.ts
97
cli/index.ts
|
|
@ -1,23 +1,21 @@
|
||||||
// This is a CLI tool, using console is OK
|
// This is a CLI tool, using console is OK
|
||||||
/* eslint no-console: 0 */
|
/* eslint no-console: 0 */
|
||||||
import {spawn, exec} from 'child_process';
|
import {spawn, exec} from 'child_process';
|
||||||
import type {SpawnOptions} from 'child_process';
|
|
||||||
import {existsSync} from 'fs';
|
|
||||||
import {isAbsolute, resolve} from 'path';
|
import {isAbsolute, resolve} from 'path';
|
||||||
import {promisify} from 'util';
|
import {existsSync} from 'fs';
|
||||||
|
import {version} from '../app/package.json';
|
||||||
|
import pify from 'pify';
|
||||||
import args from 'args';
|
import args from 'args';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import _columnify from 'columnify';
|
|
||||||
import got from 'got';
|
|
||||||
import open from 'open';
|
import open from 'open';
|
||||||
|
import columnify from 'columnify';
|
||||||
|
import got from 'got';
|
||||||
import ora from 'ora';
|
import ora from 'ora';
|
||||||
|
|
||||||
import {version} from '../app/package.json';
|
|
||||||
|
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
|
|
||||||
let commandPromise: Promise<void> | undefined;
|
const PLUGIN_PREFIX = 'hyper-';
|
||||||
|
|
||||||
|
let commandPromise: Promise<void>;
|
||||||
|
|
||||||
const assertPluginName = (pluginName: string) => {
|
const assertPluginName = (pluginName: string) => {
|
||||||
if (!pluginName) {
|
if (!pluginName) {
|
||||||
|
|
@ -36,22 +34,6 @@ const checkConfig = () => {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnify = (data: {name: string; description: string}[]) => {
|
|
||||||
const maxNameLength = Math.max(...data.map((entry) => entry.name.length), 0);
|
|
||||||
const descriptionWidth = process.stdout.columns - maxNameLength - 1;
|
|
||||||
return _columnify(data, {
|
|
||||||
showHeaders: false,
|
|
||||||
config: {
|
|
||||||
description: {
|
|
||||||
maxWidth: descriptionWidth
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
dataTransform: (nameValue) => chalk.green(nameValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).replace(/\s+$/gm, ''); // remove padding from the end of all lines
|
|
||||||
};
|
|
||||||
|
|
||||||
args.command(
|
args.command(
|
||||||
'install',
|
'install',
|
||||||
'Install a plugin',
|
'Install a plugin',
|
||||||
|
|
@ -62,7 +44,7 @@ args.command(
|
||||||
commandPromise = api
|
commandPromise = api
|
||||||
.install(pluginName)
|
.install(pluginName)
|
||||||
.then(() => console.log(chalk.green(`${pluginName} installed successfully!`)))
|
.then(() => console.log(chalk.green(`${pluginName} installed successfully!`)))
|
||||||
.catch((err) => console.error(chalk.red(err)));
|
.catch((err: any) => console.error(chalk.red(err)));
|
||||||
},
|
},
|
||||||
['i']
|
['i']
|
||||||
);
|
);
|
||||||
|
|
@ -77,7 +59,7 @@ args.command(
|
||||||
commandPromise = api
|
commandPromise = api
|
||||||
.uninstall(pluginName)
|
.uninstall(pluginName)
|
||||||
.then(() => console.log(chalk.green(`${pluginName} uninstalled successfully!`)))
|
.then(() => console.log(chalk.green(`${pluginName} uninstalled successfully!`)))
|
||||||
.catch((err) => console.error(chalk.red(err)));
|
.catch(err => console.log(chalk.red(err)));
|
||||||
},
|
},
|
||||||
['u', 'rm', 'remove']
|
['u', 'rm', 'remove']
|
||||||
);
|
);
|
||||||
|
|
@ -101,17 +83,21 @@ args.command(
|
||||||
|
|
||||||
const lsRemote = (pattern?: string) => {
|
const lsRemote = (pattern?: string) => {
|
||||||
// note that no errors are catched by this function
|
// note that no errors are catched by this function
|
||||||
const URL = `https://api.npms.io/v2/search?q=${
|
const URL = `https://api.npms.io/v2/search?q=${(pattern && `${pattern}+`) || ''}keywords:hyper-plugin,hyper-theme`;
|
||||||
(pattern && `${pattern}+`) || ''
|
|
||||||
}keywords:hyper-plugin,hyper-theme&size=250`;
|
|
||||||
type npmResult = {package: {name: string; description: string}};
|
|
||||||
return got(URL)
|
return got(URL)
|
||||||
.then((response) => JSON.parse(response.body).results as npmResult[])
|
.then(response => JSON.parse(response.body).results as any[])
|
||||||
.then((entries) => entries.map((entry) => entry.package))
|
.then(entries => entries.map(entry => entry.package))
|
||||||
.then((entries) =>
|
.then(entries => entries.filter(entry => entry.name.indexOf(PLUGIN_PREFIX) === 0))
|
||||||
|
.then(entries =>
|
||||||
entries.map(({name, description}) => {
|
entries.map(({name, description}) => {
|
||||||
return {name, description};
|
return {name, description};
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
.then(entries =>
|
||||||
|
entries.map(entry => {
|
||||||
|
entry.name = chalk.green(entry.name);
|
||||||
|
return entry;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -123,19 +109,20 @@ args.command(
|
||||||
const query = args_[0] ? args_[0].toLowerCase() : '';
|
const query = args_[0] ? args_[0].toLowerCase() : '';
|
||||||
|
|
||||||
commandPromise = lsRemote(query)
|
commandPromise = lsRemote(query)
|
||||||
.then((entries) => {
|
.then(entries => {
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
console.error(chalk.red(`Your search '${query}' did not match any plugins`));
|
console.error(chalk.red(`Your search '${query}' did not match any plugins`));
|
||||||
console.error(`${chalk.red('Try')} ${chalk.green('hyper ls-remote')}`);
|
console.error(`${chalk.red('Try')} ${chalk.green('hyper ls-remote')}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
const msg = columnify(entries);
|
let msg = columnify(entries);
|
||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
|
msg = msg.substring(msg.indexOf('\n') + 1); // remove header
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
console.error(chalk.red(err)); // TODO
|
console.error(chalk.red(err)); // TODO
|
||||||
});
|
});
|
||||||
|
|
@ -150,12 +137,14 @@ args.command(
|
||||||
const spinner = ora('Searching').start();
|
const spinner = ora('Searching').start();
|
||||||
|
|
||||||
commandPromise = lsRemote()
|
commandPromise = lsRemote()
|
||||||
.then((entries) => {
|
.then(entries => {
|
||||||
const msg = columnify(entries);
|
let msg = columnify(entries);
|
||||||
|
|
||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
|
msg = msg.substring(msg.indexOf('\n') + 1); // remove header
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
console.error(chalk.red(err)); // TODO
|
console.error(chalk.red(err)); // TODO
|
||||||
});
|
});
|
||||||
|
|
@ -169,7 +158,7 @@ args.command(
|
||||||
(name, args_) => {
|
(name, args_) => {
|
||||||
const pluginName = args_[0];
|
const pluginName = args_[0];
|
||||||
assertPluginName(pluginName);
|
assertPluginName(pluginName);
|
||||||
void open(`http://ghub.io/${pluginName}`, {wait: false});
|
open(`http://ghub.io/${pluginName}`, {wait: false, url: true});
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
},
|
},
|
||||||
['d', 'h', 'home']
|
['d', 'h', 'home']
|
||||||
|
|
@ -195,10 +184,8 @@ const main = (argv: string[]) => {
|
||||||
version: false,
|
version: false,
|
||||||
mri: {
|
mri: {
|
||||||
boolean: ['v', 'verbose']
|
boolean: ['v', 'verbose']
|
||||||
},
|
}
|
||||||
mainColor: 'yellow',
|
} as any);
|
||||||
subColor: 'dim'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (commandPromise) {
|
if (commandPromise) {
|
||||||
return commandPromise;
|
return commandPromise;
|
||||||
|
|
@ -216,12 +203,12 @@ const main = (argv: string[]) => {
|
||||||
env['ELECTRON_ENABLE_LOGGING'] = '1';
|
env['ELECTRON_ENABLE_LOGGING'] = '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: SpawnOptions = {
|
const options: any = {
|
||||||
detached: true,
|
detached: true,
|
||||||
env
|
env
|
||||||
};
|
};
|
||||||
|
|
||||||
const args_ = args.sub.map((arg) => {
|
const args_ = args.sub.map(arg => {
|
||||||
const cwd = isAbsolute(arg) ? arg : resolve(process.cwd(), arg);
|
const cwd = isAbsolute(arg) ? arg : resolve(process.cwd(), arg);
|
||||||
if (!existsSync(cwd)) {
|
if (!existsSync(cwd)) {
|
||||||
console.error(chalk.red(`Error! Directory or file does not exist: ${cwd}`));
|
console.error(chalk.red(`Error! Directory or file does not exist: ${cwd}`));
|
||||||
|
|
@ -234,24 +221,22 @@ const main = (argv: string[]) => {
|
||||||
options['stdio'] = 'ignore';
|
options['stdio'] = 'ignore';
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
//Use `open` to prevent multiple Hyper process
|
//Use `open` to prevent multiple Hyper process
|
||||||
const cmd = `open -b com.quineglobal.hyper ${args_}`;
|
const cmd = `open -b co.zeit.hyper ${args_}`;
|
||||||
const opts = {
|
const opts = {
|
||||||
env
|
env
|
||||||
};
|
};
|
||||||
return promisify(exec)(cmd, opts);
|
return pify(exec)(cmd, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = spawn(process.execPath, args_, options);
|
const child = spawn(process.execPath, args_, options);
|
||||||
|
|
||||||
if (flags.verbose) {
|
if (flags.verbose) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
child.stdout.on('data', data => console.log(data.toString('utf8')));
|
||||||
child.stdout?.on('data', (data) => console.log(data.toString('utf8')));
|
child.stderr.on('data', data => console.error(data.toString('utf8')));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
||||||
child.stderr?.on('data', (data) => console.error(data.toString('utf8')));
|
|
||||||
}
|
}
|
||||||
if (flags.verbose) {
|
if (flags.verbose) {
|
||||||
return new Promise((c) => child.once('exit', () => c(null)));
|
return new Promise(c => child.once('exit', () => c(null)));
|
||||||
}
|
}
|
||||||
child.unref();
|
child.unref();
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
@ -263,7 +248,7 @@ function eventuallyExit(code: number) {
|
||||||
|
|
||||||
main(process.argv)
|
main(process.argv)
|
||||||
.then(() => eventuallyExit(0))
|
.then(() => eventuallyExit(0))
|
||||||
.catch((err) => {
|
.catch((err: any) => {
|
||||||
console.error(err.stack ? err.stack : err);
|
console.error(err.stack ? err.stack : err);
|
||||||
eventuallyExit(1);
|
eventuallyExit(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "http://json.schemastore.org/electron-builder",
|
|
||||||
"extends": "electron-builder.json",
|
|
||||||
"afterSign": null,
|
|
||||||
"npmRebuild": false
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"appId": "com.quineglobal.hyper",
|
"$schema": "http://json.schemastore.org/electron-builder",
|
||||||
"afterSign": "./bin/notarize.js",
|
"appId": "co.zeit.hyper",
|
||||||
"afterPack": "./bin/cp-snapshot.js",
|
|
||||||
"directories": {
|
"directories": {
|
||||||
"app": "target"
|
"app": "target"
|
||||||
},
|
},
|
||||||
|
|
@ -16,96 +15,54 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "TerminalEmulator",
|
"category": "TerminalEmulator",
|
||||||
"target": [
|
"target": [
|
||||||
"deb",
|
{
|
||||||
"AppImage",
|
"target": "deb",
|
||||||
"snap",
|
"arch": [
|
||||||
"pacman"
|
"x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "AppImage",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "rpm",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"target": "snap",
|
||||||
|
"arch": [
|
||||||
|
"x64"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"win": {
|
"win": {
|
||||||
"target": {
|
"target": [
|
||||||
"target": "nsis",
|
"squirrel"
|
||||||
"arch": [
|
],
|
||||||
"x64",
|
"rfc3161TimeStampServer": "http://timestamp.comodoca.com"
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"signtoolOptions": {
|
|
||||||
"timeStampServer": "http://timestamp.comodoca.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nsis": {
|
|
||||||
"include": "build/win/installer.nsh",
|
|
||||||
"oneClick": false,
|
|
||||||
"perMachine": false,
|
|
||||||
"allowToChangeInstallationDirectory": true
|
|
||||||
},
|
},
|
||||||
"mac": {
|
"mac": {
|
||||||
"target": {
|
|
||||||
"target": "default",
|
|
||||||
"arch": [
|
|
||||||
"x64",
|
|
||||||
"arm64"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
|
|
||||||
"category": "public.app-category.developer-tools",
|
"category": "public.app-category.developer-tools",
|
||||||
"entitlements": "./build/mac/entitlements.plist",
|
"extendInfo": "build/Info.plist",
|
||||||
"entitlementsInherit": "./build/mac/entitlements.plist",
|
|
||||||
"extendInfo": {
|
|
||||||
"CFBundleDocumentTypes": [
|
|
||||||
{
|
|
||||||
"CFBundleTypeName": "Folders",
|
|
||||||
"CFBundleTypeRole": "Viewer",
|
|
||||||
"LSHandlerRank": "Alternate",
|
|
||||||
"LSItemContentTypes": [
|
|
||||||
"public.folder",
|
|
||||||
"com.apple.bundle",
|
|
||||||
"com.apple.package",
|
|
||||||
"com.apple.resolvable"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"CFBundleTypeName": "UnixExecutables",
|
|
||||||
"CFBundleTypeRole": "Shell",
|
|
||||||
"LSHandlerRank": "Alternate",
|
|
||||||
"LSItemContentTypes": [
|
|
||||||
"public.unix-executable"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"NSAppleEventsUsageDescription": "An application in Hyper wants to use AppleScript.",
|
|
||||||
"NSCalendarsUsageDescription": "An application in Hyper wants to access Calendar data.",
|
|
||||||
"NSCameraUsageDescription": "An application in Hyper wants to use the Camera.",
|
|
||||||
"NSContactsUsageDescription": "An application in Hyper wants to access your Contacts.",
|
|
||||||
"NSDesktopFolderUsageDescription": "An application in Hyper wants to access the Desktop folder.",
|
|
||||||
"NSDocumentsFolderUsageDescription": "An application in Hyper wants to access the Documents folder.",
|
|
||||||
"NSDownloadsFolderUsageDescription": "An application in Hyper wants to access the Downloads folder.",
|
|
||||||
"NSFileProviderDomainUsageDescription": "An application in Hyper wants to access files managed by a file provider.",
|
|
||||||
"NSFileProviderPresenceUsageDescription": "An application in Hyper wants to be informed when other apps access files that it manages.",
|
|
||||||
"NSLocationUsageDescription": "An application in Hyper wants to access your location information.",
|
|
||||||
"NSMicrophoneUsageDescription": "An application in Hyper wants to use your microphone.",
|
|
||||||
"NSMotionUsageDescription": "An application in Hyper wants to use the device’s accelerometer.",
|
|
||||||
"NSNetworkVolumesUsageDescription": "An application in Hyper wants to access files on a network volume.",
|
|
||||||
"NSPhotoLibraryUsageDescription": "An application in Hyper wants to access the photo library.",
|
|
||||||
"NSRemindersUsageDescription": "An application in Hyper wants to access your reminders.",
|
|
||||||
"NSRemovableVolumesUsageDescription": "An application in Hyper wants to access files on a removable volume.",
|
|
||||||
"NSSpeechRecognitionUsageDescription": "An application in Hyper wants to send user data to Apple’s speech recognition servers.",
|
|
||||||
"NSSystemAdministrationUsageDescription": "The operation being performed by an application in Hyper requires elevated permission."
|
|
||||||
},
|
|
||||||
"darkModeSupport": true
|
"darkModeSupport": true
|
||||||
},
|
},
|
||||||
"deb": {
|
"deb": {
|
||||||
"compression": "bzip2",
|
"afterInstall": "./build/linux/after-install.tpl"
|
||||||
|
},
|
||||||
|
"rpm": {
|
||||||
"afterInstall": "./build/linux/after-install.tpl"
|
"afterInstall": "./build/linux/after-install.tpl"
|
||||||
},
|
},
|
||||||
"snap": {
|
"snap": {
|
||||||
"confinement": "classic",
|
"confinement": "classic"
|
||||||
"publish": "github"
|
|
||||||
},
|
},
|
||||||
"protocols": {
|
"protocols": {
|
||||||
"name": "ssh URL",
|
"name": "ssh URL",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import type {configOptions} from '../../typings/config';
|
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
|
||||||
import {CONFIG_LOAD, CONFIG_RELOAD} from '../../typings/constants/config';
|
import {HyperActions} from '../hyper';
|
||||||
import type {HyperActions} from '../../typings/hyper';
|
|
||||||
|
|
||||||
export function loadConfig(config: configOptions): HyperActions {
|
export function loadConfig(config: any): HyperActions {
|
||||||
return {
|
return {
|
||||||
type: CONFIG_LOAD,
|
type: CONFIG_LOAD,
|
||||||
config
|
config
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reloadConfig(config: configOptions): HyperActions {
|
export function reloadConfig(config: any): HyperActions {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
return {
|
return {
|
||||||
type: CONFIG_RELOAD,
|
type: CONFIG_RELOAD,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import {CLOSE_TAB, CHANGE_TAB} from '../../typings/constants/tabs';
|
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
|
||||||
import {
|
import {
|
||||||
UI_WINDOW_MAXIMIZE,
|
UI_WINDOW_MAXIMIZE,
|
||||||
UI_WINDOW_UNMAXIMIZE,
|
UI_WINDOW_UNMAXIMIZE,
|
||||||
UI_OPEN_HAMBURGER_MENU,
|
UI_OPEN_HAMBURGER_MENU,
|
||||||
UI_WINDOW_MINIMIZE,
|
UI_WINDOW_MINIMIZE,
|
||||||
UI_WINDOW_CLOSE
|
UI_WINDOW_CLOSE
|
||||||
} from '../../typings/constants/ui';
|
} from '../constants/ui';
|
||||||
import type {HyperDispatch} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
import rpc from '../rpc';
|
||||||
|
|
||||||
import {userExitTermGroup, setActiveGroup} from './term-groups';
|
import {userExitTermGroup, setActiveGroup} from './term-groups';
|
||||||
|
import {HyperDispatch} from '../hyper';
|
||||||
|
|
||||||
export function closeTab(uid: string) {
|
export function closeTab(uid: string) {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
|
|
@ -40,7 +39,7 @@ export function maximize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_MAXIMIZE,
|
type: UI_WINDOW_MAXIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('maximize');
|
rpc.emit('maximize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -51,7 +50,7 @@ export function unmaximize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_UNMAXIMIZE,
|
type: UI_WINDOW_UNMAXIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('unmaximize');
|
rpc.emit('unmaximize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -73,7 +72,7 @@ export function minimize() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_MINIMIZE,
|
type: UI_WINDOW_MINIMIZE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('minimize');
|
rpc.emit('minimize', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -84,7 +83,7 @@ export function close() {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_WINDOW_CLOSE,
|
type: UI_WINDOW_CLOSE,
|
||||||
effect() {
|
effect() {
|
||||||
rpc.emit('close');
|
rpc.emit('close', null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {INIT} from '../../typings/constants';
|
|
||||||
import type {HyperDispatch} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
import rpc from '../rpc';
|
||||||
|
import {INIT} from '../constants';
|
||||||
|
import {HyperDispatch} from '../hyper';
|
||||||
|
|
||||||
export default function init() {
|
export default function init() {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../../typings/constants/notifications';
|
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
|
||||||
import type {HyperActions} from '../../typings/hyper';
|
import {HyperActions} from '../hyper';
|
||||||
|
|
||||||
export function dismissNotification(id: string): HyperActions {
|
export function dismissNotification(id: string): HyperActions {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import type {Session} from '../../typings/common';
|
import rpc from '../rpc';
|
||||||
|
import {keys} from '../utils/object';
|
||||||
|
import findBySession from '../utils/term-groups';
|
||||||
import {
|
import {
|
||||||
SESSION_ADD,
|
SESSION_ADD,
|
||||||
SESSION_RESIZE,
|
SESSION_RESIZE,
|
||||||
|
|
@ -11,14 +13,12 @@ import {
|
||||||
SESSION_CLEAR_ACTIVE,
|
SESSION_CLEAR_ACTIVE,
|
||||||
SESSION_USER_DATA,
|
SESSION_USER_DATA,
|
||||||
SESSION_SET_XTERM_TITLE,
|
SESSION_SET_XTERM_TITLE,
|
||||||
SESSION_SEARCH
|
SESSION_SEARCH,
|
||||||
} from '../../typings/constants/sessions';
|
SESSION_SEARCH_CLOSE
|
||||||
import type {HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
|
} from '../constants/sessions';
|
||||||
import rpc from '../rpc';
|
import {HyperState, session, HyperDispatch, HyperActions} from '../hyper';
|
||||||
import {keys} from '../utils/object';
|
|
||||||
import findBySession from '../utils/term-groups';
|
|
||||||
|
|
||||||
export function addSession({uid, shell, pid, cols = null, rows = null, splitDirection, activeUid, profile}: Session) {
|
export function addSession({uid, shell, pid, cols, rows, splitDirection, activeUid}: session) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
const {sessions} = getState();
|
const {sessions} = getState();
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
@ -31,26 +31,26 @@ export function addSession({uid, shell, pid, cols = null, rows = null, splitDire
|
||||||
rows,
|
rows,
|
||||||
splitDirection,
|
splitDirection,
|
||||||
activeUid: activeUid ? activeUid : sessions.activeUid,
|
activeUid: activeUid ? activeUid : sessions.activeUid,
|
||||||
now,
|
now
|
||||||
profile
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestSession(profile: string | undefined) {
|
export function requestSession() {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_REQUEST,
|
type: SESSION_REQUEST,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
const {ui} = getState();
|
const {ui} = getState();
|
||||||
const {cwd} = ui;
|
// the cols and rows from preview session maybe not accurate. so remove.
|
||||||
rpc.emit('new', {cwd, profile});
|
const {/*cols, rows,*/ cwd} = ui;
|
||||||
|
rpc.emit('new', {cwd});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSessionData(uid: string, data: string) {
|
export function addSessionData(uid: string, data: any) {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_ADD_DATA,
|
type: SESSION_ADD_DATA,
|
||||||
|
|
@ -135,35 +135,27 @@ export function resizeSession(uid: string, cols: number, rows: number) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openSearch(uid?: string) {
|
export function onSearch(uid?: string) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
const targetUid = uid || getState().sessions.activeUid!;
|
const targetUid = uid || getState().sessions.activeUid!;
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_SEARCH,
|
type: SESSION_SEARCH,
|
||||||
uid: targetUid,
|
uid: targetUid
|
||||||
value: new Date()
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeSearch(uid?: string, keyEvent?: any) {
|
export function closeSearch(uid?: string) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
const targetUid = uid || getState().sessions.activeUid!;
|
const targetUid = uid || getState().sessions.activeUid!;
|
||||||
if (getState().sessions.sessions[targetUid]?.search) {
|
dispatch({
|
||||||
dispatch({
|
type: SESSION_SEARCH_CLOSE,
|
||||||
type: SESSION_SEARCH,
|
uid: targetUid
|
||||||
uid: targetUid,
|
});
|
||||||
value: null
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (keyEvent) {
|
|
||||||
keyEvent.catched = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSessionData(uid: string | null, data: string, escaped?: boolean) {
|
export function sendSessionData(uid: string | null, data: any, escaped?: any) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SESSION_USER_DATA,
|
type: SESSION_USER_DATA,
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,38 @@
|
||||||
import {SESSION_REQUEST} from '../../typings/constants/sessions';
|
import rpc from '../rpc';
|
||||||
import {
|
import {
|
||||||
DIRECTION,
|
DIRECTION,
|
||||||
TERM_GROUP_RESIZE,
|
TERM_GROUP_RESIZE,
|
||||||
TERM_GROUP_REQUEST,
|
TERM_GROUP_REQUEST,
|
||||||
TERM_GROUP_EXIT,
|
TERM_GROUP_EXIT,
|
||||||
TERM_GROUP_EXIT_ACTIVE
|
TERM_GROUP_EXIT_ACTIVE
|
||||||
} from '../../typings/constants/term-groups';
|
} from '../constants/term-groups';
|
||||||
import type {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../../typings/hyper';
|
import {SESSION_REQUEST} from '../constants/sessions';
|
||||||
import rpc from '../rpc';
|
|
||||||
import {getRootGroups} from '../selectors';
|
|
||||||
import findBySession from '../utils/term-groups';
|
import findBySession from '../utils/term-groups';
|
||||||
|
import {getRootGroups} from '../selectors';
|
||||||
import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
|
import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
|
||||||
|
import {ITermState, ITermGroup, HyperState, HyperDispatch} from '../hyper';
|
||||||
|
import {Immutable} from 'seamless-immutable';
|
||||||
|
|
||||||
function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') {
|
function requestSplit(direction: string) {
|
||||||
return (_activeUid: string | undefined, _profile: string | undefined) =>
|
return (activeUid: string) => (dispatch: HyperDispatch, getState: () => HyperState): void => {
|
||||||
(dispatch: HyperDispatch, getState: () => HyperState): void => {
|
dispatch({
|
||||||
dispatch({
|
type: SESSION_REQUEST,
|
||||||
type: SESSION_REQUEST,
|
effect: () => {
|
||||||
effect: () => {
|
const {ui, sessions} = getState();
|
||||||
const {ui, sessions} = getState();
|
rpc.emit('new', {
|
||||||
const activeUid = _activeUid ? _activeUid : sessions.activeUid;
|
splitDirection: direction,
|
||||||
const profile = _profile ? _profile : activeUid ? sessions.sessions[activeUid].profile : window.profileName;
|
cwd: ui.cwd,
|
||||||
rpc.emit('new', {
|
activeUid: activeUid ? activeUid : sessions.activeUid
|
||||||
splitDirection: direction,
|
});
|
||||||
cwd: ui.cwd,
|
}
|
||||||
activeUid,
|
});
|
||||||
profile
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const requestVerticalSplit = requestSplit(DIRECTION.VERTICAL);
|
export const requestVerticalSplit = requestSplit(DIRECTION.VERTICAL);
|
||||||
export const requestHorizontalSplit = requestSplit(DIRECTION.HORIZONTAL);
|
export const requestHorizontalSplit = requestSplit(DIRECTION.HORIZONTAL);
|
||||||
|
|
||||||
export function resizeTermGroup(uid: string, sizes: number[]): HyperActions {
|
export function resizeTermGroup(uid: string, sizes: number[]) {
|
||||||
return {
|
return {
|
||||||
uid,
|
uid,
|
||||||
type: TERM_GROUP_RESIZE,
|
type: TERM_GROUP_RESIZE,
|
||||||
|
|
@ -44,20 +40,17 @@ export function resizeTermGroup(uid: string, sizes: number[]): HyperActions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function requestTermGroup(_activeUid: string | undefined, _profile: string | undefined) {
|
export function requestTermGroup(activeUid: string) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: TERM_GROUP_REQUEST,
|
type: TERM_GROUP_REQUEST,
|
||||||
effect: () => {
|
effect: () => {
|
||||||
const {ui, sessions} = getState();
|
const {ui} = getState();
|
||||||
const {cwd} = ui;
|
const {cwd} = ui;
|
||||||
const activeUid = _activeUid ? _activeUid : sessions.activeUid;
|
|
||||||
const profile = _profile ? _profile : activeUid ? sessions.sessions[activeUid].profile : window.profileName;
|
|
||||||
rpc.emit('new', {
|
rpc.emit('new', {
|
||||||
isNewGroup: true,
|
isNewGroup: true,
|
||||||
cwd,
|
cwd,
|
||||||
activeUid,
|
activeUid
|
||||||
profile
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -74,7 +67,7 @@ export function setActiveGroup(uid: string) {
|
||||||
// When we've found the next group which we want to
|
// When we've found the next group which we want to
|
||||||
// set as active (after closing something), we also need
|
// set as active (after closing something), we also need
|
||||||
// to find the first child group which has a sessionUid.
|
// to find the first child group which has a sessionUid.
|
||||||
const findFirstSession = (state: ITermState, group: ITermGroup): string | undefined => {
|
const findFirstSession = (state: Immutable<ITermState>, group: Immutable<ITermGroup>): string | undefined => {
|
||||||
if (group.sessionUid) {
|
if (group.sessionUid) {
|
||||||
return group.sessionUid;
|
return group.sessionUid;
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +90,7 @@ const findPrevious = <T>(list: T[], old: T) => {
|
||||||
return index ? list[index - 1] : list[1];
|
return index ? list[index - 1] : list[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
const findNextSessionUid = (state: ITermState, group: ITermGroup) => {
|
const findNextSessionUid = (state: Immutable<ITermState>, group: Immutable<ITermGroup>) => {
|
||||||
// If we're closing a root group (i.e. a whole tab),
|
// If we're closing a root group (i.e. a whole tab),
|
||||||
// the next group needs to be a root group as well:
|
// the next group needs to be a root group as well:
|
||||||
if (state.activeRootGroup === group.uid) {
|
if (state.activeRootGroup === group.uid) {
|
||||||
|
|
@ -108,7 +101,7 @@ const findNextSessionUid = (state: ITermState, group: ITermGroup) => {
|
||||||
|
|
||||||
const {children} = state.termGroups[group.parentUid!];
|
const {children} = state.termGroups[group.parentUid!];
|
||||||
const nextUid = findPrevious(children.asMutable(), group.uid);
|
const nextUid = findPrevious(children.asMutable(), group.uid);
|
||||||
return findFirstSession(state, state.termGroups[nextUid]);
|
return findFirstSession(state, state.termGroups[nextUid!]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ptyExitTermGroup(sessionUid: string) {
|
export function ptyExitTermGroup(sessionUid: string) {
|
||||||
|
|
@ -159,7 +152,7 @@ export function userExitTermGroup(uid: string) {
|
||||||
if (group.sessionUid) {
|
if (group.sessionUid) {
|
||||||
dispatch(userExitSession(group.sessionUid));
|
dispatch(userExitSession(group.sessionUid));
|
||||||
} else {
|
} else {
|
||||||
group.children.forEach((childUid) => {
|
group.children.forEach(childUid => {
|
||||||
dispatch(userExitTermGroup(childUid));
|
dispatch(userExitTermGroup(childUid));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +168,7 @@ export function exitActiveTermGroup() {
|
||||||
effect() {
|
effect() {
|
||||||
const {sessions, termGroups} = getState();
|
const {sessions, termGroups} = getState();
|
||||||
const {uid} = findBySession(termGroups, sessions.activeUid!)!;
|
const {uid} = findBySession(termGroups, sessions.activeUid!)!;
|
||||||
dispatch(userExitTermGroup(uid));
|
dispatch(userExitTermGroup(uid!));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import {stat} from 'fs';
|
|
||||||
import type {Stats} from 'fs';
|
|
||||||
|
|
||||||
import type parseUrl from 'parse-url';
|
|
||||||
import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
|
import {php_escapeshellcmd as escapeShellCmd} from 'php-escape-shell';
|
||||||
|
import {isExecutable} from '../utils/file';
|
||||||
|
import {getRootGroups} from '../selectors';
|
||||||
|
import findBySession from '../utils/term-groups';
|
||||||
|
import notify from '../utils/notify';
|
||||||
|
import rpc from '../rpc';
|
||||||
|
import {requestSession, sendSessionData, setActiveSession} from './sessions';
|
||||||
import {
|
import {
|
||||||
UI_FONT_SIZE_SET,
|
UI_FONT_SIZE_SET,
|
||||||
UI_FONT_SIZE_INCR,
|
UI_FONT_SIZE_INCR,
|
||||||
|
|
@ -23,18 +24,16 @@ import {
|
||||||
UI_OPEN_SSH_URL,
|
UI_OPEN_SSH_URL,
|
||||||
UI_CONTEXTMENU_OPEN,
|
UI_CONTEXTMENU_OPEN,
|
||||||
UI_COMMAND_EXEC
|
UI_COMMAND_EXEC
|
||||||
} from '../../typings/constants/ui';
|
} from '../constants/ui';
|
||||||
import type {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../../typings/hyper';
|
|
||||||
import rpc from '../rpc';
|
|
||||||
import {getRootGroups} from '../selectors';
|
|
||||||
import {isExecutable} from '../utils/file';
|
|
||||||
import notify from '../utils/notify';
|
|
||||||
import findBySession from '../utils/term-groups';
|
|
||||||
|
|
||||||
import {requestSession, sendSessionData, setActiveSession} from './sessions';
|
|
||||||
import {setActiveGroup} from './term-groups';
|
import {setActiveGroup} from './term-groups';
|
||||||
|
import parseUrl from 'parse-url';
|
||||||
|
import {HyperState, HyperDispatch, HyperActions} from '../hyper';
|
||||||
|
import {Stats} from 'fs';
|
||||||
|
|
||||||
export function openContextMenu(uid: string, selection: string) {
|
const {stat} = window.require('fs');
|
||||||
|
|
||||||
|
export function openContextMenu(uid: string, selection: any) {
|
||||||
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
return (dispatch: HyperDispatch, getState: () => HyperState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_CONTEXTMENU_OPEN,
|
type: UI_CONTEXTMENU_OPEN,
|
||||||
|
|
@ -105,24 +104,24 @@ export function setFontSmoothing() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function windowGeometryUpdated({isMaximized}: {isMaximized: boolean}): HyperActions {
|
export function windowGeometryUpdated(): HyperActions {
|
||||||
return {
|
return {
|
||||||
type: UI_WINDOW_GEOMETRY_CHANGED,
|
type: UI_WINDOW_GEOMETRY_CHANGED
|
||||||
isMaximized
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all sessions that are below the given
|
// Find all sessions that are below the given
|
||||||
// termGroup uid in the hierarchy:
|
// termGroup uid in the hierarchy:
|
||||||
const findChildSessions = (termGroups: ITermGroups, uid: string): string[] => {
|
const findChildSessions = (termGroups: any, uid: string): string[] => {
|
||||||
const group = termGroups[uid];
|
const group = termGroups[uid];
|
||||||
if (group.sessionUid) {
|
if (group.sessionUid) {
|
||||||
return [uid];
|
return [uid];
|
||||||
}
|
}
|
||||||
|
|
||||||
return group.children
|
return group.children.reduce(
|
||||||
.asMutable()
|
(total: string[], childUid: string) => total.concat(findChildSessions(termGroups, childUid)),
|
||||||
.reduce((total: string[], childUid: string) => total.concat(findChildSessions(termGroups, childUid)), []);
|
[]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the index of the next or previous group,
|
// Get the index of the next or previous group,
|
||||||
|
|
@ -144,9 +143,10 @@ function moveToNeighborPane(type: typeof UI_MOVE_NEXT_PANE | typeof UI_MOVE_PREV
|
||||||
const {uid} = findBySession(termGroups, sessions.activeUid!)!;
|
const {uid} = findBySession(termGroups, sessions.activeUid!)!;
|
||||||
const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup!);
|
const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup!);
|
||||||
if (childGroups.length === 1) {
|
if (childGroups.length === 1) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring move for single group');
|
console.log('ignoring move for single group');
|
||||||
} else {
|
} else {
|
||||||
const index = getNeighborIndex(childGroups, uid, type);
|
const index = getNeighborIndex(childGroups, uid!, type);
|
||||||
const {sessionUid} = termGroups.termGroups[childGroups[index]];
|
const {sessionUid} = termGroups.termGroups[childGroups[index]];
|
||||||
dispatch(setActiveSession(sessionUid!));
|
dispatch(setActiveSession(sessionUid!));
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +174,7 @@ export function moveLeft() {
|
||||||
const index = groupUids.indexOf(uid);
|
const index = groupUids.indexOf(uid);
|
||||||
const next = groupUids[index - 1] || groupUids[groupUids.length - 1];
|
const next = groupUids[index - 1] || groupUids[groupUids.length - 1];
|
||||||
if (!next || uid === next) {
|
if (!next || uid === next) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring left move action');
|
console.log('ignoring left move action');
|
||||||
} else {
|
} else {
|
||||||
dispatch(setActiveGroup(next));
|
dispatch(setActiveGroup(next));
|
||||||
|
|
@ -194,6 +195,7 @@ export function moveRight() {
|
||||||
const index = groupUids.indexOf(uid);
|
const index = groupUids.indexOf(uid);
|
||||||
const next = groupUids[index + 1] || groupUids[0];
|
const next = groupUids[index + 1] || groupUids[0];
|
||||||
if (!next || uid === next) {
|
if (!next || uid === next) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring right move action');
|
console.log('ignoring right move action');
|
||||||
} else {
|
} else {
|
||||||
dispatch(setActiveGroup(next));
|
dispatch(setActiveGroup(next));
|
||||||
|
|
@ -210,7 +212,7 @@ export function moveTo(i: number | 'last') {
|
||||||
const {termGroups} = getState().termGroups;
|
const {termGroups} = getState().termGroups;
|
||||||
i =
|
i =
|
||||||
Object.keys(termGroups)
|
Object.keys(termGroups)
|
||||||
.map((uid) => termGroups[uid])
|
.map(uid => termGroups[uid])
|
||||||
.filter(({parentUid}) => !parentUid).length - 1;
|
.filter(({parentUid}) => !parentUid).length - 1;
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
@ -221,10 +223,12 @@ export function moveTo(i: number | 'last') {
|
||||||
const groupUids = getGroupUids(state);
|
const groupUids = getGroupUids(state);
|
||||||
const uid = state.termGroups.activeRootGroup;
|
const uid = state.termGroups.activeRootGroup;
|
||||||
if (uid === groupUids[i as number]) {
|
if (uid === groupUids[i as number]) {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring same uid');
|
console.log('ignoring same uid');
|
||||||
} else if (groupUids[i as number]) {
|
} else if (groupUids[i as number]) {
|
||||||
dispatch(setActiveGroup(groupUids[i as number]));
|
dispatch(setActiveGroup(groupUids[i as number]));
|
||||||
} else {
|
} else {
|
||||||
|
//eslint-disable-next-line no-console
|
||||||
console.log('ignoring inexistent index', i);
|
console.log('ignoring inexistent index', i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,11 +276,11 @@ export function openFile(path: string) {
|
||||||
}
|
}
|
||||||
rpc.once('session add', ({uid}) => {
|
rpc.once('session add', ({uid}) => {
|
||||||
rpc.once('session data', () => {
|
rpc.once('session data', () => {
|
||||||
dispatch(sendSessionData(uid, command));
|
dispatch(sendSessionData(uid, command, null));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
dispatch(requestSession(undefined));
|
dispatch(requestSession());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -295,37 +299,38 @@ export function leaveFullScreen(): HyperActions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openSSH(parsedUrl: ReturnType<typeof parseUrl>) {
|
export function openSSH(url: string) {
|
||||||
return (dispatch: HyperDispatch) => {
|
return (dispatch: HyperDispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_OPEN_SSH_URL,
|
type: UI_OPEN_SSH_URL,
|
||||||
effect() {
|
effect() {
|
||||||
let command = `${parsedUrl.protocol} ${parsedUrl.user ? `${parsedUrl.user}@` : ''}${parsedUrl.resource}`;
|
const parsedUrl = parseUrl(url, true);
|
||||||
|
let command = parsedUrl.protocol + ' ' + (parsedUrl.user ? `${parsedUrl.user}@` : '') + parsedUrl.resource;
|
||||||
|
|
||||||
if (parsedUrl.port) command += ` -p ${parsedUrl.port}`;
|
if (parsedUrl.port) command += ' -p ' + parsedUrl.port;
|
||||||
|
|
||||||
command += '\n';
|
command += '\n';
|
||||||
|
|
||||||
rpc.once('session add', ({uid}) => {
|
rpc.once('session add', ({uid}) => {
|
||||||
rpc.once('session data', () => {
|
rpc.once('session data', () => {
|
||||||
dispatch(sendSessionData(uid, command));
|
dispatch(sendSessionData(uid, command, null));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch(requestSession(undefined));
|
dispatch(requestSession());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execCommand(command: string, fn: (e: any, dispatch: HyperDispatch) => void, e: any) {
|
export function execCommand(command: any, fn: any, e: any) {
|
||||||
return (dispatch: HyperDispatch) =>
|
return (dispatch: HyperDispatch) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: UI_COMMAND_EXEC,
|
type: UI_COMMAND_EXEC,
|
||||||
command,
|
command,
|
||||||
effect() {
|
effect() {
|
||||||
if (fn) {
|
if (fn) {
|
||||||
fn(e, dispatch);
|
fn(e);
|
||||||
} else {
|
} else {
|
||||||
rpc.emit('command', command);
|
rpc.emit('command', command);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue