This commit is contained in:
yi-ge 2019-06-07 23:04:57 +08:00
commit 36b92f19e0
67 changed files with 18266 additions and 0 deletions

18
.babelrc Normal file
View File

@ -0,0 +1,18 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
["@babel/plugin-proposal-decorators", {
"legacy": true
}],
["@babel/plugin-proposal-class-properties", {
"loose": true
}],
"@babel/plugin-proposal-optional-chaining"
]
}

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
.DS_Store
node_modules
dist/
bin/
npm-debug.log
.idea/
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
yarn-error.log

22
Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM node:12.4.0-alpine
LABEL AUTHOR="yi-ge"
LABEL maintainer="a@wyr.me"
RUN apk add --no-cache \
libstdc++ \
libgcc \
rsync \
openssh-client \
bash \
ca-certificates \
git
RUN mkdir /project
ADD . /project
WORKDIR /project
EXPOSE 80
CMD ["npm", "start"]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Yige
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.

176
README.md Normal file
View File

@ -0,0 +1,176 @@
# Electron Distribution
[![license](https://img.shields.io/github/license/yi-ge/electron-distribution.svg?style=flat-square)](https://github.com/yi-ge/electron-distribution/blob/master/LICENSE)
[![GitHub last commit](https://img.shields.io/github/last-commit/yi-ge/electron-distribution.svg?style=flat-square)](https://github.com/yi-ge/electron-distribution)
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
[简体中文](README.zh-CN.md)
Electron build and auto update service, application distribution. Making application distribution easier.
A git repository corresponds to an electron app and an distributed system.
Electron Distribution server-side work in 64 bit Linux OS (required) and MacOS (optional), and build for x64 platfrom, other platfrom need to modify the code (It is easy).
## Quick Setup Guide
### In your Linux Server (x64)
#### Example
```bash
docker run -itd --name electron-distribution --restart always \
-e NAME=your-app-name \
-e SCHEME=https \
-e DOMAIN=www.example.com \
-e TOKEN=123456 \
-e REPOPATH=git@github.com:abc/def.git \
-e BUILD_TYPE=win,linux,mac \
-e WORKPATH=/data \
-e OBJECT_STORAGE_TYPE=cos
-v /data:/data \
-p 80:80 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /root/.ssh:/root/.ssh \
-v /data/icon.ico:/project/public/icon.ico \
wy373226722/electron-distribution:latest
```
China user: `docker pull registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest` or `docker pull ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest`
You need to configure nginx and SSL by yourself. SCHEME only a tip.
#### Environment
| ENV Var | Default | Description |
|---|---|---|
| `NAME` | `"APP"` | `Your app name. Numbers, letters and "-".` |
| `SCHEME` | `"https"` | `Production environment only work with SSL.` |
| `DOMAIN` | `"www.example.com"` | `Your Electron Distribution server-side domain.` |
| `TOKEN` | `"1jH27dJf9s852"` | `Your Electron Distribution API TOKEN.` |
| `REPOPATH` | `"git@github.com:yi-ge/electron-distribution.git"` | `Your electron app repository.` |
| `BUILD_TYPE` | `"win,linux"` | `win,linux,mac` |
| `WORKPATH` | `"/data"` | `-v /data:/data, The two path must be consistent.` |
| `DOCKER_SOCKET` | `"/var/run/docker.sock"` | `Docker socket path.` |
| `MAC_SERVER_HOST` | `"127.0.0.1"` | `Your macOS server host.` |
| `MAC_SERVER_PORT` | `"22"` | `Your macOS server ssh port.` |
| `MAC_SERVER_USERNAME` | `"guest"` | `Your macOS server ssh username.` |
| `LINUX_SERVER_HOST` | `"127.0.0.1"` | `Only require build mac application. Your linux server host.` |
| `LINUX_SERVER_PORT` | `"22"` | `Only require build mac application. Your linux server ssh port.` |
| `LINUX_SERVER_USERNAME` | `"guest"` | `Only require build mac application. Your linux server ssh username.` |
| `GH_TOKEN` | `""` | `If you set publish option.` |
| `CSC_LINK` | `""` | `https://www.electron.build/code-signing` |
| `CSC_KEY_PASSWORD` | `""` | `https://www.electron.build/code-signing` |
| `CSC_NAME`, | `""` | `https://www.electron.build/code-signing` |
| `BUILD_CPU_LIMIT` | `"0"` | `Linux and Windows build cpu limit. CPUs in which to allow execution (e.g., 0-3, 0,1)` |
| `BUILD_MEMORY_LIMIT` | `0` | `Linux and Windows memory limit in bytes. 1024 * 1024 * 1024 bytes = 1073741824 bytes = 1GB` |
| `OBJECT_STORAGE_TYPE` | `"cos"` | `cos: Tencent Cloud Object Storage; oss: Aliyun Object Storage; qiniu: Qiniu Object Storage.` |
| `QINIU_ACCESS_KEY` | `""` | `Qiniu Object Storage, accessKey.` |
| `QINIU_SECRET_KEY` | `""` | `Qiniu Object Storage, secretKey.` |
| `QINIU_BUCKET_KEY` | `""` | `Qiniu Object Storage, bucket.` |
| `QINIU_ZONE` | `"Zone_z0"` | `华东 Zone_z0、华北 Zone_z1、华南 Zone_z2、北美 Zone_na0` |
| `QINIU_URL` | `"https://cdn.xxx.com"` | `Qiniu Object Storage CDN url.` |
| `OSS_ACCESS_KEY_ID` | `"id"` | `Aliyun accessKeyId.` |
| `OSS_ACCESS_SECRET` | `"secret"` | `Aliyun accessKeySecret.` |
| `OSS_REGION` | `"oss-cn-qingdao"` | `Aliyun Object Storage, Region.` |
| `OSS_BUCKET` | `"bucket"` | `Aliyun Object Storage, Bucket.` |
| `OSS_URL` | `"https://cdn.xxx.com"` | `Aliyun Object Storage CDN url.` |
| `OSS_INTERNAL` | `false` | `Access aliyun OSS with aliyun internal network or not, default is false. If your servers are running on aliyun too, you can set "true" to save lot of money.` |
| `COS_SECRE_ID` | `""` | `Tencent Cloud Object Storage SecretId.` |
| `COS_SECRE_KEY` | `""` | `SecretKey.` |
| `COS_BUCKET` | `"bucketname-12345678"` | `Bucket.` |
| `COS_REGION` | `"ap-chengdu"` | `Region.` |
| `COS_URL` | `"https://cdn.xxx.com"` | `Object Storage CDN url.` |
Qiniu Object Storage: [https://developer.qiniu.com/kodo/sdk/1289/nodejs](https://developer.qiniu.com/kodo/sdk/1289/nodejs)
Aliyun Object Storage: [https://github.com/ali-sdk/ali-oss](https://github.com/ali-sdk/ali-oss)
Tencent Cloud Object Storage: [https://github.com/tencentyun/cos-nodejs-sdk-v5](https://github.com/tencentyun/cos-nodejs-sdk-v5)
#### API Document
**Swagger:** https://yourdomain/documentation
API token require `SHA-512` encrypt.
**Github webhooks:** https://yourdomain/build/webhooks
Content type: `application/json`
Secret: `your Token`
### In your Electron App
```bash
yarn add electron-builder electron-simple-updater -D
```
More about: [electron-builder](https://github.com/electron-userland/electron-builder) [electron-simple-updater](https://github.com/megahertz/electron-simple-updater)
Insert `build` configuration in your `package.json` ([https://www.electron.build](https://www.electron.build)):
```json
"scripts": {
"build": "node config/build.js && electron-builder",
"build:dir": "node config/build.js && electron-builder --dir",
...
},
"build": {
"productName": "Your App Name",
"appId": "com.appid.abc",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns"
},
"win": {
"icon": "build/icons/icon.ico",
"target": "squirrel"
},
"linux": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"icon": "build/icons"
},
"squirrelWindows": {
"iconUrl": "https://yourServer/app/icon.ico"
}
},
```
## Mac APP Build And Code Signing
Install `Xcode 10.2` \ `brew (yarn 1.15.2)` \ `nvm (node 11.13.0)` in the macOS Majave (10.14.4), run the Xcode at least once.
Git and rsync are installed by default.
Start sshd:
```bash
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
```
Adding your server public key to mac `~/.ssh/authorized_keys`.
Adding your mac public key to server `~/.ssh/authorized_keys`.
Run the `ssh macName@macIp` in the linux server at least once .
Run the `ssh linux@linuxIp` in the mac server at least once.
Install `rsync` and enable sshd in your Linux Server.

173
README.zh-CN.md Normal file
View File

@ -0,0 +1,173 @@
# Electron 应用分发系统
[![license](https://img.shields.io/github/license/yi-ge/electron-distribution.svg?style=flat-square)](https://github.com/yi-ge/electron-distribution/blob/master/LICENSE)
[![GitHub last commit](https://img.shields.io/github/last-commit/yi-ge/electron-distribution.svg?style=flat-square)](https://github.com/yi-ge/electron-distribution)
[![JavaScript Style Guide](https://cdn.rawgit.com/standard/standard/master/badge.svg)](https://github.com/standard/standard)
[简体中文](README.zh-CN.md)
`Electron 应用分发系统`提供应用程序编译、自动升级、分发服务。让`Electron`应用分发变得非常容易。
一个`git`仓库对应一个`Electron`应用程序,对应使用一套应用分发系统。
`Electron 应用分发系统`服务器端工作在64位Linux操作系统必须和MacOS可选默认编译x64应用程序, 其它平台及架构需要修改相应代码(不过这很容易实现).
## 快速开始
### Linux Server (x64) 服务器部署指南
#### 命令运行例子
```bash
docker run -itd --name electron-distribution --restart always \
-e NAME=your-app-name \
-e SCHEME=https \
-e DOMAIN=www.example.com \
-e TOKEN=123456 \
-e REPOPATH=git@github.com:abc/def.git \
-e BUILD_TYPE=win,linux,mac \
-e WORKPATH=/data \
-e OBJECT_STORAGE_TYPE=cos
-v /data:/data \
-p 80:80 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /root/.ssh:/root/.ssh \
-v /data/icon.ico:/project/public/icon.ico \
wy373226722/electron-distribution
```
国内用户: `docker pull registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest` or `docker pull ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest`
你需要自行安装Nginx并配置使其支持SSL这是必须的否则应用程序自动更新可能会失效。`SCHEME`只是一个标识。
#### 环境变量
| 变量名 | 默认值 | 描述 |
|---|---|---|
| `NAME` | `"APP"` | `Your app name. Numbers, letters and "-".` |
| `SCHEME` | `"https"` | `Production environment only work with SSL.` |
| `DOMAIN` | `"www.example.com"` | `Your Electron Distribution server-side domain.` |
| `TOKEN` | `"1jH27dJf9s852"` | `Your Electron Distribution API TOKEN.` |
| `REPOPATH` | `"git@github.com:yi-ge/electron-distribution.git"` | `Your electron app repository.` |
| `BUILD_TYPE` | `"win,linux"` | `win,linux,mac` |
| `WORKPATH` | `"/data"` | `-v /data:/data, The two path must be consistent.` |
| `DOCKER_SOCKET` | `"/var/run/docker.sock"` | `Docker socket path.` |
| `MAC_SERVER_HOST` | `"127.0.0.1"` | `Your macOS server host.` |
| `MAC_SERVER_PORT` | `"22"` | `Your macOS server ssh port.` |
| `MAC_SERVER_USERNAME` | `"guest"` | `Your macOS server ssh username.` |
| `LINUX_SERVER_HOST` | `"127.0.0.1"` | `Only require build mac application. Your linux server host.` |
| `LINUX_SERVER_PORT` | `"22"` | `Only require build mac application. Your linux server ssh port.` |
| `LINUX_SERVER_USERNAME` | `"root"` | `Only require build mac application. Your linux server ssh username.` |
| `GH_TOKEN` | `""` | `If you set publish option.` |
| `CSC_LINK` | `""` | `https://www.electron.build/code-signing` |
| `CSC_KEY_PASSWORD` | `""` | `https://www.electron.build/code-signing` |
| `CSC_NAME`, | `""` | `https://www.electron.build/code-signing` |
| `BUILD_CPU_LIMIT` | `"0"` | `Linux and Windows build cpu limit. CPUs in which to allow execution (e.g., 0-3, 0,1)` |
| `BUILD_MEMORY_LIMIT` | `0` | `Linux and Windows memory limit in bytes. 1024 * 1024 * 1024 bytes = 1073741824 bytes = 1GB` |
| `OBJECT_STORAGE_TYPE` | `"cos"` | `cos: Tencent Cloud Object Storage; oss: Aliyun Object Storage; qiniu: Qiniu Object Storage.` |
| `QINIU_ACCESS_KEY` | `""` | `Qiniu Object Storage, accessKey.` |
| `QINIU_SECRET_KEY` | `""` | `Qiniu Object Storage, secretKey.` |
| `QINIU_BUCKET_KEY` | `""` | `Qiniu Object Storage, bucket.` |
| `QINIU_ZONE` | `"Zone_z0"` | `华东 Zone_z0、华北 Zone_z1、华南 Zone_z2、北美 Zone_na0` |
| `QINIU_URL` | `"https://cdn.xxx.com"` | `Qiniu Object Storage CDN url.` |
| `OSS_ACCESS_KEY_ID` | `"id"` | `Aliyun accessKeyId.` |
| `OSS_ACCESS_SECRET` | `"secret"` | `Aliyun accessKeySecret.` |
| `OSS_REGION` | `"oss-cn-qingdao"` | `Aliyun Object Storage, Region.` |
| `OSS_BUCKET` | `"bucket"` | `Aliyun Object Storage, Bucket.` |
| `OSS_URL` | `"https://cdn.xxx.com"` | `Aliyun Object Storage CDN url.` |
| `OSS_INTERNAL` | `false` | `Access aliyun OSS with aliyun internal network or not, default is false. If your servers are running on aliyun too, you can set "true" to save lot of money.` |
| `COS_SECRE_ID` | `""` | `Tencent Cloud Object Storage SecretId.` |
| `COS_SECRE_KEY` | `""` | `SecretKey.` |
| `COS_BUCKET` | `"bucketname-12345678"` | `Bucket.` |
| `COS_REGION` | `"ap-chengdu"` | `Region.` |
| `COS_URL` | `"https://cdn.xxx.com"` | `Object Storage CDN url.` |
七牛对象存储: [https://developer.qiniu.com/kodo/sdk/1289/nodejs](https://developer.qiniu.com/kodo/sdk/1289/nodejs)
阿里云对象存储: [https://github.com/ali-sdk/ali-oss](https://github.com/ali-sdk/ali-oss)
腾讯云对象存储: [https://github.com/tencentyun/cos-nodejs-sdk-v5](https://github.com/tencentyun/cos-nodejs-sdk-v5)
#### API 文档
**Swagger:** https://yourdomain/documentation
API中的token需要进行`SHA-512`加密。
**Github webhooks:** https://yourdomain/build/webhooks
Content type: `application/json`
Secret: `your Token`
### Electron 应用程序配置指南
```bash
yarn add electron-builder electron-simple-updater -D
```
关于 [electron-builder](https://github.com/electron-userland/electron-builder) [electron-simple-updater](https://github.com/megahertz/electron-simple-updater)
在你的`package.json`文件中加入`build`配置信息([https://www.electron.build](https://www.electron.build)):
```json
"scripts": {
"build": "node config/build.js && electron-builder",
"build:dir": "node config/build.js && electron-builder --dir",
...
},
"build": {
"productName": "Your App Name",
"appId": "com.appid.abc",
"directories": {
"output": "build"
},
"files": [
"dist/electron/**/*"
],
"dmg": {
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"mac": {
"icon": "build/icons/icon.icns"
},
"win": {
"icon": "build/icons/icon.ico",
"target": "squirrel"
},
"linux": {
"artifactName": "${productName}-${version}-${arch}.${ext}",
"icon": "build/icons"
},
"squirrelWindows": {
"iconUrl": "https://yourServer/app/icon.ico"
}
},
```
## Mac APP 编译及代码签名
在macOS Majave (10.14.4)中安装 `Xcode 10.2` \ `brew (yarn 1.15.2)` \ `nvm (node 11.13.0)`, 至少运行一次`Xcode`。
操作系统默认安装了 Git 和 rsync。
开启 sshd:
```bash
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist
```
添加你的服务器公钥到Mac `~/.ssh/authorized_keys`
添加你的Mac公钥到服务器 `~/.ssh/authorized_keys`
在你的Linux服务器中安装`rsync`并开启SSH服务。

65
package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "electron-distribution",
"version": "1.0.0",
"description": "Electron Distribution",
"author": "yi-ge <a@wyr.me>",
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon src/dev.js",
"build": "babel src -d dist",
"start": "node dist/app.js"
},
"license": "MIT",
"dependencies": {
"ali-oss": "^6.1.1",
"axios": "^0.18.0",
"chalk": "^2.4.2",
"chance": "^1.0.18",
"cos-nodejs-sdk-v5": "^2.5.7",
"cross-env": "^5.2.0",
"dockerode": "^2.5.8",
"hapi": "^18.1.0",
"hapi-swagger": "^9.4.2",
"inert": "^5.1.2",
"joi": "^13.7.0",
"jssha": "^2.3.1",
"lodash": "^4.17.11",
"lowdb": "^1.0.0",
"moment-timezone": "^0.5.23",
"qiniu": "^7.2.1",
"simple-git": "^1.110.0",
"socket.io": "^2.2.0",
"ssh2": "^0.8.2",
"vision": "^5.4.4"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.4.0",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/preset-env": "^7.4.2",
"@babel/register": "^7.4.0",
"nodemon": "^1.18.10"
},
"nodemonConfig": {
"ignore": [
".git",
"node_modules/**/node_modules"
],
"delay": "2500",
"env": {
"NODE_ENV": "development"
},
"verbose": true,
"execMap": {
"js": "node --harmony"
},
"watch": [
"src/"
],
"ext": "js,json"
},
"engines": {
"node": ">= 10"
}
}

7
public/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

163
public/css/xterm.css Normal file
View File

@ -0,0 +1,163 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* 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.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
font-feature-settings: "liga" 0;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 10;
}
.xterm .xterm-helper-textarea {
/*
* HACK: to fix IE's blinking cursor
* Move textarea out of the screen to the far left, so that the cursor is not visible.
*/
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -10;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm {
cursor: text;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 100;
color: transparent;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}

BIN
public/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

78
public/index.html Normal file
View File

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Electron Distribution</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/xterm.css">
<script>
if (!document.querySelectorAll || !window.localStorage || !self.fetch) {
alert('Upgrade your browser, Thank you.')
}
</script>
<style>
body {
padding: 10px;
}
.out {
margin-top: 10px;
background-color: black;
padding: 10px;
}
#no-auth, #auth {
display: none;
}
#terminal {
width: 100%;
min-height: 428px;
}
#table {
max-height: 300px;
overflow: auto;
margin: 5px 0;
}
</style>
</head>
<body>
<div id="no-auth">
Just for developers debugging, this page is very simple, sorry.
<input type="password" size="128" placeholder="Please input the token and press enter key" id="tokenInput">
</div>
<div id="auth">
<div class="menu">
<input type="button" value="Docker Initialize" id="dockerInitBtn">
<input type="button" value="Release Log" id="listReleaseBtn">
<input type="button" value="Build Log" id="listBuildBtn">
<input type="button" value="Git Pull" id="gitPullBtn">
<input type="button" value="Build Linux" id="buildLinuxBtn">
<input type="button" value="Build Windows" id="buildWindowsBtn">
<input type="button" value="Build Mac" id="buildMacBtn">
<input type="button" value="Logout" id="logoutBtn">
</div>
<div id="table"></div>
<div class="out">
<div id="terminal"></div>
</div>
</div>
<script src="js/sha.js"></script>
<script src="js/xterm.js"></script>
<script src="js/addons/fit/fit.js"></script>
<script src="js/addons/attach/attach.js"></script>
<script src="js/socket.io.js"></script>
<script src="js/jsonToTable.js"></script>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,104 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.attach = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function attach(term, socket, bidirectional, buffered) {
var addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
addonTerminal.__flushBuffer = function () {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
addonTerminal.__pushToBuffer = function (data) {
if (addonTerminal.__attachSocketBuffer) {
addonTerminal.__attachSocketBuffer += data;
}
else {
addonTerminal.__attachSocketBuffer = data;
setTimeout(addonTerminal.__flushBuffer, 10);
}
};
var myTextDecoder;
addonTerminal.__getMessage = function (ev) {
var str;
if (typeof ev.data === 'object') {
if (!myTextDecoder) {
myTextDecoder = new TextDecoder();
}
if (ev.data instanceof ArrayBuffer) {
str = myTextDecoder.decode(ev.data);
displayData(str);
}
else {
var fileReader_1 = new FileReader();
fileReader_1.addEventListener('load', function () {
str = myTextDecoder.decode(fileReader_1.result);
displayData(str);
});
fileReader_1.readAsArrayBuffer(ev.data);
}
}
else if (typeof ev.data === 'string') {
displayData(ev.data);
}
else {
throw Error("Cannot handle \"" + typeof ev.data + "\" websocket message.");
}
};
function displayData(str, data) {
if (buffered) {
addonTerminal.__pushToBuffer(str || data);
}
else {
addonTerminal.write(str || data);
}
}
addonTerminal.__sendData = function (data) {
if (socket.readyState !== 1) {
return;
}
socket.send(data);
};
addonTerminal._core.register(addSocketListener(socket, 'message', addonTerminal.__getMessage));
if (bidirectional) {
addonTerminal._core.register(addonTerminal.addDisposableListener('data', addonTerminal.__sendData));
}
addonTerminal._core.register(addSocketListener(socket, 'close', function () { return detach(addonTerminal, socket); }));
addonTerminal._core.register(addSocketListener(socket, 'error', function () { return detach(addonTerminal, socket); }));
}
exports.attach = attach;
function addSocketListener(socket, type, handler) {
socket.addEventListener(type, handler);
return {
dispose: function () {
if (!handler) {
return;
}
socket.removeEventListener(type, handler);
handler = null;
}
};
}
function detach(term, socket) {
var addonTerminal = term;
addonTerminal.off('data', addonTerminal.__sendData);
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
socket.removeEventListener('message', addonTerminal.__getMessage);
}
delete addonTerminal.__socket;
}
exports.detach = detach;
function apply(terminalConstructor) {
terminalConstructor.prototype.attach = function (socket, bidirectional, buffered) {
attach(this, socket, bidirectional, buffered);
};
terminalConstructor.prototype.detach = function (socket) {
detach(this, socket);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=attach.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,51 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fit = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function proposeGeometry(term) {
if (!term.element.parentElement) {
return null;
}
var parentElementStyle = window.getComputedStyle(term.element.parentElement);
var parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
var parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
var elementStyle = window.getComputedStyle(term.element);
var elementPadding = {
top: parseInt(elementStyle.getPropertyValue('padding-top')),
bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),
right: parseInt(elementStyle.getPropertyValue('padding-right')),
left: parseInt(elementStyle.getPropertyValue('padding-left'))
};
var elementPaddingVer = elementPadding.top + elementPadding.bottom;
var elementPaddingHor = elementPadding.right + elementPadding.left;
var availableHeight = parentElementHeight - elementPaddingVer;
var availableWidth = parentElementWidth - elementPaddingHor - term._core.viewport.scrollBarWidth;
var geometry = {
cols: Math.floor(availableWidth / term._core.renderer.dimensions.actualCellWidth),
rows: Math.floor(availableHeight / term._core.renderer.dimensions.actualCellHeight)
};
return geometry;
}
exports.proposeGeometry = proposeGeometry;
function fit(term) {
var geometry = proposeGeometry(term);
if (geometry) {
if (term.rows !== geometry.rows || term.cols !== geometry.cols) {
term._core.renderer.clear();
term.resize(geometry.cols, geometry.rows);
}
}
}
exports.fit = fit;
function apply(terminalConstructor) {
terminalConstructor.prototype.proposeGeometry = function () {
return proposeGeometry(this);
};
terminalConstructor.prototype.fit = function () {
fit(this);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=fit.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"fit.js","sources":["../../../src/addons/fit/fit.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * Fit terminal columns and rows to the dimensions of its DOM element.\n *\n * ## Approach\n *\n * Rows: Truncate the division of the terminal parent element height by the\n * terminal row height.\n * Columns: Truncate the division of the terminal parent element width by the\n * terminal character width (apply display: inline at the terminal\n * row and truncate its width with the current number of columns).\n */\n\nimport { Terminal } from 'xterm';\n\nexport interface IGeometry {\n rows: number;\n cols: number;\n}\n\nexport function proposeGeometry(term: Terminal): IGeometry {\n if (!term.element.parentElement) {\n return null;\n }\n const parentElementStyle = window.getComputedStyle(term.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(term.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - (<any>term)._core.viewport.scrollBarWidth;\n const geometry = {\n cols: Math.floor(availableWidth / (<any>term)._core.renderer.dimensions.actualCellWidth),\n rows: Math.floor(availableHeight / (<any>term)._core.renderer.dimensions.actualCellHeight)\n };\n return geometry;\n}\n\nexport function fit(term: Terminal): void {\n const geometry = proposeGeometry(term);\n if (geometry) {\n // Force a full render\n if (term.rows !== geometry.rows || term.cols !== geometry.cols) {\n (<any>term)._core.renderer.clear();\n term.resize(geometry.cols, geometry.rows);\n }\n }\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).proposeGeometry = function (): IGeometry {\n return proposeGeometry(this);\n };\n\n (<any>terminalConstructor.prototype).fit = function (): void {\n fit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADsBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAvBA;AAyBA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AATA;AAWA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AARA;"}

View File

@ -0,0 +1,10 @@
.xterm.fullscreen {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
height: auto;
z-index: 255;
}

View File

@ -0,0 +1,29 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.fullscreen = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function toggleFullScreen(term, fullscreen) {
var fn;
if (typeof fullscreen === 'undefined') {
fn = (term.element.classList.contains('fullscreen')) ?
term.element.classList.remove : term.element.classList.add;
}
else if (!fullscreen) {
fn = term.element.classList.remove;
}
else {
fn = term.element.classList.add;
}
fn = fn.bind(term.element.classList);
fn('fullscreen');
}
exports.toggleFullScreen = toggleFullScreen;
function apply(terminalConstructor) {
terminalConstructor.prototype.toggleFullScreen = function (fullscreen) {
toggleFullScreen(this, fullscreen);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=fullscreen.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"fullscreen.js","sources":["../../../src/addons/fullscreen/fullscreen.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2014 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n * Toggle the given terminal's fullscreen mode.\n * @param term The terminal to toggle full screen mode\n * @param fullscreen Toggle fullscreen on (true) or off (false)\n */\nexport function toggleFullScreen(term: Terminal, fullscreen: boolean): void {\n let fn: (...tokens: string[]) => void;\n\n if (typeof fullscreen === 'undefined') {\n fn = (term.element.classList.contains('fullscreen')) ?\n term.element.classList.remove : term.element.classList.add;\n } else if (!fullscreen) {\n fn = term.element.classList.remove;\n } else {\n fn = term.element.classList.add;\n }\n\n fn = fn.bind(term.element.classList);\n fn('fullscreen');\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).toggleFullScreen = function (fullscreen: boolean): void {\n toggleFullScreen(this, fullscreen);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADYA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AAAA;AACA;AACA;AAEA;AACA;AACA;AAdA;AAgBA;AACA;AACA;AACA;AACA;AAJA;"}

View File

@ -0,0 +1,235 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.search = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
var LINES_CACHE_TIME_TO_LIVE = 15 * 1000;
var SearchHelper = (function () {
function SearchHelper(_terminal) {
this._terminal = _terminal;
this._linesCache = null;
this._linesCacheTimeoutId = 0;
this._destroyLinesCache = this._destroyLinesCache.bind(this);
}
SearchHelper.prototype.findNext = function (term, searchOptions) {
var selectionManager = this._terminal._core.selectionManager;
var incremental = searchOptions.incremental;
var result;
if (!term || term.length === 0) {
selectionManager.clearSelection();
return false;
}
var startCol = 0;
var startRow = this._terminal._core.buffer.ydisp;
if (selectionManager.selectionEnd) {
if (this._terminal.getSelection().length !== 0) {
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1];
startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0];
}
}
this._initLinesCache();
result = this._findInLine(term, startRow, startCol, searchOptions);
if (!result) {
for (var y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
}
}
if (!result) {
for (var y = 0; y <= startRow; y++) {
result = this._findInLine(term, y, 0, searchOptions);
if (result) {
break;
}
}
}
return this._selectResult(result);
};
SearchHelper.prototype.findPrevious = function (term, searchOptions) {
var selectionManager = this._terminal._core.selectionManager;
var result;
if (!term || term.length === 0) {
selectionManager.clearSelection();
return false;
}
var isReverseSearch = true;
var startRow = this._terminal._core.buffer.ydisp;
var startCol = this._terminal._core.buffer.lines.get(startRow).length;
if (selectionManager.selectionStart) {
if (this._terminal.getSelection().length !== 0) {
startRow = selectionManager.selectionStart[1];
startCol = selectionManager.selectionStart[0];
}
}
this._initLinesCache();
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
if (!result) {
for (var y = startRow - 1; y >= 0; y--) {
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
if (result) {
break;
}
}
}
if (!result) {
var searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1;
for (var y = searchFrom; y >= startRow; y--) {
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
if (result) {
break;
}
}
}
return this._selectResult(result);
};
SearchHelper.prototype._initLinesCache = function () {
var _this = this;
if (!this._linesCache) {
this._linesCache = new Array(this._terminal._core.buffer.length);
this._terminal.on('cursormove', this._destroyLinesCache);
}
window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = window.setTimeout(function () { return _this._destroyLinesCache(); }, LINES_CACHE_TIME_TO_LIVE);
};
SearchHelper.prototype._destroyLinesCache = function () {
this._linesCache = null;
this._terminal.off('cursormove', this._destroyLinesCache);
if (this._linesCacheTimeoutId) {
window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = 0;
}
};
SearchHelper.prototype._isWholeWord = function (searchIndex, line, term) {
return (((searchIndex === 0) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex - 1]) !== -1)) &&
(((searchIndex + term.length) === line.length) || (NON_WORD_CHARACTERS.indexOf(line[searchIndex + term.length]) !== -1)));
};
SearchHelper.prototype._findInLine = function (term, row, col, searchOptions, isReverseSearch) {
if (searchOptions === void 0) { searchOptions = {}; }
if (isReverseSearch === void 0) { isReverseSearch = false; }
if (this._terminal._core.buffer.lines.get(row).isWrapped) {
return;
}
var stringLine = this._linesCache ? this._linesCache[row] : void 0;
if (stringLine === void 0) {
stringLine = this.translateBufferLineToStringWithWrap(row, true);
if (this._linesCache) {
this._linesCache[row] = stringLine;
}
}
var searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
var searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
var resultIndex = -1;
if (searchOptions.regex) {
var searchRegex = RegExp(searchTerm, 'g');
var foundTerm = void 0;
if (isReverseSearch) {
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
term = foundTerm[0];
searchRegex.lastIndex -= (term.length - 1);
}
}
else {
foundTerm = searchRegex.exec(searchStringLine.slice(col));
if (foundTerm && foundTerm[0].length > 0) {
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
term = foundTerm[0];
}
}
}
else {
if (isReverseSearch) {
if (col - searchTerm.length >= 0) {
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
}
}
else {
resultIndex = searchStringLine.indexOf(searchTerm, col);
}
}
if (resultIndex >= 0) {
if (resultIndex >= this._terminal.cols) {
row += Math.floor(resultIndex / this._terminal.cols);
resultIndex = resultIndex % this._terminal.cols;
}
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
return;
}
var line = this._terminal._core.buffer.lines.get(row);
for (var i = 0; i < resultIndex; i++) {
var charData = line.get(i);
var char = charData[1];
if (char.length > 1) {
resultIndex -= char.length - 1;
}
var charWidth = charData[2];
if (charWidth === 0) {
resultIndex++;
}
}
return {
term: term,
col: resultIndex,
row: row
};
}
};
SearchHelper.prototype.translateBufferLineToStringWithWrap = function (lineIndex, trimRight) {
var lineString = '';
var lineWrapsToNext;
do {
var nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1);
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols);
lineIndex++;
} while (lineWrapsToNext);
return lineString;
};
SearchHelper.prototype._selectResult = function (result) {
if (!result) {
this._terminal.clearSelection();
return false;
}
this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length);
this._terminal.scrollLines(result.row - this._terminal._core.buffer.ydisp);
return true;
};
return SearchHelper;
}());
exports.SearchHelper = SearchHelper;
},{}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var SearchHelper_1 = require("./SearchHelper");
function findNext(terminal, term, searchOptions) {
if (searchOptions === void 0) { searchOptions = {}; }
var addonTerminal = terminal;
if (!addonTerminal.__searchHelper) {
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
}
return addonTerminal.__searchHelper.findNext(term, searchOptions);
}
exports.findNext = findNext;
function findPrevious(terminal, term, searchOptions) {
var addonTerminal = terminal;
if (!addonTerminal.__searchHelper) {
addonTerminal.__searchHelper = new SearchHelper_1.SearchHelper(addonTerminal);
}
return addonTerminal.__searchHelper.findPrevious(term, searchOptions);
}
exports.findPrevious = findPrevious;
function apply(terminalConstructor) {
terminalConstructor.prototype.findNext = function (term, searchOptions) {
return findNext(this, term, searchOptions);
};
terminalConstructor.prototype.findPrevious = function (term, searchOptions) {
return findPrevious(this, term, searchOptions);
};
}
exports.apply = apply;
},{"./SearchHelper":1}]},{},[2])(2)
});
//# sourceMappingURL=search.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,69 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.terminado = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function terminadoAttach(term, socket, bidirectional, buffered) {
var addonTerminal = term;
bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;
addonTerminal.__socket = socket;
addonTerminal.__flushBuffer = function () {
addonTerminal.write(addonTerminal.__attachSocketBuffer);
addonTerminal.__attachSocketBuffer = null;
};
addonTerminal.__pushToBuffer = function (data) {
if (addonTerminal.__attachSocketBuffer) {
addonTerminal.__attachSocketBuffer += data;
}
else {
addonTerminal.__attachSocketBuffer = data;
setTimeout(addonTerminal.__flushBuffer, 10);
}
};
addonTerminal.__getMessage = function (ev) {
var data = JSON.parse(ev.data);
if (data[0] === 'stdout') {
if (buffered) {
addonTerminal.__pushToBuffer(data[1]);
}
else {
addonTerminal.write(data[1]);
}
}
};
addonTerminal.__sendData = function (data) {
socket.send(JSON.stringify(['stdin', data]));
};
addonTerminal.__setSize = function (size) {
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
};
socket.addEventListener('message', addonTerminal.__getMessage);
if (bidirectional) {
addonTerminal.on('data', addonTerminal.__sendData);
}
addonTerminal.on('resize', addonTerminal.__setSize);
socket.addEventListener('close', function () { return terminadoDetach(addonTerminal, socket); });
socket.addEventListener('error', function () { return terminadoDetach(addonTerminal, socket); });
}
exports.terminadoAttach = terminadoAttach;
function terminadoDetach(term, socket) {
var addonTerminal = term;
addonTerminal.off('data', addonTerminal.__sendData);
socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;
if (socket) {
socket.removeEventListener('message', addonTerminal.__getMessage);
}
delete addonTerminal.__socket;
}
exports.terminadoDetach = terminadoDetach;
function apply(terminalConstructor) {
terminalConstructor.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
return terminadoAttach(this, socket, bidirectional, buffered);
};
terminalConstructor.prototype.terminadoDetach = function (socket) {
return terminadoDetach(this, socket);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=terminado.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"terminado.js","sources":["../../../src/addons/terminado/terminado.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2016 The xterm.js authors. All rights reserved.\n * @license MIT\n *\n * This module provides methods for attaching a terminal to a terminado\n * WebSocket stream.\n */\n\nimport { Terminal } from 'xterm';\nimport { ITerminadoAddonTerminal } from './Interfaces';\n\n/**\n * Attaches the given terminal to the given socket.\n *\n * @param term The terminal to be attached to the given socket.\n * @param socket The socket to attach the current terminal.\n * @param bidirectional Whether the terminal should send data to the socket as well.\n * @param buffered Whether the rendering of incoming data should happen instantly or at a maximum\n * frequency of 1 rendering per 10ms.\n */\nexport function terminadoAttach(term: Terminal, socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n bidirectional = (typeof bidirectional === 'undefined') ? true : bidirectional;\n addonTerminal.__socket = socket;\n\n addonTerminal.__flushBuffer = () => {\n addonTerminal.write(addonTerminal.__attachSocketBuffer);\n addonTerminal.__attachSocketBuffer = null;\n };\n\n addonTerminal.__pushToBuffer = (data: string) => {\n if (addonTerminal.__attachSocketBuffer) {\n addonTerminal.__attachSocketBuffer += data;\n } else {\n addonTerminal.__attachSocketBuffer = data;\n setTimeout(addonTerminal.__flushBuffer, 10);\n }\n };\n\n addonTerminal.__getMessage = (ev: MessageEvent) => {\n const data = JSON.parse(ev.data);\n if (data[0] === 'stdout') {\n if (buffered) {\n addonTerminal.__pushToBuffer(data[1]);\n } else {\n addonTerminal.write(data[1]);\n }\n }\n };\n\n addonTerminal.__sendData = (data: string) => {\n socket.send(JSON.stringify(['stdin', data]));\n };\n\n addonTerminal.__setSize = (size: {rows: number, cols: number}) => {\n socket.send(JSON.stringify(['set_size', size.rows, size.cols]));\n };\n\n socket.addEventListener('message', addonTerminal.__getMessage);\n\n if (bidirectional) {\n addonTerminal.on('data', addonTerminal.__sendData);\n }\n addonTerminal.on('resize', addonTerminal.__setSize);\n\n socket.addEventListener('close', () => terminadoDetach(addonTerminal, socket));\n socket.addEventListener('error', () => terminadoDetach(addonTerminal, socket));\n}\n\n/**\n * Detaches the given terminal from the given socket\n *\n * @param term The terminal to be detached from the given socket.\n * @param socket The socket from which to detach the current terminal.\n */\nexport function terminadoDetach(term: Terminal, socket: WebSocket): void {\n const addonTerminal = <ITerminadoAddonTerminal>term;\n addonTerminal.off('data', addonTerminal.__sendData);\n\n socket = (typeof socket === 'undefined') ? addonTerminal.__socket : socket;\n\n if (socket) {\n socket.removeEventListener('message', addonTerminal.__getMessage);\n }\n\n delete addonTerminal.__socket;\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n /**\n * Attaches the current terminal to the given socket\n *\n * @param socket - The socket to attach the current terminal.\n * @param bidirectional - Whether the terminal should send data to the socket as well.\n * @param buffered - Whether the rendering of incoming data should happen instantly or at a\n * maximum frequency of 1 rendering per 10ms.\n */\n (<any>terminalConstructor.prototype).terminadoAttach = function (socket: WebSocket, bidirectional: boolean, buffered: boolean): void {\n return terminadoAttach(this, socket, bidirectional, buffered);\n };\n\n /**\n * Detaches the current terminal from the given socket.\n *\n * @param socket The socket from which to detach the current terminal.\n */\n (<any>terminalConstructor.prototype).terminadoDetach = function (socket: WebSocket): void {\n return terminadoDetach(this, socket);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADoBA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAAA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AA/CA;AAuDA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AAXA;AAaA;AASA;AACA;AACA;AAOA;AACA;AACA;AACA;AArBA;"}

View File

@ -0,0 +1,41 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.webLinks = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var protocolClause = '(https?:\\/\\/)';
var domainCharacterSet = '[\\da-z\\.-]+';
var negatedDomainCharacterSet = '[^\\da-z\\.-]+';
var domainBodyClause = '(' + domainCharacterSet + ')';
var tldClause = '([a-z\\.]{2,6})';
var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
var localHostClause = '(localhost)';
var portClause = '(:\\d{1,5})';
var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
var pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])';
var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
var start = '(?:^|' + negatedDomainCharacterSet + ')(';
var end = ')($|' + negatedPathCharacterSet + ')';
var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
function handleLink(event, uri) {
window.open(uri, '_blank');
}
function webLinksInit(term, handler, options) {
if (handler === void 0) { handler = handleLink; }
if (options === void 0) { options = {}; }
options.matchIndex = 1;
term.registerLinkMatcher(strictUrlRegex, handler, options);
}
exports.webLinksInit = webLinksInit;
function apply(terminalConstructor) {
terminalConstructor.prototype.webLinksInit = function (handler, options) {
webLinksInit(this, handler, options);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=webLinks.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"webLinks.js","sources":["../../../src/addons/webLinks/webLinks.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal, ILinkMatcherOptions } from 'xterm';\n\nconst protocolClause = '(https?:\\\\/\\\\/)';\nconst domainCharacterSet = '[\\\\da-z\\\\.-]+';\nconst negatedDomainCharacterSet = '[^\\\\da-z\\\\.-]+';\nconst domainBodyClause = '(' + domainCharacterSet + ')';\nconst tldClause = '([a-z\\\\.]{2,6})';\nconst ipClause = '((\\\\d{1,3}\\\\.){3}\\\\d{1,3})';\nconst localHostClause = '(localhost)';\nconst portClause = '(:\\\\d{1,5})';\nconst hostClause = '((' + domainBodyClause + '\\\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';\nconst pathClause = '(\\\\/[\\\\/\\\\w\\\\.\\\\-%~:]*)*([^:\"\\'\\\\s])';\nconst queryStringHashFragmentCharacterSet = '[0-9\\\\w\\\\[\\\\]\\\\(\\\\)\\\\/\\\\?\\\\!#@$%&\\'*+,:;~\\\\=\\\\.\\\\-]*';\nconst queryStringClause = '(\\\\?' + queryStringHashFragmentCharacterSet + ')?';\nconst hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';\nconst negatedPathCharacterSet = '[^\\\\/\\\\w\\\\.\\\\-%]+';\nconst bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;\nconst start = '(?:^|' + negatedDomainCharacterSet + ')(';\nconst end = ')($|' + negatedPathCharacterSet + ')';\nconst strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);\n\nfunction handleLink(event: MouseEvent, uri: string): void {\n window.open(uri, '_blank');\n}\n\n/**\n * Initialize the web links addon, registering the link matcher.\n * @param term The terminal to use web links within.\n * @param handler A custom handler to use.\n * @param options Custom options to use, matchIndex will always be ignored.\n */\nexport function webLinksInit(term: Terminal, handler: (event: MouseEvent, uri: string) => void = handleLink, options: ILinkMatcherOptions = {}): void {\n options.matchIndex = 1;\n term.registerLinkMatcher(strictUrlRegex, handler, options);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).webLinksInit = function (handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void {\n webLinksInit(this, handler, options);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAQA;AAAA;AAAA;AACA;AACA;AACA;AAHA;AAKA;AACA;AACA;AACA;AACA;AAJA;"}

View File

@ -0,0 +1,33 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.winptyCompat = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var CHAR_DATA_CODE_INDEX = 3;
var NULL_CELL_CODE = 0;
var WHITESPACE_CELL_CODE = 32;
function winptyCompatInit(terminal) {
var addonTerminal = terminal;
var isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
if (!isWindows) {
return;
}
addonTerminal._core.isWinptyCompatEnabled = true;
addonTerminal.on('linefeed', function () {
var line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);
var lastChar = line.get(addonTerminal.cols - 1);
if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {
var nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);
nextLine.isWrapped = true;
}
});
}
exports.winptyCompatInit = winptyCompatInit;
function apply(terminalConstructor) {
terminalConstructor.prototype.winptyCompatInit = function () {
winptyCompatInit(this);
};
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=winptyCompat.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"winptyCompat.js","sources":["../../../src/addons/winptyCompat/winptyCompat.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\nimport { IWinptyCompatAddonTerminal } from './Interfaces';\n\nconst CHAR_DATA_CODE_INDEX = 3;\nconst NULL_CELL_CODE = 0;\nconst WHITESPACE_CELL_CODE = 32;\n\nexport function winptyCompatInit(terminal: Terminal): void {\n const addonTerminal = <IWinptyCompatAddonTerminal>terminal;\n\n // Don't do anything when the platform is not Windows\n const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;\n if (!isWindows) {\n return;\n }\n\n (addonTerminal._core as any).isWinptyCompatEnabled = true;\n\n // Winpty does not support wraparound mode which means that lines will never\n // be marked as wrapped. This causes issues for things like copying a line\n // retaining the wrapped new line characters or if consumers are listening\n // in on the data stream.\n //\n // The workaround for this is to listen to every incoming line feed and mark\n // the line as wrapped if the last character in the previous line is not a\n // space. This is certainly not without its problems, but generally on\n // Windows when text reaches the end of the terminal it's likely going to be\n // wrapped.\n addonTerminal.on('linefeed', () => {\n const line = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y - 1);\n const lastChar = line.get(addonTerminal.cols - 1);\n\n if (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE) {\n const nextLine = addonTerminal._core.buffer.lines.get(addonTerminal._core.buffer.ybase + addonTerminal._core.buffer.y);\n nextLine.isWrapped = true;\n }\n });\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n (<any>terminalConstructor.prototype).winptyCompatInit = function (): void {\n winptyCompatInit(this);\n };\n}\n",null],"names":[],"mappings":"ACAA;;;ADQA;AACA;AACA;AAEA;AACA;AAGA;AACA;AACA;AACA;AAEA;AAYA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AA9BA;AAgCA;AACA;AACA;AACA;AACA;AAJA;"}

View File

@ -0,0 +1,45 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.zmodem = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var zmodem;
function zmodemAttach(ws, opts) {
if (opts === void 0) { opts = {}; }
var term = this;
var senderFunc = function (octets) { return ws.send(new Uint8Array(octets)); };
var zsentry;
function shouldWrite() {
return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;
}
zsentry = new zmodem.Sentry({
to_terminal: function (octets) {
if (shouldWrite()) {
term.write(String.fromCharCode.apply(String, octets));
}
},
sender: senderFunc,
on_retract: function () { return term.emit('zmodemRetract'); },
on_detect: function (detection) { return term.emit('zmodemDetect', detection); }
});
function handleWSMessage(evt) {
if (typeof evt.data === 'string') {
if (shouldWrite()) {
term.write(evt.data);
}
}
else {
zsentry.consume(evt.data);
}
}
ws.binaryType = 'arraybuffer';
ws.addEventListener('message', handleWSMessage);
}
function apply(terminalConstructor) {
zmodem = (typeof window === 'object') ? window.Zmodem : { Browser: null };
terminalConstructor.prototype.zmodemAttach = zmodemAttach;
terminalConstructor.prototype.zmodemBrowser = zmodem.Browser;
}
exports.apply = apply;
},{}]},{},[1])(1)
});
//# sourceMappingURL=zmodem.js.map

