@flun/windows 3.0.10 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -14
- package/LICENSE +28 -12
- package/README.md +294 -174
- package/bin/cli.js +218 -0
- package/bin/winsw/winsw.exe.config +1 -1
- package/copy-files.js +34 -34
- package/index.d.ts +133 -129
- package/lib/binaries.js +133 -132
- package/lib/cmd.js +4 -4
- package/lib/daemon.js +82 -77
- package/lib/eventlog.js +13 -7
- package/lib/shared.js +5 -3
- package/lib/winsw.js +15 -13
- package/lib/wrapper.js +15 -15
- package/package.json +7 -3
- package/win-service.config.js +68 -0
- package/sevWin.js +0 -18
package/lib/binaries.js
CHANGED
|
@@ -1,133 +1,134 @@
|
|
|
1
|
-
import { path, fs, exec, getDirname } from './shared.js';
|
|
2
|
-
/**
|
|
3
|
-
* 从 Node.js `os` 模块导入的 `platform` 函数,用于获取操作系统平台字符串;
|
|
4
|
-
* >查看定义:@see {@link platform}
|
|
5
|
-
* @type {() => string}
|
|
6
|
-
*/
|
|
7
|
-
import { platform, tmpdir } from 'os';
|
|
8
|
-
|
|
9
|
-
const __dirname = getDirname(import.meta.url), vbsPath = path.resolve(__dirname, '../bin/elevate.vbs'),
|
|
10
|
-
/** 参数处理函数,用于标准化 options 和 callback 参数 */
|
|
11
|
-
_params = (options = {}, callback) => {
|
|
12
|
-
callback = callback || function () { };
|
|
13
|
-
if (typeof options === 'function') callback = options, options = {};
|
|
14
|
-
if (typeof options !== 'object') throw '参数 options 无效;';
|
|
15
|
-
return { options, callback };
|
|
16
|
-
},
|
|
17
|
-
|
|
18
|
-
// 文件读取
|
|
19
|
-
readFileSafe = (Path, value = '') => {
|
|
20
|
-
try {
|
|
21
|
-
return fs.readFileSync(Path, 'utf8');
|
|
22
|
-
} catch (e) { return value; }
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
* >查看定义:@see {@link elevate}
|
|
28
|
-
* @param {string} cmd - 要使用提升权限执行的命令
|
|
29
|
-
* @param {Object} [options] - 传递给 child_process.exec 的选项
|
|
30
|
-
* @param {string} [options.cwd] - 工作目录
|
|
31
|
-
* @param {number} [options.timeout] -
|
|
32
|
-
* @param {(error: Error|null, stdout: string, stderr: string) => void} [callback] - 执行完成后的回调函数
|
|
33
|
-
* @returns {void}
|
|
34
|
-
* @example
|
|
35
|
-
* import { elevate } from '@flun/windows';
|
|
36
|
-
*
|
|
37
|
-
* // 基本用法
|
|
38
|
-
* elevate('echo "Hello World" && whoami',{}, (error, stdout, stderr) => {
|
|
39
|
-
* if (error) {
|
|
40
|
-
* console.error('执行失败:', error);
|
|
41
|
-
* } else {
|
|
42
|
-
* console.log('执行成功,输出:', stdout);
|
|
43
|
-
* console.log('错误输出:', stderr);
|
|
44
|
-
* }
|
|
45
|
-
* });
|
|
46
|
-
*
|
|
47
|
-
* // 带选项参数
|
|
48
|
-
* elevate('echo "Hello World" && whoami', { cwd:'C:\\' }, callback);
|
|
49
|
-
*
|
|
50
|
-
* // 多种参数组合
|
|
51
|
-
* elevate('echo "Hello World" && whoami', (err, stdout)=> { }); // options视为回调
|
|
52
|
-
* elevate('echo "Hello World" && whoami'); // 无回调
|
|
53
|
-
*/
|
|
54
|
-
const elevate = (cmd, options, callback) => {
|
|
55
|
-
const p = _params(options, callback), tmpDir = tmpdir(),
|
|
56
|
-
[vbsFile, outFile, batFile, cmdFile] = ['flun_el.vbs', 'flun_el_output.txt', 'flun_el.bat', 'flun_el.cmd']
|
|
57
|
-
.map(name => path.join(tmpDir, name)),
|
|
58
|
-
escapedCmd = cmd.replace(/"/g, '""').replace(/%/g, "%%"),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
p.callback(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
*
|
|
85
|
-
* @
|
|
86
|
-
* @param {
|
|
87
|
-
* @param {
|
|
88
|
-
* @param {
|
|
89
|
-
* @param {
|
|
90
|
-
* @
|
|
91
|
-
* @
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* console.log('
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* @
|
|
119
|
-
* @
|
|
120
|
-
*
|
|
121
|
-
* isAdminUser
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
132
|
-
|
|
1
|
+
import { path, fs, exec, getDirname } from './shared.js';
|
|
2
|
+
/**
|
|
3
|
+
* 从 Node.js `os` 模块导入的 `platform` 函数,用于获取操作系统平台字符串;
|
|
4
|
+
* >查看定义:@see {@link platform}
|
|
5
|
+
* @type {() => string}
|
|
6
|
+
*/
|
|
7
|
+
import { platform, tmpdir } from 'os';
|
|
8
|
+
|
|
9
|
+
const __dirname = getDirname(import.meta.url), vbsPath = path.resolve(__dirname, '../bin/elevate.vbs'),
|
|
10
|
+
/** 参数处理函数,用于标准化 options 和 callback 参数 */
|
|
11
|
+
_params = (options = {}, callback) => {
|
|
12
|
+
callback = callback || function () { };
|
|
13
|
+
if (typeof options === 'function') callback = options, options = {};
|
|
14
|
+
if (typeof options !== 'object') throw '参数 options 无效;';
|
|
15
|
+
return { options, callback };
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// 文件读取
|
|
19
|
+
readFileSafe = (Path, value = '') => {
|
|
20
|
+
try {
|
|
21
|
+
return fs.readFileSync(Path, 'utf8');
|
|
22
|
+
} catch (e) { return value; }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 提升当前进程权限(Windows UAC)
|
|
27
|
+
* >查看定义:@see {@link elevate}
|
|
28
|
+
* @param {string} cmd - 要使用提升权限执行的命令
|
|
29
|
+
* @param {Object} [options] - 传递给 child_process.exec 的选项
|
|
30
|
+
* @param {string} [options.cwd] - 工作目录
|
|
31
|
+
* @param {number} [options.timeout] - 超时时间(毫秒)
|
|
32
|
+
* @param {(error: Error|null, stdout: string, stderr: string) => void} [callback] - 执行完成后的回调函数
|
|
33
|
+
* @returns {void}
|
|
34
|
+
* @example
|
|
35
|
+
* import { elevate } from '@flun/windows';
|
|
36
|
+
*
|
|
37
|
+
* // 基本用法
|
|
38
|
+
* elevate('echo "Hello World" && whoami',{}, (error, stdout, stderr) => {
|
|
39
|
+
* if (error) {
|
|
40
|
+
* console.error('执行失败:', error);
|
|
41
|
+
* } else {
|
|
42
|
+
* console.log('执行成功,输出:', stdout);
|
|
43
|
+
* console.log('错误输出:', stderr);
|
|
44
|
+
* }
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* // 带选项参数
|
|
48
|
+
* elevate('echo "Hello World" && whoami', { cwd:'C:\\' }, callback);
|
|
49
|
+
*
|
|
50
|
+
* // 多种参数组合
|
|
51
|
+
* elevate('echo "Hello World" && whoami', (err, stdout)=> { }); // options视为回调
|
|
52
|
+
* elevate('echo "Hello World" && whoami'); // 无回调
|
|
53
|
+
*/
|
|
54
|
+
const elevate = (cmd, options, callback) => {
|
|
55
|
+
const p = _params(options, callback), tmpDir = tmpdir(),
|
|
56
|
+
[vbsFile, outFile, batFile, cmdFile] = ['flun_el.vbs', 'flun_el_output.txt', 'flun_el.bat', 'flun_el.cmd']
|
|
57
|
+
.map(name => path.join(tmpDir, name)),
|
|
58
|
+
escapedCmd = cmd.replace(/"/g, '""').replace(/%/g, "%%"),
|
|
59
|
+
vbs = readFileSafe(vbsPath).replace(/{command}/g, escapedCmd);
|
|
60
|
+
|
|
61
|
+
// 写入VBS文件
|
|
62
|
+
try {
|
|
63
|
+
fs.writeFileSync(vbsFile, vbs, 'utf8');
|
|
64
|
+
} catch (writeError) {
|
|
65
|
+
return p.callback(writeError, '', '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
exec(`wscript.exe "${vbsFile}"`, { timeout: 20000 }, error => {
|
|
69
|
+
setTimeout(() => {
|
|
70
|
+
const output = readFileSafe(outFile).replace(/\r\n$|\n$/, '').trim();
|
|
71
|
+
|
|
72
|
+
// 清理临时文件
|
|
73
|
+
[batFile, cmdFile, outFile, vbsFile].forEach(f => {
|
|
74
|
+
try { fs.unlinkSync(f) } catch (e) { }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (error) return p.callback(error, '', '');
|
|
78
|
+
p.callback(null, output, '');
|
|
79
|
+
}, 1600);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 使用 sudo 方式提升权限(与 elevate 相似但体验更好)
|
|
85
|
+
* >查看定义:@see {@link sudo}
|
|
86
|
+
* @param {string} cmd - 要使用提升权限执行的命令
|
|
87
|
+
* @param {Object} [options] - 传递给 child_process.exec 的选项
|
|
88
|
+
* @param {string} [options.cwd] - 工作目录
|
|
89
|
+
* @param {number} [options.timeout] - 超时时间(毫秒)
|
|
90
|
+
* @param {(error: Error|null, stdout: string, stderr: string) => void} [callback] - 执行完成后的回调函数
|
|
91
|
+
* @returns {void}
|
|
92
|
+
* @example
|
|
93
|
+
* import { sudo } from '@flun/windows';
|
|
94
|
+
*
|
|
95
|
+
* // 基本用法 - 命令、选项和回调
|
|
96
|
+
* sudo('echo "Hello World" && whoami', {}, (error, stdout, stderr) => {
|
|
97
|
+
* if (error) {
|
|
98
|
+
* console.error('执行失败:', error);
|
|
99
|
+
* } else {
|
|
100
|
+
* console.log('执行成功,输出:', stdout);
|
|
101
|
+
* console.log('错误输出:', stderr);
|
|
102
|
+
* }
|
|
103
|
+
* });
|
|
104
|
+
*
|
|
105
|
+
* // 带选项参数
|
|
106
|
+
* sudo('echo "Hello World" && whoami', {cwd:'C:\\'}, callback);
|
|
107
|
+
*
|
|
108
|
+
* sudo('echo "Hello World" && whoami' ); // 无回调
|
|
109
|
+
*/
|
|
110
|
+
const sudo = (cmd, options, callback) => {
|
|
111
|
+
const p = _params(options, callback);
|
|
112
|
+
exec(`sudo ${cmd}`, p.options, p.callback);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 检查当前用户是否拥有管理员权限
|
|
117
|
+
* >查看定义:@see {@link isAdminUser}
|
|
118
|
+
* @param {(isAdmin: boolean) => void} callback - 回调函数,接收布尔值表示是否为管理员
|
|
119
|
+
* @returns {void}
|
|
120
|
+
* @example
|
|
121
|
+
* import { isAdminUser } from '@flun/windows';
|
|
122
|
+
* isAdminUser(isAdmin => {
|
|
123
|
+
* if (isAdmin) console.log('当前用户是管理员');
|
|
124
|
+
* else console.log('当前用户不是管理员');
|
|
125
|
+
* });
|
|
126
|
+
*/
|
|
127
|
+
const isAdminUser = callback => {
|
|
128
|
+
exec('NET SESSION', (err, so, se) => {
|
|
129
|
+
if (se.length !== 0) elevate('NET SESSION', (_err, _so, _se) => callback(_se.length === 0));
|
|
130
|
+
else callback(true);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
133
134
|
export { elevate, sudo, isAdminUser, platform };
|
package/lib/cmd.js
CHANGED
|
@@ -5,7 +5,7 @@ import { exec } from './shared.js';
|
|
|
5
5
|
* >查看定义:@see {@link kill}
|
|
6
6
|
* @param {number} pid - 进程PID
|
|
7
7
|
* @param {(error: Error|null, stdout: string, stderr: string) => void} [callback] - 执行完成后的回调函数
|
|
8
|
-
* @param {boolean} [force=false] -
|
|
8
|
+
* @param {boolean} [force=false] - 是否强制结束进程(默认: false)
|
|
9
9
|
* @returns {void}
|
|
10
10
|
* @example
|
|
11
11
|
* import { kill } from '@flun/windows';
|
|
@@ -14,8 +14,8 @@ import { exec } from './shared.js';
|
|
|
14
14
|
* });
|
|
15
15
|
*/
|
|
16
16
|
const kill = (pid, callback, force = false) => {
|
|
17
|
-
if (!pid) throw new Error('PID是kill
|
|
18
|
-
if (isNaN(pid)) throw new Error('PID
|
|
17
|
+
if (!pid) throw new Error('PID是kill操作必需的参数;');
|
|
18
|
+
if (isNaN(pid)) throw new Error('PID必须为数字;');
|
|
19
19
|
if (typeof callback !== 'function') callback = function () { };
|
|
20
20
|
exec(`taskkill /PID ${pid}${force == true ? ' /f' : ''}`, callback);
|
|
21
21
|
};
|
|
@@ -24,7 +24,7 @@ const kill = (pid, callback, force = false) => {
|
|
|
24
24
|
* 列出服务器上正在运行的进程
|
|
25
25
|
* >查看定义:@see {@link list}
|
|
26
26
|
* @param {(processes: Array<Record<string, string>>) => void} callback - 回调函数,接收进程对象数组
|
|
27
|
-
* @param {boolean} [verbose=false] -
|
|
27
|
+
* @param {boolean} [verbose=false] - 是否显示详细信息(默认: false)
|
|
28
28
|
* @returns {void}
|
|
29
29
|
* @example
|
|
30
30
|
* import { list } from '@flun/windows';
|
package/lib/daemon.js
CHANGED
|
@@ -9,24 +9,21 @@ const __dirname = getDirname(import.meta.url), writeFileAsync = promisify(fs.wri
|
|
|
9
9
|
/**
|
|
10
10
|
* Windows服务管理类,提供创建、安装、卸载、启动、停止、重启和查询服务状态等功能
|
|
11
11
|
* - 用于将Node.js脚本作为Windows服务运行,支持自动重启、日志记录和权限提升等功能
|
|
12
|
-
* >查看定义:@see {@link Service}、{@link install}、{@link uninstall}、{@link start}、{@link stop}、
|
|
12
|
+
* >查看定义:@see {@link Service}、{@link install}、{@link uninstall}、{@link start}、{@link stop}、
|
|
13
|
+
* {@link restart}、{@link exists}
|
|
13
14
|
* @example
|
|
14
|
-
* // 配置示例
|
|
15
15
|
* import { Service } from '@flun/windows';
|
|
16
|
+
* import config from './win-service.config.js';
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* description: 'nodejs.org 示例服务器', // 服务描述
|
|
21
|
-
* script: 'C:\\path\\to\\helloworld.js',// 启动服务的入口脚本路径
|
|
18
|
+
* const svc = new Service(config);
|
|
19
|
+
* // 安装并自动启动服务(默认)
|
|
20
|
+
* svc.install();
|
|
22
21
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* // svc.stop(); // 停止服务
|
|
29
|
-
* // svc.uninstall(); // 卸载服务
|
|
22
|
+
* // 只安装,不自动启动
|
|
23
|
+
* // svc.install(undefined, false);
|
|
24
|
+
*
|
|
25
|
+
* // 其余方法
|
|
26
|
+
* // svc.start(); svc.stop(); svc.uninstall();
|
|
30
27
|
*/
|
|
31
28
|
class Service extends EventEmitter {
|
|
32
29
|
/**
|
|
@@ -87,7 +84,7 @@ class Service extends EventEmitter {
|
|
|
87
84
|
scriptOptions = '', stopparentfirst = false, abortOnError = false, grow = 0.25, logpath = null, logmode = 'rotate',
|
|
88
85
|
description = '', execPath = process.execPath, workingDirectory = process.cwd(),
|
|
89
86
|
logOnAs = {},// 嵌套配置对象
|
|
90
|
-
env, logging, allowServiceLogon, // 其它配置
|
|
87
|
+
env, logging, allowServiceLogon, quiet = false, // 其它配置
|
|
91
88
|
} = config, domain = process.env.COMPUTERNAME;
|
|
92
89
|
|
|
93
90
|
// 私有字段
|
|
@@ -102,7 +99,8 @@ class Service extends EventEmitter {
|
|
|
102
99
|
this.scriptOptions = scriptOptions; // 传递给脚本的选项;
|
|
103
100
|
|
|
104
101
|
this.stopparentfirst = stopparentfirst; // 是否先停止父进程(默认:false);
|
|
105
|
-
this.abortOnError = Boolean(abortOnError);
|
|
102
|
+
this.abortOnError = Boolean(abortOnError);
|
|
103
|
+
// 当遇到导致node.js脚本无法运行错误时是否退出进程(默认:false);
|
|
106
104
|
this.grow = Number(grow); // 重启等待时间的增长百分比(默认:0.25);
|
|
107
105
|
this.logpath = logpath; // 日志文件路径(默认:与可执行文件相同的目录);
|
|
108
106
|
this.logmode = logmode; // 日志模式(默认:rotate);可选值: rotate, truncate, append;
|
|
@@ -118,6 +116,7 @@ class Service extends EventEmitter {
|
|
|
118
116
|
|
|
119
117
|
/** 环境变量配置 */
|
|
120
118
|
this.env = env, this.logging = logging, this.allowServiceLogon = allowServiceLogon;
|
|
119
|
+
this.quiet = Boolean(quiet); // 为 true 时不打印执行结果,只发事件;嵌入生产环境时建议开启
|
|
121
120
|
}
|
|
122
121
|
|
|
123
122
|
// 进程名称
|
|
@@ -139,28 +138,30 @@ class Service extends EventEmitter {
|
|
|
139
138
|
return `${this.id}.exe`;
|
|
140
139
|
}
|
|
141
140
|
|
|
142
|
-
//
|
|
141
|
+
// 服务名称(用于Windows服务管理)
|
|
143
142
|
get #serviceName() {
|
|
144
143
|
return this.id;
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
// 生成服务的XML配置
|
|
148
147
|
get #xml() {
|
|
149
|
-
const { script, scriptOptions, name, grow, wait, maxRestarts, abortOnError, stopparentfirst,
|
|
150
|
-
|
|
148
|
+
const { script, scriptOptions, name, grow, wait, maxRestarts, abortOnError, stopparentfirst,
|
|
149
|
+
maxRetries, id, nodeOptions, description, logpath, execPath, logOnAs, workingdirectory,
|
|
150
|
+
stoptimeout, logmode, env, logging, allowServiceLogon } = this;
|
|
151
151
|
|
|
152
|
-
// 构建环境变量数组,自动添加 USERPROFILE
|
|
152
|
+
// 构建环境变量数组,自动添加 USERPROFILE(如果未显式设置)
|
|
153
153
|
let envList = [];
|
|
154
154
|
if (env) envList.push(...(Array.isArray(env) ? env : [env]));
|
|
155
155
|
if (process.env.USERPROFILE && !envList.some(e => e.name === 'USERPROFILE'))
|
|
156
156
|
envList.push({ name: 'USERPROFILE', value: process.env.USERPROFILE });
|
|
157
157
|
|
|
158
|
-
const wrapperArgs = ['--file', script, `--scriptoptions=${scriptOptions}`, '--log', `${name} 包装器`,
|
|
159
|
-
'--maxrestarts', maxRestarts, '--abortonerror', abortOnError ? 'y' : 'n',
|
|
160
|
-
...(maxRetries !== null ? ['--maxretries', maxRetries] : [])];
|
|
158
|
+
const wrapperArgs = ['--file', script, `--scriptoptions=${scriptOptions}`, '--log', `${name} 包装器`,
|
|
159
|
+
'--grow', grow, '--wait', wait, '--maxrestarts', maxRestarts, '--abortonerror', abortOnError ? 'y' : 'n',
|
|
160
|
+
'--stopparentfirst', stopparentfirst, ...(maxRetries !== null ? ['--maxretries', maxRetries] : [])];
|
|
161
161
|
|
|
162
162
|
return generateXml({
|
|
163
|
-
name, id, nodeOptions, script: wrapper, scriptOptions, wrapperArgs, description, logpath, execPath,
|
|
163
|
+
name, id, nodeOptions, script: wrapper, scriptOptions, wrapperArgs, description, logpath, execPath,
|
|
164
|
+
logOnAs, workingdirectory,
|
|
164
165
|
stopparentfirst, stoptimeout, logmode, env: envList, logging, allowServiceLogon
|
|
165
166
|
});
|
|
166
167
|
}
|
|
@@ -216,10 +217,30 @@ class Service extends EventEmitter {
|
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
/**
|
|
221
|
+
* 生成 winsw 配置 XML(不写文件,不安装)
|
|
222
|
+
* >查看定义:@see {@link toXml}
|
|
223
|
+
* @returns {string} XML 字符串
|
|
224
|
+
*/
|
|
225
|
+
toXml() { return this.#xml }
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 生成 winsw 可执行文件(不安装)
|
|
229
|
+
* >查看定义:@see {@link toExe}
|
|
230
|
+
* @param {string} [dir] - 目标目录(默认与 install 相同,取脚本所在目录下的 daemon 子目录)
|
|
231
|
+
* @returns {Promise<void>}
|
|
232
|
+
*/
|
|
233
|
+
async toExe(dir) {
|
|
234
|
+
const targetDir = this.directory(dir);
|
|
235
|
+
if (!fs.existsSync(targetDir)) await mkdirAsync(targetDir, { recursive: true });
|
|
236
|
+
await new Promise((resolve, reject) => createExe(this.id, targetDir, error => error ? reject(error) : resolve()));
|
|
237
|
+
}
|
|
238
|
+
|
|
219
239
|
/**
|
|
220
240
|
* 将脚本安装为Windows服务
|
|
221
241
|
* >查看定义:@see {@link install}
|
|
222
|
-
* @param {string} [dir] -
|
|
242
|
+
* @param {string} [dir] - 服务文件将保存的目录(默认为脚本所在目录)
|
|
243
|
+
* @param {boolean} [autoStart=true] - 安装成功后是否立即启动服务
|
|
223
244
|
* @returns {Promise<void>}
|
|
224
245
|
* @event install - 当安装过程完成时触发
|
|
225
246
|
* @event alreadyinstalled - 如果服务已安装时触发
|
|
@@ -227,25 +248,19 @@ class Service extends EventEmitter {
|
|
|
227
248
|
* @event error - 在错误发生时触发
|
|
228
249
|
* @example
|
|
229
250
|
* import { Service } from '@flun/windows';
|
|
230
|
-
*
|
|
231
|
-
* name: 'Hello World', // 服务名称
|
|
232
|
-
* description: 'nodejs.org 示例服务器', // 服务描述
|
|
233
|
-
* script: 'C:\\path\\to\\helloworld.js',// 启动服务的入口脚本路径
|
|
251
|
+
* import config from './win-service.config.js';
|
|
234
252
|
*
|
|
235
|
-
*
|
|
236
|
-
* nodeOptions: [ '--harmony', '--max-old-space-size=4096' ]
|
|
237
|
-
* });
|
|
253
|
+
* const svc = new Service(config);
|
|
238
254
|
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* svc.start();
|
|
242
|
-
* });
|
|
255
|
+
* // 安装并在完成后自动启动(默认)
|
|
256
|
+
* svc.install();
|
|
243
257
|
*
|
|
244
|
-
*
|
|
258
|
+
* // 只安装,不启动
|
|
259
|
+
* // svc.install(undefined, false);
|
|
245
260
|
*/
|
|
246
|
-
async install(dir) {
|
|
261
|
+
async install(dir, autoStart = true) {
|
|
247
262
|
// 检查是否已安装
|
|
248
|
-
if (this.exists === 3) return
|
|
263
|
+
if (this.exists === 3) return this.#report('安装跳过,服务已经存在;'), this.emit('alreadyinstalled');
|
|
249
264
|
|
|
250
265
|
const targetDir = this.directory(dir);
|
|
251
266
|
if (!fs.existsSync(targetDir)) await mkdirAsync(targetDir, { recursive: true });
|
|
@@ -257,7 +272,10 @@ class Service extends EventEmitter {
|
|
|
257
272
|
});
|
|
258
273
|
|
|
259
274
|
// 执行安装命令
|
|
260
|
-
await this.#execute(`"${this.#resPath(this.#exe)}" install`), await this.#sleep(2)
|
|
275
|
+
await this.#execute(`"${this.#resPath(this.#exe)}" install`), await this.#sleep(2);
|
|
276
|
+
if (autoStart) await this.start();
|
|
277
|
+
this.#report('服务安装完成;');
|
|
278
|
+
this.emit('install');
|
|
261
279
|
} catch (error) {
|
|
262
280
|
this.emit('error', error);
|
|
263
281
|
throw error;
|
|
@@ -270,31 +288,21 @@ class Service extends EventEmitter {
|
|
|
270
288
|
* @param {number} [waitTime=2] - 等待winsw.exe完成卸载命令的秒数
|
|
271
289
|
* @returns {Promise<void>}
|
|
272
290
|
* @event uninstall - 当卸载过程完成时触发
|
|
273
|
-
* @event alreadyuninstalled - 如果服务已卸载时触发
|
|
274
291
|
* @example
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
* script: 'C:\\path\\to\\helloworld.js',// 启动服务的入口脚本路径
|
|
292
|
+
* import { Service } from '@flun/windows';
|
|
293
|
+
* import config from './win-service.config.js';
|
|
294
|
+
*
|
|
295
|
+
* const svc = new Service(config);
|
|
280
296
|
*
|
|
281
|
-
* // 传递给node进程的选项
|
|
282
|
-
* nodeOptions: [ '--harmony', '--max-old-space-size=4096' ]
|
|
283
|
-
* });
|
|
284
297
|
* svc.on('uninstall', ()=>{
|
|
285
298
|
* console.log('卸载完成');
|
|
286
|
-
* console.log('
|
|
287
|
-
* // svc.exists 返回值说明:
|
|
288
|
-
* // 0: 服务和相关文件没有
|
|
289
|
-
* // 1: 服务已注册但相关文件不存在(异常情况)
|
|
290
|
-
* // 2: 服务未注册但相关文件存在(文件残留)
|
|
291
|
-
* // 3: 服务已注册且相关文件存在(正常状态)
|
|
299
|
+
* console.log('服务是否存在:', svc.exists);
|
|
292
300
|
* });
|
|
293
301
|
*
|
|
294
302
|
* svc.uninstall();
|
|
295
303
|
*/
|
|
296
304
|
async uninstall(waitTime = 2) {
|
|
297
|
-
if (!this.exists) return
|
|
305
|
+
if (!this.exists) return this.#report('卸载已跳过,服务已经不在;'), this.emit('alreadyuninstalled');
|
|
298
306
|
|
|
299
307
|
const uninstaller = async () => {
|
|
300
308
|
await this.#execute(`"${this.#resPath(this.#exe)}" uninstall`), await this.#sleep(waitTime);
|
|
@@ -304,6 +312,7 @@ class Service extends EventEmitter {
|
|
|
304
312
|
console.error(`删除目录失败: ${error.message}`);
|
|
305
313
|
}
|
|
306
314
|
|
|
315
|
+
this.#report('服务卸载完成;');
|
|
307
316
|
this.emit('uninstall');
|
|
308
317
|
};
|
|
309
318
|
|
|
@@ -317,14 +326,10 @@ class Service extends EventEmitter {
|
|
|
317
326
|
* @event start - 当服务启动时触发
|
|
318
327
|
* @example
|
|
319
328
|
* import { Service } from '@flun/windows';
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* script: 'C:\\path\\to\\helloworld.js',// 启动服务的入口脚本路径
|
|
329
|
+
* import config from './win-service.config.js';
|
|
330
|
+
*
|
|
331
|
+
* const svc = new Service(config);
|
|
324
332
|
*
|
|
325
|
-
* // 传递给node进程的选项
|
|
326
|
-
* nodeOptions: [ '--harmony', '--max-old-space-size=4096' ]
|
|
327
|
-
* });
|
|
328
333
|
* svc.on('start', ()=>{
|
|
329
334
|
* console.log('服务已启动');
|
|
330
335
|
* });
|
|
@@ -336,7 +341,7 @@ class Service extends EventEmitter {
|
|
|
336
341
|
|
|
337
342
|
try {
|
|
338
343
|
const { stderr } = await this.#execute(`NET START "${this.#serviceName}"`);
|
|
339
|
-
if (!stderr) return this.emit('start');
|
|
344
|
+
if (!stderr) return this.#report('服务已启动;'), this.emit('start');
|
|
340
345
|
throw new Error(stderr);
|
|
341
346
|
} catch (error) {
|
|
342
347
|
if (error.message.includes('already been started')) return this.log.warn('尝试启动服务失败,因为服务已经在运行;');
|
|
@@ -352,14 +357,9 @@ class Service extends EventEmitter {
|
|
|
352
357
|
* @event alreadystopped - 当服务已经停止时触发
|
|
353
358
|
* @example
|
|
354
359
|
* import { Service } from '@flun/windows';
|
|
355
|
-
*
|
|
356
|
-
* name: 'Hello World', // 服务名称
|
|
357
|
-
* description: 'nodejs.org 示例服务器', // 服务描述
|
|
358
|
-
* script: 'C:\\path\\to\\helloworld.js',// 启动服务的入口脚本路径
|
|
360
|
+
* import config from './win-service.config.js';
|
|
359
361
|
*
|
|
360
|
-
*
|
|
361
|
-
* nodeOptions: [ '--harmony', '--max-old-space-size=4096' ]
|
|
362
|
-
* });
|
|
362
|
+
* const svc = new Service(config);
|
|
363
363
|
* svc.on('stop', ()=>{
|
|
364
364
|
* console.log('服务已停止');
|
|
365
365
|
* });
|
|
@@ -368,7 +368,7 @@ class Service extends EventEmitter {
|
|
|
368
368
|
*/
|
|
369
369
|
async stop() {
|
|
370
370
|
try {
|
|
371
|
-
await this.#execute(`NET STOP "${this.#serviceName}"`), this.emit('stop');
|
|
371
|
+
await this.#execute(`NET STOP "${this.#serviceName}"`), this.#report('服务已停止;'), this.emit('stop');
|
|
372
372
|
} catch (error) {
|
|
373
373
|
if (error.code === 2) return this.log.warn('服务未运行或已停止;'), this.emit('alreadystopped');
|
|
374
374
|
this.emit('error', error);
|
|
@@ -381,10 +381,9 @@ class Service extends EventEmitter {
|
|
|
381
381
|
* @returns {Promise<void>}
|
|
382
382
|
* @example
|
|
383
383
|
* import { Service } from '@flun/windows';
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* });
|
|
384
|
+
* import config from './win-service.config.js';
|
|
385
|
+
*
|
|
386
|
+
* const svc = new Service(config);
|
|
388
387
|
*
|
|
389
388
|
* svc.restart();
|
|
390
389
|
*/
|
|
@@ -411,7 +410,7 @@ class Service extends EventEmitter {
|
|
|
411
410
|
executor(cmd, { ...options, shell: true, windowsHide: true }, (error, stdout, stderr) => {
|
|
412
411
|
if (error) {
|
|
413
412
|
if (isPermissionError(error.message)) reject(new Error('权限被拒绝,请以管理员身份重新运行此脚本;'));
|
|
414
|
-
else console.error(error.toString())
|
|
413
|
+
else { if (!this.quiet) console.error(error.toString()); reject(error); }
|
|
415
414
|
}
|
|
416
415
|
else resolve({ stdout, stderr });
|
|
417
416
|
});
|
|
@@ -423,6 +422,12 @@ class Service extends EventEmitter {
|
|
|
423
422
|
* @param {number} seconds - 睡眠秒数
|
|
424
423
|
* @returns {Promise<void>}
|
|
425
424
|
*/
|
|
425
|
+
/**
|
|
426
|
+
* 按 quiet 配置决定是否打印执行结果;
|
|
427
|
+
* @param {string} msg 结果消息
|
|
428
|
+
*/
|
|
429
|
+
#report = msg => { if (!this.quiet) console.log(msg) };
|
|
430
|
+
|
|
426
431
|
#sleep = seconds => new Promise(r => setTimeout(r, seconds * 1000));
|
|
427
432
|
}
|
|
428
433
|
|