未验证 提交 1b7bfab8 编写于 作者: aaronchen2k2k's avatar aaronchen2k2k 提交者: GitHub

Merge pull request #1 from catouse/dev/client

+ add client files for building desktop client to use ztf.
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
.DS_Store
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Webpack
.webpack/
# Electron-Forge
out/
# ZTF binary
bin/
# ZTF UI
ui/
# ZTF 客户端
## 开发
### 安装依赖
执行:
```
npm install
```
### 启动调试模式
启动默认的调试模式,此时会预先自动启动 UI 开发服务器和 ZTF 开发服务器。
```
npm run start
```
参考:https://www.electronforge.io/cli#start
### 环境变量
在调试模式下可以通过制定环境变量来设置 ZTF UI 服务访问地址和 ZTF 可执行程序位置。
* `UI_SERVER_URL`: ZTF UI 服务访问地址或静态资源文件目录,如果不指定会自动进入 `../ui/` 目录执行 `npm run serve` 获取开发服务器地址;
* `UI_SERVER_PORT`: ZTF UI 服务端口,如果不指定则使用 `8000`
* `SERVER_EXE_PATH`:ZTF 服务可执行文件位置,如果不指定则会自动进入 `../cmd/server/` 目录下执行 `go run main.go -p 8085` 启动 ZTF 服务器。
* `SKIP_SERVER`: 跳过启动 ZTF 服务,适用于 ZTF 服务已经在外部启动的情况。
### 特殊调试模式
**模式一:使用外部 UI 服务**
```
UI_SERVER_URL=http://localhost:8000 npm run start
```
**模式二:使用本地 UI 静态文件目录**
```
UI_SERVER_URL=../ui/dist UI_SERVER_PORT=8000 npm run start
```
**模式三:自定义 ZTF 服务执行文件路径**
```
SERVER_EXE_PATH=bin/darwin/ztf npm run start
```
**模式四:跳过启动 ZTF 服务,使用外部 ZTF 服务**
```
SKIP_SERVER=1 npm run start
```
**模式五:综合使用外部 UI 服务和外部 ZTF 服务**
```
UI_SERVER_URL=http://localhost:8000 SKIP_SERVER=1 npm run start
```
## 代码检查
```
npm run lint
```
## 构建
```
npm run make
```
参考:https://www.electronforge.io/cli#make
## 打包
```
npm run package
```
参考:https://www.electronforge.io/cli#package
打包之前确保如下目录有相应的资源文件:
* `client/ui/`:包含 UI 服务相关的所有文件,并且包含 `client/ui/index.html` 文件,作为 UI 服务入口;
* `client/bin/win32/`:包含适用于 Windows 的 ZTF 程序;
* `client/bin/darwin/`:包含适用于 macOS 的 ZTF 程序;
* `client/bin/linux/`:包含适用于 Linux 的 ZTF 程序;
## 发布
```
npm run publish
```
参考:https://www.electronforge.io/cli#publish
module.exports = {
packagerConfig: {
extraResource: [
'./bin',
'./ui'
]
},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: {
name: 'ztf_client'
}
},
{
name: '@electron-forge/maker-zip',
platforms: [
'darwin'
]
},
{
name: '@electron-forge/maker-deb',
config: {}
},
{
name: '@electron-forge/maker-rpm',
config: {}
}
],
plugins: [
[
'@electron-forge/plugin-webpack',
{
mainConfig: './webpack.main.config.js',
renderer: {
config: './webpack.renderer.config.js',
entryPoints: [
// {
// html: './src/index.html',
// js: './src/renderer.js',
// name: 'main_window'
// }
]
}
}
]
]
}
此差异已折叠。
{
"name": "ztf-client",
"productName": "ztf-client",
"version": "1.0.0",
"description": "My Electron application description",
"main": ".webpack/main",
"scripts": {
"start": "NODE_ENV=development electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"lint": "echo \"No linting configured\""
},
"keywords": [],
"author": {
"name": "Hao Sun",
"email": "sunhao@easycorp.ltd"
},
"license": "MIT",
"config": {
"forge": "./forge.config.js"
},
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.61",
"@electron-forge/maker-deb": "^6.0.0-beta.61",
"@electron-forge/maker-rpm": "^6.0.0-beta.61",
"@electron-forge/maker-squirrel": "^6.0.0-beta.61",
"@electron-forge/maker-zip": "^6.0.0-beta.61",
"@electron-forge/plugin-webpack": "^6.0.0-beta.61",
"@vercel/webpack-asset-relocator-loader": "^1.7.0",
"css-loader": "^6.5.1",
"electron": "16.0.6",
"node-loader": "^2.0.0",
"style-loader": "^3.3.1"
},
"dependencies": {
"electron-squirrel-startup": "^1.0.0"
}
}
import {app, BrowserWindow} from 'electron';
import {getUIServerUrl, startZtfServer} from './services';
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
}
const createWindow = (url) => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
});
// and load the index.html of the app.
mainWindow.loadURL(url);
// Open the DevTools.
mainWindow.webContents.openDevTools();
};
let _starting = false;
async function startApp() {
if (_starting) {
return;
}
_starting = true;
try {
const ztfServerUrl = await startZtfServer();
console.log(`>> ZTF Server started successfully: ${ztfServerUrl}`);
} catch (error) {
console.error('>> Start ztf server failed: ' + error);
process.exit(1);
return;
}
const url = await getUIServerUrl();
console.log('>> UI server url is', url);
createWindow(url);
_starting = false;
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', startApp);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
startApp();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
import path from 'path';
import {spawn} from 'child_process';
import os from 'os';
import {app} from 'electron';
import express from 'express';
let _ztfServerProcess;
export function startZtfServer() {
if (process.env.SKIP_SERVER) {
console.log(`>> Skip to start ZTF Server by env "SKIP_SERVER=${process.env.SKIP_SERVER}".`);
return Promise.resolve();
}
if (_ztfServerProcess) {
return Promise.resolve(_ztfServerProcess);
}
let {SERVER_EXE_PATH: serverExePath} = process.env;
if (!serverExePath && process.env.NODE_ENV !== 'development') {
const platform = os.platform(); // 'darwin', 'linux', 'win32'
const exePath = `bin/${platform}/ztf${platform === 'win32' ? '.exe' : ''}`;
serverExePath = path.resolve(process.resourcesPath, exePath);
}
if (serverExePath) {
if (!path.isAbsolute(serverExePath)) {
serverExePath = path.resolve(app.getAppPath(), serverExePath);
}
return new Promise((resolve) => {
console.log(`>> Starting ZTF Server with exe path ${serverExePath}`);
const cmd = spawn(serverExePath, ['-P', '8085'], {
cwd: path.dirname(serverExePath),
shell: true,
});
cmd.on('close', () => {
cmd = null;
});
cmd.stdout.on('data', data => {
const dataString = String(data);
const lines = dataString.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('Now listening on: http')) {
resolve(line.split('Now listening on:')[1].trim());
break;
} else if (line.includes('启动HTTP服务于')) {
resolve(line.split(/启动HTTP服务于|,/)[1].trim());
break;
} else if (line.startsWith('[ERRO]')) {
reject(new Error(`Start ztf server failed with error: ${line.substring('[ERRO]'.length)}`));
break;
}
}
});
cmd.on('error', spawnError => {
console.error('>>> Start ztf server failed with error', spawnError);
reject(spawnError)
});
_ztfServerProcess = cmd;
});
}
return new Promise((resolve, reject) => {
console.log('>> Starting ZTF development server...');
const cmd = spawn('go', ['run', 'main.go', '-P', '8085'], {
cwd: path.resolve(app.getAppPath(), '../cmd/server'),
shell: true,
});
cmd.on('close', () => {
_ztfServerProcess = null;
});
cmd.stdout.on('data', data => {
const dataString = String(data);
const lines = dataString.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('Now listening on: http')) {
resolve(line.split('Now listening on:')[1].trim());
break;
} else if (line.startsWith('[ERRO]')) {
reject(new Error(`Start ztf server failed with error: ${line.substring('[ERRO]'.length)}`));
break;
}
}
});
cmd.on('error', spawnError => {
console.error('>>> Start ztf server failed with error', spawnError);
reject(spawnError)
});
_ztfServerProcess = cmd;
});
}
let _uiServerApp;
export function getUIServerUrl() {
if (_uiServerApp) {
return Promise.resolve();
}
let {UI_SERVER_URL: uiServerUrl} = process.env;
if (!uiServerUrl && process.env.NODE_ENV !== 'development') {
uiServerUrl = path.resolve(process.resourcesPath, 'ui');
}
if (uiServerUrl) {
if (/^https?:\/\//.test(uiServerUrl)) {
return Promise.resolve(uiServerUrl);
}
return new Promise((resolve, reject) => {
if (!path.isAbsolute(uiServerUrl)) {
uiServerUrl = path.resolve(app.getAppPath(), uiServerUrl);
}
console.log(`>> Starting UI serer at ${uiServerUrl}`);
const uiServer = express();
uiServer.use(express.static(uiServerUrl));
const server = uiServer.listen(process.env.UI_SERVER_PORT || 8000, serverError => {
if (serverError) {
console.error('>>> Start ui server failed with error', serverError);
_uiServerApp = null;
reject(serverError);
} else {
const address = server.address();
console.log(`>> UI server started successfully on http://localhost:${address.port}.`);
resolve(`http://localhost:${address.port}`);
}
});
server.on('close', () => {
_uiServerApp = null;
});
_uiServerApp = uiServer;
})
}
return new Promise((resolve, reject) => {
console.log('>> Starting UI development serve...');
let resolved = false;
const cmd = spawn('npm', ['run', 'serve'], {
cwd: path.resolve(app.getAppPath(), '../ui'),
shell: true,
});
cmd.on('close', () => {
_uiServerApp = null;
});
cmd.stdout.on('data', data => {
if (resolved) {
return;
}
const dataString = String(data);
const lines = dataString.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('App running at:')) {
const nextLine = lines[i + 1] || lines[i + 2];
if (!nextLine) {
throw new Error(`Cannot grabing running address after line "${line}".`)
}
const url = nextLine.split('Local: ')[1];
if (url) {
resolved = true;
resolve(url);
}
break;
}
}
});
cmd.on('error', spawnError => {
console.error('>>> Get ui server url failed with error', spawnError);
reject(spawnError)
});
_uiServerApp = cmd;
});
}
module.exports = {
/**
* This is the main entry point for your application, it's the first file
* that runs in the main process.
*/
entry: './src/main.js',
// Put your normal webpack config below here
module: {
rules: require('./webpack.rules'),
},
};
const rules = require('./webpack.rules');
rules.push({
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
});
module.exports = {
// Put your normal webpack config below here
module: {
rules,
},
};
module.exports = [
// Add support for native node modules
{
// We're specifying native_modules in the test because the asset relocator loader generates a
// "fake" .node file which is really a cjs file.
test: /native_modules\/.+\.node$/,
use: 'node-loader',
},
{
test: /\.(m?js|node)$/,
parser: { amd: false },
use: {
loader: '@vercel/webpack-asset-relocator-loader',
options: {
outputAssetBase: 'native_modules',
},
},
},
// Put your webpack loader rules in this array. This is where you would put
// your ts-loader configuration for instance:
/**
* Typescript Example:
*
* {
* test: /\.tsx?$/,
* exclude: /(node_modules|.webpack)/,
* loaders: [{
* loader: 'ts-loader',
* options: {
* transpileOnly: true
* }
* }]
* }
*/
];
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册