View File

@ -0,0 +1 @@
{"version":3,"file":"zmodem.js","sources":["../../../src/addons/zmodem/zmodem.ts","../../../node_modules/browser-pack/_prelude.js"],"sourcesContent":["/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport { Terminal } from 'xterm';\n\n/**\n *\n * Allow xterm.js to handle ZMODEM uploads and downloads.\n *\n * This addon is a wrapper around zmodem.js. It adds the following to the\n * Terminal class:\n *\n * - function `zmodemAttach(<WebSocket>, <Object>)` - creates a Zmodem.Sentry\n * on the passed WebSocket object. The Object passed is optional and\n * can contain:\n * - noTerminalWriteOutsideSession: Suppress writes from the Sentry\n * object to the Terminal while there is no active Session. This\n * is necessary for compatibility with, for example, the\n * `attach.js` addon.\n *\n * - event `zmodemDetect` - fired on Zmodem.Sentrys `on_detect` callback.\n * Passes the zmodem.js Detection object.\n *\n * - event `zmodemRetract` - fired on Zmodem.Sentrys `on_retract` callback.\n *\n * Youll need to provide logic to handle uploads and downloads.\n * See zmodem.jss documentation for more details.\n *\n * **IMPORTANT:** After you confirm() a zmodem.js Detection, if you have\n * used the `attach` or `terminado` addons, youll need to suspend their\n * operation for the duration of the ZMODEM session. (The demo does this\n * via `detach()` and a re-`attach()`.)\n */\n\nlet zmodem: any;\n\nexport interface IZmodemOptions {\n noTerminalWriteOutsideSession?: boolean;\n}\n\nfunction zmodemAttach(ws: WebSocket, opts: IZmodemOptions = {}): void {\n const term = this;\n const senderFunc = (octets: ArrayLike<number>) => ws.send(new Uint8Array(octets));\n\n let zsentry: any;\n\n function shouldWrite(): boolean {\n return !!zsentry.get_confirmed_session() || !opts.noTerminalWriteOutsideSession;\n }\n\n zsentry = new zmodem.Sentry({\n to_terminal: (octets: ArrayLike<number>) => {\n if (shouldWrite()) {\n term.write(\n String.fromCharCode.apply(String, octets)\n );\n }\n },\n sender: senderFunc,\n on_retract: () => (<any>term).emit('zmodemRetract'),\n on_detect: (detection: any) => (<any>term).emit('zmodemDetect', detection)\n });\n\n function handleWSMessage(evt: MessageEvent): void {\n\n // In testing with xterm.jss demo the first message was\n // always text even if the rest were binary. While that\n // may be specific to xterm.jss demo, ultimately we\n // should reject anything that isnt binary.\n if (typeof evt.data === 'string') {\n if (shouldWrite()) {\n term.write(evt.data);\n }\n }\n else {\n zsentry.consume(evt.data);\n }\n }\n\n ws.binaryType = 'arraybuffer';\n ws.addEventListener('message', handleWSMessage);\n}\n\nexport function apply(terminalConstructor: typeof Terminal): void {\n zmodem = (typeof window === 'object') ? (<any>window).Zmodem : {Browser: null}; // Nullify browser for tests\n\n (<any>terminalConstructor.prototype).zmodemAttach = zmodemAttach;\n (<any>terminalConstructor.prototype).zmodemBrowser = zmodem.Browser;\n}\n",null],"names":[],"mappings":"ACAA;;;ADoCA;AAMA;AAAA;AACA;AACA;AAEA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AALA;"}

