Merge branch 'master' into xterm

* master: (62 commits)
  1.4.3
  Disable ia32 linux releases (#2164)
  Fixed writing composed characters (#2158)
  Doc: Add yarn install to contribute instructions (#2117)
  Change "Close Session" shortcut on Linux/Windows  (#2160)
  Notice for plugins (#2114)
  Updated dependencies to the latest version (#2146)
  1.4.2
  Reverted class names to as they were before (#2139)
  1.4.1
  AppVeyor environment variables are now on the platform (#2137)
  Brought back the icon for closing tabs (#2136)
  Brought back keymap documentation to the website (#2133)
  1.4.0
  Don't build on master, except for releases (#2132)
  Ensured that `async-retry` is added to the bundle (#2131)
  Ensure correct update channel is displayed in About window (#2130)
  Retry loading it if config doesn't exist in auto updater (#2129)
  Write contents of default config to hyper.js (#2128)
  Use a string for setting the update channel (#2127)
  ...
This commit is contained in:
CHaBou 2017-09-03 22:04:03 +02:00
commit 944f9d7e89
No known key found for this signature in database
GPG key ID: EF8D073B729A0B33
52 changed files with 129442 additions and 2306 deletions

View file

@ -1,6 +1,8 @@
build
app/dist
app/renderer
app/static
app/bin
app/node_modules
assets
website
node_modules
bin

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# build output
dist
app/renderer
# dependencies
node_modules

1
.npmrc
View file

@ -1 +0,0 @@
save-exact=true

View file

@ -5,16 +5,16 @@ language: node_js
matrix:
include:
- os: osx
node_js: 7.4
- os: linux
node_js: 7.4
node_js: 8.4.0
env: CC=clang CXX=clang++ npm_config_clang=1
compiler: clang
addons:
apt:
packages:
- gcc-multilib
- g++-multilib
- libgnome-keyring-dev
- icnsutils
- graphicsmagick
@ -24,11 +24,10 @@ addons:
- snapd
before_install:
- mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start; sleep 3; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install yarn; fi
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository ppa:snappy-dev/tools-proposed -y && sudo apt update && sudo apt install snapcraft; fi
- npm install -g yarn
cache: yarn
install:
- yarn

1
.yarnrc Normal file
View file

@ -0,0 +1 @@
save-prefix false

View file

@ -1 +0,0 @@
save-exact=true

View file

@ -1,15 +1,15 @@
// Packages
const {autoUpdater} = require('electron');
const ms = require('ms');
const retry = require('async-retry');
// Utilities
const notify = require('./notify'); // eslint-disable-line no-unused-vars
const {version} = require('./package');
const {getConfig} = require('./config');
const {platform} = process;
// accepted values: `osx`, `win32`
// https://nuts.gitbook.com/update-windows.html
const platform = process.platform === 'darwin' ?
'osx' :
process.platform;
const FEED_URL = `https://hyper-updates.now.sh/update/${platform}`;
let isInit = false;
function init() {
@ -17,7 +17,28 @@ function init() {
console.error('Error fetching updates', msg + ' (' + err.stack + ')');
});
autoUpdater.setFeedURL(`${FEED_URL}/${version}`);
const config = retry(() => {
const content = getConfig();
if (!content) {
throw new Error('No config content loaded');
}
return content;
});
// Default to the "stable" update channel
let canaryUpdates = false;
// If defined in the config, switch to the "canary" channel
if (config.updateChannel && config.updateChannel === 'canary') {
canaryUpdates = true;
}
const updatePrefix = canaryUpdates ? 'releases-canary' : 'releases';
const feedURL = `https://${updatePrefix}.hyper.is/update/${platform}`;
autoUpdater.setFeedURL(`${feedURL}/${version}`);
setTimeout(() => {
autoUpdater.checkForUpdates();

View file

@ -1,4 +1,4 @@
const gaze = require('gaze');
const chokidar = require('chokidar');
const notify = require('./notify');
const _import = require('./config/import');
const _openConfig = require('./config/open');
@ -10,20 +10,23 @@ const watchers = [];
// https://github.com/zeit/hyper/pull/1772
const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
let cfg = {};
let _watcher;
const _watch = function () {
gaze(cfgPath, watchCfg, function (err) {
if (err) {
throw err;
}
this.on('changed', () => {
cfg = _import();
notify('Configuration updated', 'Hyper configuration reloaded!');
watchers.forEach(fn => fn());
});
this.on('error', () => {
// Ignore file watching errors
});
if (_watcher) {
return _watcher;
}
_watcher = chokidar.watch(cfgPath, watchCfg);
_watcher.on('change', () => {
cfg = _import();
notify('Configuration updated', 'Hyper configuration reloaded!');
watchers.forEach(fn => fn());
});
_watcher.on('error', error => {
console.error('error watching config', error);
});
};

View file

@ -4,6 +4,10 @@
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,
@ -116,5 +120,10 @@ module.exports = {
// 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: []
localPlugins: [],
keymaps: {
// Example
// 'window:devtools': 'cmd+alt+o',
}
};

View file

@ -1,5 +1,6 @@
const {writeFileSync, readFileSync} = require('fs');
const {defaultCfg, cfgPath} = require('./paths');
const {sync: mkdirpSync} = require('mkdirp');
const {defaultCfg, cfgPath, plugs} = require('./paths');
const _init = require('./init');
const _keymaps = require('./keymaps');
@ -15,13 +16,17 @@ const _write = function (path, data) {
};
const _importConf = function () {
// init plugin directories if not present
mkdirpSync(plugs.base);
mkdirpSync(plugs.local);
try {
const _defaultCfg = readFileSync(defaultCfg, 'utf8');
try {
const _cfgPath = readFileSync(cfgPath, 'utf8');
return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
} catch (err) {
_write(cfgPath, defaultCfg);
_write(cfgPath, _defaultCfg);
return {userCfg: {}, defaultCfg: _defaultCfg};
}
} catch (err) {

View file

@ -16,7 +16,6 @@ const _syntaxValidation = function (cfg) {
} catch (err) {
notify(`Error loading config: ${err.name}, see DevTools for more info`);
console.error('Error loading config:', err);
return;
}
};
@ -36,6 +35,9 @@ const _init = function (cfg) {
notify('Error reading configuration: `config` key is missing');
return _extractDefault(cfg.defaultCfg);
}
// Ignore undefined values in plugin and localPlugins array Issue #1862
_cfg.plugins = (_cfg.plugins && _cfg.plugins.filter(Boolean)) || [];
_cfg.localPlugins = (_cfg.localPlugins && _cfg.localPlugins.filter(Boolean)) || [];
return _cfg;
}
return _extractDefault(cfg.defaultCfg);

View file

@ -4,7 +4,56 @@ const {cfgPath} = require('./paths');
module.exports = () => Promise.resolve(shell.openItem(cfgPath));
if (process.platform === 'win32') {
const exec = require('child_process').exec;
const Registry = require('winreg');
const {exec} = require('child_process');
// Windows opens .js files with WScript.exe by default
// If the user hasn't set up an editor for .js files, we fallback to notepad.
const getFileExtKeys = () => new Promise((resolve, reject) => {
Registry({
hive: Registry.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js'
})
.keys((error, keys) => {
if (error) {
reject(error);
} else {
resolve(keys || []);
}
});
});
const hasDefaultSet = async () => {
const keys = await getFileExtKeys();
const valueGroups = await Promise.all(keys.map(key => new Promise((resolve, reject) => {
key.values((error, items) => {
if (error) {
reject(error);
}
resolve(items.map(item => item.value || '') || []);
});
})));
const values = valueGroups
.reduce((allValues, groupValues) => ([...allValues, ...groupValues]), [])
.filter(value => value && typeof value === 'string');
// No default app set
if (values.length === 0) {
return false;
}
// WScript is in default apps list
if (values.some(value => value.includes('WScript.exe'))) {
const userDefaults = values.filter(value => value.endsWith('.exe') && !value.includes('WScript.exe'));
// WScript.exe is overidden
return (userDefaults.length > 0);
}
return true;
};
// This mimics shell.openItem, true if it worked, false if not.
const openNotepad = file => new Promise(resolve => {
@ -13,21 +62,16 @@ if (process.platform === 'win32') {
});
});
// Windows opens .js files with WScript.exe by default
// If the user hasn't set up an editor for .js files, we fallback to notepad.
const canOpenNative = () => new Promise((resolve, reject) => {
exec('ftype JSFile', (error, stdout) => {
if (error) {
reject(error);
} else if (stdout && stdout.includes('WScript.exe')) {
reject(new Error('WScript is the default editor for .js files'));
} else {
resolve(true);
module.exports = () => hasDefaultSet()
.then(yes => {
if (yes) {
return shell.openItem(cfgPath);
}
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);
return openNotepad(cfgPath);
});
});
module.exports = () => canOpenNative()
.then(() => shell.openItem(cfgPath))
.catch(() => openNotepad(cfgPath));
}

View file

@ -15,6 +15,14 @@ const devDir = resolve(__dirname, '../..');
const devCfg = join(devDir, cfgFile);
const defaultCfg = resolve(__dirname, defaultCfgFile);
const plugins = resolve(cfgDir, '.hyper_plugins');
const plugs = {
base: plugins,
local: resolve(plugins, 'local'),
cache: resolve(plugins, 'cache')
};
const yarn = resolve(__dirname, '../../bin/yarn-standalone.js');
const icon = resolve(__dirname, '../static/icon.png');
const keymapPath = resolve(__dirname, '../keymaps');
@ -44,5 +52,5 @@ if (isDev) {
}
module.exports = {
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath, plugs, yarn
};

View file

@ -31,7 +31,7 @@
<div id="mount"></div>
<script>start = performance.now();</script>
<script src="dist/bundle.js"></script>
<script src="renderer/bundle.js"></script>
<script>console.log('total init time', performance.now() - start);</script>
</body>
</html>

View file

@ -9,6 +9,19 @@ if (['--help', '-v', '--version'].includes(process.argv[1])) {
process.exit();
}
const checkSquirrel = () => {
let squirrel;
try {
squirrel = require('electron-squirrel-startup');
} catch (err) {}
if (squirrel) {
// eslint-disable-next-line unicorn/no-process-exit
process.exit();
}
};
// handle startup squirrel events
if (process.platform === 'win32') {
// eslint-disable-next-line import/order
@ -18,50 +31,36 @@ if (process.platform === 'win32') {
case '--squirrel-install':
case '--squirrel-updated':
systemContextMenu.add(() => {
// eslint-disable-next-line curly, unicorn/no-process-exit
if (require('electron-squirrel-startup')) process.exit();
checkSquirrel();
});
break;
case '--squirrel-uninstall':
systemContextMenu.remove(() => {
// eslint-disable-next-line curly, unicorn/no-process-exit
if (require('electron-squirrel-startup')) process.exit();
checkSquirrel();
});
break;
default:
// eslint-disable-next-line curly, unicorn/no-process-exit
if (require('electron-squirrel-startup')) process.exit();
checkSquirrel();
}
}
// Native
const {resolve, isAbsolute} = require('path');
const {homedir} = require('os');
const {resolve} = require('path');
// Packages
const {parse: parseUrl} = require('url');
const {app, BrowserWindow, shell, Menu} = require('electron');
const {app, BrowserWindow, Menu} = require('electron');
const {gitDescribe} = require('git-describe');
const uuid = require('uuid');
const fileUriToPath = require('file-uri-to-path');
const isDev = require('electron-is-dev');
// Ours
const AutoUpdater = require('./auto-updater');
const toElectronBackgroundColor = require('./utils/to-electron-background-color');
const AppMenu = require('./menus/menu');
const createRPC = require('./rpc');
const notify = require('./notify');
const fetchNotifications = require('./notifications');
const config = require('./config');
app.commandLine.appendSwitch('js-flags', '--harmony-async-await');
// set up config
config.setup();
const plugins = require('./plugins');
const Session = require('./session');
const Window = require('./ui/window');
const windowSet = new Set([]);
@ -103,7 +102,7 @@ console.log('electron will open', url);
app.on('ready', () => installDevExtensions(isDev).then(() => {
function createWindow(fn, options = {}) {
let cfg = plugins.getDecoratedConfig();
const cfg = plugins.getDecoratedConfig();
const winSet = config.getWin();
let [startX, startY] = winSet.position;
@ -139,239 +138,23 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
}
}
const browserDefaults = {
width,
height,
minHeight: 190,
minWidth: 370,
titleBarStyle: 'hidden-inset', // macOS only
title: 'Hyper.app',
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
// we want to go frameless on windows and linux
frame: process.platform === 'darwin',
transparent: process.platform === 'darwin',
icon: resolve(__dirname, 'static/icon.png'),
// we only want to show when the prompt is ready for user input
// HYPERTERM_DEBUG for backwards compatibility with hyperterm
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
x: startX,
y: startY,
acceptFirstMouse: true
};
const browserOptions = plugins.getDecoratedBrowserOptions(browserDefaults);
const win = new BrowserWindow(browserOptions);
windowSet.add(win);
win.loadURL(url);
const rpc = createRPC(win);
const sessions = new Map();
// config changes
const cfgUnsubscribe = config.subscribe(() => {
const cfg_ = plugins.getDecoratedConfig();
// notify renderer
win.webContents.send('config change');
// notify user that shell changes require new sessions
if (cfg_.shell !== cfg.shell ||
JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) {
notify(
'Shell configuration changed!',
'Open a new tab or window to start using the new shell'
);
}
// update background color if necessary
cfg = cfg_;
});
rpc.on('init', () => {
// we update the backgroundColor once the init is called.
// when we do a win.reload() we need need to reset the backgroundColor
win.setBackgroundColor(toElectronBackgroundColor(cfg.backgroundColor || '#000'));
win.show();
// If no callback is passed to createWindow,
// a new session will be created by default.
if (!fn) {
fn = win => win.rpc.emit('termgroup add req');
}
// app.windowCallback is the createWindow callback
// that can be set before the 'ready' app event
// and createWindow deifinition. It's executed in place of
// the callback passed as parameter, and deleted right after.
(app.windowCallback || fn)(win);
delete (app.windowCallback);
fetchNotifications(win);
// auto updates
if (!isDev && process.platform !== 'linux') {
AutoUpdater(win);
} else {
console.log('ignoring auto updates during dev');
}
});
rpc.on('new', ({rows = 40, cols = 100, cwd = process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : homedir(), splitDirection}) => {
const shell = cfg.shell;
const shellArgs = cfg.shellArgs && Array.from(cfg.shellArgs);
initSession({rows, cols, cwd, shell, shellArgs}, (uid, session) => {
sessions.set(uid, session);
rpc.emit('session add', {
rows,
cols,
uid,
splitDirection,
shell: session.shell,
pid: session.pty.pid
});
session.on('data', data => {
rpc.emit('session data', uid + data);
});
session.on('exit', () => {
rpc.emit('session exit', {uid});
sessions.delete(uid);
});
});
});
rpc.on('exit', ({uid}) => {
const session = sessions.get(uid);
if (session) {
session.exit();
} else {
console.log('session not found by', uid);
}
});
rpc.on('unmaximize', () => {
win.unmaximize();
});
rpc.on('maximize', () => {
win.maximize();
});
rpc.on('resize', ({uid, cols, rows}) => {
const session = sessions.get(uid);
session.resize({cols, rows});
});
rpc.on('data', ({uid, data}) => {
sessions.get(uid).write(data);
});
rpc.on('open external', ({url}) => {
shell.openExternal(url);
});
rpc.win.on('move', () => {
rpc.emit('move');
});
rpc.on('open hamburger menu', ({x, y}) => {
Menu.getApplicationMenu().popup(Math.ceil(x), Math.ceil(y));
});
rpc.on('minimize', () => {
win.minimize();
});
rpc.on('close', () => {
win.close();
});
const deleteSessions = () => {
sessions.forEach((session, key) => {
session.removeAllListeners();
session.destroy();
sessions.delete(key);
});
};
// we reset the rpc channel only upon
// subsequent refreshes (ie: F5)
let i = 0;
win.webContents.on('did-navigate', () => {
if (i++) {
deleteSessions();
}
});
// If file is dropped onto the terminal window, navigate event is prevented
// and his path is added to active session.
win.webContents.on('will-navigate', (event, url) => {
const protocol = typeof url === 'string' && parseUrl(url).protocol;
if (protocol === 'file:') {
event.preventDefault();
const path = fileUriToPath(url).replace(/ /g, '\\ ');
rpc.emit('session data send', {data: path});
} else if (protocol === 'http:' || protocol === 'https:') {
event.preventDefault();
rpc.emit('session data send', {data: url});
}
});
// expose internals to extension authors
win.rpc = rpc;
win.sessions = sessions;
const load = () => {
plugins.onWindow(win);
};
// load plugins
load();
const pluginsUnsubscribe = plugins.subscribe(err => {
if (!err) {
load();
win.webContents.send('plugins change');
}
});
// Keep track of focus time of every window, to figure out
// which one of the existing window is the last focused.
// Works nicely even if a window is closed and removed.
const updateFocusTime = () => {
win.focusTime = process.uptime();
};
win.on('focus', () => {
updateFocusTime();
});
// Ensure focusTime is set on window open. The focus event doesn't
// fire from the dock (see bug #583)
updateFocusTime();
const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn);
windowSet.add(hwin);
hwin.loadURL(url);
// the window can be closed by the browser process itself
win.on('close', () => {
config.winRecord(win);
windowSet.delete(win);
rpc.destroy();
deleteSessions();
cfgUnsubscribe();
pluginsUnsubscribe();
hwin.on('close', () => {
hwin.clean();
windowSet.delete(hwin);
});
// Same deal as above, grabbing the window titlebar when the window
// is maximized on Windows results in unmaximize, without hitting any
// app buttons
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore']) {
win.on(ev, () => rpc.emit('windowGeometry change'));
}
win.on('closed', () => {
hwin.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit();
}
});
return hwin;
}
// when opening create a new window
@ -393,8 +176,9 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
const menu = plugins.decorateMenu(
AppMenu(createWindow, () => {
plugins.updatePlugins({force: true});
})
);
},
plugins.getLoadedPluginVersions
));
// If we're on Mac make a Dock Menu
if (process.platform === 'darwin') {
@ -424,10 +208,6 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
console.error('Error while loading devtools extensions', err);
}));
function initSession(opts, fn) {
fn(uuid.v4(), new Session(opts));
}
app.on('open-file', (event, path) => {
const lastWindow = app.getLastFocusedWindow();
const callback = win => win.rpc.emit('open file', {path});

View file

@ -18,7 +18,7 @@
"pane:prev": "ctrl+pagedown",
"pane:splitVertical": "ctrl+shift+d",
"pane:splitHorizontal": "ctrl+shift+e",
"pane:close": "ctrl+shift+w",
"pane:close": "ctrl+alt+w",
"editor:undo": "ctrl+shift+z",
"editor:redo": "ctrl+shift+y",
"editor:cut": "ctrl+shift+x",
@ -27,4 +27,4 @@
"editor:selectAll": "ctrl+shift+a",
"editor:clearBuffer": "ctrl+shift+k",
"plugins:update": "ctrl+shift+u"
}
}

View file

@ -18,7 +18,7 @@
"pane:prev": "ctrl+pagedown",
"pane:splitVertical": "ctrl+shift+d",
"pane:splitHorizontal": "ctrl+shift+e",
"pane:close": "ctrl+shift+w",
"pane:close": "ctrl+alt+w",
"editor:undo": "ctrl+shift+z",
"editor:redo": "ctrl+shift+y",
"editor:cut": "ctrl+shift+x",
@ -27,4 +27,4 @@
"editor:selectAll": "ctrl+shift+a",
"editor:clearBuffer": "ctrl+shift+k",
"plugins:update": "ctrl+shift+u"
}
}

View file

@ -1,6 +1,9 @@
const {getKeymaps} = require('../config');
// Packages
const {app, dialog} = require('electron');
// menus
// Utilities
const {getKeymaps, getConfig} = require('../config');
const {icon} = require('../config/paths');
const viewMenu = require('./menus/view');
const shellMenu = require('./menus/shell');
const editMenu = require('./menus/edit');
@ -9,16 +12,41 @@ const windowMenu = require('./menus/window');
const helpMenu = require('./menus/help');
const darwinMenu = require('./menus/darwin');
module.exports = (createWindow, updatePlugins) => {
const commands = getKeymaps().commands;
const appName = app.getName();
const appVersion = app.getVersion();
module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
const config = getConfig();
const {commands} = getKeymaps();
let updateChannel = 'stable';
if (config && config.updateChannel && config.updateChannel === 'canary') {
updateChannel = 'canary';
}
const showAbout = () => {
const loadedPlugins = getLoadedPluginVersions();
const pluginList = loadedPlugins.length === 0 ?
'none' :
loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
dialog.showMessageBox({
title: `About ${appName}`,
message: `${appName} ${appVersion} (${updateChannel})`,
detail: `Plugins: ${pluginList}\n\nCreated by Guillermo Rauch\nCopyright © 2017 Zeit, Inc.`,
buttons: [],
icon
});
};
const menu = [
...(process.platform === 'darwin' ? [darwinMenu(commands)] : []),
...(process.platform === 'darwin' ? [darwinMenu(commands, showAbout)] : []),
shellMenu(commands, createWindow),
editMenu(commands),
viewMenu(commands),
pluginsMenu(commands, updatePlugins),
windowMenu(commands),
helpMenu(commands)
helpMenu(commands, showAbout)
];
return menu;

View file

@ -3,12 +3,15 @@
const {app} = require('electron');
const {openConfig} = require('../../config');
module.exports = function (commands) {
module.exports = function (commands, showAbout) {
return {
label: `${app.getName()}`,
submenu: [
{
role: 'about'
label: 'About Hyper',
click() {
showAbout();
}
},
{
type: 'separator'

View file

@ -1,8 +1,7 @@
const os = require('os');
const {app, shell, dialog} = require('electron');
const {icon} = require('../../config/paths');
const {app, shell} = require('electron');
module.exports = function () {
module.exports = function (commands, showAbout) {
const submenu = [
{
label: `${app.getName()} Website`,
@ -31,13 +30,7 @@ module.exports = function () {
{
role: 'about',
click() {
dialog.showMessageBox({
title: `About ${app.getName()}`,
message: `${app.getName()} ${app.getVersion()}`,
detail: 'Created by Guillermo Rauch',
icon,
buttons: []
});
showAbout();
}
}
);

View file

@ -2,7 +2,7 @@
"name": "hyper",
"productName": "Hyper",
"description": "A terminal built on web technologies",
"version": "1.3.3",
"version": "1.4.3",
"license": "MIT",
"author": {
"name": "Zeit, Inc.",
@ -11,22 +11,24 @@
"repository": "zeit/hyper",
"xo": false,
"dependencies": {
"color": "0.11.3",
"async-retry": "1.1.3",
"chokidar": "1.7.0",
"color": "2.0.0",
"convert-css-color-name-to-hex": "0.1.1",
"default-shell": "1.0.1",
"electron-config": "0.2.1",
"electron-is-dev": "0.1.1",
"electron-config": "1.0.0",
"electron-is-dev": "0.3.0",
"electron-squirrel-startup": "1.0.0",
"file-uri-to-path": "0.0.2",
"gaze": "1.1.2",
"git-describe": "3.0.2",
"file-uri-to-path": "1.0.0",
"git-describe": "4.0.2",
"mkdirp": "0.5.1",
"ms": "0.7.1",
"node-fetch": "1.6.3",
"node-pty": "0.6.6",
"semver": "5.3.0",
"shell-env": "0.2.0",
"uuid": "3.0.0",
"winreg": "1.2.2"
"ms": "2.0.0",
"node-fetch": "1.7.2",
"node-pty": "0.7.0",
"queue": "4.4.0",
"semver": "5.4.1",
"shell-env": "0.3.0",
"uuid": "3.1.0",
"winreg": "1.2.4"
}
}

View file

@ -1,43 +1,21 @@
const {exec} = require('child_process');
const {app, dialog} = require('electron');
const {resolve, basename} = require('path');
const {writeFileSync} = require('fs');
const {app, dialog} = require('electron');
const {sync: mkdirpSync} = require('mkdirp');
const Config = require('electron-config');
const ms = require('ms');
const shellEnv = require('shell-env');
const config = require('./config');
const notify = require('./notify');
const _keys = require('./config/keymaps');
const {availableExtensions} = require('./plugins/extensions');
const {install} = require('./plugins/install');
const {plugs} = require('./config/paths');
// local storage
const cache = new Config();
// modules path
const path = resolve(config.getConfigDir(), '.hyper_plugins');
const localPath = resolve(path, 'local');
const availableExtensions = new Set([
'onApp', 'onWindow', 'onRendererWindow', 'onUnload', 'middleware',
'reduceUI', 'reduceSessions', 'reduceTermGroups',
'decorateMenu', 'decorateTerm', 'decorateHyper',
'decorateHyperTerm', // for backwards compatibility with hyperterm
'decorateHeader', 'decorateTerms', 'decorateTab',
'decorateNotification', 'decorateNotifications',
'decorateTabs', 'decorateConfig', 'decorateEnv',
'decorateTermGroup', 'decorateSplitPane', 'getTermProps',
'getTabProps', 'getTabsProps', 'getTermGroupProps',
'mapHyperTermState', 'mapTermsState',
'mapHeaderState', 'mapNotificationsState',
'mapHyperTermDispatch', 'mapTermsDispatch',
'mapHeaderDispatch', 'mapNotificationsDispatch',
'extendKeymaps'
]);
// init plugin directories if not present
mkdirpSync(path);
mkdirpSync(localPath);
const path = plugs.base;
const localPath = plugs.local;
// caches
let plugins = config.getPlugins();
@ -79,17 +57,10 @@ function updatePlugins({force = false} = {}) {
if (err) {
console.error(err.stack);
if (/not a recognized/.test(err.message) || /command not found/.test(err.message)) {
notify(
'Error updating plugins.',
'We could not find the `npm` command. Make sure it\'s in $PATH'
);
} else {
notify(
'Error updating plugins.',
'Check `~/.hyper_plugins/npm-debug.log` for more information.'
);
}
notify(
'Error updating plugins.',
err.message
);
} else {
// flag successful plugin update
cache.set('hyper.plugins', id_);
@ -161,6 +132,10 @@ function clearCache() {
exports.updatePlugins = updatePlugins;
exports.getLoadedPluginVersions = function () {
return modules.map(mod => ({name: mod._name, version: mod._version}));
};
// we schedule the initial plugins update
// a bit after the user launches the terminal
// to prevent slowness
@ -223,49 +198,6 @@ function toDependencies(plugins) {
return obj;
}
function install(fn) {
const {shell: cfgShell, npmRegistry} = exports.getDecoratedConfig();
const shell = cfgShell && cfgShell !== '' ? cfgShell : undefined;
shellEnv(shell).then(env => {
if (npmRegistry) {
env.NPM_CONFIG_REGISTRY = npmRegistry;
}
/* eslint-disable camelcase */
env.npm_config_runtime = 'electron';
env.npm_config_target = process.versions.electron;
env.npm_config_disturl = 'https://atom.io/download/atom-shell';
/* eslint-enable camelcase */
// Shell-specific installation commands
const installCommands = {
fish: 'npm prune; and npm install --production --no-shrinkwrap',
posix: 'npm prune && npm install --production --no-shrinkwrap'
};
// determine the shell we're running in
const whichShell = (typeof cfgShell === 'string' && cfgShell.match(/fish/)) ? 'fish' : 'posix';
const execOptions = {
cwd: path,
env
};
// https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
// node.js requires command line parsing should be compatible with cmd.exe on Windows, should able to accept `/d /s /c`
// but most custom shell doesn't. Instead, falls back to default shell
if (process.platform !== 'win32') {
execOptions.shell = shell;
}
// Use the install command that is appropriate for our shell
exec(installCommands[whichShell], execOptions, err => {
if (err) {
return fn(err);
}
fn(null);
});
}).catch(fn);
}
exports.subscribe = function (fn) {
watchers.push(fn);
return () => {
@ -309,6 +241,13 @@ function requirePlugins() {
// populate the name for internal errors here
mod._name = basename(path);
try {
// eslint-disable-next-line import/no-dynamic-require
mod._version = require(resolve(path, 'package.json')).version;
} catch (err) {
console.warn(`No package.json found in ${path}`);
}
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
return mod;
} catch (err) {

18
app/plugins/extensions.js Normal file
View file

@ -0,0 +1,18 @@
module.exports = {
availableExtensions: new Set([
'onApp', 'onWindow', 'onRendererWindow', 'onUnload', 'middleware',
'reduceUI', 'reduceSessions', 'reduceTermGroups',
'decorateMenu', 'decorateTerm', 'decorateHyper',
'decorateHyperTerm', // for backwards compatibility with hyperterm
'decorateHeader', 'decorateTerms', 'decorateTab',
'decorateNotification', 'decorateNotifications',
'decorateTabs', 'decorateConfig', 'decorateEnv',
'decorateTermGroup', 'decorateSplitPane', 'getTermProps',
'getTabProps', 'getTabsProps', 'getTermGroupProps',
'mapHyperTermState', 'mapTermsState',
'mapHeaderState', 'mapNotificationsState',
'mapHyperTermDispatch', 'mapTermsDispatch',
'mapHeaderDispatch', 'mapNotificationsDispatch',
'extendKeymaps'
])
};

46
app/plugins/install.js Normal file
View file

@ -0,0 +1,46 @@
const cp = require('child_process');
const queue = require('queue');
const ms = require('ms');
const {yarn, plugs} = require('../config/paths');
module.exports = {
install: fn => {
const spawnQueue = queue({concurrency: 1});
function yarnFn(args, cb) {
const env = {
NODE_ENV: 'production',
ELECTRON_RUN_AS_NODE: 'true'
};
spawnQueue.push(end => {
const cmd = [process.execPath, yarn].concat(args).join(' ');
console.log('Launching yarn:', cmd);
cp.exec(cmd, {
cwd: plugs.base,
env,
shell: true,
timeout: ms('5m'),
stdio: ['ignore', 'ignore', 'inherit']
}, err => {
if (err) {
cb(err);
} else {
cb(null);
}
end();
spawnQueue.start();
});
});
spawnQueue.start();
}
yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], err => {
if (err) {
return fn(err);
}
fn(null);
});
}
};

240
app/ui/window.js Normal file
View file

@ -0,0 +1,240 @@
const {app, BrowserWindow, shell, Menu} = require('electron');
const {isAbsolute} = require('path');
const {parse: parseUrl} = require('url');
const uuid = require('uuid');
const fileUriToPath = require('file-uri-to-path');
const isDev = require('electron-is-dev');
const AutoUpdater = require('../auto-updater');
const toElectronBackgroundColor = require('../utils/to-electron-background-color');
const {icon, cfgDir} = require('../config/paths');
const createRPC = require('../rpc');
const notify = require('../notify');
const fetchNotifications = require('../notifications');
const Session = require('../session');
module.exports = class Window {
constructor(options, cfg, fn) {
const opts = Object.assign({
minWidth: 370,
minHeight: 190,
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
titleBarStyle: 'hidden-inset',
title: 'Hyper.app',
// we want to go frameless on windows and linux
frame: process.platform === 'darwin',
transparent: process.platform === 'darwin',
icon,
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
acceptFirstMouse: true
}, options);
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(opts));
const rpc = createRPC(window);
const sessions = new Map();
// config changes
const cfgUnsubscribe = app.config.subscribe(() => {
const cfg_ = app.plugins.getDecoratedConfig();
// notify renderer
window.webContents.send('config change');
// notify user that shell changes require new sessions
if (cfg_.shell !== cfg.shell ||
JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) {
notify(
'Shell configuration changed!',
'Open a new tab or window to start using the new shell'
);
}
// update background color if necessary
cfg = cfg_;
});
rpc.on('init', () => {
window.setBackgroundColor(toElectronBackgroundColor(cfg.backgroundColor || '#000'));
window.show();
// If no callback is passed to createWindow,
// a new session will be created by default.
if (!fn) {
fn = win => win.rpc.emit('termgroup add req');
}
// app.windowCallback is the createWindow callback
// that can be set before the 'ready' app event
// and createWindow deifinition. It's executed in place of
// the callback passed as parameter, and deleted right after.
(app.windowCallback || fn)(window);
delete (app.windowCallback);
fetchNotifications(window);
// auto updates
if (!isDev && process.platform !== 'linux') {
AutoUpdater(window);
} else {
console.log('ignoring auto updates during dev');
}
});
rpc.on('new', options => {
const opts = Object.assign({
rows: 40,
cols: 100,
cwd: process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : cfgDir,
splitDirection: undefined,
shell: cfg.shell,
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
}, options);
const initSession = (opts, fn) => {
fn(uuid.v4(), new Session(opts));
};
initSession(opts, (uid, session) => {
sessions.set(uid, session);
rpc.emit('session add', {
rows: opts.rows,
cols: opts.cols,
uid,
splitDirection: opts.splitDirection,
shell: session.shell,
pid: session.pty.pid
});
session.on('data', data => {
rpc.emit('session data', uid + data);
});
session.on('exit', () => {
rpc.emit('session exit', {uid});
});
});
});
rpc.on('exit', ({uid}) => {
const session = sessions.get(uid);
if (session) {
session.exit();
} else {
console.log('session not found by', uid);
}
});
rpc.on('unmaximize', () => {
window.unmaximize();
});
rpc.on('maximize', () => {
window.maximize();
});
rpc.on('minimize', () => {
window.minimize();
});
rpc.on('resize', ({uid, cols, rows}) => {
const session = sessions.get(uid);
session.resize({cols, rows});
});
rpc.on('data', ({uid, data, escaped}) => {
const session = sessions.get(uid);
if (escaped) {
const escapedData = session.shell.endsWith('cmd.exe') ?
`"${data}"` : // This is how cmd.exe does it
`'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
session.write(escapedData);
} else {
session.write(data);
}
});
rpc.on('open external', ({url}) => {
shell.openExternal(url);
});
rpc.on('open hamburger menu', ({x, y}) => {
Menu.getApplicationMenu().popup(Math.ceil(x), Math.ceil(y));
});
// Same deal as above, grabbing the window titlebar when the window
// is maximized on Windows results in unmaximize, without hitting any
// app buttons
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore']) {
window.on(ev, () => rpc.emit('windowGeometry change'));
}
rpc.win.on('move', () => {
rpc.emit('move');
});
rpc.on('close', () => {
window.close();
});
const deleteSessions = () => {
sessions.forEach((session, key) => {
session.removeAllListeners();
session.destroy();
sessions.delete(key);
});
};
// we reset the rpc channel only upon
// subsequent refreshes (ie: F5)
let i = 0;
window.webContents.on('did-navigate', () => {
if (i++) {
deleteSessions();
}
});
// If file is dropped onto the terminal window, navigate event is prevented
// and his path is added to active session.
window.webContents.on('will-navigate', (event, url) => {
const protocol = typeof url === 'string' && parseUrl(url).protocol;
if (protocol === 'file:') {
event.preventDefault();
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});
}
});
// expose internals to extension authors
window.rpc = rpc;
window.sessions = sessions;
const load = () => {
app.plugins.onWindow(window);
};
// load plugins
load();
const pluginsUnsubscribe = app.plugins.subscribe(err => {
if (!err) {
load();
window.webContents.send('plugins change');
}
});
// Keep track of focus time of every window, to figure out
// which one of the existing window is the last focused.
// Works nicely even if a window is closed and removed.
const updateFocusTime = () => {
window.focusTime = process.uptime();
};
window.on('focus', () => {
updateFocusTime();
});
// the window can be closed by the browser process itself
window.clean = () => {
app.config.winRecord(window);
rpc.destroy();
deleteSessions();
cfgUnsubscribe();
pluginsUnsubscribe();
};
// Ensure focusTime is set on window open. The focus event doesn't
// fire from the dock (see bug #583)
updateFocusTime();
return window;
}
};

View file

@ -1,3 +1,4 @@
// Packages
const Color = require('color');
// returns a background color that's in hex
@ -5,11 +6,12 @@ const Color = require('color');
// input can be any css value (rgb, hsl, string…)
module.exports = bgColor => {
const color = Color(bgColor);
if (color.alpha() === 1) {
return color.hexString();
return color.hex().toString();
}
// http://stackoverflow.com/a/11019879/1202488
const alphaHex = Math.round(color.alpha() * 255).toString(16);
return '#' + alphaHex + color.hexString().substr(1);
return '#' + alphaHex + color.hex().toString().substr(1);
};

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,6 @@
environment:
matrix:
- platform: x64
GH_TOKEN:
secure: tEHrAaRFMrUzagNJsnU+6Esvaw/FdUXZKWz7a690VAy6zzEThB0lThHLFVKZrQrP
image: Visual Studio 2015
@ -12,12 +10,15 @@ init:
- yarn config set msvs_version 2015 # we need this to build `pty.js`
install:
- ps: Install-Product node 6 x64
- ps: Install-Product node 8 x64
- set CI=true
- yarn
build: off
matrix:
fast_finish: true
shallow_clone: true
test_script:

3716
bin/rimraf-standalone.js Normal file

File diff suppressed because it is too large Load diff

121409
bin/yarn-standalone.js Normal file

File diff suppressed because one or more lines are too long

31
circle.yml Normal file
View file

@ -0,0 +1,31 @@
machine:
xcode:
version: 8.2
pre:
- mkdir ~/.yarn-cache
dependencies:
pre:
- npm install -g yarn
- yarn config set cache-folder ~/.yarn-cache
cache_directories:
- ~/.yarn-cache
override:
- yarn
test:
override:
- yarn test
deployment:
artifacts:
branch: /^(?!master$).*$/
owner: zeit
commands:
- yarn run dist -- -p 'never'
- cp dist/*.zip $CIRCLE_ARTIFACTS
release:
tag: /.*/
owner: zeit
commands:
- yarn run dist

View file

@ -140,7 +140,7 @@ export function resizeSession(uid, cols, rows) {
};
}
export function sendSessionData(uid, data) {
export function sendSessionData(uid, data, escaped) {
return function (dispatch, getState) {
dispatch({
type: SESSION_USER_DATA,
@ -148,7 +148,8 @@ export function sendSessionData(uid, data) {
effect() {
// If no uid is passed, data is sent to the active session.
const targetUid = uid || getState().sessions.activeUid;
rpc.emit('data', {uid: targetUid, data});
rpc.emit('data', {uid: targetUid, data, escaped});
}
});
};

View file

@ -106,8 +106,8 @@ export default class Header extends Component {
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized ?
'./dist/assets/icons.svg#restore-window' :
'./dist/assets/icons.svg#maximize-window';
'./renderer/assets/icons.svg#restore-window' :
'./renderer/assets/icons.svg#maximize-window';
return (<header
className={css('header', isMac && 'headerRounded')}
@ -126,7 +126,7 @@ export default class Header extends Component {
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
onClick={this.handleHamburgerMenuClick}
>
<use xlinkHref="./dist/assets/icons.svg#hamburger-menu"/>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu"/>
</svg>
}
<span className={css('appTitle')}>{title}</span>
@ -137,7 +137,7 @@ export default class Header extends Component {
className={css('shape', left && 'minimizeWindowLeft')}
onClick={this.handleMinimizeClick}
>
<use xlinkHref="./dist/assets/icons.svg#minimize-window"/>
<use xlinkHref="./renderer/assets/icons.svg#minimize-window"/>
</svg>
<svg
className={css('shape', left && 'maximizeWindowLeft')}
@ -149,7 +149,7 @@ export default class Header extends Component {
className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
onClick={this.handleCloseClick}
>
<use xlinkHref="./dist/assets/icons.svg#close-window"/>
<use xlinkHref="./renderer/assets/icons.svg#close-window"/>
</svg>
</div>
}

View file

@ -72,8 +72,8 @@ export default class Notification extends Component {
const {backgroundColor} = this.props;
const opacity = this.state.dismissing ? 0 : 1;
return (<div
style={{opacity, backgroundColor}}
ref={this.onElement}
style={{opacity, backgroundColor}}
className={css('indicator')}
>
{ this.props.customChildrenBefore }

View file

@ -47,12 +47,17 @@ export default class SplitPane extends Component {
onDrag(ev) {
let {sizes} = this.props;
let sizes_;
if (sizes) {
sizes_ = [].concat(sizes);
} else {
const total = this.props.children.length;
sizes = sizes_ = new Array(total).fill(1 / total);
const count = new Array(total).fill(1 / total);
sizes = count;
sizes_ = count;
}
const i = this.paneIndex;
const pos = ev[this.d3];
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;

View file

@ -82,7 +82,7 @@ export default class Tab extends Component {
onClick={this.props.onClose}
>
<svg className={css('shape')}>
<use xlinkHref="./dist/assets/icons.svg#close-tab"/>
<use xlinkHref="./renderer/assets/icons.svg#close-tab"/>
</svg>
</i>
{ this.props.customChildren }

View file

@ -65,20 +65,22 @@ class Hyper extends Component {
const borderWidth = isMac_ ? '' :
`${maximized ? '0' : '1'}px`;
return (<div>
<div
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')}
return (
<div>
<div
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')}
>
<HeaderContainer/>
<TermsContainer ref_={this.onTermsRef}/>
{ this.props.customInnerChildren }
</div>
<HeaderContainer/>
<TermsContainer ref_={this.onTermsRef}/>
{ this.props.customInnerChildren }
</div>
<NotificationsContainer/>
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
{ this.props.customChildren }
</div>);
<NotificationsContainer/>
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
{ this.props.customChildren }
</div>
);
}
styles() {

View file

@ -72,7 +72,7 @@ lib.wc.strWidth = function (str) {
if (width < 0) {
return -1;
}
rv += width * ((codePoint <= 0xffff) ? 1 : 2);
rv += width * ((codePoint <= 0xFFFF) ? 1 : 2);
}
if (shouldCache) {
cache[str] = rv;
@ -256,7 +256,9 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
];
if (!clearBlacklist.includes(e.code.toLowerCase()) &&
!clearBlacklist.includes(e.key.toLowerCase())) {
selection.clear(this.terminal);
// Since Electron 1.6.X, there is a race condition with character composition
// if this selection clearing is made synchronously. See #2140.
setTimeout(() => selection.clear(this.terminal), 0);
}
// If the `hyperCaret` was removed on `selectAll`, we need to insert it back
@ -449,7 +451,7 @@ lib.colors.hexToRGB = function (arg) {
')';
}
if (arg instanceof Array) {
if (Array.isArray(arg)) {
for (let i = 0; i < arg.length; i++) {
arg[i] = convert(arg[i]);
}

View file

@ -70,8 +70,8 @@ rpc.on('session data', d => {
}
});
rpc.on('session data send', ({uid, data}) => {
store_.dispatch(sessionActions.sendSessionData(uid, data));
rpc.on('session data send', ({uid, data, escaped}) => {
store_.dispatch(sessionActions.sendSessionData(uid, data, escaped));
});
rpc.on('session exit', ({uid}) => {

View file

@ -73,7 +73,19 @@ const clearModulesCache = () => {
}
};
const getPluginName = path => window.require('path').basename(path);
const pathModule = window.require('path');
const getPluginName = path => pathModule.basename(path);
const getPluginVersion = path => {
let version = null;
try {
version = window.require(pathModule.resolve(path, 'package.json')).version;
} catch (err) {
console.warn(`No package.json found in ${path}`);
}
return version;
};
const loadModules = () => {
console.log('(re)loading renderer plugins');
@ -112,6 +124,7 @@ const loadModules = () => {
.map(path => {
let mod;
const pluginName = getPluginName(path);
const pluginVersion = getPluginVersion(path);
// window.require allows us to ensure this doesn't get
// in the way of our build
@ -126,6 +139,7 @@ const loadModules = () => {
for (const i in mod) {
if ({}.hasOwnProperty.call(mod, i)) {
mod[i]._pluginName = pluginName;
mod[i]._pluginVersion = pluginVersion;
}
}
@ -209,6 +223,8 @@ const loadModules = () => {
mod.onRendererWindow(window);
}
console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`);
return mod;
})
.filter(mod => Boolean(mod));

View file

@ -1,7 +1,8 @@
import path from 'path';
import * as regex from './url-regex';
export default function isUrlCommand(shell, data) {
const matcher = regex[shell]; // eslint-disable-line import/namespace
const matcher = regex[path.parse(shell).name]; // eslint-disable-line import/namespace
if (undefined === matcher || !data) {
return null;
}

View file

@ -1,6 +1,6 @@
# MIT License
Copyright (c) 2016 Zeit, Inc.
Copyright (c) 2017 Zeit, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,19 +1,19 @@
{
"repository": "zeit/hyper",
"scripts": {
"start": "echo 'please run `npm run dev` in one tab and then `npm run app` in another one'",
"start": "echo 'please run `yarn run dev` in one tab and then `yarn run app` in another one'",
"app": "electron app",
"dev": "webpack -w",
"build": "cross-env NODE_ENV=production webpack",
"lint": "eslint .",
"test": "npm run lint",
"test": "yarn run lint",
"test:unit": "ava test/unit",
"test:unit:watch": "npm run test:unit -- --watch",
"prepush": "npm test",
"postinstall": "install-app-deps && npm run rebuild-node-pty",
"test:unit:watch": "yarn run test:unit -- --watch",
"prepush": "yarn test",
"postinstall": "electron-builder install-app-deps && yarn run rebuild-node-pty",
"rebuild-node-pty": "electron-rebuild -f -w app/node_modules/node-pty -m app",
"dist": "npm run build && cross-env BABEL_ENV=production babel --out-file app/dist/bundle.js --no-comments --minified app/dist/bundle.js && build",
"clean": "npm cache clear && rm -rf node_modules && rm -rf app/node_modules && rm -rf app/dist"
"dist": "yarn run build && cross-env BABEL_ENV=production babel --out-file app/renderer/bundle.js --no-comments --minified app/renderer/bundle.js && build",
"clean": "node ./bin/rimraf-standalone.js node_modules && node ./bin/rimraf-standalone.js ./app/node_modules && node ./bin/rimraf-standalone.js ./app/renderer"
},
"eslintConfig": {
"plugins": [
@ -86,27 +86,24 @@
},
"build": {
"appId": "co.zeit.hyper",
"asar": true,
"extraResources": "./bin/yarn-standalone.js",
"linux": {
"target": [
{
"target": "deb",
"arch": [
"ia32",
"x64"
]
},
{
"target": "AppImage",
"arch": [
"ia32",
"x64"
]
},
{
"target": "rpm",
"arch": [
"ia32",
"x64"
]
}
@ -129,47 +126,47 @@
},
"dependencies": {
"aphrodite-simple": "0.4.1",
"color": "0.11.4",
"css-loader": "^0.28.4",
"color": "2.0.0",
"css-loader": "0.28.4",
"hterm-umdjs": "1.1.3",
"json-loader": "0.5.4",
"json-loader": "0.5.7",
"mousetrap": "1.6.1",
"ms": "0.7.2",
"ms": "2.0.0",
"php-escape-shell": "1.0.0",
"react": "15.5.4",
"react": "15.6.1",
"react-deep-force-update": "2.0.1",
"react-dom": "15.5.4",
"react-redux": "5.0.5",
"redux": "3.6.0",
"react-dom": "15.6.1",
"react-redux": "5.0.6",
"redux": "3.7.2",
"redux-thunk": "2.2.0",
"reselect": "3.0.1",
"runes": "0.4.0",
"seamless-immutable": "6.1.3",
"semver": "5.3.0",
"style-loader": "^0.18.2",
"uuid": "3.0.1",
"runes": "0.4.2",
"seamless-immutable": "7.1.2",
"semver": "5.4.1",
"uuid": "3.1.0",
"xterm": "2.8.1"
},
"devDependencies": {
"asar": "0.13.0",
"ava": "0.17.0",
"babel-cli": "6.24.1",
"babel-core": "6.24.1",
"babel-loader": "7.0.0",
"babel-preset-babili": "0.1.2",
"ava": "0.22.0",
"babel-cli": "6.26.0",
"babel-core": "6.26.0",
"babel-loader": "7.1.2",
"babel-preset-babili": "0.1.4",
"babel-preset-react": "6.24.1",
"copy-webpack-plugin": "4.0.1",
"cross-env": "5.0.0",
"electron": "1.6.11",
"electron-builder": "18.3.5",
"electron-builder-squirrel-windows": "18.3.0",
"cross-env": "5.0.5",
"electron": "1.7.5",
"electron-builder": "19.27.3",
"electron-builder-squirrel-windows": "19.27.3",
"electron-devtools-installer": "2.2.0",
"electron-rebuild": "1.5.11",
"eslint": "^3.19.0",
"eslint-plugin-react": "^7.0.1",
"husky": "0.13.4",
"electron-rebuild": "1.6.0",
"eslint": "3.19.0",
"eslint-plugin-react": "7.3.0",
"husky": "0.14.3",
"node-gyp": "3.6.2",
"redux-logger": "3.0.6",
"spectron": "3.6.4",
"webpack": "2.6.1"
"spectron": "3.7.2",
"style-loader": "0.18.2",
"webpack": "3.5.5"
}
}

View file

@ -1,7 +1,8 @@
![](https://github.com/zeit/art/blob/525bd1bb39d97dd3b91c976106a6d5cc5766b678/hyper/repo-banner.png)
[![Build Status](https://travis-ci.org/zeit/hyper.svg?branch=master)](https://travis-ci.org/zeit/hyper)
[![Build status](https://ci.appveyor.com/api/projects/status/txg5qb0x35h0h65p/branch/master?svg=true)](https://ci.appveyor.com/project/appveyor-zeit/hyper/branch/master)
[![macOS CI Status](https://circleci.com/gh/zeit/hyper.svg?style=shield)](https://circleci.com/gh/zeit/hyper)
[![Windows CI status](https://ci.appveyor.com/api/projects/status/kqvb4oa772an58sc?svg=true)](https://ci.appveyor.com/project/zeit/hyper)
[![Linux CI status](https://travis-ci.org/zeit/hyper.svg?branch=master)](https://travis-ci.org/zeit/hyper)
[![Slack Channel](http://zeit-slackin.now.sh/badge.svg)](https://zeit.chat/)
[![Changelog #213](https://img.shields.io/badge/changelog-%23213-lightgrey.svg)](https://changelog.com/213)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
@ -20,17 +21,19 @@ brew cask install hyper
```
If you're on windows, you can use [chocolatey](https://chocolatey.org/) to install the app by running the following command (package information can be found [here](https://chocolatey.org/packages/hyper/)):
```bash
choco install hyper
```
Note: the version of hyper available from chocolatey may not be the latest. Consider using the direct download link, https://hyper-updates.now.sh/download/win
**Note:** The version available on [Homebrew Cask](https://caskroom.github.io/) or [Chocolatey](https://chocolatey.org) may not be the latest. Please consider downloading it from [here](https://hyper.is/#installation) if that's the case.
## Contribute
1. Install the dependencies
* If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils` and `rpm`
* If you are running Windows, install `windows-build-tools` with `yarn global add windows-build-tools`.
* If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils`, `npm`, `rpm` and `yarn`
* If you are running Windows, install `yarn` (If you use the MSI installer you will need to install npm), and `windows-build-tools` with `yarn global add windows-build-tools`.
* Yarn installation instructions: https://yarnpkg.com/en/docs/install
2. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
3. Install the dependencies: `yarn`
4. Build the code and watch for changes: `yarn run dev`
@ -44,7 +47,9 @@ yarn run dist
After that, you'll see the binary in the `./dist` folder!
### node-pty issues
#### Known issues that can happen during development
##### Error building `node-pty`
If after building during development you get an alert dialog related to `node-pty` issues,
make sure its build process is working correctly by running `yarn run rebuild-node-pty`.
@ -52,6 +57,11 @@ make sure its build process is working correctly by running `yarn run rebuild-no
If you're 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).
##### Error with `codesign` on macOS when running `yarn run dist`
If you have issues in the `codesign` step when running `yarn run dist` on macOS, you can temporarily disable code signing locally by setting
`export CSC_IDENTITY_AUTO_DISCOVERY=false` for the current terminal session.
## Related Repositories
- [Art](https://github.com/zeit/art/tree/master/hyper)

View file

@ -7,14 +7,17 @@ const nodeEnv = process.env.NODE_ENV || 'development';
const isProd = nodeEnv === 'production';
module.exports = {
resolve: {
extensions: ['.js', '.jsx']
},
devtool: isProd ? 'hidden-source-map' : 'cheap-module-source-map',
entry: './lib/index.js',
output: {
path: path.join(__dirname, 'app', 'dist'),
path: path.join(__dirname, 'app', 'renderer'),
filename: 'bundle.js'
},
module: {
loaders: [
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,

View file

@ -1,19 +0,0 @@
0.3.1 / 2016-07-04
==================
* Added changelog for `0.3.0` release
* Added changelog for `0.3.1` release
0.2.1 / 2016-07-03
==================
* Added changelog for `0.2.1` release
* Reduce caching interval
* Rename `data.json` to `updates.json`
0.2.0 / 2016-07-03
==================
* Added changelog for `0.2.0` release
* Increase `line-height` of `<ul>`

View file

@ -1,21 +0,0 @@
# MIT License
Copyright (c) 2016 Zeit, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -287,6 +287,10 @@
#content h3:before {
content: '### ';
}
#content h4:before {
content: '#### ';
}
#content code {
font: 12px Menlo, "DejaVu Sans Mono", "Lucida Console", monospace;
@ -604,37 +608,37 @@
<td style="width: 33.333%">64-bit</td>
</tr>
<tr>
<td><b>macOS</b> (.dmg)</td>
<td><b>macOS</b> (.app)</td>
<td id="td-mac-os" colspan="2">
<a href="https://hyper-latest.now.sh/latest/dmg">DOWNLOAD <img src="static/download-icon.svg"/></a>
<a href="https://releases.hyper.is/download/mac">DOWNLOAD <img src="static/download-icon.svg"/></a>
</td>
</tr>
<tr>
<td><b>Windows</b> (.exe)</td>
<td class="soon">COMING SOON</td>
<td id="td-win">
<a href="https://hyper-latest.now.sh/latest/exe">DOWNLOAD <img src="static/download-icon.svg"/></a>
<a href="https://releases.hyper.is/download/win">DOWNLOAD <img src="static/download-icon.svg"/></a>
</td>
</tr>
<tr>
<td><b>Debian</b> (.deb)</td>
<td class="soon">COMING SOON</td>
<td id="td-debian">
<a href="https://hyper-latest.now.sh/latest/deb">DOWNLOAD <img src="static/download-icon.svg"/></a>
<a href="https://releases.hyper.is/download/deb">DOWNLOAD <img src="static/download-icon.svg"/></a>
</td>
</tr>
<tr>
<td><b>Fedora</b> (.rpm)</td>
<td class="soon">COMING SOON</td>
<td id="td-fedora">
<a href="https://hyper-latest.now.sh/latest/rpm">DOWNLOAD <img src="static/download-icon.svg"/></a>
<a href="https://releases.hyper.is/download/rpm">DOWNLOAD <img src="static/download-icon.svg"/></a>
</td>
</tr>
<tr>
<td><b>Other Linux distros</b> (.AppImage)</td>
<td class="soon">COMING SOON</td>
<td id="td-appimage" colspan="2">
<a href="https://hyper-latest.now.sh/latest/AppImage">DOWNLOAD <img src="static/download-icon.svg"/></a>
<a href="https://releases.hyper.is/download/AppImage">DOWNLOAD <img src="static/download-icon.svg"/></a>
</td>
</tr>
</table>
@ -678,6 +682,23 @@
<a href="https://github.com/bnb/awesome-hyper">Awesome Hyper</a>
for a curated list of plugins and resources.</p>
<h2 id="keymaps"><a href="#keymaps">Keymaps</a></h2>
<P> All command keys can be changed. In order to change them, edit
<code>~/.hyper.js</code> and add your desired change to <code>keymaps</code>.
</p>
<p> Then Hyper will change the default with your custom change.</p>
<p> Example: <code>'window:devtools': 'Cmd+Alt+O'</code> <p>
<pre><code>module.exports = {
config: { /*... */ },
keymaps: {
'window:devtools': 'cmd+alt+o'
}
};</code></pre>
<h2 id="cfg"><a href="#cfg">Configuration</a></h2>
<p>The <code>config</code> object seen above in <code>~/.hyper.js</code>
@ -1200,6 +1221,9 @@
<p>The user can hot-load and hot-reload plugins by pressing
Command + R (refresh). Please strive to make plugins that don't
require a complete restart of the application to work.</p>
<h4>Notice</h4>
<p>Plugins affecting the `BrowserWindow` will the effect on new windows after hot-reload.</p>
<p>In the future we might do this automatically.</p>
@ -1598,27 +1622,27 @@ exports.mapTermsState = (state, map) => {
var downloadButtonText = document.getElementById('download-button-text');
if (/Mac/.test(userAgent) && !/iPhone/.test(userAgent) && !/iPad/.test(userAgent)) {
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/mac');
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/mac');
downloadButtonText.innerHTML += ' for <strong>macOS</strong>';
document.getElementById('td-mac-os').classList.add('highlighted');
document.getElementById('mac-os-icon').classList.remove('is-hidden');
} else if (/Windows/.test(userAgent)) {
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/win');
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/win');
downloadButtonText.innerHTML += ' for <strong>Windows</strong>';
document.getElementById('td-win').classList.add('highlighted');
document.getElementById('windows-icon').classList.remove('is-hidden');
} else if (/Linux/.test(userAgent) && !/Android/.test(userAgent)) {
document.getElementById('linux-icon').classList.remove('is-hidden');
if (/Ubuntu/.test(userAgent)) { // needs to be improved with other debian distros
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/linux_deb');
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/deb');
downloadButtonText.innerHTML += ' for <strong>Debian</strong>';
document.getElementById('td-debian').classList.add('highlighted');
} else if (/Fedora/.test(userAgent)) {
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/linux_rpm');
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/rpm');
downloadButtonText.innerHTML += ' for <strong>Fedora</strong>';
document.getElementById('td-fedora').classList.add('highlighted');
} else {
downloadButton.setAttribute('href', 'https://hyper-latest.now.sh/latest/AppImage');
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/AppImage');
downloadButtonText.innerHTML += ' for <strong>Linux</strong>';
document.getElementById('td-appimage').classList.add('highlighted');
}

1196
website/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{
"name": "hyper-website",
"name": "website",
"version": "0.0.1",
"description": "The official website for hyper.app",
"dependencies": {
@ -7,5 +7,8 @@
},
"scripts": {
"start": "serve . -c 5"
},
"now": {
"alias": "hyper.is"
}
}

2942
yarn.lock

File diff suppressed because it is too large Load diff