mirror of
https://github.com/wallace5303/ee-core.git
synced 2026-04-04 23:19:03 +08:00
release: v4
This commit is contained in:
2
ee-bin-ts/.gitignore
vendored
Normal file
2
ee-bin-ts/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
49
ee-bin-ts/package.json
Normal file
49
ee-bin-ts/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "ee-bin-ts",
|
||||
"version": "0.1.0-beta.4",
|
||||
"description": "ee bin for ts",
|
||||
"main": "./dist/index.js",
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"require": {
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"import": {
|
||||
"default": "./ems/index.mjs"
|
||||
},
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": "npm run check:type",
|
||||
"check:type": "npm run check:type:js && npm run check:type:ts",
|
||||
"check:type:ts": "tsd && tsc -p tsconfig.ts.json",
|
||||
"check:type:js": "tsc -p tsconfig.js.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"ee-bin-m": "./ems/index.mjs",
|
||||
"ee-bin-c": "./dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^20.9.0",
|
||||
"adm-zip": "^0.4.11",
|
||||
"bytenode": "^1.3.6",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.0.0",
|
||||
"config-file-ts": "^0.2.8-rc1",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"fs-extra": "^10.0.0",
|
||||
"globby": "^10.0.0",
|
||||
"is-type-of": "^1.2.1",
|
||||
"javascript-obfuscator": "^4.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"mkdirp": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsd": "^0.31.2",
|
||||
"typescript": "^5.4.2"
|
||||
}
|
||||
}
|
||||
1
ee-bin-ts/readme.md
Normal file
1
ee-bin-ts/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
## ee-bin
|
||||
118
ee-bin-ts/src/index.ts
Normal file
118
ee-bin-ts/src/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env node
|
||||
import { program } from 'commander';
|
||||
import moveScript from './tools/move';
|
||||
import encrypt from './tools/encrypt';
|
||||
import serve from './tools/serve';
|
||||
import updater from './tools/incrUpdater';
|
||||
import iconGen from './tools/iconGen';
|
||||
|
||||
/**
|
||||
* move - Moves resources
|
||||
*/
|
||||
program
|
||||
.command('move')
|
||||
.description('Move multip resources')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.option('--flag <flag>', 'Custom flag')
|
||||
.action(function () {
|
||||
moveScript.run(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* encrypt - Code encryption
|
||||
*/
|
||||
program
|
||||
.command('encrypt')
|
||||
.description('Code encryption')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--out <folder>', 'output directory', './public')
|
||||
.action(function () {
|
||||
encrypt.run(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* clean - Clear the encrypted code
|
||||
*/
|
||||
program
|
||||
.command('clean')
|
||||
.description('Clear the encrypted code')
|
||||
.option('-d, --dir <folder>', 'clean directory')
|
||||
.action(function () {
|
||||
encrypt.clean(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* icon
|
||||
*/
|
||||
program
|
||||
.command('icon')
|
||||
.description('Generate logo')
|
||||
.option('-i, --input <file>', 'image file default /public/images/logo.png')
|
||||
.option('-o, --output <folder>', 'output directory default /build/icons/')
|
||||
.action(function () {
|
||||
iconGen.run(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* dev
|
||||
*/
|
||||
program
|
||||
.command('dev')
|
||||
.description('create frontend-serve and electron-serve')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.option('--serve <mode>', 'serve mode')
|
||||
.action(function () {
|
||||
serve.dev(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* build
|
||||
*/
|
||||
program
|
||||
.command('build')
|
||||
.description('building multiple resources')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.option('--cmds <flag>', 'custom commands')
|
||||
.action(function () {
|
||||
serve.build(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* start
|
||||
*/
|
||||
program
|
||||
.command('start')
|
||||
.description('preview effect')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.action(function () {
|
||||
serve.start(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* exec
|
||||
*/
|
||||
program
|
||||
.command('exec')
|
||||
.description('create frontend-serve and electron-serve')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.option('--command <command>', 'Custom command')
|
||||
.option('--cmds <flag>', 'custom commands')
|
||||
.action(function () {
|
||||
// command 选项是关键字,不再使用,改为 cmds
|
||||
serve.exec(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* updater
|
||||
*/
|
||||
program
|
||||
.command('updater')
|
||||
.description('updater commands')
|
||||
.option('--config <folder>', 'config file', './electron/config/bin.js')
|
||||
.option('--asar-file <file>', 'asar file path')
|
||||
.option('--platform <flag>', 'platform')
|
||||
.action(function () {
|
||||
updater.run(this.opts());
|
||||
});
|
||||
|
||||
program.parse();
|
||||
208
ee-bin-ts/src/lib/utils.ts
Normal file
208
ee-bin-ts/src/lib/utils.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import chalk from 'chalk';
|
||||
import is from 'is-type-of';
|
||||
import { loadTsConfig } from 'config-file-ts';
|
||||
import JsonLib from 'json5';
|
||||
import mkdirp from 'mkdirp';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import OS from 'os';
|
||||
|
||||
const _basePath = process.cwd();
|
||||
|
||||
function checkConfig(prop: string): boolean {
|
||||
const filepath = path.join(_basePath, prop);
|
||||
return fs.existsSync(filepath);
|
||||
}
|
||||
|
||||
function loadConfig(prop: string): any {
|
||||
const configFile = path.join(_basePath, prop);
|
||||
if (!fs.existsSync(configFile)) {
|
||||
const errorTips = `config file ${chalk.blue(`${configFile}`)} does not exist !`;
|
||||
throw new Error(errorTips);
|
||||
}
|
||||
|
||||
let result: any;
|
||||
if (configFile.endsWith(".json5") || configFile.endsWith(".json")) {
|
||||
const data = fs.readFileSync(configFile, 'utf8');
|
||||
return JsonLib.parse(data);
|
||||
}
|
||||
if (configFile.endsWith(".js") || configFile.endsWith(".cjs")) {
|
||||
result = require(configFile);
|
||||
if (result.default != null) {
|
||||
result = result.default;
|
||||
}
|
||||
} else if (configFile.endsWith(".ts")) {
|
||||
result = loadTsConfig(configFile);
|
||||
}
|
||||
if (is.function(result) && !is.class(result)) {
|
||||
result = result();
|
||||
}
|
||||
return result || {};
|
||||
}
|
||||
|
||||
function loadEncryptConfig(): any {
|
||||
const configFile = './electron/config/encrypt.js';
|
||||
const filepath = path.join(_basePath, configFile);
|
||||
if (!fs.existsSync(filepath)) {
|
||||
const errorTips = `config file ${chalk.blue(`${filepath}`)} does not exist !`;
|
||||
throw new Error(errorTips);
|
||||
}
|
||||
const obj = require(filepath);
|
||||
if (!obj) return obj;
|
||||
|
||||
let ret = obj;
|
||||
if (is.function(obj) && !is.class(obj)) {
|
||||
ret = obj();
|
||||
}
|
||||
|
||||
return ret || {};
|
||||
}
|
||||
|
||||
function getElectronProgram(): string {
|
||||
let electronPath: any;
|
||||
const electronModulePath = path.dirname(require.resolve('electron'));
|
||||
const pathFile = path.join(electronModulePath, 'path.txt');
|
||||
const executablePath = fs.readFileSync(pathFile, 'utf-8');
|
||||
if (executablePath) {
|
||||
electronPath = path.join(electronModulePath, 'dist', executablePath);
|
||||
} else {
|
||||
throw new Error('Check that electron is installed!');
|
||||
}
|
||||
return electronPath;
|
||||
}
|
||||
|
||||
function compareVersion(v1: string, v2: string): number {
|
||||
const v1Arr: string[] = v1.split('.');
|
||||
const v2Arr: string[] = v2.split('.');
|
||||
const len = Math.max(v1Arr.length, v2Arr.length);
|
||||
|
||||
while (v1Arr.length < len) {
|
||||
v1Arr.push('0');
|
||||
}
|
||||
while (v2Arr.length < len) {
|
||||
v2Arr.push('0');
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1Arr[i]);
|
||||
const num2 = parseInt(v2Arr[i]);
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1;
|
||||
} else if (num1 < num2) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isWindows(): boolean {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
function isOSX(): boolean {
|
||||
return process.platform === 'darwin';
|
||||
}
|
||||
|
||||
function isMacOS(): boolean {
|
||||
return isOSX();
|
||||
}
|
||||
|
||||
function isLinux(): boolean {
|
||||
return process.platform === 'linux';
|
||||
}
|
||||
|
||||
function isx86(): boolean {
|
||||
return process.arch === 'ia32';
|
||||
}
|
||||
|
||||
function isx64(): boolean {
|
||||
return process.arch === 'x64';
|
||||
}
|
||||
|
||||
function rm(name: string): void {
|
||||
if (!fs.existsSync(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeVersion = (process.versions && process.versions.node) || null;
|
||||
if (nodeVersion && compareVersion(nodeVersion, '14.14.0') === 1) {
|
||||
fs.rmSync(name, { recursive: true });
|
||||
} else {
|
||||
fs.rmdirSync(name, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
function getPackage(): any {
|
||||
const homeDir = process.cwd();
|
||||
const content = readJsonSync(path.join(homeDir, 'package.json'));
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function readJsonSync(filepath: string): any {
|
||||
if (!fs.existsSync(filepath)) {
|
||||
throw new Error(`${filepath} is not found`);
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
||||
}
|
||||
|
||||
function writeJsonSync(filepath: string, str: any, options?: { replacer?: any; space?: number }) {
|
||||
options = options || {};
|
||||
if (!('space' in options)) {
|
||||
options.space = 2;
|
||||
}
|
||||
|
||||
mkdirp.sync(path.dirname(filepath));
|
||||
if (typeof str === 'object') {
|
||||
str = JSON.stringify(str, options.replacer, options.space) + '\n';
|
||||
}
|
||||
|
||||
fs.writeFileSync(filepath, str);
|
||||
}
|
||||
|
||||
function getPlatform(delimiter: string = "_", isDiffArch: boolean = false): string {
|
||||
let os = "";
|
||||
if (isWindows()) {
|
||||
os = "windows";
|
||||
if (isDiffArch) {
|
||||
const arch = isx64() ? "64" : "32";
|
||||
os += delimiter + arch;
|
||||
}
|
||||
} else if (isMacOS()) {
|
||||
let isAppleSilicon = false;
|
||||
const cpus = OS.cpus();
|
||||
for (let cpu of cpus) {
|
||||
if (cpu.model.includes('Apple')) {
|
||||
isAppleSilicon = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const core = isAppleSilicon ? "apple" : "intel";
|
||||
os = "macos" + delimiter + core;
|
||||
} else if (isLinux()) {
|
||||
os = "linux";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
export {
|
||||
loadConfig,
|
||||
checkConfig,
|
||||
loadEncryptConfig,
|
||||
getElectronProgram,
|
||||
compareVersion,
|
||||
isWindows,
|
||||
isOSX,
|
||||
isMacOS,
|
||||
isLinux,
|
||||
isx86,
|
||||
isx64,
|
||||
getPlatform,
|
||||
rm,
|
||||
getPackage,
|
||||
readJsonSync,
|
||||
writeJsonSync
|
||||
};
|
||||
269
ee-bin-ts/src/tools/encrypt.ts
Normal file
269
ee-bin-ts/src/tools/encrypt.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import fsPro from 'fs-extra';
|
||||
import bytenode from 'bytenode';
|
||||
import crypto from 'crypto';
|
||||
import JavaScriptObfuscator from 'javascript-obfuscator';
|
||||
import globby from 'globby';
|
||||
import chalk from 'chalk';
|
||||
import Utils from '../lib/utils';
|
||||
|
||||
type Config = {
|
||||
fileExt?: string[];
|
||||
type?: string;
|
||||
bytecodeOptions?: Record<string, any>;
|
||||
confusionOptions?: Record<string, any>;
|
||||
cleanFiles?: string[];
|
||||
files?: string[];
|
||||
directory?: string[];
|
||||
};
|
||||
|
||||
class Encrypt {
|
||||
basePath: string;
|
||||
encryptCodeDir: string;
|
||||
config: Config;
|
||||
filesExt: string[];
|
||||
type: string;
|
||||
bOpt: Record<string, any>;
|
||||
cOpt: Record<string, any>;
|
||||
cleanFiles: string[];
|
||||
patterns: string[] | null;
|
||||
specificFiles: string[];
|
||||
dirs: string[];
|
||||
codefiles: string[];
|
||||
|
||||
constructor(options: { out?: string; config?: string } = {}) {
|
||||
const outputFolder = options.out || './public';
|
||||
const configFile = options.config || './electron/config/bin.js';
|
||||
|
||||
this.basePath = process.cwd();
|
||||
this.encryptCodeDir = path.join(this.basePath, outputFolder);
|
||||
|
||||
const hasConfig = Utils.checkConfig(configFile);
|
||||
if (hasConfig) {
|
||||
const cfg = Utils.loadConfig(configFile);
|
||||
this.config = cfg.encrypt;
|
||||
}
|
||||
if (!this.config) {
|
||||
this.config = Utils.loadEncryptConfig();
|
||||
}
|
||||
|
||||
this.filesExt = this.config.fileExt || ['.js'];
|
||||
this.type = this.config.type || 'confusion';
|
||||
this.bOpt = this.config.bytecodeOptions || {};
|
||||
this.cOpt = this.config.confusionOptions || {};
|
||||
this.cleanFiles = this.config.cleanFiles || ['./public/electron'];
|
||||
this.patterns = this.config.files || null;
|
||||
this.specificFiles = ['electron/preload/bridge.js'];
|
||||
|
||||
this.dirs = [];
|
||||
const directory = this.config.directory || ['electron'];
|
||||
for (let i = 0; i < directory.length; i++) {
|
||||
let codeDirPath = path.join(this.basePath, directory[i]);
|
||||
if (fs.existsSync(codeDirPath)) {
|
||||
this.dirs.push(directory[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.codefiles = this._initCodeFiles();
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'cleanFiles:' + this.cleanFiles);
|
||||
}
|
||||
|
||||
_initCodeFiles(): string[] {
|
||||
if (!this.patterns) return [];
|
||||
|
||||
const files = globby.sync(this.patterns, { cwd: this.basePath });
|
||||
return files;
|
||||
}
|
||||
|
||||
backup(): boolean {
|
||||
this.cleanCode();
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'backup start');
|
||||
if (this.patterns) {
|
||||
this.codefiles.forEach((filepath) => {
|
||||
let source = path.join(this.basePath, filepath);
|
||||
if (fs.existsSync(source)) {
|
||||
let target = path.join(this.encryptCodeDir, filepath);
|
||||
fsPro.copySync(source, target);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
for (let i = 0; i < this.dirs.length; i++) {
|
||||
let codeDirPath = path.join(this.basePath, this.dirs[i]);
|
||||
if (!fs.existsSync(codeDirPath)) {
|
||||
console.log('[ee-bin] [encrypt] ERROR: backup %s is not exist', codeDirPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
let targetDir = path.join(this.encryptCodeDir, this.dirs[i]);
|
||||
console.log('[ee-bin] [encrypt] backup target Dir:', targetDir);
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
this.mkdir(targetDir);
|
||||
this.chmodPath(targetDir, '777');
|
||||
}
|
||||
|
||||
fsPro.copySync(codeDirPath, targetDir);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'backup end');
|
||||
return true;
|
||||
}
|
||||
|
||||
cleanCode(): void {
|
||||
this.cleanFiles.forEach((file) => {
|
||||
let tmpFile = path.join(this.basePath, file);
|
||||
this.rmBackup(tmpFile);
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'clean up tmp files:' + chalk.magenta(`${tmpFile}`));
|
||||
});
|
||||
}
|
||||
|
||||
encrypt(): void {
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'start ciphering');
|
||||
if (this.patterns) {
|
||||
for (const file of this.codefiles) {
|
||||
const fullpath = path.join(this.encryptCodeDir, file);
|
||||
if (!fs.statSync(fullpath).isFile()) continue;
|
||||
|
||||
if (this.specificFiles.includes(file)) {
|
||||
this.generate(fullpath, 'confusion');
|
||||
continue;
|
||||
}
|
||||
|
||||
this.generate(fullpath);
|
||||
}
|
||||
} else {
|
||||
console.log('[ee-bin] [encrypt] !!!!!! please use the new encryption method !!!!!!');
|
||||
for (let i = 0; i < this.dirs.length; i++) {
|
||||
let codeDirPath = path.join(this.encryptCodeDir, this.dirs[i]);
|
||||
this.loop(codeDirPath);
|
||||
}
|
||||
console.log('[ee-bin] [encrypt] !!!!!! please use the new encryption method !!!!!!');
|
||||
}
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'end ciphering');
|
||||
}
|
||||
|
||||
loop(dirPath: string): void {
|
||||
let files = [];
|
||||
if (fs.existsSync(dirPath)) {
|
||||
files = fs.readdirSync(dirPath);
|
||||
files.forEach((file, index) => {
|
||||
let curPath = dirPath + '/' + file;
|
||||
if (fs.statSync(curPath).isDirectory()) {
|
||||
this.loop(curPath);
|
||||
} else {
|
||||
const extname = path.extname(curPath);
|
||||
if (this.filesExt.indexOf(extname) !== -1) {
|
||||
this.generate(curPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
generate(curPath: string, type?: string): void {
|
||||
let encryptType = type ? type : this.type;
|
||||
|
||||
let tips = chalk.blue('[ee-bin] [encrypt] ') + 'file: ' + chalk.green(`${curPath}`) + ' ' + chalk.cyan(`(${encryptType})`);
|
||||
console.log(tips);
|
||||
|
||||
if (encryptType == 'bytecode') {
|
||||
this.generateBytecodeFile(curPath);
|
||||
} else if (encryptType == 'confusion') {
|
||||
this.generateJSConfuseFile(curPath);
|
||||
} else {
|
||||
this.generateJSConfuseFile(curPath);
|
||||
this.generateBytecodeFile(curPath);
|
||||
}
|
||||
}
|
||||
|
||||
generateJSConfuseFile(file: string): void {
|
||||
let opt = Object.assign({
|
||||
compact: true,
|
||||
stringArray: true,
|
||||
stringArrayThreshold: 1,
|
||||
}, this.cOpt);
|
||||
|
||||
let code = fs.readFileSync(file, "utf8");
|
||||
let result = JavaScriptObfuscator.obfuscate(code, opt);
|
||||
fs.writeFileSync(file, result.getObfuscatedCode(), "utf8");
|
||||
}
|
||||
|
||||
generateBytecodeFile(curPath: string): void {
|
||||
if (path.extname(curPath) !== '.js') {
|
||||
return
|
||||
}
|
||||
let jscFile = curPath + 'c';
|
||||
let opt = Object.assign({
|
||||
filename: curPath,
|
||||
output: jscFile,
|
||||
electron: true
|
||||
}, this.bOpt);
|
||||
|
||||
bytenode.compileFile(opt);
|
||||
|
||||
fsPro.removeSync(curPath);
|
||||
}
|
||||
|
||||
rmBackup(file: string): void {
|
||||
if (fs.existsSync(file)) {
|
||||
fsPro.removeSync(file);
|
||||
}
|
||||
}
|
||||
|
||||
fileExist(filePath: string): boolean {
|
||||
try {
|
||||
return fs.statSync(filePath).isFile();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
mkdir(dirpath: string, dirname?: string): void {
|
||||
if (typeof dirname === 'undefined') {
|
||||
if (fs.existsSync(dirpath)) {
|
||||
return;
|
||||
}
|
||||
this.mkdir(dirpath, path.dirname(dirpath));
|
||||
} else {
|
||||
if (dirname !== path.dirname(dirpath)) {
|
||||
this.mkdir(dirpath);
|
||||
return;
|
||||
}
|
||||
if (fs.existsSync(dirname)) {
|
||||
fs.mkdirSync(dirpath);
|
||||
} else {
|
||||
this.mkdir(dirname, path.dirname(dirname));
|
||||
fs.mkdirSync(dirpath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
chmodPath(path: string, mode: string): void {
|
||||
let files = [];
|
||||
if (fs.existsSync(path)) {
|
||||
files = fs.readdirSync(path);
|
||||
files.forEach((file, index) => {
|
||||
const curPath = path + '/' + file;
|
||||
if (fs.statSync(curPath).isDirectory()) {
|
||||
this.chmodPath(curPath, mode); // 递归删除文件夹
|
||||
} else {
|
||||
fs.chmodSync(curPath, mode);
|
||||
}
|
||||
});
|
||||
fs.chmodSync(path, mode);
|
||||
}
|
||||
};
|
||||
|
||||
md5(file: string): string {
|
||||
const buffer = fs.readFileSync(file);
|
||||
const hash = crypto.createHash('md5');
|
||||
hash.update(buffer, 'utf8');
|
||||
const str = hash.digest('hex');
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
const run = (options: { out?: string; config?:
|
||||
177
ee-bin-ts/src/tools/iconGen.ts
Normal file
177
ee-bin-ts/src/tools/iconGen.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
interface IconOptions {
|
||||
report: boolean;
|
||||
ico: {
|
||||
name: string;
|
||||
sizes: number[];
|
||||
};
|
||||
favicon: {
|
||||
name: string;
|
||||
pngSizes: number[];
|
||||
};
|
||||
}
|
||||
|
||||
interface Params {
|
||||
input: string;
|
||||
output: string;
|
||||
size: string;
|
||||
clear: boolean;
|
||||
imagesDir: string;
|
||||
}
|
||||
|
||||
class IconGen {
|
||||
private params: Params;
|
||||
private input: string;
|
||||
private output: string;
|
||||
private imagesDir: string;
|
||||
private iconOptions: IconOptions;
|
||||
|
||||
constructor() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
_init(): void {
|
||||
const args = process.argv.slice(3);
|
||||
this.params = {
|
||||
input: "/public/images/logo.png",
|
||||
output: "/build/icons/",
|
||||
size: "16,32,64,256,512",
|
||||
clear: false,
|
||||
imagesDir: "/public/images/",
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg.match(/^-i/) || arg.match(/^-input/)) {
|
||||
this.params.input = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-o/) || arg.match(/^-output/)) {
|
||||
this.params.output = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-s/) || arg.match(/^-size/)) {
|
||||
this.params.size = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-c/) || arg.match(/^-clear/)) {
|
||||
this.params.clear = true;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-img/) || arg.match(/^-images/)) {
|
||||
this.params.imagesDir = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
this.input = path.join(process.cwd(), this.params.input);
|
||||
this.output = path.join(process.cwd(), this.params.output);
|
||||
this.imagesDir = path.join(process.cwd(), this.params.imagesDir);
|
||||
|
||||
const sizeList = this.params.size.split(",").map((item) => parseInt(item));
|
||||
this.iconOptions = {
|
||||
report: false,
|
||||
ico: {
|
||||
name: "icon",
|
||||
sizes: [256],
|
||||
},
|
||||
favicon: {
|
||||
name: "logo-",
|
||||
pngSizes: sizeList,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
generateIcons(): void {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 开始处理生成logo图片");
|
||||
if (!fs.existsSync(this.input)) {
|
||||
console.error("[ee-bin] [icon-gen] input: ", this.input);
|
||||
throw new Error("输入的图片不存在或路径错误");
|
||||
}
|
||||
if (!fs.existsSync(this.output)) {
|
||||
fs.mkdirSync(this.output, { recursive: true });
|
||||
} else {
|
||||
if (this.params.clear) this.deleteGenFile(this.output);
|
||||
}
|
||||
if (!fs.existsSync(this.imagesDir)) {
|
||||
fs.mkdirSync(this.imagesDir, { recursive: true });
|
||||
}
|
||||
const icongen = require("icon-gen");
|
||||
icongen(this.input, this.output, this.iconOptions)
|
||||
.then((results) => {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 已生成下方图片资源");
|
||||
console.log(results);
|
||||
this._renameForEE(results);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw new Error("[ee-bin] [icon-gen] iconGen 生成失败!");
|
||||
});
|
||||
}
|
||||
|
||||
deleteGenFile(dirPath: string): void {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
const files = fs.readdirSync(dirPath);
|
||||
files.forEach((file) => {
|
||||
const curPath = path.join(dirPath, file);
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
this.deleteGenFile(curPath);
|
||||
} else {
|
||||
if ([".ico", ".png"].includes(path.extname(curPath))) {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_renameForEE(filesPath: string[]): void {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 开始重新命名logo图片资源");
|
||||
try {
|
||||
for (let i = 0; i < filesPath.length; i++) {
|
||||
const filePath = filesPath[i];
|
||||
const extname = path.extname(filePath);
|
||||
if ([".png"].includes(extname)) {
|
||||
const filename = path.basename(filePath, extname);
|
||||
const basename = filename.split("-")[1];
|
||||
const dirname = path.dirname(filePath);
|
||||
if ("16" === basename) {
|
||||
const newName = "tray" + extname;
|
||||
fs.copyFileSync(filePath, path.join(this.imagesDir, newName));
|
||||
console.log(`${filename}${extname} --> ${this.params.imagesDir}/${newName} 复制成功!`);
|
||||
fs.unlinkSync(filePath);
|
||||
continue;
|
||||
}
|
||||
if ("32" === basename) {
|
||||
const newName = filename + extname;
|
||||
fs.copyFileSync(filePath, path.join(this.imagesDir, newName));
|
||||
console.log(`${filename}${extname} --> ${this.params.imagesDir}/${newName} 复制成功!`);
|
||||
}
|
||||
const newName = basename + "x" + basename + extname;
|
||||
const newPath = path.join(dirname, newName);
|
||||
fs.renameSync(filePath, newPath);
|
||||
console.log(`${filename}${extname} --> ${newName} 重命名成功!`);
|
||||
}
|
||||
}
|
||||
console.log("[ee-bin] [icon-gen] iconGen 资源处理完成!");
|
||||
} catch (e) {
|
||||
console.error("[ee-bin] [icon-gen] ERROR: ", e);
|
||||
throw new Error("重命名logo图片资源失败!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const run = (): void => {
|
||||
const i = new IconGen();
|
||||
i.generateIcons();
|
||||
}
|
||||
|
||||
export const IconGenModule = {
|
||||
run,
|
||||
};
|
||||
163
ee-bin-ts/src/tools/incrUpdater.ts
Normal file
163
ee-bin-ts/src/tools/incrUpdater.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import crypto from 'crypto';
|
||||
import chalk from 'chalk';
|
||||
import * as Utils from '../lib/utils';
|
||||
import admZip from 'adm-zip';
|
||||
|
||||
type Config = {
|
||||
asarFile?: string | string[];
|
||||
output: {
|
||||
zip: string;
|
||||
file: string;
|
||||
directory: string;
|
||||
};
|
||||
extraResources?: string[];
|
||||
cleanCache?: boolean;
|
||||
};
|
||||
|
||||
type PlatformConfig = {
|
||||
[key: string]: any; // 使用索引签名作为示例,根据实际结构定义类型
|
||||
};
|
||||
|
||||
class Updater {
|
||||
/**
|
||||
* 执行
|
||||
*/
|
||||
static run(options: { config: string; asarFile?: string; platform: string }) {
|
||||
console.log('[ee-bin] [updater] Start');
|
||||
const { config, asarFile, platform } = options;
|
||||
const binCfg = Utils.loadConfig(config);
|
||||
const cfg: PlatformConfig = binCfg.updater;
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${cfg} config does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.generateFile(cfg, asarFile, platform);
|
||||
|
||||
console.log('[ee-bin] [updater] End');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成增量升级文件
|
||||
*/
|
||||
static generateFile(config: PlatformConfig, asarFile: string | undefined, platform: string) {
|
||||
const cfg = config[platform];
|
||||
let latestVersionInfo: any = {}; // 根据实际结构定义类型
|
||||
const homeDir = process.cwd();
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.green(`${platform} config:`), cfg);
|
||||
|
||||
let asarFilePath = "";
|
||||
if (asarFile) {
|
||||
asarFilePath = path.normalize(path.join(homeDir, asarFile));
|
||||
} else if (Array.isArray(cfg.asarFile)) {
|
||||
for (const filep of cfg.asarFile) {
|
||||
asarFilePath = path.normalize(path.join(homeDir, filep));
|
||||
if (fs.existsSync(asarFilePath)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asarFilePath = path.normalize(path.join(homeDir, cfg.asarFile as string));
|
||||
}
|
||||
|
||||
if (!fs.existsSync(asarFilePath)) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${asarFilePath} does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJson = Utils.getPackage();
|
||||
const version = packageJson.version;
|
||||
let platformForFilename = platform;
|
||||
if (platform.indexOf("_") !== -1) {
|
||||
const platformArr = platform.split("_");
|
||||
platformForFilename = platformArr.join("-");
|
||||
}
|
||||
|
||||
// 生成 zip
|
||||
let zipName = "";
|
||||
zipName = path.basename(cfg.output.zip, '.zip') + `-${platformForFilename}-${version}.zip`;
|
||||
const asarZipPath = path.join(homeDir, cfg.output.directory, zipName);
|
||||
if (fs.existsSync(asarZipPath)) {
|
||||
Utils.rm(asarZipPath);
|
||||
}
|
||||
const zip = new admZip();
|
||||
zip.addLocalFile(asarFilePath);
|
||||
if (cfg.extraResources && cfg.extraResources.length > 0) {
|
||||
for (const extraRes of cfg.extraResources) {
|
||||
const extraResPath = path.normalize(path.join(homeDir, extraRes));
|
||||
if (fs.existsSync(extraResPath)) {
|
||||
zip.addLocalFile(extraResPath, "extraResources");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zip.writeZip(asarZipPath, (err) => {
|
||||
if (err) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] create zip ') + chalk.red(`Error: ${err}`));
|
||||
}
|
||||
});
|
||||
|
||||
// 生成 latest.json
|
||||
const sha1 = this.generateSha1(asarFilePath);
|
||||
const date = this._getFormattedDate();
|
||||
const fileStat = fs.statSync(asarFilePath);
|
||||
const item = {
|
||||
version: version,
|
||||
file: zipName,
|
||||
size: fileStat.size,
|
||||
sha1: sha1,
|
||||
releaseDate: date,
|
||||
};
|
||||
const jsonName = path.basename(cfg.output.file, '.json') + `-${platformForFilename}.json`;
|
||||
latestVersionInfo = item;
|
||||
const updaterJsonFilePath = path.join(homeDir, cfg.output.directory, jsonName);
|
||||
Utils.writeJsonSync(updaterJsonFilePath, latestVersionInfo);
|
||||
|
||||
// 删除缓存文件,防止生成的 zip 是旧版本
|
||||
if (cfg.cleanCache) {
|
||||
Utils.rm(path.join(homeDir, cfg.output.directory, 'mac'));
|
||||
Utils.rm(path.join(homeDir, cfg.output.directory, 'mac-arm64'));
|
||||
Utils.rm(path.join(homeDir, cfg.output.directory, 'win-unpacked'));
|
||||
Utils.rm(path.join(homeDir, cfg.output.directory, 'win-ia32-unpacked'));
|
||||
Utils.rm(path.join(homeDir, cfg.output.directory, 'linux-unpacked'));
|
||||
}
|
||||
}
|
||||
|
||||
static generateSha1(filepath: string = ""): string {
|
||||
let sha1 = '';
|
||||
if (filepath.length === 0) {
|
||||
return sha1;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filepath)) {
|
||||
return sha1;
|
||||
}
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + `generate sha1 for filepath:${filepath}`);
|
||||
try {
|
||||
const buffer = fs.readFileSync(filepath);
|
||||
const fsHash = crypto.createHash('sha1');
|
||||
fsHash.update(buffer);
|
||||
sha1 = fsHash.digest('hex');
|
||||
return sha1;
|
||||
} catch (error) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: generate sha1 error!`));
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${error}`));
|
||||
}
|
||||
return sha1;
|
||||
}
|
||||
|
||||
static _getFormattedDate(): string {
|
||||
const date = new Date();
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
export = Updater;
|
||||
92
ee-bin-ts/src/tools/move.ts
Normal file
92
ee-bin-ts/src/tools/move.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import fsPro from 'fs-extra';
|
||||
import chalk from 'chalk';
|
||||
import * as Utils from '../lib/utils';
|
||||
|
||||
interface MoveConfig {
|
||||
dist: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
interface BinConfig {
|
||||
move: Record<string, MoveConfig>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动资源
|
||||
*/
|
||||
const moveResources = {
|
||||
/**
|
||||
* 执行
|
||||
*/
|
||||
run(options: { config: string; flag: string } = {}) {
|
||||
console.log('[ee-bin] [move] Start moving resources');
|
||||
const homeDir = process.cwd();
|
||||
const { config, flag } = options;
|
||||
const binCfg: BinConfig = Utils.loadConfig(config);
|
||||
|
||||
let flags: string[];
|
||||
const flagString = flag.trim();
|
||||
if (flagString.indexOf(',') !== -1) {
|
||||
flags = flagString.split(',');
|
||||
} else {
|
||||
flags = [flagString];
|
||||
}
|
||||
|
||||
for (let i = 0; i < flags.length; i++) {
|
||||
let f = flags[i];
|
||||
let cfg: MoveConfig | undefined = binCfg.move[f];
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.red(`Error: ${f} config does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.green(`Move flag: ${f}`));
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.green('config:'), cfg);
|
||||
|
||||
const distResource = path.join(homeDir, cfg.dist);
|
||||
if (!fs.existsSync(distResource)) {
|
||||
const errorTips = chalk.bgRed('Error') + ` ${cfg.dist} resource does not exist !`;
|
||||
console.error(errorTips);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear the historical resource and copy it to the ee resource directory
|
||||
const targetResource = path.join(homeDir, cfg.target);
|
||||
if (fs.statSync(distResource).isDirectory() && !fs.existsSync(targetResource)) {
|
||||
fs.mkdirSync(targetResource, { recursive: true, mode: 0o777 });
|
||||
} else {
|
||||
this._rm(targetResource);
|
||||
console.log('[ee-bin] [move] Clear history resources:', targetResource);
|
||||
}
|
||||
|
||||
fsPro.copySync(distResource, targetResource);
|
||||
|
||||
// [todo] go project, special treatment of package.json, reserved only necessary
|
||||
console.log(`[ee-bin] [move] Copy ${distResource} to ${targetResource}`);
|
||||
}
|
||||
|
||||
console.log('[ee-bin] [move] End');
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a file or folder
|
||||
*/
|
||||
_rm(name: string) {
|
||||
// check
|
||||
if (!fs.existsSync(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeVersion = (process.versions && process.versions.node) || null;
|
||||
if (nodeVersion && Utils.compareVersion(nodeVersion, '14.14.0') === 1) {
|
||||
fs.rmSync(name, { recursive: true });
|
||||
} else {
|
||||
fs.rmdirSync(name, { recursive: true });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export = moveResources;
|
||||
175
ee-bin-ts/src/tools/serve.ts
Normal file
175
ee-bin-ts/src/tools/serve.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
import path from 'path';
|
||||
import * as Utils from '../lib/utils';
|
||||
import is from 'is-type-of';
|
||||
import chalk from 'chalk';
|
||||
import crossSpawn from 'cross-spawn';
|
||||
|
||||
interface CommandConfig {
|
||||
cmd: string;
|
||||
args?: string | string[];
|
||||
directory: string;
|
||||
stdio?: string | string[];
|
||||
sync?: boolean;
|
||||
}
|
||||
|
||||
interface BinCmdConfig {
|
||||
[key: string]: CommandConfig;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
config?: string;
|
||||
serve?: string;
|
||||
cmds?: string;
|
||||
command?: string;
|
||||
}
|
||||
|
||||
const execProcess: Record<string, any> = {}; // 使用索引签名作为示例,根据实际结构定义类型
|
||||
|
||||
const moduleExports = {
|
||||
/**
|
||||
* 启动前端、主进程服务
|
||||
*/
|
||||
dev(options: Options = {}) {
|
||||
const { config, serve } = options;
|
||||
const binCmd = 'dev';
|
||||
const binCfg = Utils.loadConfig(config);
|
||||
const binCmdConfig: BinCmdConfig = binCfg[binCmd];
|
||||
|
||||
let command = serve;
|
||||
if (!command) {
|
||||
command = Object.keys(binCmdConfig).join();
|
||||
}
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command,
|
||||
};
|
||||
moduleExports.multiExec(opt);
|
||||
},
|
||||
|
||||
/**
|
||||
* 启动主进程服务
|
||||
*/
|
||||
start(options: Options = {}) {
|
||||
const { config } = options;
|
||||
const binCmd = 'start';
|
||||
const binCfg = Utils.loadConfig(config);
|
||||
const binCmdConfig: BinCmdConfig = {
|
||||
start: binCfg[binCmd],
|
||||
};
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: binCmd,
|
||||
};
|
||||
moduleExports.multiExec(opt);
|
||||
},
|
||||
|
||||
sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
},
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*/
|
||||
build(options: Options = {}) {
|
||||
const { config, cmds } = options;
|
||||
const binCmd = 'build';
|
||||
const binCfg = Utils.loadConfig(config);
|
||||
const binCmdConfig: BinCmdConfig = binCfg[binCmd];
|
||||
|
||||
if (!cmds || cmds === "") {
|
||||
let tip = chalk.bgYellow('Warning') + ' Please modify the ' + chalk.blue('build') + ' config, See: ';
|
||||
tip += chalk.underline('https://www.kaka996.com/pages/c492f8/');
|
||||
console.log(tip);
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: cmds,
|
||||
};
|
||||
moduleExports.multiExec(opt);
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行自定义命令
|
||||
*/
|
||||
exec(options: Options = {}) {
|
||||
let { config, command, cmds } = options;
|
||||
const binCmd = 'exec';
|
||||
const binCfg = Utils.loadConfig(config);
|
||||
const binCmdConfig: BinCmdConfig = binCfg[binCmd];
|
||||
|
||||
if (typeof command === "string") {
|
||||
cmds = command;
|
||||
}
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: cmds,
|
||||
};
|
||||
moduleExports.multiExec(opt);
|
||||
},
|
||||
|
||||
/**
|
||||
* 支持多个命令
|
||||
*/
|
||||
multiExec(opt: { binCmd: string; binCmdConfig: BinCmdConfig; command: string }) {
|
||||
const { binCmd, binCmdConfig, command } = opt;
|
||||
|
||||
let cmds: string[];
|
||||
const cmdString = command.trim();
|
||||
if (cmdString.indexOf(',') !== -1) {
|
||||
cmds = cmdString.split(',');
|
||||
} else {
|
||||
cmds = [cmdString];
|
||||
}
|
||||
|
||||
for (let i = 0; i < cmds.length; i++) {
|
||||
let cmd = cmds[i];
|
||||
let cfg: CommandConfig | undefined = binCmdConfig[cmd];
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.red(`Error: [${binCmd} ${cmd}] config does not exist`));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cmd === 'frontend' && cfg.protocol === 'file://') {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + "Run " + chalk.green(`[${binCmd} ${cmd}]`) + " command");
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.green('config:'), JSON.stringify(cfg));
|
||||
|
||||
let execDir = path.join(process.cwd(), cfg.directory);
|
||||
let execArgs = is.string(cfg.args) ? [cfg.args] : cfg.args;
|
||||
let stdio = cfg.stdio ? cfg.stdio : 'inherit';
|
||||
|
||||
const handler = cfg.sync ? crossSpawn.sync : crossSpawn;
|
||||
|
||||
execProcess[cmd] = handler(
|
||||
cfg.cmd,
|
||||
execArgs,
|
||||
{ stdio: stdio, cwd: execDir, maxBuffer: 1024 * 1024 * 1024 },
|
||||
);
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + 'The ' + chalk.green(`[${binCmd} ${cmd}]`) + ` command is ${cfg.sync ? 'run completed' : 'running'}`);
|
||||
|
||||
if (!cfg.sync) {
|
||||
execProcess[cmd].on('exit', () => {
|
||||
if (cmd === 'electron') {
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.green('Press "CTRL+C" to exit'));
|
||||
return;
|
||||
}
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + 'The ' + chalk.green(`[${binCmd} ${cmd}]`) + ' command is executed and exits');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export = moduleExports;
|
||||
12
ee-bin-ts/tsconfig.js.json
Normal file
12
ee-bin-ts/tsconfig.js.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
/* All JavaScript targets from tsconfig.json include. */
|
||||
// "*.js",
|
||||
// "*.mjs",
|
||||
// "lib/**/*.js",
|
||||
// "tools/**/*.js",
|
||||
"src/**/*.js",
|
||||
"src/**/*.mjs"
|
||||
],
|
||||
}
|
||||
41
ee-bin-ts/tsconfig.json
Normal file
41
ee-bin-ts/tsconfig.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es2021"],
|
||||
"module": "node16",
|
||||
"target": "es2021",
|
||||
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
|
||||
/* Strict by default, but dial it down to reduce churn in our JavaScript code. */
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"strictNullChecks": false,
|
||||
"useUnknownInCatchVariables": false,
|
||||
"noImplicitThis": false,
|
||||
//"noUnusedLocals": true, /* 是否允许未使用的局部变量 */
|
||||
//"noUnusedParameters": true, /* 是否允许未使用的函数参数 */
|
||||
// "noFallthroughCasesInSwitch": true, /* 没有break语句的 switch 分支报错 */
|
||||
|
||||
|
||||
"types": ["node"],
|
||||
// "noEmit": true /* just type checking and not emitting transpiled files */,
|
||||
"skipLibCheck": false /* we want to check our hand crafted definitions */,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true /* common TypeScript config */,
|
||||
"resolveJsonModule": true /* needed for globals in node_modules?! */,
|
||||
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
// "include": [
|
||||
// "src/**/*.js",
|
||||
// "src/**/*.mjs",
|
||||
|
||||
// "src/**/*.ts",
|
||||
// "src/**/*.mts"
|
||||
// ],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
14
ee-bin-ts/tsconfig.ts.json
Normal file
14
ee-bin-ts/tsconfig.ts.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Full strict is fine for the TypeScript files, so turn back on the checks we turned off for mixed-use. */
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"useUnknownInCatchVariables": true,
|
||||
},
|
||||
"include": [
|
||||
/* All TypeScript targets from tsconfig.json include. */
|
||||
"src/**/*.ts",
|
||||
"src/**/*.mts",
|
||||
],
|
||||
}
|
||||
2
ee-bin/.gitignore
vendored
Normal file
2
ee-bin/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
181
ee-bin/config/bin_default.js
Normal file
181
ee-bin/config/bin_default.js
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* ee-bin 配置
|
||||
* 仅适用于开发环境
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* development serve ("frontend" "electron" )
|
||||
* ee-bin dev
|
||||
*/
|
||||
dev: {
|
||||
frontend: {
|
||||
directory: './frontend',
|
||||
cmd: 'npm',
|
||||
args: ['run', 'dev'],
|
||||
protocol: 'http://',
|
||||
hostname: 'localhost',
|
||||
port: 8080,
|
||||
indexPath: 'index.html',
|
||||
force: false,
|
||||
sync: false,
|
||||
},
|
||||
electron: {
|
||||
directory: './',
|
||||
cmd: 'electron',
|
||||
args: ['.', '--env=local'],
|
||||
loadingPage: '/public/html/loading.html',
|
||||
watch: false,
|
||||
sync: false,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 构建
|
||||
* ee-bin build
|
||||
*/
|
||||
build: {
|
||||
frontend: {
|
||||
directory: './frontend',
|
||||
cmd: 'npm',
|
||||
args: ['run', 'build'],
|
||||
},
|
||||
electron: {
|
||||
type: 'javascript',
|
||||
bundler: 'esbuild',
|
||||
bundleType: 'bundle', // copy | bundle
|
||||
javascript: {
|
||||
entryPoints: ['./electron/**/*.js'],
|
||||
platform: 'node',
|
||||
bundle: false,
|
||||
minify: false,
|
||||
outdir: 'public/electron',
|
||||
packages: 'external',
|
||||
sourcemap:false,
|
||||
sourcesContent: false
|
||||
},
|
||||
typescript: {
|
||||
entryPoints: ['./electron/**/*.ts'],
|
||||
tsconfig: './tsconfig.json',
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
bundle: false,
|
||||
minify: false,
|
||||
outdir: 'public/electron',
|
||||
packages: 'external',
|
||||
sourcemap:false,
|
||||
sourcesContent: false
|
||||
}
|
||||
},
|
||||
win32: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder.json', '-w=nsis', '--ia32'],
|
||||
},
|
||||
win64: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder.json', '-w=nsis', '--x64'],
|
||||
},
|
||||
win_e: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder.json', '-w=portable', '--x64'],
|
||||
},
|
||||
win_7z: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder.json', '-w=7z', '--x64'],
|
||||
},
|
||||
mac: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder-mac.json', '-m'],
|
||||
},
|
||||
mac_arm64: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder-mac-arm64.json', '-m', '--arm64'],
|
||||
},
|
||||
linux: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder-linux.json', '-l=deb', '--x64'],
|
||||
},
|
||||
linux_arm64: {
|
||||
cmd: 'electron-builder',
|
||||
directory: './',
|
||||
args: ['--config=./cmd/builder-linux.json', '-l=deb', '--arm64'],
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 移动资源
|
||||
* ee-bin move
|
||||
*/
|
||||
move: {
|
||||
frontend_dist: {
|
||||
src: './frontend/dist',
|
||||
dest: './public/dist'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 预发布模式(prod)
|
||||
* ee-bin start
|
||||
*/
|
||||
start: {
|
||||
directory: './',
|
||||
cmd: 'electron',
|
||||
args: ['.', '--env=prod']
|
||||
},
|
||||
|
||||
/**
|
||||
* 加密
|
||||
*/
|
||||
encrypt: {
|
||||
frontend: {
|
||||
type: 'none',
|
||||
files: [
|
||||
'./public/dist/**/*.(js|json)',
|
||||
],
|
||||
fileExt: ['.js'],
|
||||
cleanFiles: ['./public/dist'],
|
||||
specificFiles: [],
|
||||
encryptDir: './',
|
||||
confusionOptions: {
|
||||
compact: true,
|
||||
stringArray: true,
|
||||
stringArrayEncoding: ['none'],
|
||||
deadCodeInjection: false,
|
||||
stringArrayCallsTransform: true,
|
||||
numbersToExpressions: true,
|
||||
target: 'browser',
|
||||
}
|
||||
},
|
||||
electron: {
|
||||
type: 'none',
|
||||
files: [
|
||||
'./public/electron/**/*.(js|json)',
|
||||
],
|
||||
fileExt: ['.js'],
|
||||
cleanFiles: ['./public/electron'],
|
||||
specificFiles: ['./public/electron/preload/bridge.js'],
|
||||
encryptDir: './',
|
||||
confusionOptions: {
|
||||
compact: true,
|
||||
stringArray: true,
|
||||
stringArrayEncoding: ['rc4'],
|
||||
deadCodeInjection: false,
|
||||
stringArrayCallsTransform: true,
|
||||
numbersToExpressions: true,
|
||||
target: 'node',
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 执行自定义命令
|
||||
* ee-bin exec
|
||||
*/
|
||||
exec: {},
|
||||
};
|
||||
118
ee-bin/index.js
Executable file
118
ee-bin/index.js
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { program } = require('commander');
|
||||
const { move } = require('./tools/move');
|
||||
const { encrypt, cleanEncrypt } = require('./tools/encrypt');
|
||||
const { serveProcess } = require('./tools/serve');
|
||||
const { incrUpdater } = require('./tools/incrUpdater');
|
||||
|
||||
/**
|
||||
* dev
|
||||
*/
|
||||
program
|
||||
.command('dev')
|
||||
.description('create frontend-serve and electron-serve')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--serve <mode>', 'serve mode')
|
||||
.action(function() {
|
||||
serveProcess.dev(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* build
|
||||
*/
|
||||
program
|
||||
.command('build')
|
||||
.description('building multiple resources')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--cmds <flag>', 'custom commands')
|
||||
.option('--env <env>', 'environment')
|
||||
.action(function() {
|
||||
serveProcess.build(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* start
|
||||
*/
|
||||
program
|
||||
.command('start')
|
||||
.description('preview effect')
|
||||
.option('--config <folder>', 'config file')
|
||||
.action(function() {
|
||||
serveProcess.start(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* exec
|
||||
*/
|
||||
program
|
||||
.command('exec')
|
||||
.description('create frontend-serve and electron-serve')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--cmds <flag>', 'custom commands')
|
||||
.action(function() {
|
||||
serveProcess.exec(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* move - Moves resources
|
||||
*/
|
||||
program
|
||||
.command('move')
|
||||
.description('Move multip resources')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--flag <flag>', 'Custom flag')
|
||||
.action(function() {
|
||||
move(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* encrypt - Code encryption
|
||||
*/
|
||||
program
|
||||
.command('encrypt')
|
||||
.description('Code encryption')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--out <folder>', 'output directory')
|
||||
.action(function() {
|
||||
encrypt(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* clean - Clear the encrypted code
|
||||
*/
|
||||
program
|
||||
.command('clean')
|
||||
.description('Clear the encrypted code')
|
||||
.option('-d, --dir <folder>', 'clean directory')
|
||||
.action(function() {
|
||||
cleanEncrypt(this.opts());
|
||||
});
|
||||
|
||||
/**
|
||||
* icon
|
||||
*/
|
||||
program
|
||||
.command('icon')
|
||||
.description('Generate logo')
|
||||
.option('-i, --input <file>', 'image file default /public/images/logo.png')
|
||||
.option('-o, --output <folder>', 'output directory default /build/icons/')
|
||||
.action(function() {
|
||||
const iconGen = require('./tools/iconGen');
|
||||
iconGen.run();
|
||||
});
|
||||
|
||||
/**
|
||||
* updater
|
||||
*/
|
||||
program
|
||||
.command('updater')
|
||||
.description('updater commands')
|
||||
.option('--config <folder>', 'config file')
|
||||
.option('--asar-file <file>', 'asar file path')
|
||||
.option('--platform <flag>', 'platform')
|
||||
.action(function() {
|
||||
incrUpdater.run(this.opts());
|
||||
});
|
||||
|
||||
program.parse();
|
||||
78
ee-bin/lib/extend.js
Normal file
78
ee-bin/lib/extend.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const hasOwn = Object.prototype.hasOwnProperty;
|
||||
const toStr = Object.prototype.toString;
|
||||
|
||||
function isPlainObject(obj) {
|
||||
if (!obj || toStr.call(obj) !== '[object Object]') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasOwnConstructor = hasOwn.call(obj, 'constructor');
|
||||
var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
|
||||
// Not own constructor property must be Object
|
||||
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Own properties are enumerated firstly, so to speed up,
|
||||
// if last one is own, then all properties are own.
|
||||
var key;
|
||||
for (key in obj) { /**/ }
|
||||
|
||||
return typeof key === 'undefined' || hasOwn.call(obj, key);
|
||||
};
|
||||
|
||||
function extend() {
|
||||
var options, name, src, copy, clone;
|
||||
var target = arguments[0];
|
||||
var i = 1;
|
||||
var length = arguments.length;
|
||||
var deep = false;
|
||||
|
||||
// Handle a deep copy situation
|
||||
if (typeof target === 'boolean') {
|
||||
deep = target;
|
||||
target = arguments[1] || {};
|
||||
// skip the boolean and the target
|
||||
i = 2;
|
||||
} else if ((typeof target !== 'object' && typeof target !== 'function') || target == null) {
|
||||
target = {};
|
||||
}
|
||||
|
||||
for (; i < length; ++i) {
|
||||
options = arguments[i];
|
||||
// Only deal with non-null/undefined values
|
||||
if (options == null) continue;
|
||||
|
||||
// Extend the base object
|
||||
for (name in options) {
|
||||
if (name === '__proto__') continue;
|
||||
|
||||
src = target[name];
|
||||
copy = options[name];
|
||||
|
||||
// Prevent never-ending loop
|
||||
if (target === copy) continue;
|
||||
|
||||
// Recurse if we're merging plain objects
|
||||
if (deep && copy && isPlainObject(copy)) {
|
||||
clone = src && isPlainObject(src) ? src : {};
|
||||
// Never move original objects, clone them
|
||||
target[name] = extend(deep, clone, copy);
|
||||
|
||||
// Don't bring in undefined values
|
||||
} else if (typeof copy !== 'undefined') {
|
||||
target[name] = copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the modified object
|
||||
return target;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
extend,
|
||||
isPlainObject,
|
||||
};
|
||||
263
ee-bin/lib/pargv.js
Normal file
263
ee-bin/lib/pargv.js
Normal file
@@ -0,0 +1,263 @@
|
||||
'use strict';
|
||||
|
||||
function hasKey(obj, keys) {
|
||||
var o = obj;
|
||||
keys.slice(0, -1).forEach(function (key) {
|
||||
o = o[key] || {};
|
||||
});
|
||||
|
||||
var key = keys[keys.length - 1];
|
||||
return key in o;
|
||||
}
|
||||
|
||||
function isNumber(x) {
|
||||
if (typeof x === 'number') { return true; }
|
||||
if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
|
||||
return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
|
||||
}
|
||||
|
||||
function isConstructorOrProto(obj, key) {
|
||||
return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
|
||||
}
|
||||
|
||||
module.exports = function (args, opts) {
|
||||
if (!opts) { opts = {}; }
|
||||
|
||||
var flags = {
|
||||
bools: {},
|
||||
strings: {},
|
||||
unknownFn: null,
|
||||
};
|
||||
|
||||
if (typeof opts.unknown === 'function') {
|
||||
flags.unknownFn = opts.unknown;
|
||||
}
|
||||
|
||||
if (typeof opts.boolean === 'boolean' && opts.boolean) {
|
||||
flags.allBools = true;
|
||||
} else {
|
||||
[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
|
||||
flags.bools[key] = true;
|
||||
});
|
||||
}
|
||||
|
||||
var aliases = {};
|
||||
|
||||
function aliasIsBoolean(key) {
|
||||
return aliases[key].some(function (x) {
|
||||
return flags.bools[x];
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(opts.alias || {}).forEach(function (key) {
|
||||
aliases[key] = [].concat(opts.alias[key]);
|
||||
aliases[key].forEach(function (x) {
|
||||
aliases[x] = [key].concat(aliases[key].filter(function (y) {
|
||||
return x !== y;
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
[].concat(opts.string).filter(Boolean).forEach(function (key) {
|
||||
flags.strings[key] = true;
|
||||
if (aliases[key]) {
|
||||
[].concat(aliases[key]).forEach(function (k) {
|
||||
flags.strings[k] = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var defaults = opts.default || {};
|
||||
|
||||
var argv = { _: [] };
|
||||
|
||||
function argDefined(key, arg) {
|
||||
return (flags.allBools && (/^--[^=]+$/).test(arg))
|
||||
|| flags.strings[key]
|
||||
|| flags.bools[key]
|
||||
|| aliases[key];
|
||||
}
|
||||
|
||||
function setKey(obj, keys, value) {
|
||||
var o = obj;
|
||||
for (var i = 0; i < keys.length - 1; i++) {
|
||||
var key = keys[i];
|
||||
if (isConstructorOrProto(o, key)) { return; }
|
||||
if (o[key] === undefined) { o[key] = {}; }
|
||||
if (
|
||||
o[key] === Object.prototype
|
||||
|| o[key] === Number.prototype
|
||||
|| o[key] === String.prototype
|
||||
) {
|
||||
o[key] = {};
|
||||
}
|
||||
if (o[key] === Array.prototype) { o[key] = []; }
|
||||
o = o[key];
|
||||
}
|
||||
|
||||
var lastKey = keys[keys.length - 1];
|
||||
if (isConstructorOrProto(o, lastKey)) { return; }
|
||||
if (
|
||||
o === Object.prototype
|
||||
|| o === Number.prototype
|
||||
|| o === String.prototype
|
||||
) {
|
||||
o = {};
|
||||
}
|
||||
if (o === Array.prototype) { o = []; }
|
||||
if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
|
||||
o[lastKey] = value;
|
||||
} else if (Array.isArray(o[lastKey])) {
|
||||
o[lastKey].push(value);
|
||||
} else {
|
||||
o[lastKey] = [o[lastKey], value];
|
||||
}
|
||||
}
|
||||
|
||||
function setArg(key, val, arg) {
|
||||
if (arg && flags.unknownFn && !argDefined(key, arg)) {
|
||||
if (flags.unknownFn(arg) === false) { return; }
|
||||
}
|
||||
|
||||
var value = !flags.strings[key] && isNumber(val)
|
||||
? Number(val)
|
||||
: val;
|
||||
setKey(argv, key.split('.'), value);
|
||||
|
||||
(aliases[key] || []).forEach(function (x) {
|
||||
setKey(argv, x.split('.'), value);
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(flags.bools).forEach(function (key) {
|
||||
setArg(key, defaults[key] === undefined ? false : defaults[key]);
|
||||
});
|
||||
|
||||
var notFlags = [];
|
||||
|
||||
if (args.indexOf('--') !== -1) {
|
||||
notFlags = args.slice(args.indexOf('--') + 1);
|
||||
args = args.slice(0, args.indexOf('--'));
|
||||
}
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
var arg = args[i];
|
||||
var key;
|
||||
var next;
|
||||
|
||||
if ((/^--.+=/).test(arg)) {
|
||||
// Using [\s\S] instead of . because js doesn't support the
|
||||
// 'dotall' regex modifier. See:
|
||||
// http://stackoverflow.com/a/1068308/13216
|
||||
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
|
||||
key = m[1];
|
||||
var value = m[2];
|
||||
if (flags.bools[key]) {
|
||||
value = value !== 'false';
|
||||
}
|
||||
setArg(key, value, arg);
|
||||
} else if ((/^--no-.+/).test(arg)) {
|
||||
key = arg.match(/^--no-(.+)/)[1];
|
||||
setArg(key, false, arg);
|
||||
} else if ((/^--.+/).test(arg)) {
|
||||
key = arg.match(/^--(.+)/)[1];
|
||||
next = args[i + 1];
|
||||
if (
|
||||
next !== undefined
|
||||
&& !(/^(-|--)[^-]/).test(next)
|
||||
&& !flags.bools[key]
|
||||
&& !flags.allBools
|
||||
&& (aliases[key] ? !aliasIsBoolean(key) : true)
|
||||
) {
|
||||
setArg(key, next, arg);
|
||||
i += 1;
|
||||
} else if ((/^(true|false)$/).test(next)) {
|
||||
setArg(key, next === 'true', arg);
|
||||
i += 1;
|
||||
} else {
|
||||
setArg(key, flags.strings[key] ? '' : true, arg);
|
||||
}
|
||||
} else if ((/^-[^-]+/).test(arg)) {
|
||||
var letters = arg.slice(1, -1).split('');
|
||||
|
||||
var broken = false;
|
||||
for (var j = 0; j < letters.length; j++) {
|
||||
next = arg.slice(j + 2);
|
||||
|
||||
if (next === '-') {
|
||||
setArg(letters[j], next, arg);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
|
||||
setArg(letters[j], next.slice(1), arg);
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
(/[A-Za-z]/).test(letters[j])
|
||||
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
|
||||
) {
|
||||
setArg(letters[j], next, arg);
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
|
||||
setArg(letters[j], arg.slice(j + 2), arg);
|
||||
broken = true;
|
||||
break;
|
||||
} else {
|
||||
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
|
||||
}
|
||||
}
|
||||
|
||||
key = arg.slice(-1)[0];
|
||||
if (!broken && key !== '-') {
|
||||
if (
|
||||
args[i + 1]
|
||||
&& !(/^(-|--)[^-]/).test(args[i + 1])
|
||||
&& !flags.bools[key]
|
||||
&& (aliases[key] ? !aliasIsBoolean(key) : true)
|
||||
) {
|
||||
setArg(key, args[i + 1], arg);
|
||||
i += 1;
|
||||
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
|
||||
setArg(key, args[i + 1] === 'true', arg);
|
||||
i += 1;
|
||||
} else {
|
||||
setArg(key, flags.strings[key] ? '' : true, arg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
|
||||
argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
|
||||
}
|
||||
if (opts.stopEarly) {
|
||||
argv._.push.apply(argv._, args.slice(i + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(defaults).forEach(function (k) {
|
||||
if (!hasKey(argv, k.split('.'))) {
|
||||
setKey(argv, k.split('.'), defaults[k]);
|
||||
|
||||
(aliases[k] || []).forEach(function (x) {
|
||||
setKey(argv, x.split('.'), defaults[k]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (opts['--']) {
|
||||
argv['--'] = notFlags.slice();
|
||||
} else {
|
||||
notFlags.forEach(function (k) {
|
||||
argv._.push(k);
|
||||
});
|
||||
}
|
||||
|
||||
return argv;
|
||||
};
|
||||
209
ee-bin/lib/utils.js
Normal file
209
ee-bin/lib/utils.js
Normal file
@@ -0,0 +1,209 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-bin:lib:utils');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
const is = require('is-type-of');
|
||||
const { loadTsConfig } = require('config-file-ts');
|
||||
const JsonLib = require('json5');
|
||||
const mkdirp = require('mkdirp');
|
||||
const OS = require('os');
|
||||
const defaultConfig = require('../config/bin_default');
|
||||
const { extend } = require('./extend');
|
||||
|
||||
const _basePath = process.cwd();
|
||||
const userBin = './cmd/bin.js';
|
||||
|
||||
function loadConfig(binFile) {
|
||||
const binPath = binFile ? binFile : userBin;
|
||||
const userConfig = loadFile(binPath);
|
||||
const result = extend(true, defaultConfig, userConfig);
|
||||
debug('[loadConfig] bin:%j', result)
|
||||
|
||||
return result
|
||||
};
|
||||
|
||||
function loadFile(filepath) {
|
||||
const configFile = path.join(_basePath, filepath);
|
||||
if (!fs.existsSync(configFile)) {
|
||||
const errorTips = 'file ' + chalk.blue(`${configFile}`) + ' does not exist !';
|
||||
throw new Error(errorTips)
|
||||
}
|
||||
|
||||
let result;
|
||||
if (configFile.endsWith(".json5") || configFile.endsWith(".json")) {
|
||||
const data = fs.readFileSync(configFile, 'utf8');
|
||||
return JsonLib.parse(data);
|
||||
}
|
||||
if (configFile.endsWith(".js") || configFile.endsWith(".cjs")) {
|
||||
result = require(configFile);
|
||||
if (result.default != null) {
|
||||
result = result.default;
|
||||
}
|
||||
} else if (configFile.endsWith(".ts")) {
|
||||
result = loadTsConfig(configFile);
|
||||
}
|
||||
if (is.function(result) && !is.class(result)) {
|
||||
result = result();
|
||||
}
|
||||
return result || {}
|
||||
};
|
||||
|
||||
/**
|
||||
* get electron program
|
||||
*/
|
||||
function getElectronProgram() {
|
||||
let electronPath
|
||||
const electronModulePath = path.dirname(require.resolve('electron'))
|
||||
const pathFile = path.join(electronModulePath, 'path.txt')
|
||||
const executablePath = fs.readFileSync(pathFile, 'utf-8')
|
||||
if (executablePath) {
|
||||
electronPath = path.join(electronModulePath, 'dist', executablePath)
|
||||
} else {
|
||||
throw new Error('Check that electron is installed!')
|
||||
}
|
||||
return electronPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* 版本号比较
|
||||
*/
|
||||
function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i])
|
||||
const num2 = parseInt(v2[i])
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function isWindows() {
|
||||
return process.platform === 'win32'
|
||||
}
|
||||
|
||||
function isOSX() {
|
||||
return process.platform === 'darwin'
|
||||
}
|
||||
|
||||
function isMacOS() {
|
||||
return isOSX()
|
||||
}
|
||||
|
||||
function isLinux() {
|
||||
return process.platform === 'linux'
|
||||
}
|
||||
|
||||
function isx86() {
|
||||
return process.arch === 'ia32'
|
||||
}
|
||||
|
||||
function isx64() {
|
||||
return process.arch === 'x64'
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file or folder
|
||||
*/
|
||||
function rm(name) {
|
||||
// check
|
||||
if (!fs.existsSync(name)) {
|
||||
return
|
||||
}
|
||||
|
||||
const nodeVersion = (process.versions && process.versions.node) || null;
|
||||
if (nodeVersion && compareVersion(nodeVersion, '14.14.0') == 1) {
|
||||
fs.rmSync(name, {recursive: true});
|
||||
} else {
|
||||
fs.rmdirSync(name, {recursive: true});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目根目录package.json
|
||||
*/
|
||||
function getPackage () {
|
||||
const content = readJsonSync(path.join(_basePath, 'package.json'));
|
||||
return content;
|
||||
}
|
||||
|
||||
function readJsonSync (filepath) {
|
||||
if (!fs.existsSync(filepath)) {
|
||||
throw new Error(filepath + ' is not found');
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(filepath));
|
||||
}
|
||||
|
||||
function writeJsonSync (filepath, str, options) {
|
||||
options = options || {};
|
||||
if (!('space' in options)) {
|
||||
options.space = 2;
|
||||
}
|
||||
|
||||
mkdirp.sync(path.dirname(filepath));
|
||||
if (typeof str === 'object') {
|
||||
str = JSON.stringify(str, options.replacer, options.space) + '\n';
|
||||
}
|
||||
|
||||
fs.writeFileSync(filepath, str);
|
||||
};
|
||||
|
||||
function getPlatform(delimiter = "_", isDiffArch = false) {
|
||||
let os = "";
|
||||
if (isWindows()) {
|
||||
os = "windows";
|
||||
if (isDiffArch) {
|
||||
const arch = isx64() ? "64" : "32";
|
||||
os += delimiter + arch;
|
||||
}
|
||||
} else if (isMacOS()) {
|
||||
let isAppleSilicon = false;
|
||||
const cpus = OS.cpus();
|
||||
for (let cpu of cpus) {
|
||||
if (cpu.model.includes('Apple')) {
|
||||
isAppleSilicon = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const core = isAppleSilicon? "apple" : "intel";
|
||||
os = "macos" + delimiter + core;
|
||||
} else if (isLinux()) {
|
||||
os = "linux";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadConfig,
|
||||
getElectronProgram,
|
||||
compareVersion,
|
||||
isWindows,
|
||||
isOSX,
|
||||
isMacOS,
|
||||
isLinux,
|
||||
isx86,
|
||||
isx64,
|
||||
getPlatform,
|
||||
rm,
|
||||
getPackage,
|
||||
readJsonSync,
|
||||
writeJsonSync
|
||||
}
|
||||
31
ee-bin/package.json
Normal file
31
ee-bin/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "ee-bin",
|
||||
"version": "4.0.0-beta.1",
|
||||
"description": "ee bin",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"ee-bin": "index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.4.11",
|
||||
"bytenode": "^1.3.6",
|
||||
"chalk": "^4.1.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"commander": "^11.0.0",
|
||||
"config-file-ts": "^0.2.8-rc1",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"debug": "^4.4.0",
|
||||
"esbuild": "0.24.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"globby": "^10.0.0",
|
||||
"is-type-of": "^1.2.1",
|
||||
"javascript-obfuscator": "^4.0.2",
|
||||
"json5": "^2.2.3",
|
||||
"mkdirp": "^2.1.3"
|
||||
}
|
||||
}
|
||||
1
ee-bin/readme.md
Normal file
1
ee-bin/readme.md
Normal file
@@ -0,0 +1 @@
|
||||
## ee-bin
|
||||
179
ee-bin/tools/encrypt.js
Normal file
179
ee-bin/tools/encrypt.js
Normal file
@@ -0,0 +1,179 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fsPro = require('fs-extra');
|
||||
const is = require('is-type-of');
|
||||
const bytenode = require('bytenode');
|
||||
const JavaScriptObfuscator = require('javascript-obfuscator');
|
||||
const globby = require('globby');
|
||||
const chalk = require('chalk');
|
||||
const { loadConfig } = require('../lib/utils');
|
||||
const { extend } = require('../lib/extend');
|
||||
|
||||
const EncryptTypes = ['bytecode', 'confusion', 'strict'];
|
||||
|
||||
class Encrypt {
|
||||
constructor(options = {}) {
|
||||
// cli args
|
||||
const { config, out, target } = options;
|
||||
this.basePath = process.cwd();
|
||||
this.target = target;
|
||||
|
||||
const conf = loadConfig(config).encrypt;
|
||||
this.config = conf[target];
|
||||
const outputFolder = out || this.config.encryptDir;
|
||||
this.encryptDir = path.join(this.basePath, outputFolder);
|
||||
this.filesExt = this.config.fileExt;
|
||||
this.type = this.config.type;
|
||||
this.bOpt = this.config.bytecodeOptions;
|
||||
this.cOpt = this.config.confusionOptions;
|
||||
this.cleanFiles = this.config.cleanFiles;
|
||||
this.patterns = this.config.files || null;
|
||||
this.specFiles = this.config.specificFiles;
|
||||
|
||||
this.codefiles = this._initCodeFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化需要加密的文件列表
|
||||
*/
|
||||
_initCodeFiles() {
|
||||
if (!this.patterns) return;
|
||||
|
||||
const files = globby.sync(this.patterns, { cwd: this.basePath });
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密代码
|
||||
*/
|
||||
encrypt() {
|
||||
if (EncryptTypes.indexOf(this.type) == -1) return;
|
||||
if (this.target == 'frontend' && (this.type == 'bytecode' || this.type == 'strict')) return;
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + `start ciphering ${this.target}`);
|
||||
for (const file of this.codefiles) {
|
||||
const fullpath = path.join(this.encryptDir, file);
|
||||
if (!fs.statSync(fullpath).isFile()) continue;
|
||||
|
||||
// 特殊文件处理
|
||||
if (this.specFiles.includes(file)) {
|
||||
this.generate(fullpath, 'confusion');
|
||||
continue;
|
||||
}
|
||||
|
||||
this.generate(fullpath);
|
||||
}
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'end ciphering');
|
||||
};
|
||||
|
||||
/**
|
||||
* 递归
|
||||
*/
|
||||
loop(dirPath) {
|
||||
let files = [];
|
||||
if (fs.existsSync(dirPath)) {
|
||||
files = fs.readdirSync(dirPath);
|
||||
files.forEach((file, index) => {
|
||||
let curPath = dirPath + '/' + file;
|
||||
if (fs.statSync(curPath).isDirectory()) {
|
||||
this.loop(curPath);
|
||||
} else {
|
||||
const extname = path.extname(curPath);
|
||||
if (this.filesExt.indexOf(extname) !== -1) {
|
||||
this.generate(curPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成文件
|
||||
*/
|
||||
generate(curPath, type) {
|
||||
let encryptType = type ? type : this.type;
|
||||
|
||||
let tips = chalk.blue('[ee-bin] [encrypt] ') + 'file: ' + chalk.green(`${curPath}`) + ' ' + chalk.cyan(`(${encryptType})`);
|
||||
console.log(tips);
|
||||
if (encryptType == 'strict') {
|
||||
this.generateJSConfuseFile(curPath);
|
||||
this.generateBytecodeFile(curPath);
|
||||
} else if (encryptType == 'bytecode') {
|
||||
this.generateBytecodeFile(curPath);
|
||||
} else if (encryptType == 'confusion') {
|
||||
this.generateJSConfuseFile(curPath);
|
||||
} else {
|
||||
// none
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 javascript-obfuscator 生成压缩/混淆文件
|
||||
*/
|
||||
generateJSConfuseFile(file) {
|
||||
let opt = Object.assign({
|
||||
compact: true,
|
||||
stringArray: true,
|
||||
stringArrayThreshold: 1,
|
||||
}, this.cOpt);
|
||||
|
||||
let code = fs.readFileSync(file, "utf8");
|
||||
let result = JavaScriptObfuscator.obfuscate(code, opt);
|
||||
fs.writeFileSync(file, result.getObfuscatedCode(), "utf8");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成字节码文件
|
||||
*/
|
||||
generateBytecodeFile(curPath) {
|
||||
if (path.extname(curPath) !== '.js') {
|
||||
return
|
||||
}
|
||||
//let jscFile = curPath.replace(/.js/g, '.jsc');
|
||||
let jscFile = curPath + 'c';
|
||||
let opt = Object.assign({
|
||||
filename: curPath,
|
||||
output: jscFile,
|
||||
electron: true
|
||||
}, this.bOpt);
|
||||
|
||||
bytenode.compileFile(opt);
|
||||
fsPro.removeSync(curPath);
|
||||
}
|
||||
}
|
||||
|
||||
function encrypt(options = {}) {
|
||||
const electronOpt = extend(true, {
|
||||
target: 'electron',
|
||||
}, options);
|
||||
const electronEpt = new Encrypt(electronOpt);
|
||||
electronEpt.encrypt();
|
||||
|
||||
const frontendOpt = extend(true, {
|
||||
target: 'frontend',
|
||||
}, options);
|
||||
const frontendEpt = new Encrypt(frontendOpt);
|
||||
frontendEpt.encrypt();
|
||||
}
|
||||
|
||||
function cleanEncrypt(options = {}) {
|
||||
// [todo] 删除前端和主进程代码
|
||||
return;
|
||||
let files = options.dir !== undefined ? options.dir : ['./public/electron'];
|
||||
files = is.string(files) ? [files] : files;
|
||||
|
||||
files.forEach((file) => {
|
||||
const tmpFile = path.join(process.cwd(), file);
|
||||
if (fs.existsSync(tmpFile)) {
|
||||
fsPro.removeSync(tmpFile);
|
||||
console.log(chalk.blue('[ee-bin] [encrypt] ') + 'clean up tmp files: ' + chalk.magenta(`${tmpFile}`));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
encrypt,
|
||||
cleanEncrypt,
|
||||
};
|
||||
183
ee-bin/tools/iconGen.js
Normal file
183
ee-bin/tools/iconGen.js
Normal file
@@ -0,0 +1,183 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const icongen = require("icon-gen");
|
||||
|
||||
class IconGen {
|
||||
constructor() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
/**
|
||||
* _init
|
||||
*/
|
||||
_init() {
|
||||
// ---> 处理参数
|
||||
const args = process.argv.splice(3);
|
||||
let params = {
|
||||
input: "/public/images/logo.png",
|
||||
output: "/build/icons/",
|
||||
size: "16,32,64,256,512",
|
||||
clear: false,
|
||||
imagesDir: "/public/images/",
|
||||
};
|
||||
try {
|
||||
const len = args.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const arg = args[i];
|
||||
if (arg.match(/^-i/) || arg.match(/^-input/)) {
|
||||
params["input"] = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-o/) || arg.match(/^-output/)) {
|
||||
params["output"] = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-s/) || arg.match(/^-size/)) {
|
||||
params["size"] = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-c/) || arg.match(/^-clear/)) {
|
||||
params["clear"] = true;
|
||||
continue;
|
||||
}
|
||||
if (arg.match(/^-img/) || arg.match(/^-images/)) {
|
||||
params["imagesDir"] = args[i + 1];
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[ee-bin] [icon-gen] args: ", args);
|
||||
console.error("[ee-bin] [icon-gen] ERROR: ", e);
|
||||
throw new Error("参数错误!!");
|
||||
}
|
||||
this.params = params;
|
||||
|
||||
// ---> 组装参数
|
||||
console.log("[ee-bin] [icon-gen] icon 当前路径: ", process.cwd());
|
||||
this.input = path.join(process.cwd(), params.input);
|
||||
this.output = path.join(process.cwd(), params.output);
|
||||
this.imagesDir = path.join(process.cwd(), params.imagesDir);
|
||||
|
||||
const sizeList = params.size.split(",").map((item) => parseInt(item));
|
||||
this.iconOptions = {
|
||||
report: false,
|
||||
ico: {
|
||||
name: "icon",
|
||||
sizes: [256],
|
||||
},
|
||||
favicon: {
|
||||
name: "logo-",
|
||||
pngSizes: sizeList,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成图标
|
||||
*/
|
||||
generateIcons() {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 开始处理生成logo图片");
|
||||
if (!fs.existsSync(this.input)) {
|
||||
console.error("[ee-bin] [icon-gen] input: ", this.input);
|
||||
throw new Error("输入的图片不存在或路径错误");
|
||||
}
|
||||
if (!fs.existsSync(this.output)) {
|
||||
fs.mkdirSync(this.output, { recursive: true });
|
||||
} else {
|
||||
// 清空目录
|
||||
this.params.clear && this.deleteGenFile(this.output);
|
||||
}
|
||||
if (!fs.existsSync(this.imagesDir)) {
|
||||
fs.mkdirSync(this.imagesDir, { recursive: true });
|
||||
}
|
||||
icongen(this.input, this.output, this.iconOptions)
|
||||
.then((results) => {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 已生成下方图片资源");
|
||||
console.log(results);
|
||||
this._renameForEE(results);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw new Error("[ee-bin] [icon-gen] iconGen 生成失败!");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除生成的文件(.ico .png)
|
||||
*/
|
||||
deleteGenFile(dirPath) {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
// 读取文件夹下的文件目录
|
||||
const files = fs.readdirSync(dirPath);
|
||||
files.forEach((file) => {
|
||||
const curPath = path.join(dirPath, file);
|
||||
// 判断是不是文件夹,如果是,继续递归
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
this.deleteGenFile(curPath);
|
||||
} else {
|
||||
// 删除文件
|
||||
if ([".ico", ".png"].includes(path.extname(curPath))) {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为生成的资源重命名 (logo-32.png -> 32x32.png)
|
||||
*/
|
||||
_renameForEE(filesPath) {
|
||||
console.log("[ee-bin] [icon-gen] iconGen 开始重新命名logo图片资源");
|
||||
try {
|
||||
const len = filesPath.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
const filePath = filesPath[i];
|
||||
const extname = path.extname(filePath);
|
||||
if ([".png"].includes(extname)) {
|
||||
const filename = path.basename(filePath, extname);
|
||||
const basename = filename.split("-")[1];
|
||||
const dirname = path.dirname(filePath);
|
||||
// 处理 tray 图标 --> 复制到 public/images 目录下
|
||||
if ("16" === basename) {
|
||||
const newName = "tray" + extname;
|
||||
fs.copyFileSync(filePath, path.join(this.imagesDir, newName));
|
||||
console.log(`${filename}${extname} --> ${this.params.imagesDir}/${newName} 复制成功!`);
|
||||
fs.unlinkSync(filePath);
|
||||
continue;
|
||||
}
|
||||
// 处理 win 窗口图标 --> 复制到 public/images 目录下
|
||||
if ("32" === basename) {
|
||||
const newName = filename + extname;
|
||||
fs.copyFileSync(filePath, path.join(this.imagesDir, newName));
|
||||
console.log(`${filename}${extname} --> ${this.params.imagesDir}/${newName} 复制成功!`);
|
||||
}
|
||||
// 重命名 --> 32x32.png
|
||||
const newName = basename + "x" + basename + extname;
|
||||
const newPath = path.join(dirname, newName);
|
||||
fs.renameSync(filePath, newPath);
|
||||
console.log(`${filename}${extname} --> ${newName} 重命名成功!`);
|
||||
}
|
||||
}
|
||||
console.log("[ee-bin] [icon-gen] iconGen 资源处理完成!");
|
||||
} catch (e) {
|
||||
console.error("[ee-bin] [icon-gen] ERROR: ", e);
|
||||
throw new Error("重命名logo图片资源失败!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const run = () => {
|
||||
const i = new IconGen();
|
||||
i.generateIcons();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
run,
|
||||
};
|
||||
168
ee-bin/tools/incrUpdater.js
Normal file
168
ee-bin/tools/incrUpdater.js
Normal file
@@ -0,0 +1,168 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto')
|
||||
const chalk = require('chalk');
|
||||
const { loadConfig, rm, getPackage, writeJsonSync } = require('../lib/utils');
|
||||
const admZip = require('adm-zip')
|
||||
|
||||
/**
|
||||
* 增量升级
|
||||
* @class
|
||||
*/
|
||||
|
||||
class IncrUpdater {
|
||||
|
||||
/**
|
||||
* 执行
|
||||
*/
|
||||
run(options = {}) {
|
||||
console.log('[ee-bin] [updater] Start');
|
||||
const { config, asarFile, platform } = options;
|
||||
const binCfg = loadConfig(config);
|
||||
const cfg = binCfg.updater;
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${cfg} config does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
this.generateFile(cfg, asarFile, platform);
|
||||
|
||||
console.log('[ee-bin] [updater] End');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成增量升级文件
|
||||
*/
|
||||
generateFile(config, asarFile, platform) {
|
||||
const cfg = config[platform];
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${platform} config does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
let latestVersionInfo = {}
|
||||
const homeDir = process.cwd();
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.green(`${platform} config:`), cfg);
|
||||
|
||||
let asarFilePath = "";
|
||||
if (asarFile) {
|
||||
asarFilePath = path.normalize(path.join(homeDir, asarFile));
|
||||
} else if (Array.isArray(cfg.asarFile)) {
|
||||
// 检查文件列表,如果存在就跳出
|
||||
for (const filep of cfg.asarFile) {
|
||||
asarFilePath = path.normalize(path.join(homeDir, filep));
|
||||
if (fs.existsSync(asarFilePath)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asarFilePath = path.normalize(path.join(homeDir, cfg.asarFile));
|
||||
}
|
||||
|
||||
if (!fs.existsSync(asarFilePath)) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${asarFilePath} does not exist`));
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJson = getPackage();
|
||||
const version = packageJson.version;
|
||||
let platformForFilename = platform;
|
||||
if (platform.indexOf("_") !== -1) {
|
||||
const platformArr = platform.split("_");
|
||||
platformForFilename = platformArr.join("-");
|
||||
}
|
||||
|
||||
// 生成 zip
|
||||
let zipName = "";
|
||||
zipName = path.basename(cfg.output.zip, '.zip') + `-${platformForFilename}-${version}.zip`;
|
||||
const asarZipPath = path.join(homeDir, cfg.output.directory, zipName);
|
||||
if (fs.existsSync(asarZipPath)) {
|
||||
rm(asarZipPath);
|
||||
}
|
||||
const zip = new admZip();
|
||||
// 添加 asar 文件
|
||||
zip.addLocalFile(asarFilePath);
|
||||
// 添加 extraResources
|
||||
if (cfg.extraResources && cfg.extraResources.length > 0) {
|
||||
for (const extraRes of cfg.extraResources) {
|
||||
const extraResPath = path.normalize(path.join(homeDir, extraRes));
|
||||
if (fs.existsSync(extraResPath)) {
|
||||
zip.addLocalFile(extraResPath, "extraResources");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zip.writeZip(asarZipPath, (err) => {
|
||||
if (err) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] create zip ') + chalk.red(`Error: ${err}`));
|
||||
}
|
||||
});
|
||||
|
||||
// 生成 latest.json
|
||||
const sha1 = this.generateSha1(asarFilePath);
|
||||
const date = this._getFormattedDate();
|
||||
const fileStat = fs.statSync(asarFilePath);
|
||||
const item = {
|
||||
version: version,
|
||||
file: zipName,
|
||||
size: fileStat.size,
|
||||
sha1: sha1,
|
||||
releaseDate: date,
|
||||
};
|
||||
const jsonName = path.basename(cfg.output.file, '.json') + `-${platformForFilename}.json`;
|
||||
latestVersionInfo = item;
|
||||
const updaterJsonFilePath = path.join(homeDir, cfg.output.directory, jsonName);
|
||||
writeJsonSync(updaterJsonFilePath, latestVersionInfo);
|
||||
|
||||
// 删除缓存文件,防止生成的 zip 是旧版本
|
||||
if (cfg.cleanCache) {
|
||||
rm(path.join(homeDir, cfg.output.directory, 'mac'));
|
||||
rm(path.join(homeDir, cfg.output.directory, 'mac-arm64'));
|
||||
rm(path.join(homeDir, cfg.output.directory, 'win-unpacked'));
|
||||
rm(path.join(homeDir, cfg.output.directory, 'win-ia32-unpacked'));
|
||||
rm(path.join(homeDir, cfg.output.directory, 'linux-unpacked'));
|
||||
}
|
||||
}
|
||||
|
||||
generateSha1(filepath = "") {
|
||||
let sha1 = '';
|
||||
if (filepath.length == 0) {
|
||||
return sha1;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filepath)) {
|
||||
return sha1;
|
||||
}
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + `generate sha1 for filepath:${filepath}`);
|
||||
try {
|
||||
const buffer = fs.readFileSync(filepath);
|
||||
const fsHash = crypto.createHash('sha1');
|
||||
fsHash.update(buffer);
|
||||
sha1 = fsHash.digest('hex');
|
||||
return sha1;
|
||||
|
||||
} catch (error) {
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: generate sha1 error!`));
|
||||
console.log(chalk.blue('[ee-bin] [updater] ') + chalk.red(`Error: ${error}`));
|
||||
}
|
||||
return sha1;
|
||||
}
|
||||
|
||||
_getFormattedDate() {
|
||||
const date = new Date(); // 获取当前日期
|
||||
const year = date.getFullYear(); // 获取年份
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // 获取月份,月份从0开始计数
|
||||
const day = date.getDate().toString().padStart(2, '0'); // 获取日
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
IncrUpdater,
|
||||
incrUpdater: new IncrUpdater()
|
||||
}
|
||||
69
ee-bin/tools/move.js
Normal file
69
ee-bin/tools/move.js
Normal file
@@ -0,0 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const fsPro = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
const { loadConfig, rm } = require('../lib/utils');
|
||||
|
||||
const homeDir = process.cwd();
|
||||
|
||||
// 移动资源
|
||||
function move(options = {}) {
|
||||
console.log('[ee-bin] [move] Start moving resources');
|
||||
const { config, flag } = options;
|
||||
const binCfg = loadConfig(config);
|
||||
const moveConfig = binCfg.move;
|
||||
|
||||
let flags;
|
||||
const flagString = flag.trim();
|
||||
if (flagString.indexOf(',') !== -1) {
|
||||
flags = flagString.split(',');
|
||||
} else {
|
||||
flags = [flagString];
|
||||
}
|
||||
|
||||
for (let i = 0; i < flags.length; i++) {
|
||||
let f = flags[i];
|
||||
let cfg = moveConfig[f];
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.red(`Error: ${f} config does not exist` ));
|
||||
return;
|
||||
}
|
||||
|
||||
const { src, dest, dist, target } = cfg;
|
||||
const source = dist ? dist : src;
|
||||
const destination = target ? target : dest;
|
||||
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.green(`Move flag: ${f}`));
|
||||
console.log(chalk.blue('[ee-bin] [move] ') + chalk.green('config:'), cfg);
|
||||
|
||||
const srcResource = path.join(homeDir, source);
|
||||
if (!fs.existsSync(srcResource)) {
|
||||
const errorTips = chalk.bgRed('Error') + ` ${source} resource does not exist !`;
|
||||
console.error(errorTips);
|
||||
return
|
||||
}
|
||||
|
||||
// clear the historical resource and copy it to the ee resource directory
|
||||
const destResource = path.join(homeDir, destination);
|
||||
if (fs.statSync(srcResource).isDirectory() && !fs.existsSync(destResource)) {
|
||||
fs.mkdirSync(destResource, {recursive: true, mode: 0o777});
|
||||
} else {
|
||||
rm(destResource);
|
||||
console.log('[ee-bin] [move] Clear history resources:', destResource);
|
||||
}
|
||||
|
||||
fsPro.copySync(srcResource, destResource);
|
||||
|
||||
// [todo] go project, special treatment of package.json, reserved only necessary
|
||||
console.log(`[ee-bin] [move] Copy ${srcResource} to ${destResource}`);
|
||||
}
|
||||
|
||||
console.log('[ee-bin] [move] End');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
move
|
||||
}
|
||||
240
ee-bin/tools/serve.js
Normal file
240
ee-bin/tools/serve.js
Normal file
@@ -0,0 +1,240 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-bin:serve');
|
||||
const path = require('path');
|
||||
const fsPro = require('fs-extra');
|
||||
const { loadConfig } = require('../lib/utils');
|
||||
const is = require('is-type-of');
|
||||
const chalk = require('chalk');
|
||||
const crossSpawn = require('cross-spawn');
|
||||
const { buildSync } = require('esbuild');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
class ServeProcess {
|
||||
|
||||
constructor() {
|
||||
process.env.NODE_ENV = 'prod'; // dev / prod
|
||||
this.execProcess = {};
|
||||
this.electronDir = './electron';
|
||||
this.defaultBundleDir = './public/electron';
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动前端、主进程服务
|
||||
*/
|
||||
dev(options = {}) {
|
||||
// 设置一个环境变量
|
||||
process.env.NODE_ENV = 'dev';
|
||||
const { config, serve } = options;
|
||||
const binCfg = loadConfig(config);
|
||||
const binCmd = 'dev';
|
||||
const binCmdConfig = binCfg[binCmd];
|
||||
|
||||
let command = serve;
|
||||
if (!command) {
|
||||
command = Object.keys(binCmdConfig).join();
|
||||
}
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command,
|
||||
}
|
||||
|
||||
// build electron main code
|
||||
const cmds = this._formatCmds(command);
|
||||
if (cmds.indexOf("electron") !== -1) {
|
||||
// watche electron main code
|
||||
const electronConfig = binCmdConfig.electron;
|
||||
if (electronConfig.watch) {
|
||||
const cmd = 'electron';
|
||||
const watcher = chokidar.watch([this.electronDir], {
|
||||
persistent: true
|
||||
});
|
||||
watcher.on('change', async (f) => {
|
||||
console.log(chalk.blue('[ee-bin] [dev] ') + `File ${f} has been changed`);
|
||||
console.log(chalk.blue('[ee-bin] [dev] ') + `Restart ${cmd}`);
|
||||
|
||||
// rebuild code
|
||||
this.bundle(binCfg.build.electron);
|
||||
let subPorcess = this.execProcess[cmd];
|
||||
subPorcess.kill();
|
||||
delete this.execProcess[cmd];
|
||||
|
||||
// restart electron command
|
||||
let onlyElectronOpt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: cmd,
|
||||
}
|
||||
this.multiExec(onlyElectronOpt);
|
||||
});
|
||||
}
|
||||
|
||||
// When starting for the first time, build the code for the electron directory
|
||||
this.bundle(binCfg.build.electron);
|
||||
}
|
||||
|
||||
this.multiExec(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动主进程服务
|
||||
*/
|
||||
start(options = {}) {
|
||||
const { config } = options;
|
||||
const binCfg = loadConfig(config);
|
||||
const binCmd = 'start';
|
||||
const binCmdConfig = {
|
||||
start: binCfg[binCmd]
|
||||
};
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: binCmd,
|
||||
}
|
||||
this.multiExec(opt);
|
||||
}
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建
|
||||
*/
|
||||
build(options = {}) {
|
||||
const { config, cmds, env } = options;
|
||||
process.env.NODE_ENV = env;
|
||||
const binCfg = loadConfig(config);
|
||||
const binCmd = 'build';
|
||||
const binCmdConfig = binCfg[binCmd];
|
||||
|
||||
if (!cmds || cmds == "") {
|
||||
const tip = chalk.bgYellow('Warning') + ' Please modify the ' + chalk.blue('build') + ' property in the bin file';
|
||||
console.log(tip);
|
||||
return
|
||||
}
|
||||
|
||||
if (cmds.indexOf("electron") !== -1) {
|
||||
this.bundle(binCmdConfig.electron);
|
||||
return;
|
||||
}
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: cmds,
|
||||
}
|
||||
this.multiExec(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自定义命令
|
||||
*/
|
||||
exec(options = {}) {
|
||||
const { config, cmds } = options;
|
||||
const binCfg = loadConfig(config);
|
||||
const binCmd = 'exec';
|
||||
const binCmdConfig = binCfg[binCmd];
|
||||
|
||||
const opt = {
|
||||
binCmd,
|
||||
binCmdConfig,
|
||||
command: cmds,
|
||||
}
|
||||
this.multiExec(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持多个命令
|
||||
*/
|
||||
multiExec(opt = {}) {
|
||||
//console.log('multiExec opt:', opt)
|
||||
const { binCmd, binCmdConfig, command } = opt;
|
||||
const cmds = this._formatCmds(command);
|
||||
|
||||
for (let i = 0; i < cmds.length; i++) {
|
||||
let cmd = cmds[i];
|
||||
const cfg = binCmdConfig[cmd];
|
||||
|
||||
if (!cfg) {
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.red(`Error: [${binCmd} ${cmd}] config does not exist` ));
|
||||
continue;
|
||||
}
|
||||
|
||||
// frontend 如果是 file:// 协议,则不启动
|
||||
if (binCmd == 'dev' && cmd == 'frontend' && cfg.protocol == 'file://') {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + "Run " + chalk.green(`[${binCmd} ${cmd}]` + " command"));
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.green('config:'), JSON.stringify(cfg));
|
||||
|
||||
const execDir = path.join(process.cwd(), cfg.directory);
|
||||
const execArgs = is.string(cfg.args) ? [cfg.args] : cfg.args;
|
||||
const stdio = cfg.stdio ? cfg.stdio: 'inherit';
|
||||
|
||||
const handler = cfg.sync ? crossSpawn.sync : crossSpawn;
|
||||
|
||||
this.execProcess[cmd] = handler(
|
||||
cfg.cmd,
|
||||
execArgs,
|
||||
{ stdio: stdio, cwd: execDir, maxBuffer: 1024 * 1024 * 1024 },
|
||||
);
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + 'The ' + chalk.green(`[${binCmd} ${cmd}]`) + ` command is ${cfg.sync ? 'run completed' : 'running'}`);
|
||||
|
||||
if(!cfg.sync) {
|
||||
this.execProcess[cmd].on('exit', () => {
|
||||
if (cmd == 'electron') {
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + chalk.green('Press "CTRL+C" to exit'));
|
||||
return
|
||||
}
|
||||
console.log(chalk.blue(`[ee-bin] [${binCmd}] `) + 'The ' + chalk.green(`[${binCmd} ${cmd}]`) + ' command has been executed and exited');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// esbuild
|
||||
bundle(bundleConfig) {
|
||||
const { bundleType } = bundleConfig;
|
||||
if (bundleType == 'copy') {
|
||||
const srcResource = path.join(process.cwd(), this.electronDir);
|
||||
const destResource = path.join(process.cwd(), this.defaultBundleDir);
|
||||
fsPro.removeSync(destResource);
|
||||
fsPro.copySync(srcResource, destResource);
|
||||
} else {
|
||||
const esbuildOptions = bundleConfig[bundleConfig.type];
|
||||
// todo 不压缩
|
||||
// if (this.isDev()) {
|
||||
// esbuildOptions.minify = false;
|
||||
// }
|
||||
debug('esbuild options:%O', esbuildOptions);
|
||||
buildSync(esbuildOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// format commands
|
||||
_formatCmds(command) {
|
||||
let cmds;
|
||||
const cmdString = command.trim();
|
||||
if (cmdString.indexOf(',') !== -1) {
|
||||
cmds = cmdString.split(',');
|
||||
} else {
|
||||
cmds = [cmdString];
|
||||
}
|
||||
|
||||
return cmds;
|
||||
}
|
||||
|
||||
// env
|
||||
isDev() {
|
||||
return process.env.NODE_ENV === 'dev';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ServeProcess,
|
||||
serveProcess: new ServeProcess()
|
||||
}
|
||||
2
ee-core/.gitignore
vendored
Normal file
2
ee-core/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
package-lock.json
|
||||
21
ee-core/LICENSE
Normal file
21
ee-core/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Wallace Gao
|
||||
|
||||
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.
|
||||
66
ee-core/README.md
Normal file
66
ee-core/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
### introduction
|
||||
|
||||
Powerful electron third party module, offering **100+ API**
|
||||
|
||||
[introduction](https://www.kaka996.com/pages/85c531/)
|
||||
|
||||
[中文文档](https://www.kaka996.com/pages/85c531/)
|
||||
|
||||
### module list
|
||||
|
||||
#### addon
|
||||
plug-in module
|
||||
|
||||
#### bin
|
||||
Command line module.
|
||||
|
||||
#### config
|
||||
Configuration Module
|
||||
|
||||
#### const
|
||||
Define the universal constants used in the framework.
|
||||
|
||||
#### controller
|
||||
Controller module
|
||||
|
||||
#### ee
|
||||
EE module
|
||||
|
||||
#### electron
|
||||
The electric function encapsulated by the framework will provide APIs in this module.
|
||||
|
||||
#### exception
|
||||
Capture exception modules.
|
||||
|
||||
#### httpclient
|
||||
HTTP client.
|
||||
|
||||
#### jobs
|
||||
Jobs module.
|
||||
|
||||
#### loader
|
||||
loader module
|
||||
|
||||
#### log
|
||||
log module
|
||||
|
||||
#### message
|
||||
Message module. Sending messages between the main process and child processes.
|
||||
|
||||
#### ps
|
||||
Process tool class module.
|
||||
|
||||
#### services
|
||||
services module
|
||||
|
||||
#### socket
|
||||
Provide socket communication function.
|
||||
|
||||
#### storage
|
||||
Storage module. Provide JSON database and sqlite database.
|
||||
|
||||
#### tools
|
||||
Script module. Encrypt and move resources.
|
||||
|
||||
#### utils
|
||||
Tool library module.
|
||||
5
ee-core/app/application.d.ts
vendored
Normal file
5
ee-core/app/application.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export declare class Appliaction {
|
||||
register(eventName: string, handler: Function): void;
|
||||
run(): void;
|
||||
}
|
||||
export declare const app: Appliaction;
|
||||
31
ee-core/app/application.js
Normal file
31
ee-core/app/application.js
Normal file
@@ -0,0 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:app:appliaction');
|
||||
const { loadController } = require('../controller');
|
||||
const { eventBus, Ready } = require('./events');
|
||||
const { loadSocket } = require('../socket');
|
||||
const { loadElectron } = require('../electron');
|
||||
|
||||
class Appliaction {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
register(eventName, handler) {
|
||||
return eventBus.register(eventName, handler);
|
||||
}
|
||||
|
||||
run() {
|
||||
loadController();
|
||||
loadSocket();
|
||||
eventBus.emitLifecycle(Ready);
|
||||
loadElectron();
|
||||
}
|
||||
}
|
||||
|
||||
const app = new Appliaction();
|
||||
|
||||
module.exports = {
|
||||
Appliaction,
|
||||
app,
|
||||
};
|
||||
5
ee-core/app/boot.d.ts
vendored
Normal file
5
ee-core/app/boot.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
export declare class ElectronEgg {
|
||||
init(): void;
|
||||
register(eventName: string, handler: Function): void;
|
||||
run(): void;
|
||||
}
|
||||
74
ee-core/app/boot.js
Normal file
74
ee-core/app/boot.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:app:boot');
|
||||
const path = require('path');
|
||||
const { loadException } = require('../exception');
|
||||
const { electronApp } = require('../electron/app');
|
||||
const { getArgumentByName, getBundleDir } = require('../ps');
|
||||
const { loadConfig } = require('../config');
|
||||
const { loadLog } = require('../log');
|
||||
const { app } = require('./application');
|
||||
const { loadDir } = require('./dir');
|
||||
|
||||
class ElectronEgg {
|
||||
constructor() {
|
||||
const baseDir = electronApp.getAppPath();
|
||||
const { env } = process;
|
||||
const environmet = getArgumentByName('env') || 'prod';
|
||||
|
||||
const options = {
|
||||
env: environmet,
|
||||
baseDir,
|
||||
electronDir: getBundleDir(baseDir),
|
||||
appName: electronApp.getName(),
|
||||
userHome: electronApp.getPath('home'),
|
||||
appData: electronApp.getPath('appData'),
|
||||
appUserData: electronApp.getPath('userData'),
|
||||
appVersion: electronApp.getVersion(),
|
||||
isPackaged: electronApp.isPackaged,
|
||||
execDir: baseDir,
|
||||
}
|
||||
|
||||
// exec directory (exe dmg dep) for prod
|
||||
if (environmet == 'prod' && options.isPackaged) {
|
||||
options.execDir = path.dirname(electronApp.getPath('exe'));
|
||||
}
|
||||
|
||||
// normalize env
|
||||
env.EE_ENV = environmet;
|
||||
env.EE_APP_NAME = options.appName;
|
||||
env.EE_APP_VERSION = options.appVersion;
|
||||
env.EE_BASE_DIR = options.baseDir;
|
||||
env.EE_ELECTRON_DIR = options.electronDir;
|
||||
env.EE_USER_HOME = options.userHome;
|
||||
env.EE_APP_DATA = options.appData;
|
||||
env.EE_APP_USER_DATA = options.appUserData;
|
||||
env.EE_EXEC_DIR = options.execDir;
|
||||
env.EE_IS_PACKAGED = options.isPackaged;
|
||||
env.EE_SOCKET_PORT = null;
|
||||
env.EE_HTTP_PORT = null;
|
||||
debug('[constructor] options:%j', options)
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// basic functions
|
||||
loadException();
|
||||
loadConfig();
|
||||
loadDir();
|
||||
loadLog();
|
||||
}
|
||||
|
||||
register(eventName, handler) {
|
||||
return app.register(eventName, handler);
|
||||
}
|
||||
|
||||
run() {
|
||||
app.run();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ElectronEgg,
|
||||
};
|
||||
1
ee-core/app/dir.d.ts
vendored
Normal file
1
ee-core/app/dir.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function loadDir(): void;
|
||||
27
ee-core/app/dir.js
Normal file
27
ee-core/app/dir.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const { getUserHomeHiddenAppDir, getLogDir, getDataDir } = require('../ps');
|
||||
const { mkdir } = require('../utils/helper');
|
||||
function loadDir() {
|
||||
initDir();
|
||||
}
|
||||
|
||||
function initDir() {
|
||||
const homeHiddenAppDir = getUserHomeHiddenAppDir();
|
||||
if (!fs.existsSync(homeHiddenAppDir)) {
|
||||
mkdir(homeHiddenAppDir, { mode: 0o755 });
|
||||
}
|
||||
const dataDir = getDataDir();
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
mkdir(dataDir, { mode: 0o755 });
|
||||
}
|
||||
const logDir = getLogDir();
|
||||
if (!fs.existsSync(logDir)) {
|
||||
mkdir(logDir, { mode: 0o755 });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadDir
|
||||
};
|
||||
14
ee-core/app/events.d.ts
vendored
Normal file
14
ee-core/app/events.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export declare class EventBus {
|
||||
lifecycleEvents: {};
|
||||
eventsMap: {};
|
||||
register(eventName: string, handler: Function): void;
|
||||
emitLifecycle(eventName: string, ...args: any[]): void;
|
||||
on(eventName: string, handler: Function): void;
|
||||
emit(eventName: string, ...args: any[]): void;
|
||||
}
|
||||
export declare const eventBus: EventBus;
|
||||
export declare const Ready: "ready";
|
||||
export declare const ElectronAppReady: "electron-app-ready";
|
||||
export declare const WindowReady: "window-ready";
|
||||
export declare const Preload: "preload";
|
||||
export declare const BeforeClose: "before-close";
|
||||
56
ee-core/app/events.js
Normal file
56
ee-core/app/events.js
Normal file
@@ -0,0 +1,56 @@
|
||||
'use strict';
|
||||
|
||||
const Ready = "ready";
|
||||
const ElectronAppReady = "electron-app-ready";
|
||||
const WindowReady = "window-ready";
|
||||
const BeforeClose = "before-close";
|
||||
const Preload = "preload";
|
||||
|
||||
class EventBus {
|
||||
constructor() {
|
||||
this.lifecycleEvents = {};
|
||||
this.eventsMap = {};
|
||||
}
|
||||
|
||||
// add lifecycle event
|
||||
register(eventName, handler) {
|
||||
if (!this.lifecycleEvents[eventName]) {
|
||||
this.lifecycleEvents[eventName] = handler;
|
||||
}
|
||||
}
|
||||
|
||||
// call lifecycle event
|
||||
emitLifecycle(eventName, ...args) {
|
||||
const eventFn = this.lifecycleEvents[eventName];
|
||||
if (eventFn) {
|
||||
eventFn(...args);
|
||||
}
|
||||
}
|
||||
|
||||
// add listener
|
||||
on(eventName, handler) {
|
||||
if (!this.eventsMap[eventName]) {
|
||||
this.eventsMap[eventName] = handler;
|
||||
}
|
||||
}
|
||||
|
||||
// emit listener
|
||||
emit(eventName, ...args) {
|
||||
const eventFn = this.eventsMap[eventName];
|
||||
if (eventFn) {
|
||||
eventFn(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const eventBus = new EventBus();
|
||||
|
||||
module.exports = {
|
||||
EventBus,
|
||||
eventBus,
|
||||
Ready,
|
||||
ElectronAppReady,
|
||||
WindowReady,
|
||||
Preload,
|
||||
BeforeClose
|
||||
};
|
||||
2
ee-core/app/index.d.ts
vendored
Normal file
2
ee-core/app/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ElectronEgg };
|
||||
import { ElectronEgg } from "./boot";
|
||||
7
ee-core/app/index.js
Normal file
7
ee-core/app/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { ElectronEgg } = require('./boot');
|
||||
|
||||
module.exports = {
|
||||
ElectronEgg,
|
||||
};
|
||||
13
ee-core/config/config_loader.d.ts
vendored
Normal file
13
ee-core/config/config_loader.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Timing } from "../core/utils/timing";
|
||||
import { EEConfig } from "./default_config";
|
||||
export declare class ConfigLoader {
|
||||
timing: Timing;
|
||||
/**
|
||||
* Load config/config.xxx.js
|
||||
*/
|
||||
load(): EEConfig;
|
||||
_AppConfig(): EEConfig;
|
||||
_loadConfig(dirpath: string, filename: string): EEConfig;
|
||||
}
|
||||
|
||||
|
||||
66
ee-core/config/config_loader.js
Normal file
66
ee-core/config/config_loader.js
Normal file
@@ -0,0 +1,66 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:config:config_loader');
|
||||
const path = require('path');
|
||||
const { appName, env, getElectronDir, getBaseDir, getRootDir } = require('../ps');
|
||||
const { extend } = require('../utils/extend');
|
||||
const { loadFile } = require('../loader');
|
||||
const { Timing } = require('../core/utils/timing');
|
||||
const defaultConfig = require('./default_config');
|
||||
|
||||
class ConfigLoader {
|
||||
constructor() {
|
||||
this.timing = new Timing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load config/config.xxx.js
|
||||
*/
|
||||
load() {
|
||||
this.timing.start('Load Config');
|
||||
|
||||
// Load Application config
|
||||
const appConfig = this._AppConfig();
|
||||
// debug("[load] appConfig: %O", appConfig);
|
||||
|
||||
const defaultConf = defaultConfig();
|
||||
const config = extend(true, defaultConf, appConfig);
|
||||
debug("[load] config: %o", config);
|
||||
|
||||
this.timing.end('Load Config');
|
||||
return config;
|
||||
}
|
||||
|
||||
_AppConfig() {
|
||||
const names = [
|
||||
'config.default',
|
||||
`config.${env()}`,
|
||||
];
|
||||
const target = {};
|
||||
for (const filename of names) {
|
||||
const config = this._loadConfig(getElectronDir(), filename);
|
||||
extend(true, target, config);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
_loadConfig(dirpath, filename) {
|
||||
const appInfo = {
|
||||
name: appName(),
|
||||
baseDir: getBaseDir(),
|
||||
electronDir: getElectronDir(),
|
||||
env: env(),
|
||||
root: getRootDir(),
|
||||
}
|
||||
const filepath = path.join(dirpath, 'config', filename);
|
||||
const config = loadFile(filepath, appInfo);
|
||||
debug("[_loadConfig] filepath: %s", filepath);
|
||||
if (!config) return null;
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ConfigLoader
|
||||
};
|
||||
95
ee-core/config/default_config.d.ts
vendored
Normal file
95
ee-core/config/default_config.d.ts
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import { BrowserWindowConstructorOptions } from 'electron';
|
||||
|
||||
export declare interface AppConfig {
|
||||
openDevTools?: boolean | Object;
|
||||
singleLock?: boolean;
|
||||
windowsOption?: BrowserWindowConstructorOptions;
|
||||
logger?: LoggerConfig;
|
||||
socketServer?: SocketConfig;
|
||||
httpServer?: HttpConfig;
|
||||
remote?: RemoteConfig;
|
||||
mainServer?: MainConfig;
|
||||
exception?: ExceptionConfig;
|
||||
job?: JobConfig;
|
||||
cross?: CrossConfig;
|
||||
}
|
||||
export declare interface LoggerConfig {
|
||||
type?: string;
|
||||
dir?: string;
|
||||
encoding?: string;
|
||||
env?: string;
|
||||
level?: string;
|
||||
consoleLevel?: string;
|
||||
disableConsoleAfterReady?: boolean;
|
||||
outputJSON?: boolean;
|
||||
buffer?: boolean;
|
||||
appLogName: string;
|
||||
coreLogName?: string;
|
||||
agentLogName?: string;
|
||||
errorLogName: string;
|
||||
coreLogger?: {};
|
||||
allowDebugAtProd?: boolean;
|
||||
enablePerformanceTimer?: boolean;
|
||||
rotator?: string;
|
||||
}
|
||||
export declare interface SocketConfig {
|
||||
enable: boolean;
|
||||
port: number;
|
||||
path?: string;
|
||||
connectTimeout?: number;
|
||||
pingTimeout?: number;
|
||||
pingInterval?: number;
|
||||
maxHttpBufferSize?: number;
|
||||
transports?: string[];
|
||||
cors?: {
|
||||
origin?: boolean;
|
||||
};
|
||||
channel?: string;
|
||||
}
|
||||
export declare interface HttpConfig {
|
||||
enable: boolean;
|
||||
https?: {
|
||||
enable: boolean;
|
||||
key: string;
|
||||
cert: string;
|
||||
};
|
||||
protocol?: string;
|
||||
host?: string;
|
||||
port: number;
|
||||
cors?: {
|
||||
origin?: string;
|
||||
};
|
||||
body?: {
|
||||
multipart?: boolean;
|
||||
formidable?: {
|
||||
keepExtensions?: boolean;
|
||||
};
|
||||
};
|
||||
filterRequest?: {
|
||||
uris?: string[];
|
||||
returnData?: string;
|
||||
};
|
||||
}
|
||||
export declare interface RemoteConfig {
|
||||
enable: boolean;
|
||||
url: string;
|
||||
}
|
||||
export declare interface MainConfig {
|
||||
protocol?: string;
|
||||
indexPath: string;
|
||||
options?: {};
|
||||
takeover?: string;
|
||||
loadingPage?: string;
|
||||
channelSeparator?: string;
|
||||
}
|
||||
export declare interface ExceptionConfig {
|
||||
mainExit?: boolean;
|
||||
childExit?: boolean;
|
||||
rendererExit?: boolean;
|
||||
}
|
||||
export declare interface JobConfig {
|
||||
messageLog: boolean;
|
||||
}
|
||||
export declare interface CrossConfig {}
|
||||
declare function config(): AppConfig;
|
||||
export = config;
|
||||
111
ee-core/config/default_config.js
Normal file
111
ee-core/config/default_config.js
Normal file
@@ -0,0 +1,111 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { env, getBaseDir, getLogDir } = require('../ps');
|
||||
const { SocketIO } = require('../const/channel');
|
||||
|
||||
/**
|
||||
* default
|
||||
*/
|
||||
module.exports = () => {
|
||||
return {
|
||||
openDevTools: false,
|
||||
singleLock: true,
|
||||
windowsOption: {
|
||||
title: 'electron-egg',
|
||||
width: 980,
|
||||
height: 650,
|
||||
minWidth: 400,
|
||||
minHeight: 300,
|
||||
webPreferences: {
|
||||
//webSecurity: false,
|
||||
contextIsolation: false, // false -> 可在渲染进程中使用electron的api,true->需要bridge.js(contextBridge)
|
||||
nodeIntegration: true,
|
||||
//preload: path.join(appInfo.electronDir, 'preload', 'bridge.js'),
|
||||
},
|
||||
frame: true,
|
||||
show: false,
|
||||
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
|
||||
},
|
||||
logger: {
|
||||
type: 'application',
|
||||
dir: getLogDir(),
|
||||
encoding: 'utf8',
|
||||
env: env(),
|
||||
level: 'INFO',
|
||||
consoleLevel: 'INFO',
|
||||
disableConsoleAfterReady: env() !== 'local',
|
||||
outputJSON: false,
|
||||
buffer: true,
|
||||
appLogName: `ee.log`,
|
||||
coreLogName: 'ee-core.log',
|
||||
agentLogName: 'ee-agent.log',
|
||||
errorLogName: `ee-error.log`,
|
||||
coreLogger: {},
|
||||
allowDebugAtProd: false,
|
||||
enablePerformanceTimer: false,
|
||||
rotator: 'day',
|
||||
},
|
||||
socketServer: {
|
||||
enable: false, // is it enabled
|
||||
port: 7070, // default port (if the port is in use, randomly select one)
|
||||
path: "/socket.io/", // path
|
||||
connectTimeout: 45000, // client connection timeout
|
||||
pingTimeout: 30000, // heartbeat detection timeout
|
||||
pingInterval: 25000, // heartbeat detection interval
|
||||
maxHttpBufferSize: 1e8, // the data size of each message 1M
|
||||
transports: ["polling", "websocket"], // http polling or websocket
|
||||
cors: {
|
||||
origin: true, // http协议时,要设置跨域 类型 Boolean String RegExp Array Function
|
||||
},
|
||||
channel: SocketIO.partySoftware
|
||||
},
|
||||
httpServer: {
|
||||
enable: false, // Is it enabled
|
||||
https: {
|
||||
enable: false,
|
||||
key: '/public/ssl/localhost+1.key',
|
||||
cert: '/public/ssl/localhost+1.pem'
|
||||
},
|
||||
protocol: 'http://',
|
||||
host: '127.0.0.1',
|
||||
port: 7071, // Default port (if the port is in use, randomly select one)
|
||||
cors: {
|
||||
origin: "*"
|
||||
},
|
||||
body: {
|
||||
multipart: true,
|
||||
formidable: {
|
||||
keepExtensions: true
|
||||
}
|
||||
},
|
||||
filterRequest: {
|
||||
uris: [
|
||||
'favicon.ico'
|
||||
],
|
||||
returnData: ''
|
||||
}
|
||||
},
|
||||
remote: {
|
||||
enable: false,
|
||||
url: ''
|
||||
},
|
||||
mainServer: {
|
||||
protocol: 'file://', // file://
|
||||
indexPath: '/public/dist/index.html',
|
||||
options: {},
|
||||
takeover: '',
|
||||
loadingPage: '',
|
||||
channelSeparator: '/',
|
||||
},
|
||||
exception: {
|
||||
mainExit: false,
|
||||
childExit: false,
|
||||
rendererExit: true,
|
||||
},
|
||||
jobs: {
|
||||
messageLog: false
|
||||
},
|
||||
cross: {}
|
||||
}
|
||||
}
|
||||
28
ee-core/config/index.d.ts
vendored
Normal file
28
ee-core/config/index.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
AppConfig,
|
||||
WindowsConfig,
|
||||
LoggerConfig,
|
||||
SocketConfig,
|
||||
HttpConfig,
|
||||
RemoteConfig,
|
||||
MainConfig,
|
||||
ExceptionConfig,
|
||||
JobConfig,
|
||||
CrossConfig
|
||||
} from "./default_config";
|
||||
declare function loadConfig(): AppConfig;
|
||||
declare function getConfig(): AppConfig;
|
||||
export {
|
||||
AppConfig,
|
||||
WindowsConfig,
|
||||
LoggerConfig,
|
||||
SocketConfig,
|
||||
HttpConfig,
|
||||
RemoteConfig,
|
||||
MainConfig,
|
||||
ExceptionConfig,
|
||||
JobConfig,
|
||||
CrossConfig,
|
||||
loadConfig,
|
||||
getConfig
|
||||
}
|
||||
25
ee-core/config/index.js
Normal file
25
ee-core/config/index.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const { ConfigLoader } = require('./config_loader');
|
||||
|
||||
const Instance = {
|
||||
config: null,
|
||||
};
|
||||
|
||||
function loadConfig() {
|
||||
const configLoader = new ConfigLoader();
|
||||
Instance["config"] = configLoader.load();
|
||||
return Instance["config"];
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
if (!Instance["config"]) {
|
||||
loadConfig();
|
||||
};
|
||||
return Instance["config"];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadConfig,
|
||||
getConfig,
|
||||
};
|
||||
16
ee-core/const/channel.d.ts
vendored
Normal file
16
ee-core/const/channel.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export declare namespace Processes {
|
||||
let showException: string;
|
||||
let sendToMain: string;
|
||||
}
|
||||
export declare namespace SocketIO {
|
||||
let partySoftware: string;
|
||||
}
|
||||
export declare namespace Events {
|
||||
let childProcessExit: string;
|
||||
let childProcessError: string;
|
||||
}
|
||||
export declare namespace Receiver {
|
||||
let childJob: string;
|
||||
let forkProcess: string;
|
||||
let all: string;
|
||||
}
|
||||
28
ee-core/const/channel.js
Normal file
28
ee-core/const/channel.js
Normal file
@@ -0,0 +1,28 @@
|
||||
'use strict';
|
||||
|
||||
const Processes = {
|
||||
showException: 'ee#showException',
|
||||
sendToMain: 'ee#sendToMain'
|
||||
}
|
||||
|
||||
const SocketIO = {
|
||||
partySoftware: 'socket-channel',
|
||||
}
|
||||
|
||||
const Events = {
|
||||
childProcessExit: 'ee#childProcess#exit',
|
||||
childProcessError: 'ee#childProcess#error',
|
||||
}
|
||||
|
||||
const Receiver = {
|
||||
childJob: 'job',
|
||||
forkProcess: 'task',
|
||||
all: 'all'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Processes,
|
||||
SocketIO,
|
||||
Events,
|
||||
Receiver
|
||||
};
|
||||
9
ee-core/controller/controller_loader.d.ts
vendored
Normal file
9
ee-core/controller/controller_loader.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Timing } from "../core/utils/timing";
|
||||
export declare class ControllerLoader {
|
||||
timing: Timing;
|
||||
/**
|
||||
* Load controller/xxx.js
|
||||
*/
|
||||
load(): any;
|
||||
}
|
||||
|
||||
78
ee-core/controller/controller_loader.js
Normal file
78
ee-core/controller/controller_loader.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:controller:controller_loader');
|
||||
const path = require('path');
|
||||
const is = require('is-type-of');
|
||||
const { getElectronDir } = require('../ps');
|
||||
const { Timing } = require('../core/utils/timing');
|
||||
const { FileLoader, FULLPATH } = require('../core/loader/file_loader');
|
||||
const { isBytecodeClass, callFn } = require('../core/utils');
|
||||
|
||||
class ControllerLoader {
|
||||
constructor() {
|
||||
this.timing = new Timing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load controller/xxx.js
|
||||
*/
|
||||
load() {
|
||||
this.timing.start('Load Controller');
|
||||
|
||||
const opt = {
|
||||
caseStyle: 'lower',
|
||||
directory: path.join(getElectronDir(), 'controller'),
|
||||
initializer: (obj, opt) => {
|
||||
if (is.class(obj) || isBytecodeClass(obj)) {
|
||||
obj.prototype.pathName = opt.pathName;
|
||||
obj.prototype.fullPath = opt.path;
|
||||
return wrapClass(obj);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
const target = new FileLoader(opt).load();
|
||||
debug("[load] controllers: %o", target);
|
||||
this.timing.end('Load Controller');
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the class, yield a object with middlewares
|
||||
function wrapClass(Controller) {
|
||||
let proto = Controller.prototype;
|
||||
const ret = {};
|
||||
// tracing the prototype chain
|
||||
while (proto !== Object.prototype) {
|
||||
const keys = Object.getOwnPropertyNames(proto);
|
||||
// debug("[wrapClass] keys:", keys);
|
||||
for (const key of keys) {
|
||||
// getOwnPropertyNames will return constructor
|
||||
// that should be ignored
|
||||
if (key === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
// skip getter, setter & non-function properties
|
||||
const d = Object.getOwnPropertyDescriptor(proto, key);
|
||||
// prevent to override sub method
|
||||
if (is.function(d.value) && !ret.hasOwnProperty(key)) {
|
||||
ret[key] = methodToMiddleware(Controller, key);
|
||||
ret[key][FULLPATH] = Controller.prototype.fullPath + '#' + Controller.name + '.' + key + '()';
|
||||
}
|
||||
}
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function methodToMiddleware(Controller, key) {
|
||||
return function classControllerMiddleware(...args) {
|
||||
const controller = new Controller();
|
||||
return callFn(controller[key], args, controller);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ControllerLoader
|
||||
};
|
||||
2
ee-core/controller/index.d.ts
vendored
Normal file
2
ee-core/controller/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export declare function loadController(): void;
|
||||
export declare function getController(): any;
|
||||
24
ee-core/controller/index.js
Normal file
24
ee-core/controller/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
'use strict'
|
||||
|
||||
const { ControllerLoader } = require('./controller_loader');
|
||||
|
||||
const Instance = {
|
||||
controller: null,
|
||||
};
|
||||
|
||||
function loadController() {
|
||||
const controllerLoader = new ControllerLoader();
|
||||
Instance.controller = controllerLoader.load();
|
||||
}
|
||||
|
||||
function getController() {
|
||||
if (!Instance.controller) {
|
||||
loadController();
|
||||
}
|
||||
return Instance.controller;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadController,
|
||||
getController
|
||||
};
|
||||
2
ee-core/core/index.d.ts
vendored
Normal file
2
ee-core/core/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import utils = require("./utils");
|
||||
export { EeLoader, BaseContextClass, utils };
|
||||
12
ee-core/core/index.js
Normal file
12
ee-core/core/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const EeLoader = require('./loader/ee_loader');
|
||||
const BaseContextClass = require('./utils/base_context_class');
|
||||
const utils = require('./utils');
|
||||
|
||||
|
||||
module.exports = {
|
||||
EeLoader,
|
||||
BaseContextClass,
|
||||
utils,
|
||||
};
|
||||
66
ee-core/core/loader/file_loader.d.ts
vendored
Normal file
66
ee-core/core/loader/file_loader.d.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Load files from directory to target object.
|
||||
*/
|
||||
export declare class FileLoader {
|
||||
/**
|
||||
* @class
|
||||
* @param {Object} options - options
|
||||
* @param {String|Array} options.directory - directories to be loaded
|
||||
* @param {Object} options.target - attach the target object from loaded files
|
||||
* @param {String} options.match - match the files when load, support glob, default to all js files
|
||||
* @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`
|
||||
* @param {Boolean} options.call - determine whether invoke when exports is function
|
||||
* @param {Object} options.inject - an object that be the argument when invoke the function
|
||||
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
|
||||
*/
|
||||
constructor(options: {
|
||||
directory: string | any[];
|
||||
target: any;
|
||||
match: string;
|
||||
initializer: Function;
|
||||
call: boolean;
|
||||
inject: any;
|
||||
caseStyle: string | Function;
|
||||
});
|
||||
options: {
|
||||
directory: any;
|
||||
target: any;
|
||||
match: any;
|
||||
caseStyle: string;
|
||||
initializer: any;
|
||||
call: boolean;
|
||||
inject: any;
|
||||
} & {
|
||||
directory: string | any[];
|
||||
target: any;
|
||||
match: string;
|
||||
initializer: Function;
|
||||
call: boolean;
|
||||
inject: any;
|
||||
caseStyle: string | Function;
|
||||
};
|
||||
/**
|
||||
* attach items to target object. Mapping the directory to properties.
|
||||
* `xxx/group/repository.js` => `target.group.repository`
|
||||
* @return {Object} target
|
||||
*/
|
||||
load(): any;
|
||||
/**
|
||||
* Parse files from given directories, then return an items list, each item contains properties and exports.
|
||||
* For example, parse `controller/group/repository.js`
|
||||
* It returns a item
|
||||
* ```
|
||||
* {
|
||||
* fullpath: '',
|
||||
* properties: [ 'group', 'repository' ],
|
||||
* exports: { ... },
|
||||
* }
|
||||
* ```
|
||||
* `Properties` is an array that contains the directory of a filepath.
|
||||
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
|
||||
* @return {Array} items
|
||||
*/
|
||||
parse(): any[];
|
||||
}
|
||||
export declare const EXPORTS: unique symbol;
|
||||
export declare const FULLPATH: unique symbol;
|
||||
212
ee-core/core/loader/file_loader.js
Normal file
212
ee-core/core/loader/file_loader.js
Normal file
@@ -0,0 +1,212 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:core:loader:file_loader');
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const globby = require('globby');
|
||||
const is = require('is-type-of');
|
||||
const { isBytecodeClass, loadFile, filePatterns } = require('../utils');
|
||||
const FULLPATH = Symbol('LOADER_ITEM_FULLPATH');
|
||||
const EXPORTS = Symbol('LOADER_ITEM_EXPORTS');
|
||||
|
||||
const defaults = {
|
||||
directory: null,
|
||||
target: null,
|
||||
match: undefined,
|
||||
caseStyle: 'camel',
|
||||
initializer: null,
|
||||
call: true,
|
||||
inject: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* Load files from directory to target object.
|
||||
*/
|
||||
class FileLoader {
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @param {Object} options - options
|
||||
* @param {String|Array} options.directory - directories to be loaded
|
||||
* @param {Object} options.target - attach the target object from loaded files
|
||||
* @param {String} options.match - match the files when load, support glob, default to all js files
|
||||
* @param {Function} options.initializer - custom file exports, receive two parameters, first is the inject object(if not js file, will be content buffer), second is an `options` object that contain `path`
|
||||
* @param {Boolean} options.call - determine whether invoke when exports is function
|
||||
* @param {Object} options.inject - an object that be the argument when invoke the function
|
||||
* @param {String|Function} options.caseStyle - set property's case when converting a filepath to property list.
|
||||
*/
|
||||
constructor(options) {
|
||||
assert(options.directory, 'options.directory is required');
|
||||
this.options = Object.assign({}, defaults, options);
|
||||
debug("[constructor] options: %o", this.options);
|
||||
}
|
||||
|
||||
/**
|
||||
* attach items to target object. Mapping the directory to properties.
|
||||
* `xxx/group/repository.js` => `target.group.repository`
|
||||
* @return {Object} target
|
||||
*/
|
||||
load() {
|
||||
const items = this.parse();
|
||||
const target = {};
|
||||
for (const item of items) {
|
||||
// item { fullpath, properties: [ 'a', 'b', 'c'], exports }
|
||||
item.properties.reduce((target, property, index) => {
|
||||
let obj;
|
||||
// properties is a path slice, only the last value is the file name
|
||||
if (index === item.properties.length - 1) {
|
||||
obj = item.exports;
|
||||
if (obj && !is.primitive(obj)) {
|
||||
obj[FULLPATH] = item.fullpath;
|
||||
obj[EXPORTS] = true;
|
||||
}
|
||||
} else {
|
||||
obj = target[property] || {};
|
||||
}
|
||||
|
||||
target[property] = obj;
|
||||
// const properties = item.properties.slice(0, index + 1).join('.');
|
||||
// debug('[load] properties: %s', properties);
|
||||
return obj;
|
||||
}, target);
|
||||
}
|
||||
//debug('[load] target: %O', target);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse files from given directories, then return an items list, each item contains properties and exports.
|
||||
* For example, parse `controller/group/repository.js`
|
||||
* It returns a item
|
||||
* ```
|
||||
* {
|
||||
* fullpath: '',
|
||||
* properties: [ 'group', 'repository' ],
|
||||
* exports: { ... },
|
||||
* }
|
||||
* ```
|
||||
* `Properties` is an array that contains the directory of a filepath.
|
||||
* `Exports` depends on type, if exports is a function, it will be called. if initializer is specified, it will be called with exports for customizing.
|
||||
* @return {Array} items
|
||||
*/
|
||||
parse() {
|
||||
let files = this.options.match;
|
||||
if (!files) {
|
||||
files = filePatterns();
|
||||
} else {
|
||||
files = Array.isArray(files) ? files : [ files ];
|
||||
}
|
||||
|
||||
let directories = this.options.directory;
|
||||
if (!Array.isArray(directories)) {
|
||||
directories = [ directories ];
|
||||
}
|
||||
|
||||
const items = [];
|
||||
debug('[parse] directories %o', directories);
|
||||
|
||||
for (const directory of directories) {
|
||||
const filepaths = globby.sync(files, { cwd: directory });
|
||||
debug('[parse] filepaths %o', filepaths);
|
||||
for (const filepath of filepaths) {
|
||||
const fullpath = path.join(directory, filepath);
|
||||
if (!fs.statSync(fullpath).isFile()) continue;
|
||||
// get properties
|
||||
// controller/foo/bar.js => [ 'foo', 'bar' ]
|
||||
const properties = getProperties(filepath, this.options);
|
||||
// debug('[parse] properties %o', properties);
|
||||
// controller/foo/bar.js => controller.foo.bar
|
||||
const pathName = directory.split(/[/\\]/).slice(-1) + '.' + properties.join('.');
|
||||
// debug('[parse] pathName %s', pathName);
|
||||
// get exports from the file
|
||||
let exports = getExports(fullpath, this.options, pathName);
|
||||
// ignore exports when it's null or false returned by filter function
|
||||
if (exports == null) continue;
|
||||
|
||||
// set properties of class
|
||||
if (is.class(exports) || isBytecodeClass(exports)) {
|
||||
exports.prototype.pathName = pathName;
|
||||
exports.prototype.fullPath = fullpath;
|
||||
}
|
||||
items.push({ fullpath, properties, exports });
|
||||
//debug('[parse] fullpath %s, properties %o, export %o', fullpath, properties, exports);
|
||||
}
|
||||
}
|
||||
//debug('[parse] items %O', items);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
// convert file path to an array of properties
|
||||
// a/b/c.js => ['a', 'b', 'c']
|
||||
function getProperties(filepath, { caseStyle }) {
|
||||
// if caseStyle is function, return the result of function
|
||||
if (is.function(caseStyle)) {
|
||||
const result = caseStyle(filepath);
|
||||
assert(is.array(result), `caseStyle expect an array, but got ${result}`);
|
||||
return result;
|
||||
}
|
||||
// use default camelize
|
||||
return defaultCamelize(filepath, caseStyle);
|
||||
}
|
||||
|
||||
// Get exports from filepath
|
||||
// If exports is null/undefined, it will be ignored
|
||||
function getExports(fullpath, { initializer, call, inject }, pathName) {
|
||||
let exports = loadFile(fullpath);
|
||||
//debug('[getExports] exports %o', exports);
|
||||
if (initializer) {
|
||||
// exports type is Class or Object
|
||||
exports = initializer(exports, { path: fullpath, pathName });
|
||||
}
|
||||
|
||||
if (is.class(exports) || is.generatorFunction(exports) || is.asyncFunction(exports) || isBytecodeClass(exports)) {
|
||||
return exports;
|
||||
}
|
||||
|
||||
// whether to execute the function
|
||||
if (call && is.function(exports)) {
|
||||
exports = exports(inject);
|
||||
if (exports != null) {
|
||||
return exports;
|
||||
}
|
||||
}
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
function defaultCamelize(filepath, caseStyle) {
|
||||
const properties = filepath.substring(0, filepath.lastIndexOf('.')).split('/');
|
||||
return properties.map(property => {
|
||||
if (!/^[a-z][a-z0-9_-]*$/i.test(property)) {
|
||||
throw new Error(`${property} is not match 'a-z0-9_-' in ${filepath}`);
|
||||
}
|
||||
|
||||
// use default camelize, will capitalize the first letter
|
||||
// foo_bar.js > FooBar
|
||||
// fooBar.js > FooBar
|
||||
// FooBar.js > FooBar
|
||||
// FooBar.js > FooBar
|
||||
// FooBar.js > fooBar
|
||||
property = property.replace(/[_-][a-z]/ig, s => s.substring(1).toUpperCase());
|
||||
let first = property[0];
|
||||
switch (caseStyle) {
|
||||
case 'lower':
|
||||
first = first.toLowerCase();
|
||||
break;
|
||||
case 'upper':
|
||||
first = first.toUpperCase();
|
||||
break;
|
||||
case 'camel':
|
||||
default:
|
||||
}
|
||||
return first + property.substring(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FileLoader,
|
||||
EXPORTS,
|
||||
FULLPATH,
|
||||
};
|
||||
12
ee-core/core/utils/index.d.ts
vendored
Normal file
12
ee-core/core/utils/index.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export declare const extensions: any;
|
||||
export declare function loadFile(filepath: any): any;
|
||||
export declare function callFn(fn: any, args: any, ctx: any): Promise<any>;
|
||||
export declare function getResolvedFilename(filepath: any, baseDir: any): any;
|
||||
/**
|
||||
* 字节码类
|
||||
*/
|
||||
export declare function isBytecodeClass(exports: any): boolean;
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
export declare function filePatterns(): string[];
|
||||
83
ee-core/core/utils/index.js
Normal file
83
ee-core/core/utils/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
require('bytenode');
|
||||
const is = require('is-type-of');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const BuiltinModule = require('module');
|
||||
|
||||
// Guard against poorly mocked module constructors.
|
||||
const Module = module.constructor.length > 1
|
||||
? module.constructor
|
||||
/* istanbul ignore next */
|
||||
: BuiltinModule;
|
||||
|
||||
// Module._extensions:
|
||||
// '.js': [Function (anonymous)],
|
||||
// '.json': [Function (anonymous)],
|
||||
// '.node': [Function: func],
|
||||
// '.jsc': [Function (anonymous)]
|
||||
|
||||
const extensions = Module._extensions;
|
||||
|
||||
function loadFile(filepath) {
|
||||
try {
|
||||
// if not js module, just return content buffer
|
||||
const extname = path.extname(filepath);
|
||||
if (extname && !Module._extensions[extname]) {
|
||||
return fs.readFileSync(filepath);
|
||||
}
|
||||
|
||||
// require js module
|
||||
const obj = require(filepath);
|
||||
if (!obj) return obj;
|
||||
// it's es module
|
||||
if (obj.__esModule) return 'default' in obj ? obj.default : obj;
|
||||
return obj;
|
||||
} catch (err) {
|
||||
err.message = `[ee-core] load file: ${filepath}, error: ${err.message}`;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function callFn(fn, args, ctx) {
|
||||
args = args || [];
|
||||
if (!is.function(fn)) return;
|
||||
return ctx ? fn.call(ctx, ...args) : fn(...args);
|
||||
}
|
||||
|
||||
function getResolvedFilename(filepath, baseDir) {
|
||||
const reg = /[/\\]/g;
|
||||
return filepath.replace(baseDir + path.sep, '').replace(reg, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节码类
|
||||
*/
|
||||
function isBytecodeClass(exports) {
|
||||
let isClass = false;
|
||||
|
||||
// 标识
|
||||
if (exports.toString().indexOf('[class') != -1) {
|
||||
isClass = true;
|
||||
}
|
||||
|
||||
return isClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件类型
|
||||
*/
|
||||
function filePatterns() {
|
||||
const files = [ '**/*.js','**/*.jsc' ];
|
||||
return files;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extensions,
|
||||
loadFile,
|
||||
callFn,
|
||||
getResolvedFilename,
|
||||
isBytecodeClass,
|
||||
filePatterns,
|
||||
};
|
||||
22
ee-core/core/utils/timing.d.ts
vendored
Normal file
22
ee-core/core/utils/timing.d.ts
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
export declare class Timing {
|
||||
_enable: boolean;
|
||||
init(): void;
|
||||
start(name: any, start: any): {
|
||||
name: any;
|
||||
start: any;
|
||||
end: any;
|
||||
duration: any;
|
||||
pid: number;
|
||||
index: number;
|
||||
};
|
||||
end(name: any): any;
|
||||
enable(): void;
|
||||
disable(): void;
|
||||
clear(): void;
|
||||
toJSON(): any[];
|
||||
[MAP]: Map<any, any>;
|
||||
[LIST]: any[];
|
||||
}
|
||||
declare const MAP: unique symbol;
|
||||
declare const LIST: unique symbol;
|
||||
export {};
|
||||
78
ee-core/core/utils/timing.js
Normal file
78
ee-core/core/utils/timing.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const MAP = Symbol('Timing#map');
|
||||
const LIST = Symbol('Timing#list');
|
||||
|
||||
class Timing {
|
||||
|
||||
constructor() {
|
||||
this._enable = true;
|
||||
this[MAP] = new Map();
|
||||
this[LIST] = [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// process start time
|
||||
this.start('Process Start', Date.now() - Math.floor((process.uptime() * 1000)));
|
||||
this.end('Process Start');
|
||||
|
||||
if (typeof process.scriptStartTime === 'number') {
|
||||
// js script start execute time
|
||||
this.start('Script Start', process.scriptStartTime);
|
||||
this.end('Script Start');
|
||||
}
|
||||
}
|
||||
|
||||
start(name, start) {
|
||||
if (!name || !this._enable) return;
|
||||
|
||||
if (this[MAP].has(name)) this.end(name);
|
||||
|
||||
start = start || Date.now();
|
||||
const item = {
|
||||
name,
|
||||
start,
|
||||
end: undefined,
|
||||
duration: undefined,
|
||||
pid: process.pid,
|
||||
index: this[LIST].length,
|
||||
};
|
||||
this[MAP].set(name, item);
|
||||
this[LIST].push(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
end(name) {
|
||||
if (!name || !this._enable) return;
|
||||
assert(this[MAP].has(name), `should run timing.start('${name}') first`);
|
||||
|
||||
const item = this[MAP].get(name);
|
||||
item.end = Date.now();
|
||||
item.duration = item.end - item.start;
|
||||
return item;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this._enable = true;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._enable = false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this[MAP].clear();
|
||||
this[LIST] = [];
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return this[LIST];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Timing
|
||||
};
|
||||
19
ee-core/cross/cross.d.ts
vendored
Normal file
19
ee-core/cross/cross.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { CrossProcess } from "./crossProcess";
|
||||
import internal = require("stream");
|
||||
export declare class Cross {
|
||||
emitter: EventEmitter<[never]>;
|
||||
children: {};
|
||||
childrenMap: {};
|
||||
create(): Promise<void>;
|
||||
run(service: string, opt?: {}): Promise<CrossProcess>;
|
||||
killAll(): void;
|
||||
kill(pid: string|number): void;
|
||||
killByName(name: string): void;
|
||||
getUrl(name: string): string;
|
||||
getProcByName(name: string): CrossProcess;
|
||||
getProc(pid: string|number): CrossProcess;
|
||||
getPids(): string[];
|
||||
_initEventEmitter(): void;
|
||||
}
|
||||
export declare let cross: Cross;
|
||||
152
ee-core/cross/cross.js
Normal file
152
ee-core/cross/cross.js
Normal file
@@ -0,0 +1,152 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const { getConfig } = require('../config');
|
||||
const { sleep, getValueFromArgv, replaceArgsValue } = require('../utils/helper');
|
||||
const { CrossProcess } = require('./crossProcess');
|
||||
const { Events } = require('../const/channel');
|
||||
const { extend } = require('../utils/extend');
|
||||
const { getPort } = require('../utils/port');
|
||||
|
||||
class Cross {
|
||||
constructor() {
|
||||
this.emitter = undefined;
|
||||
|
||||
// pid唯一
|
||||
// {pid:{name,entity}, pid:{name,entity}, ...}
|
||||
this.children = {};
|
||||
|
||||
// name唯一
|
||||
// {name:pid, name:pid, ...}
|
||||
this.childrenMap = {};
|
||||
|
||||
// eventEmitter
|
||||
this._initEventEmitter();
|
||||
}
|
||||
|
||||
async create() {
|
||||
|
||||
// boot services
|
||||
const crossCfg = getConfig().cross;
|
||||
//await sleep(5 * 1000);
|
||||
|
||||
for (let key of Object.keys(crossCfg)) {
|
||||
let val = crossCfg[key];
|
||||
if (val.enable == true) {
|
||||
this.run(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// run
|
||||
async run(service, opt = {}) {
|
||||
const crossConf = getConfig().cross;
|
||||
const defaultOpt = crossConf[service] || {};
|
||||
const targetConf = extend(true, {}, defaultOpt, opt);
|
||||
if (Object.keys(targetConf).length == 0) {
|
||||
throw new Error(`[ee-core] [cross] The service [${service}] config does not exit`);
|
||||
}
|
||||
|
||||
// format params
|
||||
let tmpArgs = targetConf.args;
|
||||
let confPort = parseInt(getValueFromArgv(tmpArgs, 'port'));
|
||||
// 某些程序给它传入不存在的参数时会报错
|
||||
if (isNaN(confPort) && targetConf.port > 0) {
|
||||
confPort = targetConf.port;
|
||||
}
|
||||
if (confPort > 0) {
|
||||
// 动态生成port,传入的端口必须为int
|
||||
confPort = await getPort({ port: confPort });
|
||||
// 替换port
|
||||
targetConf.args = replaceArgsValue(tmpArgs, "port", String(confPort));
|
||||
}
|
||||
|
||||
// 创建进程
|
||||
const subProcess = new CrossProcess(this, { targetConf, port: confPort });
|
||||
let uniqueName = targetConf.name;
|
||||
if (this.childrenMap.hasOwnProperty(uniqueName)) {
|
||||
uniqueName = uniqueName + "-" + String(subProcess.pid);
|
||||
}
|
||||
this.childrenMap[uniqueName] = subProcess.pid;
|
||||
subProcess.name = uniqueName;
|
||||
this.children[subProcess.pid] = {
|
||||
name: uniqueName,
|
||||
entity: subProcess
|
||||
};
|
||||
|
||||
return subProcess;
|
||||
}
|
||||
|
||||
killAll() {
|
||||
Object.keys(this.children).forEach(pid => {
|
||||
this.kill(pid)
|
||||
});
|
||||
}
|
||||
|
||||
kill(pid) {
|
||||
const entity = this.getProc(pid);
|
||||
if (entity) {
|
||||
entity.kill();
|
||||
}
|
||||
}
|
||||
|
||||
killByName(name) {
|
||||
const entity = this.getProcByName(name);
|
||||
if (entity) {
|
||||
entity.kill();
|
||||
}
|
||||
}
|
||||
|
||||
getUrl(name) {
|
||||
const entity = this.getProcByName(name);
|
||||
const url = entity.getUrl();
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// 获取 proc
|
||||
getProcByName(name) {
|
||||
const pid = this.childrenMap[name];
|
||||
if (!pid) {
|
||||
throw new Error(`[ee-core] [cross] The process named [${name}] does not exit`);
|
||||
}
|
||||
const entity = this.getProc(pid);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
// 获取 proc
|
||||
getProc(pid) {
|
||||
const child = this.children[pid];
|
||||
if (!pid) {
|
||||
throw new Error(`[ee-core] [cross] The process pid [${pid}] does not exit`);
|
||||
}
|
||||
|
||||
return child.entity;
|
||||
}
|
||||
|
||||
// 获取pids
|
||||
getPids() {
|
||||
let pids = Object.keys(this.children);
|
||||
return pids;
|
||||
}
|
||||
|
||||
_initEventEmitter() {
|
||||
this.emitter = new EventEmitter();
|
||||
this.emitter.on(Events.childProcessExit, (data) => {
|
||||
const child = this.children[data.pid];
|
||||
delete this.childrenMap[child.name];
|
||||
delete this.children[data.pid];
|
||||
});
|
||||
this.emitter.on(Events.childProcessError, (data) => {
|
||||
const child = this.children[data.pid];
|
||||
delete this.childrenMap[child.name];
|
||||
delete this.children[data.pid];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Cross,
|
||||
cross: new Cross()
|
||||
};
|
||||
29
ee-core/cross/crossProcess.d.ts
vendored
Normal file
29
ee-core/cross/crossProcess.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
import EventEmitter = require("events");
|
||||
export declare class CrossProcess {
|
||||
constructor(host: any, opt?: {});
|
||||
emitter: EventEmitter<[never]>;
|
||||
host: any;
|
||||
child: any;
|
||||
pid: number;
|
||||
port: number;
|
||||
name: string;
|
||||
config: {};
|
||||
/**
|
||||
* 初始化子进程
|
||||
*/
|
||||
_init(options?: {}): void;
|
||||
/**
|
||||
* kill
|
||||
*/
|
||||
kill(timeout?: number): void;
|
||||
getUrl(): string;
|
||||
getArgsObj(): {
|
||||
_: any[];
|
||||
};
|
||||
setPort(port: string|number): void;
|
||||
_generateId(): string;
|
||||
/**
|
||||
* exit electron
|
||||
*/
|
||||
_exitElectron(timeout?: number): void;
|
||||
}
|
||||
168
ee-core/cross/crossProcess.js
Normal file
168
ee-core/cross/crossProcess.js
Normal file
@@ -0,0 +1,168 @@
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events');
|
||||
const path = require('path');
|
||||
const crossSpawn = require('cross-spawn');
|
||||
const { coreLogger } = require('../log');
|
||||
const { getExtraResourcesDir, isPackaged, isDev, getBaseDir } = require('../ps');
|
||||
const { Events } = require('../const/channel');
|
||||
const { getRandomString, getValueFromArgv } = require('../utils/helper');
|
||||
const { is } = require('../utils');
|
||||
const { parseArgv } = require('../utils/pargv');
|
||||
const { app: electronApp } = require('electron');
|
||||
|
||||
class CrossProcess {
|
||||
constructor(host, opt = {}) {
|
||||
this.emitter = new EventEmitter();
|
||||
this.host = host;
|
||||
this.child = undefined;
|
||||
this.pid = 0;
|
||||
this.port = 0;
|
||||
this.name = "";
|
||||
this.config = {};
|
||||
this._init(opt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化子进程
|
||||
*/
|
||||
_init(options = {}) {
|
||||
const { targetConf, port } = options;
|
||||
this.config = targetConf;
|
||||
this.port = port;
|
||||
|
||||
// 该名称如果在childrenMap重复,会被重写
|
||||
this.name = targetConf.name;
|
||||
|
||||
// Launch executable program
|
||||
let cmdPath = '';
|
||||
let cmdArgs = targetConf.args;
|
||||
let execDir = getExtraResourcesDir();
|
||||
let standardOutput = ['inherit', 'inherit', 'inherit', 'ipc'];
|
||||
if (isPackaged()) {
|
||||
standardOutput = ['ignore', 'ignore', 'ignore', 'ipc'];
|
||||
}
|
||||
if (targetConf.stdio) {
|
||||
standardOutput = targetConf.stdio;
|
||||
}
|
||||
|
||||
const { cmd, directory } = targetConf;
|
||||
// use cmd first
|
||||
if (cmd) {
|
||||
if (!directory) {
|
||||
throw new Error(`[ee-core] [cross] The config [directory] attribute does not exist`);
|
||||
}
|
||||
cmdPath = cmd;
|
||||
if (!path.isAbsolute(cmd) && !isDev()) {
|
||||
cmdPath = path.join(getExtraResourcesDir(), cmd);
|
||||
}
|
||||
} else {
|
||||
cmdPath = path.join(getExtraResourcesDir(), targetConf.name);
|
||||
}
|
||||
|
||||
// windows
|
||||
if (is.windows() && path.extname(cmdPath) != '.exe') {
|
||||
// Complete the executable program extension
|
||||
// notice: python.exe may bring up the App Store
|
||||
if (targetConf.windowsExtname === true || !isDev()) {
|
||||
cmdPath += ".exe";
|
||||
}
|
||||
}
|
||||
|
||||
// executable program directory
|
||||
if (directory && path.isAbsolute(directory)) {
|
||||
execDir = directory;
|
||||
} else if (directory && !path.isAbsolute(directory)) {
|
||||
if (isDev()) {
|
||||
execDir = path.join(getBaseDir(), directory);
|
||||
} else {
|
||||
execDir = path.join(getExtraResourcesDir(), directory);
|
||||
}
|
||||
} else {
|
||||
execDir = getExtraResourcesDir();
|
||||
}
|
||||
|
||||
coreLogger.info(`[ee-core] [cross/run] cmd: ${cmdPath}, args: ${cmdArgs}`);
|
||||
const coreProcess = crossSpawn(cmdPath, cmdArgs, {
|
||||
stdio: standardOutput,
|
||||
detached: false,
|
||||
cwd: execDir,
|
||||
maxBuffer: 1024 * 1024 * 1024
|
||||
});
|
||||
this.child = coreProcess;
|
||||
this.pid = coreProcess.pid;
|
||||
|
||||
coreProcess.on('exit', (code, signal) => {
|
||||
let data = {
|
||||
pid: this.pid
|
||||
}
|
||||
this.host.emitter.emit(Events.childProcessExit, data);
|
||||
// Child process closed: The child process was killed externally or an internal error caused the application to stop, resulting in the application exiting
|
||||
coreLogger.info(`[ee-core] [corss/process] received a exit from child-process, code:${code}, signal:${signal}, pid:${this.pid}, cmd:${cmdPath}, args: ${cmdArgs}`);
|
||||
this._exitElectron();
|
||||
});
|
||||
|
||||
coreProcess.on('error', (err) => {
|
||||
let data = {
|
||||
pid: this.pid
|
||||
}
|
||||
this.host.emitter.emit(Events.childProcessError, data);
|
||||
coreLogger.error(`[ee-core] [corss/process] received a error from child-process, error: ${err}, pid:${this.pid}`);
|
||||
this._exitElectron();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* kill
|
||||
*/
|
||||
kill(timeout = 1000) {
|
||||
this.child.kill('SIGINT');
|
||||
setTimeout(() => {
|
||||
if (this.child.killed) return;
|
||||
this.child.kill('SIGKILL');
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
getUrl() {
|
||||
const ssl = getValueFromArgv(this.config.args, 'ssl');
|
||||
let hostname = getValueFromArgv(this.config.args, 'hostname')
|
||||
let protocol = 'http://';
|
||||
if (ssl && (ssl == 'true' || ssl == '1')) {
|
||||
protocol = 'https://';
|
||||
}
|
||||
hostname = hostname ? hostname : '127.0.0.1';
|
||||
const url = protocol + hostname + ":" + this.port;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
getArgsObj() {
|
||||
const obj = parseArgv(this.config.args);
|
||||
return obj;
|
||||
}
|
||||
|
||||
setPort(port) {
|
||||
this.port = parseInt(port);
|
||||
}
|
||||
|
||||
_generateId() {
|
||||
const rid = getRandomString();
|
||||
return `node:${this.pid}:${rid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* exit electron
|
||||
*/
|
||||
_exitElectron(timeout = 1000) {
|
||||
if (this.config.appExit) {
|
||||
setTimeout(() => {
|
||||
// 主进程退出
|
||||
electronApp.quit();
|
||||
}, timeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CrossProcess
|
||||
};
|
||||
2
ee-core/cross/index.d.ts
vendored
Normal file
2
ee-core/cross/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import { Cross, cross } from "./cross";
|
||||
export { Cross, cross };
|
||||
8
ee-core/cross/index.js
Normal file
8
ee-core/cross/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const { Cross, cross} = require('./cross');
|
||||
|
||||
module.exports = {
|
||||
Cross,
|
||||
cross
|
||||
};
|
||||
5
ee-core/electron/app/index.d.ts
vendored
Normal file
5
ee-core/electron/app/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* 创建electron应用
|
||||
*/
|
||||
export declare function createElectron(): void;
|
||||
export { electronApp };
|
||||
48
ee-core/electron/app/index.js
Normal file
48
ee-core/electron/app/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:electron:app');
|
||||
const { app: electronApp } = require('electron');
|
||||
const { coreLogger } = require('../../log');
|
||||
const { is } = require('../../utils');
|
||||
const { cross } = require('../../cross');
|
||||
const { createMainWindow, setCloseAndQuit, loadServer } = require('../window');
|
||||
const { eventBus, ElectronAppReady, BeforeClose, Preload } = require('../../app/events');
|
||||
const { getConfig } = require('../../config');
|
||||
|
||||
/**
|
||||
* 创建electron应用
|
||||
*/
|
||||
function createElectron() {
|
||||
const { singleLock } = getConfig();
|
||||
// 允许多个实例
|
||||
const gotTheLock = electronApp.requestSingleInstanceLock();
|
||||
if (singleLock && !gotTheLock) {
|
||||
electronApp.quit();
|
||||
}
|
||||
|
||||
electronApp.whenReady().then(() => {
|
||||
createMainWindow();
|
||||
eventBus.emitLifecycle(Preload);
|
||||
loadServer();
|
||||
})
|
||||
|
||||
electronApp.on('window-all-closed', () => {
|
||||
if (!is.macOS()) {
|
||||
coreLogger.info('[ee-core] [lib/eeApp] window-all-closed quit');
|
||||
electronApp.quit();
|
||||
}
|
||||
})
|
||||
|
||||
electronApp.on('before-quit', () => {
|
||||
setCloseAndQuit(true);
|
||||
eventBus.emitLifecycle(BeforeClose);
|
||||
cross.killAll();
|
||||
})
|
||||
|
||||
eventBus.emitLifecycle(ElectronAppReady);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
electronApp,
|
||||
createElectron,
|
||||
};
|
||||
3
ee-core/electron/index.d.ts
vendored
Normal file
3
ee-core/electron/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { getMainWindow, setCloseAndQuit, getCloseAndQuit } from "./window";
|
||||
export declare function loadElectron(): void;
|
||||
export { getMainWindow, setCloseAndQuit, getCloseAndQuit };
|
||||
16
ee-core/electron/index.js
Normal file
16
ee-core/electron/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
const { createElectron } = require("./app");
|
||||
const { getMainWindow, setCloseAndQuit, getCloseAndQuit } = require("./window");
|
||||
|
||||
// load socket server
|
||||
function loadElectron() {
|
||||
createElectron();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadElectron,
|
||||
getMainWindow,
|
||||
setCloseAndQuit,
|
||||
getCloseAndQuit
|
||||
};
|
||||
6
ee-core/electron/window/index.d.ts
vendored
Normal file
6
ee-core/electron/window/index.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export declare function getMainWindow(): any;
|
||||
export declare function createMainWindow(): any;
|
||||
export declare function restoreMainWindow(): void;
|
||||
export declare function setCloseAndQuit(flag: any): void;
|
||||
export declare function getCloseAndQuit(): boolean;
|
||||
export declare function loadServer(): Promise<void>;
|
||||
269
ee-core/electron/window/index.js
Normal file
269
ee-core/electron/window/index.js
Normal file
@@ -0,0 +1,269 @@
|
||||
'use strict';
|
||||
|
||||
const debug = require('debug')('ee-core:electron:window');
|
||||
const is = require('is-type-of');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const { BrowserWindow } = require('electron');
|
||||
const { getConfig } = require('../../config');
|
||||
const { eventBus, WindowReady } = require('../../app/events');
|
||||
const { env, isDev, getBaseDir } = require('../../ps');
|
||||
const { loadFile } = require('../../loader');
|
||||
const { isFileProtocol } = require('../../utils');
|
||||
const { getHtmlFilepath } = require('../../html');
|
||||
const { fileIsExist, sleep } = require('../../utils/helper');
|
||||
const { coreLogger } = require('../../log');
|
||||
const { extend } = require('../../utils/extend');
|
||||
const { cross } = require('../../cross');
|
||||
|
||||
const Instance = {
|
||||
mainWindow: null,
|
||||
closeAndQuit: true,
|
||||
};
|
||||
|
||||
// getMainWindow
|
||||
function getMainWindow() {
|
||||
return Instance.mainWindow;
|
||||
}
|
||||
|
||||
// Create the main application window
|
||||
function createMainWindow() {
|
||||
const { openDevTools, windowsOption } = getConfig();
|
||||
const win = new BrowserWindow(windowsOption);
|
||||
Instance.mainWindow = win;
|
||||
|
||||
// DevTools
|
||||
if (is.object(openDevTools)) {
|
||||
win.webContents.openDevTools(openDevTools);
|
||||
} else if (openDevTools === true) {
|
||||
win.webContents.openDevTools({
|
||||
mode: 'bottom'
|
||||
});
|
||||
}
|
||||
|
||||
eventBus.emitLifecycle(WindowReady);
|
||||
return win;
|
||||
}
|
||||
|
||||
// restored window
|
||||
function restoreMainWindow() {
|
||||
if (Instance.mainWindow) {
|
||||
if (Instance.mainWindow.isMinimized()) {
|
||||
Instance.mainWindow.restore();
|
||||
}
|
||||
Instance.mainWindow.show();
|
||||
Instance.mainWindow.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the flag for exiting after close all windows
|
||||
function setCloseAndQuit(flag) {
|
||||
Instance.closeAndQuit = flag;
|
||||
}
|
||||
|
||||
function getCloseAndQuit() {
|
||||
return Instance.closeAndQuit;
|
||||
}
|
||||
|
||||
// load server
|
||||
// type: remote | single
|
||||
async function loadServer() {
|
||||
let type = 'spa';
|
||||
let url = '';
|
||||
const { remote, mainServer } = getConfig();
|
||||
const win = getMainWindow();
|
||||
|
||||
// remote model
|
||||
if (remote.enable == true) {
|
||||
type = 'remote';
|
||||
url = remote.url;
|
||||
loadMainUrl(type, url);
|
||||
return;
|
||||
}
|
||||
|
||||
// 开发环境
|
||||
if (isDev()) {
|
||||
let url;
|
||||
let load = 'url';
|
||||
|
||||
const binFile = path.join(getBaseDir(), "./cmd/bin.js");
|
||||
const binConfig = loadFile(binFile);
|
||||
const { dev } = binConfig;
|
||||
// tips: match with ee-bin
|
||||
const frontendConf = extend(true, {
|
||||
protocol: 'http://',
|
||||
hostname: 'localhost',
|
||||
port: 8080,
|
||||
indexPath: 'index.html',
|
||||
}, dev.frontend);
|
||||
const electronConf = extend(true, {
|
||||
loadingPage: '/public/html/loading.html',
|
||||
}, dev.electron);
|
||||
|
||||
url = frontendConf.protocol + frontendConf.hostname + ':' + frontendConf.port;
|
||||
if (isFileProtocol(frontendConf.protocol)) {
|
||||
url = path.join(getBaseDir(), frontendConf.directory, frontendConf.indexPath);
|
||||
load = 'file';
|
||||
}
|
||||
|
||||
// Check if UI serve is started, load a boot page first
|
||||
if (load == 'url') {
|
||||
// loading page
|
||||
let lp = getHtmlFilepath('boot.html');
|
||||
if (electronConf.hasOwnProperty('loadingPage') && electronConf.loadingPage != '') {
|
||||
lp = path.join(getBaseDir(), electronConf.loadingPage);
|
||||
}
|
||||
_loadingPage(lp);
|
||||
|
||||
// check frontend is ready
|
||||
const retryTimes = frontendConf.force === true ? 3 : 60;
|
||||
let count = 0;
|
||||
let frontendReady = false;
|
||||
while(!frontendReady && count < retryTimes){
|
||||
await sleep(1 * 1000);
|
||||
try {
|
||||
await axios({
|
||||
method: 'get',
|
||||
url,
|
||||
timeout: 1000,
|
||||
proxy: false,
|
||||
headers: {
|
||||
'Accept': 'text/html, application/json, text/plain, */*',
|
||||
},
|
||||
//responseType: 'text',
|
||||
});
|
||||
frontendReady = true;
|
||||
} catch(err) {
|
||||
// console.warn(err.stack)
|
||||
}
|
||||
count++;
|
||||
}
|
||||
debug('it takes %d seconds to start the frontend', count);
|
||||
|
||||
if (frontendReady == false && frontendConf.force !== true) {
|
||||
const bootFailurePage = getHtmlFilepath('failure.html');
|
||||
win.loadFile(bootFailurePage);
|
||||
coreLogger.error(`[ee-core] Please check the ${url} !`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loadMainUrl(type, url, load);
|
||||
return;
|
||||
}
|
||||
|
||||
// 生产环境
|
||||
// cross takeover web
|
||||
if (mainServer.takeover.length > 0) {
|
||||
await crossTakeover()
|
||||
return
|
||||
}
|
||||
|
||||
// 主进程
|
||||
url = path.join(getBaseDir(), mainServer.indexPath);
|
||||
loadMainUrl(type, url, 'file');
|
||||
}
|
||||
|
||||
/**
|
||||
* 主服务
|
||||
* @params load <string> value: "url" 、 "file"
|
||||
*/
|
||||
function loadMainUrl(type, url, load = 'url') {
|
||||
const { mainServer } = getConfig();
|
||||
const mainWindow = getMainWindow();
|
||||
coreLogger.info('[ee-core] Env: %s, Type: %s', env(), type);
|
||||
coreLogger.info('[ee-core] App running at: %s', url);
|
||||
if (load == 'file') {
|
||||
mainWindow.loadFile(url, mainServer.options)
|
||||
.then()
|
||||
.catch((err)=>{
|
||||
coreLogger.error(`[ee-core] Please check the ${url} !`);
|
||||
});
|
||||
} else {
|
||||
mainWindow.loadURL(url, mainServer.options)
|
||||
.then()
|
||||
.catch((err)=>{
|
||||
coreLogger.error(`[ee-core] Please check the ${url} !`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// loading page
|
||||
function _loadingPage(name) {
|
||||
if (!fileIsExist(name)) {
|
||||
return
|
||||
}
|
||||
const win = getMainWindow();
|
||||
win.loadFile(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* cross takeover web
|
||||
*/
|
||||
async function crossTakeover() {
|
||||
const crossConf = getConfig().cross;
|
||||
const mainConf = getConfig().mainServer;
|
||||
|
||||
// loading page
|
||||
if (mainConf.loadingPage.length > 0) {
|
||||
const lp = path.join(getBaseDir, mainConf.loadingPage);
|
||||
_loadingPage(lp);
|
||||
}
|
||||
|
||||
// cross service url
|
||||
const service = mainConf.takeover;
|
||||
if (!crossConf.hasOwnProperty(service)) {
|
||||
throw new Error(`[ee-core] Please Check the value of mainServer.takeover in the config file !`);
|
||||
}
|
||||
// check service
|
||||
if (crossConf[service].enable != true) {
|
||||
throw new Error(`[ee-core] Please Check the value of cross.${service} enable is true !`);
|
||||
}
|
||||
|
||||
const entityName = crossConf[service].name;
|
||||
const url = cross.getUrl(entityName);
|
||||
|
||||
// 循环检查
|
||||
let count = 0;
|
||||
let serviceReady = false;
|
||||
const times = isDev() ? 20 : 100;
|
||||
const sleeptime = isDev() ? 1000 : 200;
|
||||
while(!serviceReady && count < times){
|
||||
await sleep(sleeptime);
|
||||
try {
|
||||
await axios({
|
||||
method: 'get',
|
||||
url,
|
||||
timeout: 100,
|
||||
proxy: false,
|
||||
headers: {
|
||||
'Accept': 'text/html, application/json, text/plain, */*',
|
||||
},
|
||||
});
|
||||
serviceReady = true;
|
||||
} catch(err) {
|
||||
// console.warn(err.stack)
|
||||
}
|
||||
count++;
|
||||
}
|
||||
debug('it takes %d seconds to start the cross serivce', count * sleeptime);
|
||||
|
||||
if (serviceReady == false) {
|
||||
const bootFailurePage = getHtmlFilepath('cross-failure.html');
|
||||
const mainWindow = getMainWindow();
|
||||
mainWindow.loadFile(bootFailurePage);
|
||||
throw new Error(`[ee-core] Please check cross service [${service}] ${url} !`)
|
||||
}
|
||||
|
||||
coreLogger.info(`[ee-core] cross service [${service}] is started successfully`);
|
||||
loadMainUrl('spa', url);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getMainWindow,
|
||||
createMainWindow,
|
||||
restoreMainWindow,
|
||||
setCloseAndQuit,
|
||||
getCloseAndQuit,
|
||||
loadServer
|
||||
};
|
||||
9
ee-core/exception/index.d.ts
vendored
Normal file
9
ee-core/exception/index.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// Capture exceptions
|
||||
export declare function loadException(): void;
|
||||
// When an exception is thrown on a process without being caught, trigger the event and silence the exception
|
||||
export declare function uncaughtExceptionHandler(): void;
|
||||
// When the reject exception in the promise is not caught using catch in the synchronization task, it will trigger the event,
|
||||
// Even if catch is used in asynchronous situations, it will trigger the event
|
||||
export declare function unhandledRejectionHandler(): void;
|
||||
// This event is triggered when an exception is thrown on the process without being caught.
|
||||
export declare function uncaughtExceptionMonitorHandler(): void;
|
||||
101
ee-core/exception/index.js
Normal file
101
ee-core/exception/index.js
Normal file
@@ -0,0 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const { coreLogger } = require('../log');
|
||||
const { isForkedChild, isRenderer, isDev, isMain } = require('../ps');
|
||||
const { getConfig } = require('../config');
|
||||
const { childMessage } = require('../message');
|
||||
|
||||
// 捕获异常
|
||||
function loadException() {
|
||||
uncaughtExceptionHandler();
|
||||
unhandledRejectionHandler();
|
||||
}
|
||||
|
||||
// 当进程上抛出异常而没有被捕获时触发该事件,并且使异常静默。
|
||||
function uncaughtExceptionHandler() {
|
||||
process.on('uncaughtException', function(err) {
|
||||
if (!(err instanceof Error)) {
|
||||
err = new Error(String(err));
|
||||
}
|
||||
|
||||
if (err.name === 'Error') {
|
||||
err.name = 'unhandledExceptionError';
|
||||
}
|
||||
|
||||
coreLogger.error(err);
|
||||
|
||||
_devError(err);
|
||||
|
||||
_exit();
|
||||
});
|
||||
}
|
||||
|
||||
// 当进程上抛出异常而没有被捕获时触发该事件。
|
||||
function uncaughtExceptionMonitorHandler() {
|
||||
process.on('uncaughtExceptionMonitor', function(err, origin) {
|
||||
if (!(err instanceof Error)) {
|
||||
err = new Error(String(err));
|
||||
}
|
||||
coreLogger.error('uncaughtExceptionMonitor:',err);
|
||||
});
|
||||
}
|
||||
|
||||
// 当promise中reject的异常在同步任务中没有使用catch捕获就会触发该事件,
|
||||
// 即便是在异步情况下使用了catch也会触发该事件
|
||||
function unhandledRejectionHandler() {
|
||||
process.on('unhandledRejection', function(err) {
|
||||
if (!(err instanceof Error)) {
|
||||
const newError = new Error(String(err));
|
||||
// err maybe an object, try to copy the name, message and stack to the new error instance
|
||||
if (err) {
|
||||
if (err.name) newError.name = err.name;
|
||||
if (err.message) newError.message = err.message;
|
||||
if (err.stack) newError.stack = err.stack;
|
||||
}
|
||||
err = newError;
|
||||
}
|
||||
if (err.name === 'Error') {
|
||||
err.name = 'unhandledRejectionError';
|
||||
}
|
||||
coreLogger.error(err);
|
||||
_devError(err);
|
||||
_exit();
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是子进程,发送错误到主进程控制台
|
||||
function _devError (err) {
|
||||
if (isForkedChild() && isDev()) {
|
||||
childMessage.sendErrorToTerminal(err);
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获异常后是否退出
|
||||
function _exit () {
|
||||
const { mainExit, childExit, rendererExit } = getConfig().exception;
|
||||
|
||||
if (isMain() && mainExit == true) {
|
||||
_delayExit();
|
||||
} else if (isForkedChild() && childExit == true) {
|
||||
_delayExit();
|
||||
} else if (isRenderer() && rendererExit == true) {
|
||||
_delayExit();
|
||||
} else {
|
||||
// other
|
||||
}
|
||||
}
|
||||
|
||||
// 捕获异常后是否退出
|
||||
function _delayExit() {
|
||||
// 等待日志等异步写入完成
|
||||
setTimeout(() => {
|
||||
process.exit();
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadException,
|
||||
uncaughtExceptionHandler,
|
||||
unhandledRejectionHandler,
|
||||
uncaughtExceptionMonitorHandler
|
||||
};
|
||||
98
ee-core/html/boot.html
Normal file
98
ee-core/html/boot.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<style>
|
||||
#loadingPage {
|
||||
background-color: #dedede;
|
||||
font-size: 12px;
|
||||
}
|
||||
.base {
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.desc {
|
||||
margin: 0, 0, 20px, 0;
|
||||
}
|
||||
.loading,
|
||||
.loading > div {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.loading {
|
||||
display: block;
|
||||
font-size: 0;
|
||||
color: #06b359;
|
||||
}
|
||||
.loading.la-dark {
|
||||
color: #07C160;
|
||||
}
|
||||
.loading > div {
|
||||
display: inline-block;
|
||||
float: none;
|
||||
background-color: currentColor;
|
||||
border: 0 solid currentColor;
|
||||
}
|
||||
.loading {
|
||||
width: 92px;
|
||||
height: 92px;
|
||||
}
|
||||
.loading > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
background: transparent;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
border-radius: 100%;
|
||||
animation: ball-clip-rotate-multiple-rotate 1s ease-in-out infinite;
|
||||
}
|
||||
.loading > div:first-child {
|
||||
position: absolute;
|
||||
width: 92px;
|
||||
height: 92px;
|
||||
border-right-color: transparent;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
.loading > div:last-child {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
animation-duration: 0.5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
@keyframes ball-clip-rotate-multiple-rotate {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translate(-50%, -50%) rotate(180deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="boot">
|
||||
<div class='base'>
|
||||
<!-- Booting the frontend service ... -->
|
||||
<!-- <div class='desc'>
|
||||
Booting frontend
|
||||
</div> -->
|
||||
<div class="loading">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
ee-core/html/cross-failure.html
Normal file
28
ee-core/html/cross-failure.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<title>Booting failure</title>
|
||||
<style>
|
||||
#failure {
|
||||
background-color: #dedede;
|
||||
font-size: 14px;
|
||||
}
|
||||
.base {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="failure">
|
||||
<div class='base'>
|
||||
Startup failed, please check cross service is runing !
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
ee-core/html/failure.html
Normal file
28
ee-core/html/failure.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<title>Booting failure</title>
|
||||
<style>
|
||||
#failure {
|
||||
background-color: #dedede;
|
||||
font-size: 14px;
|
||||
}
|
||||
.base {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="failure">
|
||||
<div class='base'>
|
||||
Startup failed, please check frontend service is runing !
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
ee-core/html/index.d.ts
vendored
Normal file
1
ee-core/html/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare function getHtmlFilepath(name: string): string;
|
||||
11
ee-core/html/index.js
Normal file
11
ee-core/html/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const path = require('path');
|
||||
|
||||
// Html
|
||||
function getHtmlFilepath(name){
|
||||
const pagePath = path.join(__dirname, name);
|
||||
return pagePath;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getHtmlFilepath
|
||||
};
|
||||
2
ee-core/index.d.ts
vendored
Normal file
2
ee-core/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export { ElectronEgg };
|
||||
import { ElectronEgg } from "./app";
|
||||
7
ee-core/index.js
Normal file
7
ee-core/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { ElectronEgg } = require('./app');
|
||||
|
||||
module.exports = {
|
||||
ElectronEgg
|
||||
}
|
||||
26
ee-core/jobs/child-pool/index.d.ts
vendored
Normal file
26
ee-core/jobs/child-pool/index.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import EventEmitter = require("events");
|
||||
import LoadBalancer = require("../load-balancer");
|
||||
export declare class ChildPoolJob extends EventEmitter<[never]> {
|
||||
constructor(opt?: {});
|
||||
config: any;
|
||||
boundMap: Map<any, any>;
|
||||
children: {};
|
||||
min: number;
|
||||
max: number;
|
||||
strategy: string;
|
||||
weights: any[];
|
||||
LB: LoadBalancer;
|
||||
_initEvents(): void;
|
||||
_removeChild(pid: any): void;
|
||||
create(number?: number): Promise<string[]>;
|
||||
_childCreated(childProcess: any): void;
|
||||
run(filepath: any, params?: {}): any;
|
||||
runPromise(filepath: any, params?: {}): Promise<any>;
|
||||
getBoundChild(boundId: any): any;
|
||||
getChildByPid(pid: any): any;
|
||||
getChild(): any;
|
||||
getPids(): string[];
|
||||
// kill all
|
||||
// type: sequence | parallel
|
||||
killAll(type?: string): void;
|
||||
}
|
||||
190
ee-core/jobs/child-pool/index.js
Normal file
190
ee-core/jobs/child-pool/index.js
Normal file
@@ -0,0 +1,190 @@
|
||||
const EventEmitter = require('events');
|
||||
const LoadBalancer = require('../load-balancer');
|
||||
const { getFullpath } = require('../../loader');
|
||||
const { JobProcess } = require('../child/jobProcess');
|
||||
const { Events } = require('../../const/channel');
|
||||
const { validValue } = require('../../utils/helper');
|
||||
const { getConfig } = require('../../config');
|
||||
|
||||
class ChildPoolJob extends EventEmitter {
|
||||
|
||||
constructor(opt = {}) {
|
||||
super();
|
||||
let options = Object.assign({
|
||||
weights: [],
|
||||
}, opt);
|
||||
|
||||
this.config = {};
|
||||
this.boundMap = new Map();
|
||||
this.children = {};
|
||||
this.min = 3;
|
||||
this.max = 6;
|
||||
this.strategy = 'polling';
|
||||
this.weights = new Array(this.max).fill().map((v, i) => {
|
||||
let w = validValue(options.weights[i]) ? options.weights[i] : 1
|
||||
return w;
|
||||
});
|
||||
|
||||
let lbOpt = {
|
||||
algorithm: LoadBalancer.Algorithm.polling,
|
||||
targets: [],
|
||||
}
|
||||
this.LB = new LoadBalancer(lbOpt);
|
||||
|
||||
const cfg = getConfig().jobs;
|
||||
if (cfg) {
|
||||
this.config = cfg;
|
||||
}
|
||||
|
||||
this._initEvents();
|
||||
}
|
||||
|
||||
_initEvents() {
|
||||
this.on(Events.childProcessExit, (data) => {
|
||||
this._removeChild(data.pid);
|
||||
});
|
||||
this.on(Events.childProcessError, (data) => {
|
||||
this._removeChild(data.pid);
|
||||
});
|
||||
}
|
||||
|
||||
_removeChild(pid) {
|
||||
const length = Object.keys(this.children).length;
|
||||
const lbOpt = {
|
||||
id: pid,
|
||||
weight: this.weights[length - 1],
|
||||
}
|
||||
this.LB.del(lbOpt);
|
||||
delete this.children[pid];
|
||||
}
|
||||
|
||||
async create(number = 3) {
|
||||
if (number < 0 || number > this.max) {
|
||||
throw new Error(`[ee-core] [jobs/child-pool] The number is invalid !`);
|
||||
}
|
||||
let currentNumber = this.children.length;
|
||||
if (currentNumber > this.max) {
|
||||
throw new Error(`[ee-core] [jobs/child-pool] The number of current processes number: ${currentNumber} is greater than the maximum: ${this.max} !`);
|
||||
}
|
||||
|
||||
if (number + currentNumber > this.max) {
|
||||
number = this.max - currentNumber;
|
||||
}
|
||||
|
||||
// args
|
||||
let options = Object.assign({
|
||||
processArgs: {
|
||||
type: 'childPoolJob'
|
||||
}
|
||||
}, {});
|
||||
for (let i = 1; i <= number; i++) {
|
||||
let task = new JobProcess(this, options);
|
||||
this._childCreated(task);
|
||||
}
|
||||
|
||||
let pids = Object.keys(this.children);
|
||||
|
||||
return pids;
|
||||
}
|
||||
|
||||
// Post creation processing of child processes
|
||||
_childCreated(childProcess) {
|
||||
let pid = childProcess.pid;
|
||||
this.children[pid] = childProcess;
|
||||
|
||||
const length = Object.keys(this.children).length;
|
||||
let lbTask = {
|
||||
id: pid,
|
||||
weight: this.weights[length - 1],
|
||||
}
|
||||
this.LB.add(lbTask);
|
||||
}
|
||||
|
||||
// Execute a job file
|
||||
run(filepath, params = {}) {
|
||||
const jobPath = getFullpath(filepath);
|
||||
const childProcess = this.getChild();
|
||||
childProcess.dispatch('run', jobPath, params);
|
||||
|
||||
return childProcess;
|
||||
}
|
||||
|
||||
// Asynchronous execution of a job file
|
||||
async runPromise(filepath, params = {}) {
|
||||
return this.run(filepath, params);
|
||||
}
|
||||
|
||||
// Get the bound process object
|
||||
getBoundChild(boundId) {
|
||||
let proc;
|
||||
const boundPid = this.boundMap.get(boundId);
|
||||
if (boundPid) {
|
||||
proc = this.children[boundPid];
|
||||
return proc;
|
||||
}
|
||||
|
||||
// 获取进程并绑定
|
||||
proc = this.getChild();
|
||||
this.boundMap.set(boundId, proc.pid);
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
// Retrieve a sub process object through PID
|
||||
getChildByPid(pid) {
|
||||
let proc = this.children[pid] || null;
|
||||
return proc;
|
||||
}
|
||||
|
||||
// Get a sub process object
|
||||
getChild() {
|
||||
let proc;
|
||||
const currentPids = Object.keys(this.children);
|
||||
|
||||
// 没有则创建
|
||||
if (currentPids.length == 0) {
|
||||
let subIds = this.create(1);
|
||||
proc = this.children[subIds[0]];
|
||||
} else {
|
||||
// 从池子中获取一个
|
||||
let onePid = this.LB.pickOne().id;
|
||||
proc = this.children[onePid];
|
||||
}
|
||||
|
||||
if (!proc) {
|
||||
let errorMessage = `[ee-core] [jobs/child-pool] Failed to obtain the child process !`
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
// Get current pigs
|
||||
getPids() {
|
||||
let pids = Object.keys(this.children);
|
||||
return pids;
|
||||
}
|
||||
|
||||
// kill all
|
||||
// type: sequence | parallel
|
||||
killAll(type = 'parallel') {
|
||||
let i = 1;
|
||||
Object.keys(this.children).forEach(key => {
|
||||
let proc = this.children[key];
|
||||
if (proc) {
|
||||
if (type == 'sequence') {
|
||||
setTimeout(()=>{
|
||||
proc.kill();
|
||||
}, i * 1000)
|
||||
i++;
|
||||
} else {
|
||||
proc.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ChildPoolJob
|
||||
};
|
||||
1
ee-core/jobs/child/app.d.ts
vendored
Normal file
1
ee-core/jobs/child/app.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
||||
65
ee-core/jobs/child/app.js
Normal file
65
ee-core/jobs/child/app.js
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
const is = require('is-type-of');
|
||||
const { loadException } = require('ee-core/exception');
|
||||
const { requireFile } = require('ee-core/loader');
|
||||
const { coreLogger } = require('ee-core/log');
|
||||
const { isBytecodeClass } = require('ee-core/core/utils');
|
||||
|
||||
loadException();
|
||||
const commands = ['run'];
|
||||
|
||||
class ChildApp {
|
||||
constructor() {
|
||||
this._initEvents();
|
||||
this.jobMap = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化事件监听
|
||||
*/
|
||||
_initEvents() {
|
||||
process.on('message', this._handleMessage.bind(this));
|
||||
process.once('exit', (code) => {
|
||||
coreLogger.info(`[ee-core] [jobs/child] received a exit from main-process, code:${code}, pid:${process.pid}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听消息
|
||||
*/
|
||||
_handleMessage(m) {
|
||||
if (commands.indexOf(m.cmd) == -1) {
|
||||
return
|
||||
}
|
||||
switch (m.cmd) {
|
||||
case 'run':
|
||||
this.run(m);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
coreLogger.info(`[ee-core] [jobs/child] received a message from main-process, message: ${JSON.stringify(m)}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行脚本
|
||||
*/
|
||||
run(msg = {}) {
|
||||
const {jobPath, jobParams, jobFunc, jobFuncParams} = msg;
|
||||
let mod = requireFile(jobPath);
|
||||
if (is.class(mod) || isBytecodeClass(mod)) {
|
||||
if (!this.jobMap.has(jobPath)) {
|
||||
const instance = new mod(...jobParams);
|
||||
instance.handle(...jobParams);
|
||||
this.jobMap.set(jobPath, instance);
|
||||
} else {
|
||||
const instance = this.jobMap.get(jobPath);
|
||||
instance[jobFunc](...jobFuncParams);
|
||||
}
|
||||
|
||||
} else if (is.function(mod)) {
|
||||
mod(jobParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new ChildApp();
|
||||
13
ee-core/jobs/child/index.d.ts
vendored
Normal file
13
ee-core/jobs/child/index.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import EventEmitter = require("events");
|
||||
import { JobProcess } from "./jobProcess";
|
||||
export declare class ChildJob extends EventEmitter<[never]> {
|
||||
constructor();
|
||||
jobs: {};
|
||||
config: any;
|
||||
_initEvents(): void;
|
||||
exec(filepath: string, params?: {}, opt?: {}): JobProcess;
|
||||
createProcess(opt?: {}): JobProcess;
|
||||
getPids(): string[];
|
||||
execPromise(filepath: string, params?: {}, opt?: {}): Promise<JobProcess>;
|
||||
}
|
||||
|
||||
85
ee-core/jobs/child/index.js
Normal file
85
ee-core/jobs/child/index.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const EventEmitter = require('events');
|
||||
const { JobProcess } = require('./jobProcess');
|
||||
const { getFullpath } = require('../../loader');
|
||||
const { Events } = require('../../const/channel');
|
||||
const { getConfig } = require('../../config');
|
||||
const { extend } = require('../../utils/extend');
|
||||
|
||||
class ChildJob extends EventEmitter {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.jobs = {};
|
||||
this.config = {};
|
||||
|
||||
const cfg = getConfig().jobs;
|
||||
if (cfg) {
|
||||
this.config = cfg;
|
||||
}
|
||||
|
||||
this._initEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化监听
|
||||
*/
|
||||
_initEvents() {
|
||||
this.on(Events.childProcessExit, (data) => {
|
||||
delete this.jobs[data.pid];
|
||||
});
|
||||
this.on(Events.childProcessError, (data) => {
|
||||
delete this.jobs[data.pid];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个job文件
|
||||
*/
|
||||
exec(filepath, params = {}, opt = {}) {
|
||||
const jobPath = getFullpath(filepath);
|
||||
const proc = this.createProcess(opt);
|
||||
const cmd = 'run';
|
||||
proc.dispatch(cmd, jobPath, params);
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建子进程
|
||||
*/
|
||||
createProcess(opt = {}) {
|
||||
const options = extend(true, {
|
||||
processArgs: {
|
||||
type: 'childJob'
|
||||
}
|
||||
}, opt);
|
||||
const proc = new JobProcess(this, options);
|
||||
if (!proc) {
|
||||
let errorMessage = `[ee-core] [jobs/child] Failed to obtain the child process !`
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
this.jobs[proc.pid] = proc;
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前pids
|
||||
*/
|
||||
getPids() {
|
||||
let pids = Object.keys(this.jobs);
|
||||
return pids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行一个job文件 todo this指向
|
||||
*/
|
||||
async execPromise(filepath, params = {}, opt = {}) {
|
||||
return this.exec(filepath, params, opt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ChildJob
|
||||
};
|
||||
16
ee-core/jobs/child/jobProcess.d.ts
vendored
Normal file
16
ee-core/jobs/child/jobProcess.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { ChildProcess } from "child_process";
|
||||
declare export class JobProcess {
|
||||
constructor(host: any, opt?: {});
|
||||
emitter: EventEmitter<[never]>;
|
||||
host: any;
|
||||
args: string[];
|
||||
sleeping: boolean;
|
||||
child: ChildProcess;
|
||||
pid: number;
|
||||
_init(): void;
|
||||
_eventEmit(m: any): void;
|
||||
dispatch(cmd: string, jobPath?: string, ...params: any[]): void;
|
||||
callFunc(jobPath?: string, funcName?: string, ...params: any[]): void;
|
||||
kill(timeout?: number): void;
|
||||
}
|
||||
134
ee-core/jobs/child/jobProcess.js
Normal file
134
ee-core/jobs/child/jobProcess.js
Normal file
@@ -0,0 +1,134 @@
|
||||
const path = require('path');
|
||||
const EventEmitter = require('events');
|
||||
const serialize = require('serialize-javascript');
|
||||
const { fork } = require('child_process');
|
||||
const { coreLogger } = require('../../log');
|
||||
const { getBaseDir, isPackaged, allEnv } = require('../../ps');
|
||||
const { Processes, Events, Receiver } = require('../../const/channel');
|
||||
const { getRandomString } = require('../../utils/helper');
|
||||
const { getFullpath } = require('../../loader');
|
||||
const { extend } = require('../../utils/extend');
|
||||
|
||||
class JobProcess {
|
||||
constructor(host, opt = {}) {
|
||||
|
||||
let cwd = getBaseDir();
|
||||
const appPath = path.join(__dirname, 'app.js');
|
||||
if (isPackaged()) {
|
||||
// todo fork的cwd目录为什么要在app.asar外 ?
|
||||
cwd = path.join(getBaseDir(), '..');
|
||||
}
|
||||
|
||||
const options = extend(true, {
|
||||
processArgs: {},
|
||||
processOptions: {
|
||||
cwd: cwd,
|
||||
env: allEnv(),
|
||||
stdio: 'ignore' // pipe
|
||||
}
|
||||
}, opt);
|
||||
|
||||
this.emitter = new EventEmitter();
|
||||
this.host = host;
|
||||
this.args = [];
|
||||
this.sleeping = false;
|
||||
|
||||
// 传递给子进程的参数
|
||||
this.args.push(JSON.stringify(options.processArgs));
|
||||
|
||||
this.child = fork(appPath, this.args, options.processOptions);
|
||||
this.pid = this.child.pid;
|
||||
this._init();
|
||||
}
|
||||
|
||||
_init() {
|
||||
const { messageLog } = this.host.config;
|
||||
this.child.on('message', (m) => {
|
||||
if (messageLog == true) {
|
||||
coreLogger.info(`[ee-core] [jobs/child] received a message from child-process, message: ${serialize(m)}`);
|
||||
}
|
||||
|
||||
if (m.channel == Processes.showException) {
|
||||
coreLogger.error(`${m.data}`);
|
||||
}
|
||||
|
||||
// 收到子进程消息,转发到 event
|
||||
if (m.channel == Processes.sendToMain) {
|
||||
this._eventEmit(m);
|
||||
}
|
||||
});
|
||||
|
||||
this.child.on('exit', (code, signal) => {
|
||||
let data = {
|
||||
pid: this.pid
|
||||
}
|
||||
this.host.emit(Events.childProcessExit, data);
|
||||
coreLogger.info(`[ee-core] [jobs/child] received a exit from child-process, code:${code}, signal:${signal}, pid:${this.pid}`);
|
||||
});
|
||||
|
||||
this.child.on('error', (err) => {
|
||||
let data = {
|
||||
pid: this.pid
|
||||
}
|
||||
this.host.emit(Events.childProcessError, data);
|
||||
coreLogger.error(`[ee-core] [jobs/child] received a error from child-process, error: ${err}, pid:${this.pid}`);
|
||||
});
|
||||
}
|
||||
|
||||
_eventEmit(m) {
|
||||
switch (m.eventReceiver) {
|
||||
case Receiver.forkProcess:
|
||||
this.emitter.emit(m.event, m.data);
|
||||
break;
|
||||
case Receiver.childJob:
|
||||
this.host.emit(m.event, m.data);
|
||||
break;
|
||||
default:
|
||||
this.host.emit(m.event, m.data);
|
||||
this.emitter.emit(m.event, m.data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(cmd, jobPath = '', ...params) {
|
||||
// 消息对象
|
||||
const mid = getRandomString();
|
||||
let msg = {
|
||||
mid,
|
||||
cmd,
|
||||
jobPath,
|
||||
jobParams: params
|
||||
}
|
||||
|
||||
// todo 是否会发生监听未完成时,接收不到消息?
|
||||
// 发消息到子进程
|
||||
this.child.send(msg);
|
||||
}
|
||||
|
||||
callFunc(jobPath = '', funcName = '', ...params) {
|
||||
jobPath = getFullpath(jobPath);
|
||||
|
||||
// 消息对象
|
||||
const mid = getRandomString();
|
||||
let msg = {
|
||||
mid,
|
||||
cmd:'run',
|
||||
jobPath,
|
||||
jobFunc: funcName,
|
||||
jobFuncParams: params
|
||||
}
|
||||
this.child.send(msg);
|
||||
}
|
||||
|
||||
kill(timeout = 1000) {
|
||||
this.child.kill('SIGINT');
|
||||
setTimeout(() => {
|
||||
if (this.child.killed) return;
|
||||
this.child.kill('SIGKILL');
|
||||
}, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
JobProcess
|
||||
};
|
||||
3
ee-core/jobs/index.d.ts
vendored
Normal file
3
ee-core/jobs/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import { ChildJob } from "./child";
|
||||
import { ChildPoolJob } from "./child-pool";
|
||||
export { ChildJob, ChildPoolJob };
|
||||
7
ee-core/jobs/index.js
Normal file
7
ee-core/jobs/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { ChildJob } = require('./child');
|
||||
const { ChildPoolJob } = require('./child-pool');
|
||||
|
||||
module.exports = {
|
||||
ChildJob,
|
||||
ChildPoolJob
|
||||
};
|
||||
4
ee-core/jobs/load-balancer/algorithm/index.d.ts
vendored
Normal file
4
ee-core/jobs/load-balancer/algorithm/index.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare const _exports: {
|
||||
[x: string]: (tasks: any, weightIndex: any, weightTotal: any, context: any) => any;
|
||||
};
|
||||
export = _exports;
|
||||
12
ee-core/jobs/load-balancer/algorithm/index.js
Normal file
12
ee-core/jobs/load-balancer/algorithm/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const Consts = require("../consts");
|
||||
|
||||
module.exports = {
|
||||
[Consts.polling]: require('./polling'),
|
||||
[Consts.weights]: require('./weights'),
|
||||
[Consts.random]: require('./random'),
|
||||
[Consts.specify]: require('./specify'),
|
||||
[Consts.minimumConnection]: require('./minimumConnection'),
|
||||
[Consts.weightsPolling]: require('./weightsPolling'),
|
||||
[Consts.weightsRandom]: require('./weightsRandom'),
|
||||
[Consts.weightsMinimumConnection]: require('./weightsMinimumConnection'),
|
||||
};
|
||||
2
ee-core/jobs/load-balancer/algorithm/minimumConnection.d.ts
vendored
Normal file
2
ee-core/jobs/load-balancer/algorithm/minimumConnection.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _exports(tasks: any, conMap?: {}): any;
|
||||
export = _exports;
|
||||
19
ee-core/jobs/load-balancer/algorithm/minimumConnection.js
Normal file
19
ee-core/jobs/load-balancer/algorithm/minimumConnection.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 最小连接数
|
||||
*/
|
||||
module.exports = function (tasks, conMap={}) {
|
||||
if (tasks.length < 2) return tasks[0] || null;
|
||||
|
||||
let min = conMap[tasks[0].id];
|
||||
let minIndex = 0;
|
||||
|
||||
for (let i = 1; i < tasks.length; i++) {
|
||||
const con = conMap[tasks[i].id] || 0;
|
||||
if (con <= min) {
|
||||
min = con;
|
||||
minIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return tasks[minIndex] || null;
|
||||
};
|
||||
2
ee-core/jobs/load-balancer/algorithm/polling.d.ts
vendored
Normal file
2
ee-core/jobs/load-balancer/algorithm/polling.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _exports(tasks: any, currentIndex: any, context: any): any;
|
||||
export = _exports;
|
||||
12
ee-core/jobs/load-balancer/algorithm/polling.js
Normal file
12
ee-core/jobs/load-balancer/algorithm/polling.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 轮询
|
||||
*/
|
||||
module.exports = function (tasks, currentIndex, context) {
|
||||
if (!tasks.length) return null;
|
||||
|
||||
const task = tasks[currentIndex];
|
||||
context.currentIndex ++;
|
||||
context.currentIndex %= tasks.length;
|
||||
|
||||
return task || null;
|
||||
};
|
||||
2
ee-core/jobs/load-balancer/algorithm/random.d.ts
vendored
Normal file
2
ee-core/jobs/load-balancer/algorithm/random.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _exports(tasks: any): any;
|
||||
export = _exports;
|
||||
10
ee-core/jobs/load-balancer/algorithm/random.js
Normal file
10
ee-core/jobs/load-balancer/algorithm/random.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 随机
|
||||
*/
|
||||
module.exports = function (tasks) {
|
||||
|
||||
const length = tasks.length;
|
||||
const target = tasks[Math.floor(Math.random() * length)];
|
||||
|
||||
return target || null;
|
||||
};
|
||||
2
ee-core/jobs/load-balancer/algorithm/specify.d.ts
vendored
Normal file
2
ee-core/jobs/load-balancer/algorithm/specify.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare function _exports(tasks: any, id: any): any;
|
||||
export = _exports;
|
||||
15
ee-core/jobs/load-balancer/algorithm/specify.js
Normal file
15
ee-core/jobs/load-balancer/algorithm/specify.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 声明绑定
|
||||
*/
|
||||
module.exports = function (tasks, id) {
|
||||
let task;
|
||||
|
||||
for (let i = 0; i < tasks.length; i++) {
|
||||
if (tasks[i].id === id) {
|
||||
task = tasks[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return task || null;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user