187
public/js/jsonToTable.js Normal file
View File

@ -0,0 +1,187 @@
/* eslint-disable no-extend-native */
// https://github.com/afshinm/Json-to-HTML-Table/blob/master/json-to-table.js
/**
* JavaScript format string function
*
*/
String.prototype.format = function () {
var args = arguments
return this.replace(/{(\d+)}/g, function (match, number) {
return typeof args[number] !== 'undefined' ? args[number]
: '{' + number + '}'
})
}
/**
* Return just the keys from the input array, optionally only for the specified search_value
* version: 1109.2015
* discuss at: http://phpjs.org/functions/arrayKeys
* + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
* + input by: Brett Zamir (http://brett-zamir.me)
* + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
* + improved by: jd
* + improved by: Brett Zamir (http://brett-zamir.me)
* + input by: P
* + bugfixed by: Brett Zamir (http://brett-zamir.me)
* * example 1: arrayKeys( {firstname: 'Kevin', surname: 'van Zonneveld'} );
* * returns 1: {0: 'firstname', 1: 'surname'}
*/
const arrayKeys = (input, searchValue, argStrict) => {
var search = typeof searchValue !== 'undefined'
var tmpArr = []
var strict = !!argStrict
var include = true
var key = ''
if (input && typeof input === 'object' && input.change_key_case) { // Duck-type check for our own array()-created PHPJS_Array
return input.keys(searchValue, argStrict)
}
for (key in input) {
if (input.hasOwnProperty(key)) {
include = true
if (search) {
if (strict && input[key] !== searchValue) {
include = false
} else if (input[key] !== searchValue) {
include = false
}
}
if (include) {
tmpArr[tmpArr.length] = key
}
}
}
return tmpArr
}
/**
* Convert a Javascript Oject array or String array to an HTML table
* JSON parsing has to be made before function call
* It allows use of other JSON parsing methods like jQuery.parseJSON
* http(s)://, ftp://, file:// and javascript:; links are automatically computed
*
* JSON data samples that should be parsed and then can be converted to an HTML table
* var objectArray = '[{"Total":"34","Version":"1.0.4","Office":"New York"},{"Total":"67","Version":"1.1.0","Office":"Paris"}]';
* var stringArray = '["New York","Berlin","Paris","Marrakech","Moscow"]';
* var nestedTable = '[{ key1: "val1", key2: "val2", key3: { tableId: "tblIdNested1", tableClassName: "clsNested", linkText: "Download", data: [{ subkey1: "subval1", subkey2: "subval2", subkey3: "subval3" }] } }]';
*
* Code sample to create a HTML table Javascript String
* var jsonHtmlTable = ConvertJsonToTable(eval(dataString), 'jsonTable', null, 'Download');
*
* Code sample explaned
* - eval is used to parse a JSON dataString
* - table HTML id attribute will be 'jsonTable'
* - table HTML class attribute will not be added
* - 'Download' text will be displayed instead of the link itself
*
* @author Afshin Mehrabani <afshin dot meh at gmail dot com>
*
* @class ConvertJsonToTable
*
* @method ConvertJsonToTable
*
* @param parsedJson object Parsed JSON data
* @param tableId string Optional table id
* @param tableClassName string Optional table css class name
* @param linkText string Optional text replacement for link pattern
*
* @return string Converted JSON to HTML table
*/
const convertJsonToTable = (parsedJson, tableId, tableClassName, linkText) => {
// Patterns for links and NULL value
let italic = '<i>{0}</i>'
let link = linkText ? '<a href="{0}">' + linkText + '</a>'
: '<a href="{0}">{0}</a>'
let log = '<a href="javascript:showLog(\'{0}\')">{0}</a>'
// Pattern for table
let idMarkup = tableId ? ' id="' + tableId + '"'
: ''
let classMarkup = tableClassName ? ' class="' + tableClassName + '"'
: ''
let tbl = '<table border="1" cellpadding="1" cellspacing="1"' + idMarkup + classMarkup + '>{0}{1}</table>'
// Patterns for table content
let th = '<thead>{0}</thead>'
let tb = '<tbody>{0}</tbody>'
let tr = '<tr>{0}</tr>'
let thRow = '<th>{0}</th>'
let tdRow = '<td>{0}</td>'
let thCon = ''
let tbCon = ''
let trCon = ''
if (parsedJson) {
let isStringArray = typeof (parsedJson[0]) === 'string'
let headers
// Create table headers from JSON data
// If JSON data is a simple string array we create a single table header
if (isStringArray) {
thCon += thRow.format('value')
} else {
// If JSON data is an object array, headers are automatically computed
if (typeof (parsedJson[0]) === 'object') {
headers = arrayKeys(parsedJson[0])
for (let i = 0; i < headers.length; i++) {
thCon += thRow.format(headers[i])
}
}
}
th = th.format(tr.format(thCon))
// Create table rows from Json data
if (isStringArray) {
for (let i = 0; i < parsedJson.length; i++) {
tbCon += tdRow.format(parsedJson[i])
trCon += tr.format(tbCon)
tbCon = ''
}
} else {
if (headers) {
let urlRegExp = new RegExp(/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig) // eslint-disable-line
let javascriptRegExp = new RegExp(/(^javascript:[\s\S]*;$)/ig)
let logRegExp = new RegExp(/(.log$)/ig)
for (let i = 0; i < parsedJson.length; i++) {
for (let j = 0; j < headers.length; j++) {
let value = parsedJson[i][headers[j]]
let isUrl = urlRegExp.test(value) || javascriptRegExp.test(value)
let isLog = logRegExp.test(value)
if (isUrl) { // If value is URL we auto-create a link
tbCon += tdRow.format(link.format(value))
} else if (isLog) {
tbCon += tdRow.format(log.format(value))
} else {
if (value) {
if (typeof (value) === 'object') {
// for supporting nested tables
tbCon += tdRow.format(convertJsonToTable(eval(value.data), value.tableId, value.tableClassName, value.linkText)) // eslint-disable-line
} else {
tbCon += tdRow.format(value)
}
} else { // If value == null we format it like PhpMyAdmin NULL values
tbCon += tdRow.format(italic.format(value).toUpperCase())
}
}
}
trCon += tr.format(tbCon)
tbCon = ''
}
}
}
tb = tb.format(trCon)
tbl = tbl.format(th, tb)
return tbl
}
return null
}
window.convertJsonToTable = convertJsonToTable

252
public/js/main.js Normal file
View File

@ -0,0 +1,252 @@
window.onload = function () {
let buildType = null;
Terminal.applyAddon(attach);
Terminal.applyAddon(fit);
const term = new Terminal({
useStyle: true,
convertEol: true,
screenKeys: true,
cursorBlink: false,
visualBell: true,
colors: Terminal.xtermColors
});
window.term = term;
const terminalDom = document.getElementById('terminal');
const socket = io();
// 对Date的扩展将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// 例子:
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt))
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
for(var k in o)
if(new RegExp("("+ k +")").test(fmt))
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
return fmt;
}
const checkToken = () => {
if (window.localStorage.token) {
document.querySelector('#no-auth').style.display = 'none';
document.querySelector('#auth').style.display = 'block';
return true;
} else {
document.querySelector('#no-auth').style.display = 'block';
document.querySelector('#auth').style.display = 'none';
return false;
}
}
const dockerInitailize = () => {
socket.emit('pull', 'win');
socket.emit('pull', 'linux');
term.on('data', (data) => {
socket.emit('cmd', data);
})
}
const showLog = (path) => {
term.clear();
term.writeln('Loading...');
socket.emit('log', encodeURI(path));
}
const getHash = () => {
try {
const shaObj = new jsSHA("SHA-512", "TEXT");
shaObj.update(window.localStorage.token);
return shaObj.getHash("HEX");
} catch (err) {
console.error(err);
alert(err.toString());
}
}
const checkAuth = async () => {
try {
const hash = getHash();
const response = await fetch('/app/auth?token=' + hash);
const data = await response.json();
if (data.status === 1) {
buildType = data.result.buildType;
if (!buildType.includes('linux')) document.getElementById('buildLinuxBtn').style.display = 'none';
if (!buildType.includes('win')) document.getElementById('buildWindowsBtn').style.display = 'none';
if (!buildType.includes('mac')) document.getElementById('buildMacBtn').style.display = 'none';
} else {
alert(data.msg);
window.localStorage.removeItem('token');
window.location.reload();
return false;
}
socket.on('requireAuth', (data) => {
if (data === 'distribution') {
socket.emit('auth', hash);
}
})
socket.on('auth', (data) => {
if (data === 'success') {
term.clear();
term.write('Welcome \x1B[1;3;31mElectron Distribution\x1B[0m ! \n');
} else {
term.clear();
term.write('Auth fail. \n');
}
})
return true;
} catch (e) {
console.error(e);
alert('Network Error.');
return false;
}
}
const gitPull = () => {
socket.emit('gitPull', '');
}
const buildApp = async (type) => {
document.getElementById('table').innerHTML = '';
term.clear();
term.writeln('Loading...');
try {
const hash = getHash();
const response = await fetch('/build/' + type + '?token=' + hash);
const data = await response.json();
if (data.status === 1) {
term.clear();
term.write('Runing yarn... \n');
socket.emit('logs', data.result);
} else {
alert(data.msg);
}
} catch (e) {
console.error(e);
alert('Network Error.');
}
}
const getLocalTime = (nS) => {
return new Date(Number(nS)).Format("yyyy-MM-dd hh:mm:ss");
}
const getList = async (type) => {
try {
const hash = getHash();
const response = await fetch('/app/list/' + type + '?token=' + hash);
const data = await response.json();
if (data.status === 1) {
if (data.result.list && data.result.list.length) {
for (const n in data.result.list) {
if (data.result.list[n].startDate) {
data.result.list[n].startDate = getLocalTime(data.result.list[n].startDate)
} else if (data.result.list[n].releaseDate) {
data.result.list[n].releaseDate = getLocalTime(data.result.list[n].releaseDate)
}
}
document.getElementById('table').innerHTML = convertJsonToTable(data.result.list);
} else {
document.getElementById('table').innerHTML = 'No data.';
}
} else {
alert(data.msg);
}
} catch (e) {
console.error(e);
alert('Network Error.');
}
}
const init = async () => {
if (!checkToken()) return;
if (!(await checkAuth())) return;
window.showLog = showLog;
socket.on('show', (data) => {
term.write(data);
})
socket.on('progress', (data) => {
term.clear();
term.write(data);
})
socket.on('end', (data) => {
term.write(data);
})
socket.on('err', (data) => {
term.write(data);
})
term.open(terminalDom);
term.fit();
term.write('Verifying... Please wait ...\n');
}
document.querySelector('#tokenInput').onkeyup = function (e) {
if (e && e.keyCode == 13) {
window.localStorage.token = document.querySelector('#tokenInput').value;
window.location.reload();
}
}
document.querySelector('#logoutBtn').onclick = function () {
window.localStorage.removeItem('token');
window.location.reload();
}
document.querySelector('#listReleaseBtn').onclick = function () {
getList('release')
}
document.querySelector('#listBuildBtn').onclick = function () {
getList('build')
}
document.querySelector('#dockerInitBtn').onclick = function () {
document.getElementById('table').innerHTML = '';
term.clear();
term.writeln('Loading...');
dockerInitailize();
}
document.querySelector('#gitPullBtn').onclick = function () {
document.getElementById('table').innerHTML = '';
term.clear();
term.writeln('Loading...');
gitPull();
}
document.querySelector('#buildLinuxBtn').onclick = function () {
buildApp('linux');
}
document.querySelector('#buildWindowsBtn').onclick = function () {
buildApp('win');
}
document.querySelector('#buildMacBtn').onclick = function () {
buildApp('mac');
}
init();
}

42
public/js/sha.js Normal file
View File

@ -0,0 +1,42 @@
/*
A JavaScript implementation of the SHA family of hashes, as
defined in FIPS PUB 180-4 and FIPS PUB 202, as well as the corresponding
HMAC implementation as defined in FIPS PUB 198a
Copyright 2008-2018 Brian Turek, 1998-2009 Paul Johnston & Contributors
Distributed under the BSD License
See http://caligatio.github.com/jsSHA/ for more information
*/
'use strict';(function(Y){function C(c,a,b){var e=0,h=[],n=0,g,l,d,f,m,q,u,r,I=!1,v=[],w=[],t,y=!1,z=!1,x=-1;b=b||{};g=b.encoding||"UTF8";t=b.numRounds||1;if(t!==parseInt(t,10)||1>t)throw Error("numRounds must a integer >= 1");if("SHA-1"===c)m=512,q=K,u=Z,f=160,r=function(a){return a.slice()};else if(0===c.lastIndexOf("SHA-",0))if(q=function(a,b){return L(a,b,c)},u=function(a,b,h,e){var k,f;if("SHA-224"===c||"SHA-256"===c)k=(b+65>>>9<<4)+15,f=16;else if("SHA-384"===c||"SHA-512"===c)k=(b+129>>>10<<
5)+31,f=32;else throw Error("Unexpected error in SHA-2 implementation");for(;a.length<=k;)a.push(0);a[b>>>5]|=128<<24-b%32;b=b+h;a[k]=b&4294967295;a[k-1]=b/4294967296|0;h=a.length;for(b=0;b<h;b+=f)e=L(a.slice(b,b+f),e,c);if("SHA-224"===c)a=[e[0],e[1],e[2],e[3],e[4],e[5],e[6]];else if("SHA-256"===c)a=e;else if("SHA-384"===c)a=[e[0].a,e[0].b,e[1].a,e[1].b,e[2].a,e[2].b,e[3].a,e[3].b,e[4].a,e[4].b,e[5].a,e[5].b];else if("SHA-512"===c)a=[e[0].a,e[0].b,e[1].a,e[1].b,e[2].a,e[2].b,e[3].a,e[3].b,e[4].a,
e[4].b,e[5].a,e[5].b,e[6].a,e[6].b,e[7].a,e[7].b];else throw Error("Unexpected error in SHA-2 implementation");return a},r=function(a){return a.slice()},"SHA-224"===c)m=512,f=224;else if("SHA-256"===c)m=512,f=256;else if("SHA-384"===c)m=1024,f=384;else if("SHA-512"===c)m=1024,f=512;else throw Error("Chosen SHA variant is not supported");else if(0===c.lastIndexOf("SHA3-",0)||0===c.lastIndexOf("SHAKE",0)){var F=6;q=D;r=function(a){var c=[],e;for(e=0;5>e;e+=1)c[e]=a[e].slice();return c};x=1;if("SHA3-224"===
c)m=1152,f=224;else if("SHA3-256"===c)m=1088,f=256;else if("SHA3-384"===c)m=832,f=384;else if("SHA3-512"===c)m=576,f=512;else if("SHAKE128"===c)m=1344,f=-1,F=31,z=!0;else if("SHAKE256"===c)m=1088,f=-1,F=31,z=!0;else throw Error("Chosen SHA variant is not supported");u=function(a,c,e,b,h){e=m;var k=F,f,g=[],n=e>>>5,l=0,d=c>>>5;for(f=0;f<d&&c>=e;f+=n)b=D(a.slice(f,f+n),b),c-=e;a=a.slice(f);for(c%=e;a.length<n;)a.push(0);f=c>>>3;a[f>>2]^=k<<f%4*8;a[n-1]^=2147483648;for(b=D(a,b);32*g.length<h;){a=b[l%
5][l/5|0];g.push(a.b);if(32*g.length>=h)break;g.push(a.a);l+=1;0===64*l%e&&D(null,b)}return g}}else throw Error("Chosen SHA variant is not supported");d=M(a,g,x);l=A(c);this.setHMACKey=function(a,b,h){var k;if(!0===I)throw Error("HMAC key already set");if(!0===y)throw Error("Cannot set HMAC key after calling update");if(!0===z)throw Error("SHAKE is not supported for HMAC");g=(h||{}).encoding||"UTF8";b=M(b,g,x)(a);a=b.binLen;b=b.value;k=m>>>3;h=k/4-1;if(k<a/8){for(b=u(b,a,0,A(c),f);b.length<=h;)b.push(0);
b[h]&=4294967040}else if(k>a/8){for(;b.length<=h;)b.push(0);b[h]&=4294967040}for(a=0;a<=h;a+=1)v[a]=b[a]^909522486,w[a]=b[a]^1549556828;l=q(v,l);e=m;I=!0};this.update=function(a){var c,b,k,f=0,g=m>>>5;c=d(a,h,n);a=c.binLen;b=c.value;c=a>>>5;for(k=0;k<c;k+=g)f+m<=a&&(l=q(b.slice(k,k+g),l),f+=m);e+=f;h=b.slice(f>>>5);n=a%m;y=!0};this.getHash=function(a,b){var k,g,d,m;if(!0===I)throw Error("Cannot call getHash after setting HMAC key");d=N(b);if(!0===z){if(-1===d.shakeLen)throw Error("shakeLen must be specified in options");
f=d.shakeLen}switch(a){case "HEX":k=function(a){return O(a,f,x,d)};break;case "B64":k=function(a){return P(a,f,x,d)};break;case "BYTES":k=function(a){return Q(a,f,x)};break;case "ARRAYBUFFER":try{g=new ArrayBuffer(0)}catch(p){throw Error("ARRAYBUFFER not supported by this environment");}k=function(a){return R(a,f,x)};break;default:throw Error("format must be HEX, B64, BYTES, or ARRAYBUFFER");}m=u(h.slice(),n,e,r(l),f);for(g=1;g<t;g+=1)!0===z&&0!==f%32&&(m[m.length-1]&=16777215>>>24-f%32),m=u(m,f,
0,A(c),f);return k(m)};this.getHMAC=function(a,b){var k,g,d,p;if(!1===I)throw Error("Cannot call getHMAC without first setting HMAC key");d=N(b);switch(a){case "HEX":k=function(a){return O(a,f,x,d)};break;case "B64":k=function(a){return P(a,f,x,d)};break;case "BYTES":k=function(a){return Q(a,f,x)};break;case "ARRAYBUFFER":try{k=new ArrayBuffer(0)}catch(v){throw Error("ARRAYBUFFER not supported by this environment");}k=function(a){return R(a,f,x)};break;default:throw Error("outputFormat must be HEX, B64, BYTES, or ARRAYBUFFER");
}g=u(h.slice(),n,e,r(l),f);p=q(w,A(c));p=u(g,f,m,p,f);return k(p)}}function b(c,a){this.a=c;this.b=a}function O(c,a,b,e){var h="";a/=8;var n,g,d;d=-1===b?3:0;for(n=0;n<a;n+=1)g=c[n>>>2]>>>8*(d+n%4*b),h+="0123456789abcdef".charAt(g>>>4&15)+"0123456789abcdef".charAt(g&15);return e.outputUpper?h.toUpperCase():h}function P(c,a,b,e){var h="",n=a/8,g,d,p,f;f=-1===b?3:0;for(g=0;g<n;g+=3)for(d=g+1<n?c[g+1>>>2]:0,p=g+2<n?c[g+2>>>2]:0,p=(c[g>>>2]>>>8*(f+g%4*b)&255)<<16|(d>>>8*(f+(g+1)%4*b)&255)<<8|p>>>8*(f+
(g+2)%4*b)&255,d=0;4>d;d+=1)8*g+6*d<=a?h+="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(p>>>6*(3-d)&63):h+=e.b64Pad;return h}function Q(c,a,b){var e="";a/=8;var h,d,g;g=-1===b?3:0;for(h=0;h<a;h+=1)d=c[h>>>2]>>>8*(g+h%4*b)&255,e+=String.fromCharCode(d);return e}function R(c,a,b){a/=8;var e,h=new ArrayBuffer(a),d,g;g=new Uint8Array(h);d=-1===b?3:0;for(e=0;e<a;e+=1)g[e]=c[e>>>2]>>>8*(d+e%4*b)&255;return h}function N(c){var a={outputUpper:!1,b64Pad:"=",shakeLen:-1};c=c||{};
a.outputUpper=c.outputUpper||!1;!0===c.hasOwnProperty("b64Pad")&&(a.b64Pad=c.b64Pad);if(!0===c.hasOwnProperty("shakeLen")){if(0!==c.shakeLen%8)throw Error("shakeLen must be a multiple of 8");a.shakeLen=c.shakeLen}if("boolean"!==typeof a.outputUpper)throw Error("Invalid outputUpper formatting option");if("string"!==typeof a.b64Pad)throw Error("Invalid b64Pad formatting option");return a}function M(c,a,b){switch(a){case "UTF8":case "UTF16BE":case "UTF16LE":break;default:throw Error("encoding must be UTF8, UTF16BE, or UTF16LE");
}switch(c){case "HEX":c=function(a,c,d){var g=a.length,l,p,f,m,q,u;if(0!==g%2)throw Error("String of HEX type must be in byte increments");c=c||[0];d=d||0;q=d>>>3;u=-1===b?3:0;for(l=0;l<g;l+=2){p=parseInt(a.substr(l,2),16);if(isNaN(p))throw Error("String of HEX type contains invalid characters");m=(l>>>1)+q;for(f=m>>>2;c.length<=f;)c.push(0);c[f]|=p<<8*(u+m%4*b)}return{value:c,binLen:4*g+d}};break;case "TEXT":c=function(c,h,d){var g,l,p=0,f,m,q,u,r,t;h=h||[0];d=d||0;q=d>>>3;if("UTF8"===a)for(t=-1===
b?3:0,f=0;f<c.length;f+=1)for(g=c.charCodeAt(f),l=[],128>g?l.push(g):2048>g?(l.push(192|g>>>6),l.push(128|g&63)):55296>g||57344<=g?l.push(224|g>>>12,128|g>>>6&63,128|g&63):(f+=1,g=65536+((g&1023)<<10|c.charCodeAt(f)&1023),l.push(240|g>>>18,128|g>>>12&63,128|g>>>6&63,128|g&63)),m=0;m<l.length;m+=1){r=p+q;for(u=r>>>2;h.length<=u;)h.push(0);h[u]|=l[m]<<8*(t+r%4*b);p+=1}else if("UTF16BE"===a||"UTF16LE"===a)for(t=-1===b?2:0,l="UTF16LE"===a&&1!==b||"UTF16LE"!==a&&1===b,f=0;f<c.length;f+=1){g=c.charCodeAt(f);
!0===l&&(m=g&255,g=m<<8|g>>>8);r=p+q;for(u=r>>>2;h.length<=u;)h.push(0);h[u]|=g<<8*(t+r%4*b);p+=2}return{value:h,binLen:8*p+d}};break;case "B64":c=function(a,c,d){var g=0,l,p,f,m,q,u,r,t;if(-1===a.search(/^[a-zA-Z0-9=+\/]+$/))throw Error("Invalid character in base-64 string");p=a.indexOf("=");a=a.replace(/\=/g,"");if(-1!==p&&p<a.length)throw Error("Invalid '=' found in base-64 string");c=c||[0];d=d||0;u=d>>>3;t=-1===b?3:0;for(p=0;p<a.length;p+=4){q=a.substr(p,4);for(f=m=0;f<q.length;f+=1)l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(q[f]),
m|=l<<18-6*f;for(f=0;f<q.length-1;f+=1){r=g+u;for(l=r>>>2;c.length<=l;)c.push(0);c[l]|=(m>>>16-8*f&255)<<8*(t+r%4*b);g+=1}}return{value:c,binLen:8*g+d}};break;case "BYTES":c=function(a,c,d){var g,l,p,f,m,q;c=c||[0];d=d||0;p=d>>>3;q=-1===b?3:0;for(l=0;l<a.length;l+=1)g=a.charCodeAt(l),m=l+p,f=m>>>2,c.length<=f&&c.push(0),c[f]|=g<<8*(q+m%4*b);return{value:c,binLen:8*a.length+d}};break;case "ARRAYBUFFER":try{c=new ArrayBuffer(0)}catch(e){throw Error("ARRAYBUFFER not supported by this environment");}c=
function(a,c,d){var g,l,p,f,m,q;c=c||[0];d=d||0;l=d>>>3;m=-1===b?3:0;q=new Uint8Array(a);for(g=0;g<a.byteLength;g+=1)f=g+l,p=f>>>2,c.length<=p&&c.push(0),c[p]|=q[g]<<8*(m+f%4*b);return{value:c,binLen:8*a.byteLength+d}};break;default:throw Error("format must be HEX, TEXT, B64, BYTES, or ARRAYBUFFER");}return c}function y(c,a){return c<<a|c>>>32-a}function S(c,a){return 32<a?(a-=32,new b(c.b<<a|c.a>>>32-a,c.a<<a|c.b>>>32-a)):0!==a?new b(c.a<<a|c.b>>>32-a,c.b<<a|c.a>>>32-a):c}function w(c,a){return c>>>
a|c<<32-a}function t(c,a){var k=null,k=new b(c.a,c.b);return k=32>=a?new b(k.a>>>a|k.b<<32-a&4294967295,k.b>>>a|k.a<<32-a&4294967295):new b(k.b>>>a-32|k.a<<64-a&4294967295,k.a>>>a-32|k.b<<64-a&4294967295)}function T(c,a){var k=null;return k=32>=a?new b(c.a>>>a,c.b>>>a|c.a<<32-a&4294967295):new b(0,c.a>>>a-32)}function aa(c,a,b){return c&a^~c&b}function ba(c,a,k){return new b(c.a&a.a^~c.a&k.a,c.b&a.b^~c.b&k.b)}function U(c,a,b){return c&a^c&b^a&b}function ca(c,a,k){return new b(c.a&a.a^c.a&k.a^a.a&
k.a,c.b&a.b^c.b&k.b^a.b&k.b)}function da(c){return w(c,2)^w(c,13)^w(c,22)}function ea(c){var a=t(c,28),k=t(c,34);c=t(c,39);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function fa(c){return w(c,6)^w(c,11)^w(c,25)}function ga(c){var a=t(c,14),k=t(c,18);c=t(c,41);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function ha(c){return w(c,7)^w(c,18)^c>>>3}function ia(c){var a=t(c,1),k=t(c,8);c=T(c,7);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function ja(c){return w(c,17)^w(c,19)^c>>>10}function ka(c){var a=t(c,19),k=t(c,61);
c=T(c,6);return new b(a.a^k.a^c.a,a.b^k.b^c.b)}function G(c,a){var b=(c&65535)+(a&65535);return((c>>>16)+(a>>>16)+(b>>>16)&65535)<<16|b&65535}function la(c,a,b,e){var h=(c&65535)+(a&65535)+(b&65535)+(e&65535);return((c>>>16)+(a>>>16)+(b>>>16)+(e>>>16)+(h>>>16)&65535)<<16|h&65535}function H(c,a,b,e,h){var d=(c&65535)+(a&65535)+(b&65535)+(e&65535)+(h&65535);return((c>>>16)+(a>>>16)+(b>>>16)+(e>>>16)+(h>>>16)+(d>>>16)&65535)<<16|d&65535}function ma(c,a){var d,e,h;d=(c.b&65535)+(a.b&65535);e=(c.b>>>16)+
(a.b>>>16)+(d>>>16);h=(e&65535)<<16|d&65535;d=(c.a&65535)+(a.a&65535)+(e>>>16);e=(c.a>>>16)+(a.a>>>16)+(d>>>16);return new b((e&65535)<<16|d&65535,h)}function na(c,a,d,e){var h,n,g;h=(c.b&65535)+(a.b&65535)+(d.b&65535)+(e.b&65535);n=(c.b>>>16)+(a.b>>>16)+(d.b>>>16)+(e.b>>>16)+(h>>>16);g=(n&65535)<<16|h&65535;h=(c.a&65535)+(a.a&65535)+(d.a&65535)+(e.a&65535)+(n>>>16);n=(c.a>>>16)+(a.a>>>16)+(d.a>>>16)+(e.a>>>16)+(h>>>16);return new b((n&65535)<<16|h&65535,g)}function oa(c,a,d,e,h){var n,g,l;n=(c.b&
65535)+(a.b&65535)+(d.b&65535)+(e.b&65535)+(h.b&65535);g=(c.b>>>16)+(a.b>>>16)+(d.b>>>16)+(e.b>>>16)+(h.b>>>16)+(n>>>16);l=(g&65535)<<16|n&65535;n=(c.a&65535)+(a.a&65535)+(d.a&65535)+(e.a&65535)+(h.a&65535)+(g>>>16);g=(c.a>>>16)+(a.a>>>16)+(d.a>>>16)+(e.a>>>16)+(h.a>>>16)+(n>>>16);return new b((g&65535)<<16|n&65535,l)}function B(c,a){return new b(c.a^a.a,c.b^a.b)}function A(c){var a=[],d;if("SHA-1"===c)a=[1732584193,4023233417,2562383102,271733878,3285377520];else if(0===c.lastIndexOf("SHA-",0))switch(a=
[3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428],d=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],c){case "SHA-224":break;case "SHA-256":a=d;break;case "SHA-384":a=[new b(3418070365,a[0]),new b(1654270250,a[1]),new b(2438529370,a[2]),new b(355462360,a[3]),new b(1731405415,a[4]),new b(41048885895,a[5]),new b(3675008525,a[6]),new b(1203062813,a[7])];break;case "SHA-512":a=[new b(d[0],4089235720),new b(d[1],2227873595),
new b(d[2],4271175723),new b(d[3],1595750129),new b(d[4],2917565137),new b(d[5],725511199),new b(d[6],4215389547),new b(d[7],327033209)];break;default:throw Error("Unknown SHA variant");}else if(0===c.lastIndexOf("SHA3-",0)||0===c.lastIndexOf("SHAKE",0))for(c=0;5>c;c+=1)a[c]=[new b(0,0),new b(0,0),new b(0,0),new b(0,0),new b(0,0)];else throw Error("No SHA variants supported");return a}function K(c,a){var b=[],e,d,n,g,l,p,f;e=a[0];d=a[1];n=a[2];g=a[3];l=a[4];for(f=0;80>f;f+=1)b[f]=16>f?c[f]:y(b[f-
3]^b[f-8]^b[f-14]^b[f-16],1),p=20>f?H(y(e,5),d&n^~d&g,l,1518500249,b[f]):40>f?H(y(e,5),d^n^g,l,1859775393,b[f]):60>f?H(y(e,5),U(d,n,g),l,2400959708,b[f]):H(y(e,5),d^n^g,l,3395469782,b[f]),l=g,g=n,n=y(d,30),d=e,e=p;a[0]=G(e,a[0]);a[1]=G(d,a[1]);a[2]=G(n,a[2]);a[3]=G(g,a[3]);a[4]=G(l,a[4]);return a}function Z(c,a,b,e){var d;for(d=(a+65>>>9<<4)+15;c.length<=d;)c.push(0);c[a>>>5]|=128<<24-a%32;a+=b;c[d]=a&4294967295;c[d-1]=a/4294967296|0;a=c.length;for(d=0;d<a;d+=16)e=K(c.slice(d,d+16),e);return e}function L(c,
a,k){var e,h,n,g,l,p,f,m,q,u,r,t,v,w,y,A,z,x,F,B,C,D,E=[],J;if("SHA-224"===k||"SHA-256"===k)u=64,t=1,D=Number,v=G,w=la,y=H,A=ha,z=ja,x=da,F=fa,C=U,B=aa,J=d;else if("SHA-384"===k||"SHA-512"===k)u=80,t=2,D=b,v=ma,w=na,y=oa,A=ia,z=ka,x=ea,F=ga,C=ca,B=ba,J=V;else throw Error("Unexpected error in SHA-2 implementation");k=a[0];e=a[1];h=a[2];n=a[3];g=a[4];l=a[5];p=a[6];f=a[7];for(r=0;r<u;r+=1)16>r?(q=r*t,m=c.length<=q?0:c[q],q=c.length<=q+1?0:c[q+1],E[r]=new D(m,q)):E[r]=w(z(E[r-2]),E[r-7],A(E[r-15]),E[r-
16]),m=y(f,F(g),B(g,l,p),J[r],E[r]),q=v(x(k),C(k,e,h)),f=p,p=l,l=g,g=v(n,m),n=h,h=e,e=k,k=v(m,q);a[0]=v(k,a[0]);a[1]=v(e,a[1]);a[2]=v(h,a[2]);a[3]=v(n,a[3]);a[4]=v(g,a[4]);a[5]=v(l,a[5]);a[6]=v(p,a[6]);a[7]=v(f,a[7]);return a}function D(c,a){var d,e,h,n,g=[],l=[];if(null!==c)for(e=0;e<c.length;e+=2)a[(e>>>1)%5][(e>>>1)/5|0]=B(a[(e>>>1)%5][(e>>>1)/5|0],new b(c[e+1],c[e]));for(d=0;24>d;d+=1){n=A("SHA3-");for(e=0;5>e;e+=1){h=a[e][0];var p=a[e][1],f=a[e][2],m=a[e][3],q=a[e][4];g[e]=new b(h.a^p.a^f.a^
m.a^q.a,h.b^p.b^f.b^m.b^q.b)}for(e=0;5>e;e+=1)l[e]=B(g[(e+4)%5],S(g[(e+1)%5],1));for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)a[e][h]=B(a[e][h],l[e]);for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)n[h][(2*e+3*h)%5]=S(a[e][h],W[e][h]);for(e=0;5>e;e+=1)for(h=0;5>h;h+=1)a[e][h]=B(n[e][h],new b(~n[(e+1)%5][h].a&n[(e+2)%5][h].a,~n[(e+1)%5][h].b&n[(e+2)%5][h].b));a[0][0]=B(a[0][0],X[d])}return a}var d,V,W,X;d=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,
1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,
2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];V=[new b(d[0],3609767458),new b(d[1],602891725),new b(d[2],3964484399),new b(d[3],2173295548),new b(d[4],4081628472),new b(d[5],3053834265),new b(d[6],2937671579),new b(d[7],3664609560),new b(d[8],2734883394),new b(d[9],1164996542),new b(d[10],1323610764),new b(d[11],3590304994),new b(d[12],4068182383),new b(d[13],991336113),new b(d[14],633803317),new b(d[15],3479774868),new b(d[16],2666613458),new b(d[17],944711139),new b(d[18],2341262773),
new b(d[19],2007800933),new b(d[20],1495990901),new b(d[21],1856431235),new b(d[22],3175218132),new b(d[23],2198950837),new b(d[24],3999719339),new b(d[25],766784016),new b(d[26],2566594879),new b(d[27],3203337956),new b(d[28],1034457026),new b(d[29],2466948901),new b(d[30],3758326383),new b(d[31],168717936),new b(d[32],1188179964),new b(d[33],1546045734),new b(d[34],1522805485),new b(d[35],2643833823),new b(d[36],2343527390),new b(d[37],1014477480),new b(d[38],1206759142),new b(d[39],344077627),
new b(d[40],1290863460),new b(d[41],3158454273),new b(d[42],3505952657),new b(d[43],106217008),new b(d[44],3606008344),new b(d[45],1432725776),new b(d[46],1467031594),new b(d[47],851169720),new b(d[48],3100823752),new b(d[49],1363258195),new b(d[50],3750685593),new b(d[51],3785050280),new b(d[52],3318307427),new b(d[53],3812723403),new b(d[54],2003034995),new b(d[55],3602036899),new b(d[56],1575990012),new b(d[57],1125592928),new b(d[58],2716904306),new b(d[59],442776044),new b(d[60],593698344),new b(d[61],
3733110249),new b(d[62],2999351573),new b(d[63],3815920427),new b(3391569614,3928383900),new b(3515267271,566280711),new b(3940187606,3454069534),new b(4118630271,4000239992),new b(116418474,1914138554),new b(174292421,2731055270),new b(289380356,3203993006),new b(460393269,320620315),new b(685471733,587496836),new b(852142971,1086792851),new b(1017036298,365543100),new b(1126000580,2618297676),new b(1288033470,3409855158),new b(1501505948,4234509866),new b(1607167915,987167468),new b(1816402316,
1246189591)];X=[new b(0,1),new b(0,32898),new b(2147483648,32906),new b(2147483648,2147516416),new b(0,32907),new b(0,2147483649),new b(2147483648,2147516545),new b(2147483648,32777),new b(0,138),new b(0,136),new b(0,2147516425),new b(0,2147483658),new b(0,2147516555),new b(2147483648,139),new b(2147483648,32905),new b(2147483648,32771),new b(2147483648,32770),new b(2147483648,128),new b(0,32778),new b(2147483648,2147483658),new b(2147483648,2147516545),new b(2147483648,32896),new b(0,2147483649),
new b(2147483648,2147516424)];W=[[0,36,3,41,18],[1,44,10,45,2],[62,6,43,15,61],[28,55,25,21,56],[27,20,39,8,14]];"function"===typeof define&&define.amd?define(function(){return C}):"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(module.exports=C),exports=C):Y.jsSHA=C})(this);

9
public/js/socket.io.js Normal file

File diff suppressed because one or more lines are too long

9640
public/js/xterm.js Normal file

File diff suppressed because it is too large Load Diff

19
release.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
PACKAGE_VERSION=$(grep 'version' package.json | cut -d '"' -f4)
yarn build
docker build -t wy373226722/electron-distribution:$PACKAGE_VERSION .
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:$PACKAGE_VERSION
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION ccr.ccs.tencentyun.com/yi-ge/electron-distribution:$PACKAGE_VERSION
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest
docker tag wy373226722/electron-distribution:$PACKAGE_VERSION wy373226722/electron-distribution:latest
docker push ccr.ccs.tencentyun.com/yi-ge/electron-distribution:$PACKAGE_VERSION
docker push ccr.ccs.tencentyun.com/yi-ge/electron-distribution:latest
docker push registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:$PACKAGE_VERSION
docker push registry.cn-shenzhen.aliyuncs.com/yi-ge/electron-distribution:latest
docker push wy373226722/electron-distribution:$PACKAGE_VERSION
docker push wy373226722/electron-distribution:latest

99
src/app.js Normal file
View File

@ -0,0 +1,99 @@
import hapi from 'hapi'
import swagger from './lib/swagger'
import {
SERVER,
SYSTEM
} from './config'
import routes from './routes'
import db from './lib/db'
import moment from 'moment-timezone'
import SocketIO from 'socket.io'
import websocket from './websocket'
import DockerOde from 'dockerode'
import fs from 'fs'
;(async () => {
const socketPath = SYSTEM.DOCKER_SOCKET
const stats = fs.statSync(socketPath)
if (!stats.isSocket()) {
console.log('Docker can\'t connect.')
}
const docker = new DockerOde({
socketPath: socketPath
})
const checkDockerEnv = async () => {
try {
const r = await docker.info()
if (r.Architecture !== 'x86_64') {
console.log('Require x86-64 system.')
}
} catch (err) {
console.log('Please make sure Docker is working.')
}
try {
const r = await docker.version()
if (r.Version.split('.')[0] < 18) {
console.log('Require Docker 18+ .')
}
} catch (err) {
console.log('Error: ' + err.toString())
}
console.log('Docker Runding...')
}
checkDockerEnv()
const server = hapi.server(SERVER)
await server.register([
...swagger
])
try {
server.bind({
docker,
$db: db,
$moment: moment,
/**
* send success data
*/
success (data, status = 1, msg) {
return {
status,
msg,
result: data
}
},
/**
* send fail data
*/
fail (data, status = 10000, msg) {
return {
status,
msg,
result: data
}
}
})
server.route(routes)
await server.start()
const io = SocketIO.listen(server.listener)
server.bind({
io
})
websocket(io, docker)
console.log('Server running at:', server.info.uri)
} catch (err) {
console.log(err)
process.exit(1)
}
})()

78
src/config.js Normal file
View File

@ -0,0 +1,78 @@
const isDev = process.env.NODE_ENV ? process.env.NODE_ENV === 'development' : false
export const SYSTEM = {
NAME: process.env.NAME || 'APP',
TOKEN: process.env.TOKEN || '1jH27dJf9s852',
SCHEME: [isDev ? 'http' : (process.env.SCHEME || 'http')],
DOMAIN: process.env.DOMAIN || 'www.example.com',
REPOPATH: process.env.REPOPATH || 'git@github.com:yi-ge/electron-distribution.git',
WORKPATH: process.env.WORKPATH || '/data',
BUILD_TYPE: process.env.BUILD_TYPE ? process.env.BUILD_TYPE.split(',') : ['win', 'linux'], // ['linux', 'win', 'mac']
GH_TOKEN: process.env.GH_TOKEN || '', // If you set publish option
CSC_LINK: process.env.CSC_LINK || '', // https://www.electron.build/code-signing
CSC_KEY_PASSWORD: process.env.CSC_KEY_PASSWORD || '',
CSC_NAME: process.env.CSC_NAME || '',
CSC_IDENTITY_AUTO_DISCOVERY: 'false',
DOCKER_SOCKET: process.env.DOCKER_SOCKET || '/var/run/docker.sock',
MAC_SERVER_HOST: process.env.MAC_SERVER_HOST || '127.0.0.1',
MAC_SERVER_PORT: process.env.MAC_SERVER_PORT || '22',
MAC_SERVER_USERNAME: process.env.MAC_SERVER_USERNAME || 'guest',
LINUX_SERVER_HOST: process.env.LINUX_SERVER_HOST || '127.0.0.1',
LINUX_SERVER_PORT: process.env.LINUX_SERVER_PORT || '22',
LINUX_SERVER_USERNAME: process.env.LINUX_SERVER_USERNAME || 'root',
OBJECT_STORAGE_TYPE: process.env.OBJECT_STORAGE_TYPE || 'cos'
}
export const SERVER = {
port: isDev ? '65533' : (process.env.PORT || '80'),
host: isDev ? '0.0.0.0' : (process.env.HOST || '0.0.0.0'),
routes: {
cors: {
origin: ['*'],
additionalHeaders: ['Expect', 'X-GitHub-Delivery', 'X-GitHub-Event', 'X-Hub-Signature']
},
state: {
parse: false, // parse and store in request.state
failAction: 'ignore' // may also be 'ignore' or 'log'
}
}
}
// 腾讯云
export const qcloudAccessKey = {
SecretId: process.env.COS_SECRE_ID || '',
SecretKey: process.env.COS_SECRE_KEY || ''
}
export const COS = {
bucket: process.env.COS_BUCKET || 'bucketname-12345678',
region: process.env.COS_REGION || 'ap-chengdu',
url: process.env.COS_URL || 'https://cdn.xxx.com'
}
// 阿里云OSS
const AliyunAccessKey = {
accessKeyId: process.env.OSS_ACCESS_KEY_ID || 'id',
accessKeySecret: process.env.OSS_ACCESS_SECRET || 'key'
}
export const OSS = {
config: {
region: process.env.OSS_REGION || 'oss-cn-qingdao',
accessKeyId: AliyunAccessKey.accessKeyId,
accessKeySecret: AliyunAccessKey.accessKeySecret,
bucket: process.env.OSS_BUCKET || 'bucket',
internal: process.env.OSS_INTERNAL === 'true',
secure: true,
timeout: 1200000 // 20min
},
url: process.env.OSS_URL || 'https://cdn.xxx.com'
}
export const QINIU = {
accessKey: process.env.QINIU_ACCESS_KEY || '',
secretKey: process.env.QINIU_SECRET_KEY || '',
bucket: process.env.QINIU_BUCKET_KEY || '',
url: process.env.QINIU_URL || 'https://cdn.xxx.com',
zone: process.env.QINIU_ZONE || 'Zone_z0'
}

2
src/dev.js Normal file
View File

@ -0,0 +1,2 @@
require('@babel/register')
require('./app')

135
src/lib/ali-oss.js Normal file
View File

@ -0,0 +1,135 @@
import fs from 'fs'
import { OSS as OSSConfig } from '../config'
import OSS from 'ali-oss'
// 阿里云OSS接口返回Promise使用.then()获取结果,.catch()抓取错误。
// web访问地址OSSConfig.url + object_key
const client = OSSConfig.config.accessKeyId !== 'id' ? new OSS(OSSConfig.config) : { list () {} }
/***
* 查看所有文件
* 通过list来列出当前Bucket下的所有文件主要的参数如下
prefix 指定只列出符合特定前缀的文件
marker 指定只列出文件名大于marker之后的文件
delimiter 用于获取文件的公共前缀
max-keys 用于指定最多返回的文件个数
* @type {*}
*/
// 不带任何参数默认最多返回1000个文件
export const listFiles = client.list()
/***
* 上传本地文件
* @param object_key 文件名
* @param local_file 本地文件路径
*
* join
*该方法将多个参数值字符串结合成一个路径字符串使用方式如下
*path.([path1], [path2], [...])
*在该方法中可以使用一个或多个字符串值参数该参数返回将这些字符串值参数结合而成的路径
*var joinPath = path.join(__dirname, 'a', 'b', 'c');
*console.log(joinPath); // D:\nodePro\fileTest\a\b\c
*__dirname变量值代表程序运行的根目录
*
* 用法示例
import path from 'path';
return upload_local_file("test.js", path.join(__dirname, "address.js")).then(function (result) {
console.log(result);
}).catch(function (err) {
console.log(err);
});
return list_files.then(function (result) {
console.log(result.objects);
}).catch(function (err) {
console.error(err);
});
*/
export const uploadLocalFile = (objectKey, localFile) => {
return client.put(objectKey, localFile)
}
/***
* 流式上传
* 通过putStream接口来上传一个Stream中的内容stream参数可以是任何实现了Readable Stream的对象包含文件流网络流等当使用putStream接口时SDK默认会发起一个chunked encoding的HTTP PUT请求如果在options指定了contentLength参数则不会使用chunked encoding
* @param objectKey
* @param localFile
* @param chunked 是否使用chunked encoding 默认不使用
* @returns {Object}
*/
export const uploadStream = (objectKey, localFile, chunked = false) => {
// // use 'chunked encoding'
const stream = fs.createReadStream(localFile)
if (chunked) {
return client.putStream(objectKey, stream)
} else {
const size = fs.statSync(localFile).size // don't use 'chunked encoding'
return client.putStream(objectKey, stream, {
contentLength: size
})
}
}
/***
* 上传Buffer内容
* @param objectKey
* @param buffer Buffer对象例如new Buffer('hello world')
*/
export const uploadBuffer = (objectKey, buffer) => {
return client.put(objectKey, buffer)
}
/***
* 分片上传
* 在需要上传的文件较大时可以通过multipartUpload接口进行分片上传分片上传的好处是将一个大请求分成多个小请求来执行这样当其中一些请求失败后不需要重新上传整个文件而只需要上传失败的分片就可以了一般对于大于100MB的文件建议采用分片上传的方法
* @param objectKey
* @param localFile
*/
export const uploadMultipart = (objectKey, localFile) => {
return client.multipartUpload(objectKey, localFile, {
progress: function * (p) {
console.log('Progress: ' + p)
}
})
// 上面的progress参数是一个进度回调函数用于获取上传进度。progress可以是一个generator function(function*)也可以是一个”thunk”
// const progress = function (p) {
// return function (done) {
// console.log(p);
// done();
// };
// };
}
/***
* 断点上传需要循环调用
* 分片上传提供progress参数允许用户传递一个进度回调在回调中SDK将当前已经上传成功的比例和断点信息作为参数为了实现断点上传可以在上传过程中保存断点信息checkpoint发生错误后再将已保存的checkpoint作为参数传递给multipartUpload此时将从上次失败的地方继续上传
* @param objectKey
* @param localFile
*/
export const uploadMultiparts = (objectKey, localFile) => {
let checkpoint
return client.multipartUpload(objectKey, localFile, {
checkpoint: checkpoint,
progress: function * (percentage, cpt) {
checkpoint = cpt
}
})
// 上面的代码只是将checkpoint保存在变量中如果程序崩溃的话就丢失了用户也可以将它保存在文件中然后在程序重启后将checkpoint信息从文件中读取出来。
}
/***
* 下载文件到本地
* @param objectKey
* @param localFile 本地路径
*/
export const downloadLocalFile = (objectKey, localFile) => {
return client.get(objectKey, localFile)
}
// export const download_stream = (object_key, local_file) => {
// var result = yield client.getStream(object_key)
// console.log(result)
// var writeStream = fs.createWriteStream(local_file)
// result.stream.pipe(writeStream)
// }

10
src/lib/auth.js Normal file
View File

@ -0,0 +1,10 @@
import JsSHA from 'jssha'
import { SYSTEM } from '../config'
export default (token) => {
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(SYSTEM.TOKEN)
const hash = shaObj.getHash('HEX')
if (token === hash) return true
return false
}

14
src/lib/db.js Normal file
View File

@ -0,0 +1,14 @@
import low from 'lowdb'
import path from 'path'
import FileSync from 'lowdb/adapters/FileSync'
import { SYSTEM } from '../config'
const isDev = process.env.NODE_ENV ? process.env.NODE_ENV === 'development' : false
const adapter = isDev ? new FileSync(path.join(SYSTEM.NAME + '-distribution-db.json')) : new FileSync(path.join(SYSTEM.WORKPATH, SYSTEM.NAME + '-distribution-db.json'))
const db = low(adapter)
if (!db.get('appLog').value()) {
db.defaults({ appLog: [], buildLog: [] }).write()
}
export default db

41
src/lib/qiniu.js Normal file
View File

@ -0,0 +1,41 @@
import qiniu from 'qiniu'
import { QINIU } from '../config'
const mac = new qiniu.auth.digest.Mac(QINIU.accessKey, QINIU.secretKey)
const putPolicy = new qiniu.rs.PutPolicy({
scope: QINIU.bucket
})
const uploadToken = putPolicy.uploadToken(mac)
const config = new qiniu.conf.Config()
// 空间对应的机房
config.zone = qiniu.zone[QINIU.zone]
// 是否使用https域名
config.useHttpsDomain = true
// 上传是否使用cdn加速
// config.useCdnDomain = true;
export const uploadLocalFileToQiniu = (key, localFile) => {
return new Promise((resolve, reject) => {
var resumeUploader = new qiniu.resume_up.ResumeUploader(config)
var putExtra = new qiniu.resume_up.PutExtra()
// 如果指定了断点记录文件,那么下次会从指定的该文件尝试读取上次上传的进度,以实现断点续传
// putExtra.resumeRecordFile = 'progress.log';
// 文件分片上传
const remoteKey = key.substr(1) // 删除开头的/
resumeUploader.putFile(uploadToken, remoteKey, localFile, putExtra, function (respErr,
respBody, respInfo) {
if (respErr) {
return reject(respErr)
}
if (respInfo.statusCode === 200) {
resolve(QINIU.url + key)
} else {
console.log(respInfo.statusCode)
console.log(respBody)
return reject(respInfo)
}
})
})
}

33
src/lib/swagger.js Normal file
View File

@ -0,0 +1,33 @@
import inert from 'inert'
import vision from 'vision'
import hapiSwagger from 'hapi-swagger'
import { SYSTEM } from '../config'
import pack from '../../package'
const swaggerOptions = {
schemes: SYSTEM.SCHEME,
info: {
title: 'Electron Distribution',
version: pack.version
},
grouping: 'tags',
tags: [
{
name: 'app',
description: 'App Distribution'
},
{
name: 'build',
description: 'APP Buils'
}
]
}
export default [
inert,
vision,
{
plugin: hapiSwagger,
options: swaggerOptions
}
]

26
src/lib/tencent-cos.js Normal file
View File

@ -0,0 +1,26 @@
import COSSDK from 'cos-nodejs-sdk-v5'
import { qcloudAccessKey, COS } from '../config'
const cos = new COSSDK(qcloudAccessKey)
export const uploadToCOS = (key, filePath) => {
return new Promise((resolve, reject) => {
// 分片上传
cos.sliceUploadFile(
{
Bucket: COS.bucket,
Region: COS.region,
Key: key,
FilePath: filePath
},
function (err, data) {
if (err || !data) {
reject(err)
} else {
data.realPath = COS.url + key
resolve(data)
}
}
)
})
}

23
src/lib/upload.js Normal file
View File

@ -0,0 +1,23 @@
import { uploadToCOS } from './tencent-cos'
import { uploadStream as uploadLocalFileToOSS } from './ali-oss'
import { uploadLocalFileToQiniu } from './qiniu'
import { OSS as OSSConfig, SYSTEM } from '../config'
export default async (key, localFilePath) => {
switch (SYSTEM.OBJECT_STORAGE_TYPE) {
case 'cos':
const cos = await uploadToCOS(key, localFilePath)
return cos.realPath
case 'oss':
const oss = await uploadLocalFileToOSS(key, localFilePath)
// console.log(oss.url)
if (oss) {
return OSSConfig.url + key
} else {
return null
}
case 'qiniu':
const qiniu = await uploadLocalFileToQiniu(key, localFilePath)
return qiniu
}
}

26
src/routes/auth.js Normal file
View File

@ -0,0 +1,26 @@
import Joi from 'joi'
import { SYSTEM } from '../config'
import auth from '../lib/auth'
export default [{
method: 'GET',
path: `/app/auth`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Check token.',
validate: {
query: {
token: Joi.string().required().description('Encrypted-Token')
}
}
},
async handler (request) {
if (auth(request.query.token)) {
return this.success({
buildType: SYSTEM.BUILD_TYPE
})
}
return this.fail(null, 403, 'Token Error.')
}
}]

190
src/routes/build.js Normal file
View File

@ -0,0 +1,190 @@
import Joi from 'joi'
import fs from 'fs'
import path from 'path'
import { SYSTEM } from '../config'
import { Client } from 'ssh2'
import { spawn } from 'child_process'
import auth from '../lib/auth'
import JsSHA from 'jssha'
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
const mkdirsSync = (dirname) => {
if (fs.existsSync(dirname)) {
return true
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname)
return true
}
}
}
const getHashToken = () => {
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(SYSTEM.TOKEN)
return shaObj.getHash('HEX')
}
export default [
{
method: 'GET',
path: `/build/{type}`,
config: {
auth: false,
tags: ['api', 'build'],
description: 'App Build',
validate: {
params: {
type: Joi.string().required().description('Type')
},
query: {
token: Joi.string().required().description('Encrypted-Token')
}
}
},
async handler (request) {
if (!auth(request.query.token)) {
return this.fail(null, 403, 'Token Error.')
}
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(SYSTEM.TOKEN)
const hashToken = shaObj.getHash('HEX')
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
let containerCmd = 'yarn --ignore-engines'
let imageName = null
let workPath = SYSTEM.WORKPATH
let type = request.params.type
const sourcePath = path.join(workPath, '/source')
switch (type) {
case 'win':
workPath += '/win'
imageName = WIN_IMAGE_NAME
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + hashToken + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
break
case 'linux':
workPath += '/linux'
imageName = LINUX_IMAGE_NAME
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + hashToken + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
break
}
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
const LogPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
if (type === 'mac') {
// 1. rsync server -> mac
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
const rsync = spawn('/usr/bin/rsync', ['-avrz', '-e', `'/usr/bin/ssh -p ${SYSTEM.MAC_SERVER_PORT}'`, '--delete-after', '--exclude', '"node_modules"', sourcePath + '/', SYSTEM.MAC_SERVER_USERNAME + '@' + SYSTEM.MAC_SERVER_HOST + ':/tmp/' + SYSTEM.NAME])
rsync.stdout.pipe(writerStream)
rsync.stderr.pipe(writerStream)
rsync.on('close', (code) => {
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
writerStream.write(`\nChild process exited with code ${code} \n`)
// 2. build app and rsync mac build dir -> server build dir
let bashContent = ``
if (SYSTEM.CSC_LINK) bashContent += 'export CSC_LINK=' + SYSTEM.CSC_LINK + '\n'
if (SYSTEM.CSC_KEY_PASSWORD) bashContent += 'export CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD + '\n'
if (SYSTEM.GH_TOKEN) bashContent += 'export GH_TOKEN=' + SYSTEM.GH_TOKEN + '\n'
bashContent += 'export LOG_PATH=' + LogPath + '\n'
bashContent += 'cd /tmp/' + SYSTEM.NAME + '\n'
bashContent += `yarn --ignore-engines` + ' && yarn run build --' + type + ' --publish ' + publishOpt + '\n'
// bashContent += `echo -e "Host ${SYSTEM.LINUX_SERVER_HOST}\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config\n`
bashContent += `rsync -avrz -e 'ssh -p ${SYSTEM.LINUX_SERVER_PORT}' --exclude "node_modules" /tmp/` + SYSTEM.NAME + '/build/ ' + SYSTEM.LINUX_SERVER_USERNAME + '@' + SYSTEM.LINUX_SERVER_HOST + ':' + sourcePath + '/build \n'
bashContent += 'curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&logPath=' + LogPath + '" -H "cache-control: no-cache"\n'
writerStream.write('Run command: \n')
writerStream.write(bashContent)
const conn = new Client()
conn.on('ready', function () {
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
writerStream.write('Client :: ready\n')
conn.shell(function (err, stream) {
if (err) throw err
stream.pipe(writerStream)
stream.on('close', function () {
const writerStream = fs.createWriteStream(LogPath, {flags: 'a'})
writerStream.write('Stream :: close')
conn.end()
})
stream.end(bashContent)
})
}).connect({
host: SYSTEM.MAC_SERVER_HOST,
port: Number(SYSTEM.MAC_SERVER_PORT),
username: SYSTEM.MAC_SERVER_USERNAME,
privateKey: require('fs').readFileSync('/root/.ssh/id_rsa')
})
})
} else {
const Env = [
'LOG_PATH=' + LogPath
]
if (SYSTEM.CSC_LINK) Env.push('CSC_LINK=' + SYSTEM.CSC_LINK)
if (SYSTEM.CSC_KEY_PASSWORD) Env.push('CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD)
if (SYSTEM.GH_TOKEN) Env.push('GH_TOKEN=' + SYSTEM.GH_TOKEN)
const optsc = {
'AttachStdin': true,
'AttachStdout': true,
'AttachStderr': true,
'Tty': true,
'OpenStdin': true,
'StdinOnce': false,
'Env': Env,
'Cmd': ['/bin/bash', '-c', containerCmd],
'Image': imageName,
'WorkingDir': '/project',
'Volumes': {},
'VolumesFrom': [],
'HostConfig': {
Binds: [
workPath + ':/project:rw',
'/etc/localtime:/etc/localtime:ro',
workPath + '/.cache/electron:/root/.cache/electron',
workPath + '/.cache/electron-builder:/root/.cache/electron-builder'
],
CpusetCpus: SYSTEM.BUILD_CPU_LIMIT || '0',
Memory: Number(SYSTEM.BUILD_MEMORY_LIMIT) || 0,
AutoRemove: true
}
}
const runDocker = () => {
return new Promise((resolve, reject) => {
this.docker.createContainer(optsc, (err, container) => {
if (err || !container) return reject(err || 'container is null')
container.attach({stream: true, stdout: true, stderr: true}, (err, stream) => {
if (err) return reject(err)
const writerStream = fs.createWriteStream(LogPath)
stream.pipe(writerStream)
})
container.start((err, data) => {
if (err) return reject(err)
console.log(data)
resolve(container.id)
})
})
})
}
try {
const res = await runDocker()
return this.success(res)
} catch (err) {
console.log(err)
return this.fail(null, 10001, err.toString())
}
}
}
}
]

22
src/routes/encrypt.js Normal file
View File

@ -0,0 +1,22 @@
import Joi from 'joi'
import JsSHA from 'jssha'
export default [{
method: 'GET',
path: `/app/encrypt`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Token encrypt service.',
validate: {
query: {
token: Joi.string().required().description('Token')
}
}
},
async handler (request) {
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(request.query.token)
return this.success(shaObj.getHash('HEX'))
}
}]

12
src/routes/icon.ico.js Normal file
View File

@ -0,0 +1,12 @@
export default [{
method: 'GET',
path: `/app/icon.ico`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Squirrel windows icon.'
},
handler (request, h) {
return h.file('public/icon.ico')
}
}]

19
src/routes/index.js Normal file
View File

@ -0,0 +1,19 @@
import fs from 'fs'
import path from 'path'
const modules = []
const files = fs.readdirSync(__dirname).filter((file) => {
return file.match(/\.(json|js)$/)
})
files.forEach(key => {
if (key === 'index.js') return
// const content = require(path.join(__dirname, key)).default
const content = require(path.join(__dirname, key)).default
if (Array.isArray(content)) { modules.push(...content) } else { modules.push(content) }
})
export default modules

54
src/routes/list.js Normal file
View File

@ -0,0 +1,54 @@
import Joi from 'joi'
import { SYSTEM } from '../config'
import auth from '../lib/auth'
export default [{
method: 'GET',
path: `/app/list/release`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'App release log list.',
validate: {
query: {
token: Joi.string().required().description('Encrypted-Token')
}
}
},
async handler (request) {
if (!auth(request.query.token)) {
return this.fail(null, 403, 'Token Error.')
}
return this.success({
name: SYSTEM.NAME,
list: this.$db.get('appLog') // .filter(o => o.type !== 'maczip')
.sortBy((item) => -item.releaseDate)
.value()
})
}
},
{
method: 'GET',
path: `/app/list/build`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'App build log list.',
validate: {
query: {
token: Joi.string().required().description('Encrypted-Token')
}
}
},
async handler (request) {
if (!auth(request.query.token)) {
return this.fail(null, 403, 'Token Error.')
}
return this.success({
name: SYSTEM.NAME,
list: this.$db.get('buildLog')
.sortBy((item) => -item.startDate)
.value()
})
}
}]

39
src/routes/log.log.js Normal file
View File

@ -0,0 +1,39 @@
import fs from 'fs'
import Joi from 'joi'
export default [
{
method: 'GET',
path: `/build/log.log`,
config: {
auth: false,
tags: ['api', 'build'],
description: 'Get build log.',
validate: {
query: {
path: Joi.string().required().description('Log path.')
}
}
},
async handler (request) {
const path = decodeURI(request.query.path)
const logLast = this.$db.get('appLog')
.filter({logPath: path})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
if (logLast && logLast.logPath) {
try {
return fs.readFileSync(logLast.logPath)
} catch (err) {
return this.fail(err)
}
} else {
return this.fail(null, 10001, 'No file.')
}
}
}
]

78
src/routes/nupkg.js Normal file
View File

@ -0,0 +1,78 @@
import Joi from 'joi'
import path from 'path'
import { SYSTEM, COS, OSS, QINIU } from '../config'
import axios from 'axios'
export default [
{
method: 'GET',
path: `/app/nupkg/{version}/{releases}`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'RELEASES file or download pukge.',
validate: {
params: {
version: Joi.string().required().description('Version'),
releases: Joi.string().required().description('RELEASES file or File name')
}
}
},
async handler (request, h) {
const version = request.params.version
const releases = request.params.releases
let objectStorageUrl = ''
switch (SYSTEM.OBJECT_STORAGE_TYPE) {
case 'cos':
objectStorageUrl = COS.url
break
case 'oss':
objectStorageUrl = OSS.url
break
case 'qiniu':
objectStorageUrl = QINIU.url
break
}
if (releases === 'RELEASES' || releases === 'releases') {
// TODO: ?id=name&localVersion=4.7.2&arch=amd64
const nupkgLast = this.$db.get('appLog')
.filter({type: 'RELEASES', version})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
if (nupkgLast) {
const RELEASESname = path.join('RELEASES-' + version, 'RELEASES')
const url = objectStorageUrl + '/app/' + nupkgLast.name + '/' + version + '/' + RELEASESname
try {
const { data } = await axios.get(url)
return data
} catch (err) {
console.log(err)
return ''
}
}
} else {
const fileName = releases
const nupkgLast = this.$db.get('appLog')
.filter({type: 'nupkg', version})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
if (nupkgLast) {
return h.redirect(objectStorageUrl + '/app/' + nupkgLast.name + '/' + version + '/' + fileName)
} else {
return ''
}
}
return this.fail()
}
}
]

11
src/routes/public.js Normal file
View File

@ -0,0 +1,11 @@
export default [
{
method: 'GET',
path: '/{param*}',
handler: {
directory: {
path: 'public'
}
}
}
]

40
src/routes/release.js Normal file
View File

@ -0,0 +1,40 @@
import Joi from 'joi'
import auth from '../lib/auth'
export default [
{
method: 'POST',
path: `/app/release`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'The app release.',
validate: {
payload: {
token: Joi.string().required().description('Encrypted-Token'),
name: Joi.string().required().description('The package.json name'),
downloadUrl: Joi.string().required().description('Download URL'),
version: Joi.string().required().description('APP version'),
platform: Joi.string().required().description('Platform'),
extended: Joi.string().required().description('Extended'),
type: Joi.string().required().description('Type'),
logPath: Joi.string().required().description('Log path'),
author: Joi.string().description('Author'),
authorEmail: Joi.string().description('Author Email'),
message: Joi.string().description('Message')
}
}
},
async handler (request) {
if (!auth(request.payload.token)) {
return this.fail(null, 403, 'Token Error.')
}
const data = request.payload
data.releaseDate = new Date().getTime().toString()
const result = this.$db.get('appLog').push(data).write()
return this.success(result)
}
}
]

View File

@ -0,0 +1,26 @@
export default [
{
method: 'GET',
path: `/app/release.json`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Get new JSON about MAC update.'
},
async handler () {
const maczipLast = this.$db.get('appLog')
.filter({type: 'maczip'})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
return maczipLast ? {
'url': maczipLast.downloadUrl,
'name': maczipLast.name,
'notes': maczipLast.message,
'pub_date': this.$moment(maczipLast.releaseDate).tz('Asia/Shanghai').format()
} : {}
}
}
]

View File

@ -0,0 +1,62 @@
import { SYSTEM } from '../config'
export default [{
method: 'GET',
path: `/app/updates.json`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Update check JSON.'
},
async handler () {
const macLast = this.$db.get('appLog')
.filter({
platform: 'mac',
type: 'install'
})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
const winLast = this.$db.get('appLog')
.filter({
platform: 'win',
type: 'install'
})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
const linuxLast = this.$db.get('appLog')
.filter({
platform: 'linux',
type: 'install'
})
.sortBy((item) => -item.releaseDate)
.take()
.first()
.value()
return {
'win32-x64-prod': winLast ? {
'readme': winLast.name,
'update': SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/nupkg/' + winLast.version,
'install': winLast.downloadUrl,
'version': winLast.version
} : {},
'darwin-x64-prod': macLast ? {
'readme': macLast.name,
'update': SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/release.json',
'install': macLast.downloadUrl,
'version': macLast.version
} : {},
'linux-x64-prod': linuxLast ? {
'update': linuxLast.downloadUrl,
'install': linuxLast.downloadUrl,
'version': linuxLast.version
} : {}
}
}
}]

259
src/routes/upload.js Normal file
View File

@ -0,0 +1,259 @@
import fs from 'fs'
import Joi from 'joi'
import git from 'simple-git'
import path from 'path'
import uploadToObjectStorage from '../lib/upload'
import auth from '../lib/auth'
import {
SYSTEM
} from '../config'
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
export default [{
method: 'GET',
path: `/app/upload`,
config: {
auth: false,
tags: ['api', 'app'],
description: 'Upload app to object storage.',
validate: {
query: {
platform: Joi.string().required().description('System platform'),
extended: Joi.string().default('x86-64').description('System extended'),
startDate: Joi.string().required().description('Build startDate'),
logPath: Joi.string().required().description('Log file path.'),
token: Joi.string().required().description('Encrypted-Token')
}
}
},
async handler (request) {
if (!auth(request.query.token)) {
return this.fail(null, 403, 'Token Error.')
}
const platform = request.query.platform
const extended = request.query.extended
const logPath = request.query.logPath
const startDate = request.query.startDate
const workPath = SYSTEM.WORKPATH
const sourcePath = path.join(workPath, '/source')
const gitLog = (workPath) => {
return new Promise((resolve, reject) => {
git(workPath).env({
...process.env,
GIT_SSH_COMMAND
}).log({
n: 1
}, (err, status) => {
if (err) {
return reject(err)
}
resolve(status)
})
})
}
try {
const log = await gitLog(sourcePath)
if (log && log.all && log.all.length === 1) {
const gitInfo = log.all[0]
let packageJson = null
let linuxPath = null
let winPath = null
let filePath = null
let filePath2 = null
let filename = null
let filename2 = null
let nupkg = null
let RELEASES = null
let RELEASESname = null
let nupkgname = null
switch (platform) {
case 'mac':
packageJson = JSON.parse(fs.readFileSync(path.join(sourcePath, 'package.json'), 'utf-8'))
filePath = path.join(sourcePath, 'build', packageJson.build.productName + '-' + packageJson.version + '.dmg')
filePath2 = path.join(sourcePath, 'build', packageJson.build.productName + '-' + packageJson.version + '-mac.zip')
filename = packageJson.name + '-' + packageJson.version + '.dmg'
filename2 = packageJson.name + '-' + packageJson.version + '-mac.zip'
break
case 'linux':
linuxPath = path.join(workPath, '/linux')
packageJson = JSON.parse(fs.readFileSync(path.join(linuxPath, 'package.json'), 'utf-8'))
if (extended === 'armv7l') {
filePath = path.join(linuxPath, 'build', packageJson.name + '-' + packageJson.version + '-armv7l.AppImage')
filename = packageJson.name + '-' + packageJson.version + '-armv7l.AppImage'
} else {
filePath = path.join(linuxPath, 'build', packageJson.name + '-' + packageJson.version + '-x86_64.AppImage')
filename = packageJson.name + '-' + packageJson.version + '-x86_64.AppImage'
}
break
case 'win':
winPath = path.join(workPath, '/win')
packageJson = JSON.parse(fs.readFileSync(path.join(winPath, 'package.json'), 'utf-8'))
filePath = path.join(winPath, 'build', 'squirrel-windows', packageJson.build.productName + ' Setup ' + packageJson.version + '.exe')
filename = packageJson.name + '-' + packageJson.version + '.exe'
nupkg = path.join(winPath, 'build', 'squirrel-windows', packageJson.name + '-' + packageJson.version + '-full.nupkg')
RELEASES = path.join(winPath, 'build', 'squirrel-windows', 'RELEASES')
RELEASESname = path.join('RELEASES-' + packageJson.version, 'RELEASES')
nupkgname = packageJson.name + '-' + packageJson.version + '-full.nupkg'
break
}
this.$db.get('buildLog').find({
startDate
}).assign({
status: 'uploading'
}).write()
const fileFullPath = path.join('/app/', packageJson.name, packageJson.version, filename)
try {
var upload = () => {
uploadToObjectStorage(fileFullPath, filePath).then(res => {
this.$db.get('appLog').push({
name: packageJson.name,
downloadUrl: res,
version: packageJson.version,
platform,
extended,
action: 'release',
type: 'install',
logPath,
author: gitInfo.author_name,
authorEmail: gitInfo.author_email,
message: gitInfo.message,
releaseDate: new Date().getTime().toString()
}).write()
if (platform === 'linux') {
this.$db.get('buildLog').find({
startDate
}).assign({
status: 'finish'
}).write()
}
}).catch(err => {
console.log(err)
})
}
upload()
} catch (err) {
console.log(err)
upload()
}
if (filePath2) {
const fileFullPath2 = path.join('/app/', packageJson.name, packageJson.version, filename2)
try {
var upload2 = () => {
uploadToObjectStorage(fileFullPath2, filePath2).then(res => {
this.$db.get('appLog').push({
name: packageJson.name,
downloadUrl: res,
version: packageJson.version,
platform,
extended,
action: 'release',
type: 'maczip',
logPath,
author: gitInfo.author_name,
authorEmail: gitInfo.author_email,
message: gitInfo.message,
releaseDate: new Date().getTime().toString()
}).write()
this.$db.get('buildLog').find({
startDate
}).assign({
status: 'finish'
}).write()
}).catch(err => {
console.log(err)
})
}
upload2()
} catch (err) {
console.log(err)
upload2()
}
}
if (nupkg) {
const fileFullPath3 = path.join('/app/', packageJson.name, packageJson.version, nupkgname)
try {
var upload3 = () => {
uploadToObjectStorage(fileFullPath3, nupkg).then(res => {
this.$db.get('appLog').push({
name: packageJson.name,
downloadUrl: res,
version: packageJson.version,
platform,
extended,
action: 'release',
type: 'nupkg',
logPath,
author: gitInfo.author_name,
authorEmail: gitInfo.author_email,
message: gitInfo.message,
releaseDate: new Date().getTime().toString()
}).write()
this.$db.get('buildLog').find({
startDate
}).assign({
status: 'finish'
}).write()
}).catch(err => {
console.log(err)
})
}
upload3()
} catch (err) {
console.log(err)
upload3()
}
const fileFullPath4 = path.join('/app/', packageJson.name, packageJson.version, RELEASESname)
try {
var upload4 = () => {
uploadToObjectStorage(fileFullPath4, RELEASES).then(res => {
this.$db.get('appLog').push({
name: packageJson.name,
downloadUrl: res,
version: packageJson.version,
platform,
extended,
action: 'release',
type: 'RELEASES',
logPath,
author: gitInfo.author_name,
authorEmail: gitInfo.author_email,
message: gitInfo.message,
releaseDate: new Date().getTime().toString()
}).write()
}).catch(err => {
console.log(err)
})
}
upload4()
} catch (err) {
console.log(err)
upload4()
}
}
return this.success('ok')
} else {
return this.fail(null, 10003, 'Get git log content error.')
}
} catch (err) {
console.log(err)
return this.fail(err.toString(), 10001, 'Get git log error.')
}
}
}]

363
src/routes/webhooks.js Normal file
View File

@ -0,0 +1,363 @@
import fs from 'fs'
import path from 'path'
import git from 'simple-git'
import { SYSTEM } from '../config'
import { Client } from 'ssh2'
import { spawn } from 'child_process'
import JsSHA from 'jssha'
import Joi from 'joi'
import db from '../lib/db'
const repoPath = SYSTEM.REPOPATH
const workPath = SYSTEM.WORKPATH
const sourcePath = path.join(workPath, '/source')
const linuxPath = path.join(workPath, '/linux')
const winPath = path.join(workPath, '/win')
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
const getHashToken = () => {
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(SYSTEM.TOKEN)
return shaObj.getHash('HEX')
}
const gitCodeUpdate = async (buidType) => {
const gitClone = (repoPath, workPath, type) => {
return new Promise((resolve, reject) => {
git().env({
...process.env,
GIT_SSH_COMMAND
})
.clone(repoPath, workPath, (err) => {
if (err) {
reject(err)
return
}
resolve({
code: 1,
type: 'clone',
change: true
})
})
})
}
const gitPull = (workPath, type) => {
return new Promise((resolve, reject) => {
git(workPath).env({
...process.env,
GIT_SSH_COMMAND
})
.pull((err, update) => {
if (err) {
reject(err)
return
}
if (update && update.summary.changes) {
resolve({
code: 1,
type: 'pull',
change: true
})
} else {
resolve({
code: 1,
type: 'clone',
change: false
})
}
})
})
}
const promiseList = []
if (buidType.includes('mac')) {
if (!fs.existsSync(sourcePath)) {
mkdirsSync(sourcePath)
promiseList.push(gitClone(repoPath, sourcePath, 'Source'))
} else {
if (fs.readdirSync(sourcePath).includes('.git')) {
promiseList.push(gitPull(sourcePath, 'Source'))
} else {
promiseList.push(gitClone(repoPath, sourcePath, 'Source'))
}
}
}
if (buidType.includes('linux')) {
if (!fs.existsSync(linuxPath)) {
mkdirsSync(linuxPath)
promiseList.push(gitClone(repoPath, linuxPath, 'Linux'))
} else {
if (fs.readdirSync(linuxPath).includes('.git')) {
promiseList.push(gitPull(linuxPath, 'Linux'))
} else {
promiseList.push(gitClone(repoPath, linuxPath, 'Linux'))
}
}
}
if (buidType.includes('win')) {
if (!fs.existsSync(winPath)) {
mkdirsSync(winPath)
promiseList.push(gitClone(repoPath, winPath, 'Win'))
} else {
if (fs.readdirSync(winPath).includes('.git')) {
promiseList.push(gitPull(winPath, 'Win'))
} else {
promiseList.push(gitClone(repoPath, winPath, 'Win'))
}
}
}
const res = await Promise.all(promiseList)
return res.every(a => a.code === 1)
}
const mkdirsSync = (dirname) => {
if (fs.existsSync(dirname)) {
return true
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname)
return true
}
}
}
const startDockerToBuild = async (name, version, type, docker) => {
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
const startDate = new Date().getTime().toString()
let containerCmd = 'yarn --ignore-engines'
let imageName = null
let workPath = SYSTEM.WORKPATH
switch (type) {
case 'win':
workPath += '/win'
imageName = WIN_IMAGE_NAME
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
break
case 'linux':
workPath += '/linux'
imageName = LINUX_IMAGE_NAME
containerCmd += ' && yarn run build --' + type + ' --publish ' + publishOpt + ' && curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=$LOG_PATH" -H "cache-control: no-cache"'
break
}
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
const logPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
db.get('buildLog').push({
name,
version,
platform: type,
extended: 'x86-64',
action: 'build',
status: 'buiding',
logPath,
startDate
}).write()
const Env = [
'LOG_PATH=' + logPath
]
if (SYSTEM.CSC_LINK) Env.push('CSC_LINK=' + SYSTEM.CSC_LINK)
if (SYSTEM.CSC_KEY_PASSWORD) Env.push('CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD)
if (SYSTEM.GH_TOKEN) Env.push('GH_TOKEN=' + SYSTEM.GH_TOKEN)
const optsc = {
'AttachStdin': true,
'AttachStdout': true,
'AttachStderr': true,
'Tty': true,
'OpenStdin': true,
'StdinOnce': false,
'Env': Env,
'Cmd': ['/bin/bash', '-c', containerCmd],
'Image': imageName,
'WorkingDir': '/project',
'Volumes': {},
'VolumesFrom': [],
'HostConfig': {
Binds: [
workPath + ':/project:rw',
'/etc/localtime:/etc/localtime:ro',
workPath + '/.cache/electron:/root/.cache/electron',
workPath + '/.cache/electron-builder:/root/.cache/electron-builder'
],
CpusetCpus: SYSTEM.BUILD_CPU_LIMIT || '0',
Memory: Number(SYSTEM.BUILD_MEMORY_LIMIT) || 0,
AutoRemove: true
}
}
const runDocker = (docker) => {
return new Promise((resolve, reject) => {
docker.createContainer(optsc, (err, container) => {
if (err || !container) return reject(err || 'container is null')
container.attach({stream: true, stdout: true, stderr: true}, (err, stream) => {
const writerStream = fs.createWriteStream(logPath)
if (err) return writerStream.write(err.toString())
stream.pipe(writerStream)
})
container.start((err, data) => {
if (err) return reject(err)
console.log(data)
resolve(container.id)
})
})
})
}
return runDocker(docker)
}
const macBuild = async (name, version) => {
const type = 'mac'
const publishOpt = SYSTEM.GH_TOKEN ? 'always' : 'never'
if (!fs.existsSync(SYSTEM.WORKPATH + '/logs/' + type)) mkdirsSync(SYSTEM.WORKPATH + '/logs/' + type)
const logPath = SYSTEM.WORKPATH + '/logs/' + type + '/' + (new Date()).getTime() + '.log'
const startDate = new Date().getTime().toString()
db.get('buildLog').push({
name,
version,
platform: type,
extended: 'x86-64',
action: 'build',
status: 'buiding',
logPath: logPath,
startDate
}).write()
// 1. rsync server -> mac
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
const cmd = `rsync -avrz -e 'ssh -p ${SYSTEM.MAC_SERVER_PORT}' --delete-after --exclude "node_modules" ${sourcePath}/ ${SYSTEM.MAC_SERVER_USERNAME}@${SYSTEM.MAC_SERVER_HOST}:/tmp/${SYSTEM.NAME}`
writerStream.write(cmd)
const rsync = spawn('/bin/sh', ['-c', cmd])
rsync.stdout.pipe(writerStream)
rsync.stderr.pipe(writerStream)
rsync.on('close', (code) => {
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
writerStream.write(`\nChild process exited with code ${code} \n`)
// 2. build app and rsync mac build dir -> server build dir
let bashContent = ''
if (SYSTEM.CSC_LINK) bashContent += 'export CSC_LINK=' + SYSTEM.CSC_LINK + '\n'
if (SYSTEM.CSC_KEY_PASSWORD) bashContent += 'export CSC_KEY_PASSWORD=' + SYSTEM.CSC_KEY_PASSWORD + '\n'
if (SYSTEM.GH_TOKEN) bashContent += 'export GH_TOKEN=' + SYSTEM.GH_TOKEN + '\n'
bashContent += 'export LOG_PATH=' + logPath + '\n'
bashContent += 'cd /tmp/' + SYSTEM.NAME + '\n'
bashContent += `yarn --ignore-engines` + ' && yarn run build --' + type + ' --publish ' + publishOpt + '\n'
// bashContent += `echo -e "Host ${SYSTEM.LINUX_SERVER_HOST}\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config\n`
bashContent += `rsync -avrz -e 'ssh -p ${SYSTEM.LINUX_SERVER_PORT}' --exclude "node_modules" /tmp/` + SYSTEM.NAME + '/build/ ' + SYSTEM.LINUX_SERVER_USERNAME + '@' + SYSTEM.LINUX_SERVER_HOST + ':' + sourcePath + '/build \n'
bashContent += 'curl -X GET "' + SYSTEM.SCHEME + '://' + SYSTEM.DOMAIN + '/app/upload?platform=' + type + '&extended=x86-64&token=' + getHashToken() + '&startDate=' + startDate + '&logPath=' + logPath + '" -H "cache-control: no-cache"\n'
writerStream.write('Run command: \n')
writerStream.write(bashContent)
const conn = new Client()
conn.on('ready', function () {
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
writerStream.write('Client :: ready\n')
conn.shell(function (err, stream) {
if (err) throw err
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
stream.pipe(writerStream)
stream.on('close', function () {
const writerStream = fs.createWriteStream(logPath, {flags: 'a'})
writerStream.write('\nStream :: close\n')
conn.end()
})
stream.end(bashContent)
})
}).connect({
host: SYSTEM.MAC_SERVER_HOST,
port: Number(SYSTEM.MAC_SERVER_PORT),
username: SYSTEM.MAC_SERVER_USERNAME,
privateKey: require('fs').readFileSync('/root/.ssh/id_rsa')
})
})
}
const linuxBuild = async (name, version, docker) => {
await startDockerToBuild(name, version, 'linux', docker)
}
const winBuild = async (name, version, docker) => {
await startDockerToBuild(name, version, 'win', docker)
}
const sleep = (s) => new Promise(resolve => setTimeout(resolve, s))
export default [
{
method: 'POST',
path: `/build/webhooks`,
config: {
auth: false,
tags: ['api', 'build'],
description: 'Github webhook',
validate: {
headers: {
'x-hub-signature': Joi.string().required().description('Github Secret.')
},
options: {
allowUnknown: true
}
}
},
async handler (request) {
try {
const shaObj = new JsSHA('SHA-1', 'TEXT')
shaObj.setHMACKey(SYSTEM.TOKEN, 'TEXT')
shaObj.update(JSON.stringify(request.payload))
const hash = shaObj.getHMAC('HEX')
if (request.headers && request.headers['x-hub-signature'] === 'sha1=' + hash) {
const updateCodeRes = await gitCodeUpdate(SYSTEM.BUILD_TYPE)
if (updateCodeRes) {
const packageJson = JSON.parse(fs.readFileSync(path.join(sourcePath, 'package.json'), 'utf-8'))
if (packageJson && packageJson.name && packageJson.version) {
const name = packageJson.name
const version = packageJson.version
if (SYSTEM.BUILD_TYPE.includes('mac')) {
macBuild(name, version) // async
await sleep(500)
}
if (SYSTEM.BUILD_TYPE.includes('linux')) {
linuxBuild(name, version, this.docker) // async
await sleep(500)
}
if (SYSTEM.BUILD_TYPE.includes('win')) {
winBuild(name, version, this.docker) // async
}
} else {
return this.fail(null, 10010, 'package read failed.')
}
} else {
return this.fail(null, 10009, 'code update failed.')
}
return this.success('ok')
} else {
return this.fail()
}
} catch (err) {
return this.fail(null, 10001, err.toString())
}
}
}
]

5
src/tool/mac.js Normal file
View File

@ -0,0 +1,5 @@
// import { exec } from 'child_process'
// const out = spawn(this.getJavaPath() + '\\java.exe', ['-jar', execPath], {
// cwd: path.join(__dirname.replace('app.asar', 'app.asar.unpacked'))
// })

23
src/util.js Normal file
View File

@ -0,0 +1,23 @@
import Chance from 'chance'
const chance = new Chance()
// 给数字字符串补零
function preZeroFill (num, size) {
if (num >= Math.pow(10, size)) { // 如果num本身位数不小于size位
return num.toString()
} else {
var _str = Array(size + 1).join('0') + num
return _str.slice(_str.length - size)
}
}
/**
* 获取指定位数的整数随机数
* @param {int} size 位数
* @return {string} 定位数的整数随机数字符串
*/
export let getIntRandom = (size) => preZeroFill(chance.integer({
min: 0,
max: Array(size + 1).join(9)
}), size)

168
src/websocket/container.js Normal file
View File

@ -0,0 +1,168 @@
import { Transform } from 'stream'
import chalk from 'chalk'
const WIN_IMAGE_NAME = 'electronuserland/builder:wine-mono'
const LINUX_IMAGE_NAME = 'electronuserland/builder:10'
export default (io, socket, docker) => {
socket.on('exec', function (id, w, h) {
var container = docker.getContainer(id)
var cmd = {
'AttachStdout': true,
'AttachStderr': true,
'AttachStdin': true,
'Tty': true,
Cmd: ['/bin/bash']
}
container.exec(cmd, (err, exec) => {
var options = {
'Tty': true,
stream: true,
stdin: true,
stdout: true,
stderr: true,
// fix vim
hijack: true
}
container.wait((err, data) => {
console.log(err)
socket.emit('end', 'ended')
})
if (err) {
return
}
exec.start(options, (err, stream) => {
console.log(err)
var dimensions = {
h,
w
}
if (dimensions.h !== 0 && dimensions.w !== 0) {
exec.resize(dimensions, () => {})
}
stream.on('data', (chunk) => {
socket.emit('show', chunk.toString())
})
socket.on('cmd', (data) => {
stream.write(data)
})
})
})
})
socket.on('logs', function (id) {
const container = docker.getContainer(id)
const logsOpts = {
follow: true,
stdout: true,
stderr: true,
timestamps: false
}
container.logs(logsOpts, (err, stream) => {
if (err) {
console.log(err)
socket.emit('err', chalk.red('Error:\n') + err + '.\n')
return
}
stream.on('data', (data) => { socket.emit('show', data.toString('utf-8')) })
stream.on('end', function () {
socket.emit('show', '\n===Stream finished===\n')
stream.destroy()
})
})
})
socket.on('pull', function (type) {
let imageName = null
switch (type) {
case 'win':
imageName = WIN_IMAGE_NAME
break
case 'linux':
imageName = LINUX_IMAGE_NAME
break
}
docker.pull(imageName, function (err, stream) {
if (err) {
console.log(err)
socket.emit('err', chalk.red('Error:\n') + err + '.\n')
return
}
const bytesToSize = (bytes) => {
if (bytes === 0) return '0 B'
const k = 1000 // or 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
}
let downTmp = []
let downTmpId = []
const commaSplitter = new Transform({
readableObjectMode: true,
transform (chunk, encoding, callback) {
let tmp = ''
try {
var result = chunk.toString().match(/{(.*)}/)
result = result ? result[0] : null
if (!result) callback()
tmp = JSON.parse(result)
if (tmp.id) {
if (downTmpId.includes(tmp.id)) {
for (const n in downTmp) {
if (downTmp[n].id === tmp.id) {
if (tmp.progressDetail && tmp.progressDetail.current && tmp.progressDetail.total) {
const percentage = Math.floor(100 * tmp.progressDetail.current / tmp.progressDetail.total)
downTmp[n].val = ': [' + percentage + '%] Total ' + bytesToSize(tmp.progressDetail.total)
} else if (tmp.status) {
downTmp[n].val = ': ' + tmp.status
}
}
}
} else {
downTmpId.push(tmp.id)
const temp = {}
temp.id = tmp.id
if (tmp.progressDetail && tmp.progressDetail.current && tmp.progressDetail.total) {
const percentage = Math.floor(100 * tmp.progressDetail.current / tmp.progressDetail.total)
temp.val = ': [' + percentage + '%] Total ' + bytesToSize(tmp.progressDetail.total)
} else if (tmp.status) {
temp.val = ': ' + tmp.status
}
downTmp.push(temp)
}
let str = ''
for (const n in downTmp) {
str += downTmp[n].id + downTmp[n].val + '\n'
}
socket.emit('progress', str)
}
} catch (err) {
// console.log(err)
}
callback()
}
})
stream.pipe(commaSplitter)
stream.once('end', () => {
socket.emit('progress', chalk.green('All: [100%] Finish。\n'))
// socket.emit('end', imageName + ' install ' + chalk.green('success') + '.\n')
downTmp = []
downTmpId = []
})
})
})
}

93
src/websocket/git.js Normal file
View File

@ -0,0 +1,93 @@
import git from 'simple-git'
import chalk from 'chalk'
import fs from 'fs'
import path from 'path'
import { SYSTEM } from '../config'
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
const mkdirsSync = (dirname) => {
if (fs.existsSync(dirname)) {
return true
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname)
return true
}
}
}
export default (io, socket, docker) => {
socket.on('gitPull', (data) => {
const repoPath = SYSTEM.REPOPATH
const workPath = SYSTEM.WORKPATH
const sourcePath = path.join(workPath, '/source')
const linuxPath = path.join(workPath, '/linux')
const winPath = path.join(workPath, '/win')
const gitClone = (repoPath, workPath, type) => {
git().env({
...process.env,
GIT_SSH_COMMAND
})
.clone(repoPath, workPath, (err) => {
if (err) {
socket.emit('err', chalk.red(type + ' clone error:\n') + err + '\n')
return
}
socket.emit('show', chalk.green(type + ' clone is finished!\n'))
})
}
const gitPull = (workPath, type) => {
git(workPath).env({
...process.env,
GIT_SSH_COMMAND
})
.pull((err, update) => {
if (err) {
socket.emit('err', chalk.red(type + ' pull error:\n') + err + '\n')
return
}
if (update && update.summary.changes) {
socket.emit('show', chalk.green(type + ' update success.\n'))
} else {
socket.emit('show', chalk.green(type + ' update success, no change.\n'))
}
})
}
if (!fs.existsSync(sourcePath)) {
mkdirsSync(sourcePath)
gitClone(repoPath, sourcePath, 'Source')
} else {
if (fs.readdirSync(sourcePath).includes('.git')) {
gitPull(sourcePath, 'Source')
} else {
gitClone(repoPath, sourcePath, 'Source')
}
}
if (!fs.existsSync(linuxPath)) {
mkdirsSync(linuxPath)
gitClone(repoPath, linuxPath, 'Linux')
} else {
if (fs.readdirSync(linuxPath).includes('.git')) {
gitPull(linuxPath, 'Linux')
} else {
gitClone(repoPath, linuxPath, 'Linux')
}
}
if (!fs.existsSync(winPath)) {
mkdirsSync(winPath)
gitClone(repoPath, winPath, 'Win')
} else {
if (fs.readdirSync(winPath).includes('.git')) {
gitPull(winPath, 'Win')
} else {
gitClone(repoPath, winPath, 'Win')
}
}
})
}

30
src/websocket/index.js Normal file
View File

@ -0,0 +1,30 @@
import container from './container'
import git from './git'
import log from './log'
import JsSHA from 'jssha'
import {
SYSTEM
} from '../config'
export default (io, docker) => {
console.log('Websocket Runing...')
io.on('connection', function (socket) {
console.log('One user connected - ' + socket.id)
socket.emit('requireAuth', 'distribution')
socket.emit('opend', new Date())
socket.on('auth', function (token) {
const shaObj = new JsSHA('SHA-512', 'TEXT')
shaObj.update(SYSTEM.TOKEN)
const hash = shaObj.getHash('HEX')
if (token === hash) {
container(io, socket, docker)
git(io, socket, docker)
log(io, socket, docker)
socket.emit('auth', 'success')
} else {
socket.emit('auth', 'fail')
}
})
})
}

25
src/websocket/log.js Normal file
View File

@ -0,0 +1,25 @@
import fs from 'fs'
import db from '../lib/db'
export default (io, socket, docker) => {
socket.on('log', function (path) {
path = decodeURI(path)
const logLast = db.get('buildLog')
.filter({logPath: path})
.sortBy((item) => -item.startDate)
.take()
.first()
.value()
if (logLast && logLast.logPath) {
try {
socket.emit('show', fs.readFileSync(logLast.logPath) + '\n')
} catch (err) {
socket.emit('show', err.toString() + '\n')
}
} else {
socket.emit('show', 'No file.')
}
})
}

15
test/spawn.js Normal file
View File

@ -0,0 +1,15 @@
const { spawn } = require('child_process')
const fs = require('fs')
const writerStream = fs.createWriteStream('test/test.log', {flags: 'a'})
const cmd = `rsync -avrz -e 'ssh -p 22' --delete-after --exclude "node_modules" /data/ykfz/source/ root@xxxxxxx:/tmp/ykfz`
writerStream.write(cmd)
const rsync = spawn('/bin/sh', ['-c', cmd])
rsync.stdout.pipe(writerStream)
rsync.stderr.pipe(writerStream)
rsync.on('close', (code) => {
const writerStream = fs.createWriteStream('test/test.log', {flags: 'a'})
writerStream.write(`\nChild process exited with code ${code} \n`)
})

1
test/test.log Normal file
View File

@ -0,0 +1 @@
Child process exited with code 23

4732
yarn.lock Normal file

File diff suppressed because it is too large Load Diff