@flun/windows 2.0.1
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 +4 -0
- package/LICENSE +19 -0
- package/README.md +361 -0
- package/bin/elevate.vbs +25 -0
- package/bin/winsw/winsw.exe +0 -0
- package/bin/winsw/winsw.exe.config +6 -0
- package/copy-files.js +34 -0
- package/index.d.ts +129 -0
- package/index.js +9 -0
- package/lib/binaries.js +133 -0
- package/lib/cmd.js +54 -0
- package/lib/daemon.js +422 -0
- package/lib/eventlog.js +226 -0
- package/lib/shared.js +34 -0
- package/lib/winsw.js +111 -0
- package/lib/wrapper.js +324 -0
- package/package.json +58 -0
- package/sevWin.js +19 -0
package/lib/eventlog.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { exec, execSync, promisify, isPermissionError } from './shared.js';
|
|
2
|
+
import { elevate } from './binaries.js';
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec),
|
|
5
|
+
eventLogs = ['APPLICATION', 'SYSTEM', 'SECURITY'], validTypes = ['ERROR', 'WARNING', 'INFORMATION', 'SUCCESSAUDIT', 'FAILUREAUDIT'];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 事件日志记录器类,用于向Windows事件查看器写入日志
|
|
9
|
+
* - 提供记录信息、警告、错误和审计成功、审计失败事件的方法
|
|
10
|
+
* >查看定义:@see {@link EventLogger}、{@link info}、{@link warn}、{@link error}、{@link auditSuccess}、{@link auditFailure}
|
|
11
|
+
* @class EventLogger
|
|
12
|
+
* @example
|
|
13
|
+
* // 配置示例
|
|
14
|
+
* import { EventLogger } from '@flun/windows';
|
|
15
|
+
*
|
|
16
|
+
* // 创建日志实例
|
|
17
|
+
* const log = new EventLogger('服务名称');
|
|
18
|
+
* log.info('基本信息');
|
|
19
|
+
* log.warn('警告信息');
|
|
20
|
+
* log.error('错误信息');
|
|
21
|
+
* log.auditSuccess('审计成功');
|
|
22
|
+
* log.auditFailure('审计失败');
|
|
23
|
+
*
|
|
24
|
+
* // 自定义事件代码
|
|
25
|
+
* log.error('特殊事件', 1002, ()=>{
|
|
26
|
+
* console.log('日志已写入');
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
class EventLogger {
|
|
30
|
+
/**
|
|
31
|
+
* @constructor
|
|
32
|
+
* @param {string|Object} [config] - 配置字符串(作为 source)或配置对象
|
|
33
|
+
* @param {string} [config.source='Node.js'] - 事件源名称
|
|
34
|
+
* @param {string} [config.eventLog='APPLICATION'] - 事件日志名称(APPLICATION, SYSTEM, SECURITY)
|
|
35
|
+
*/
|
|
36
|
+
constructor(config = {}) {
|
|
37
|
+
if (typeof config === 'string') config = { source: config };
|
|
38
|
+
this.#initializeProperties(config);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#logname = 'APPLICATION';
|
|
42
|
+
#usePowerShellForAudit = false;
|
|
43
|
+
|
|
44
|
+
// 初始化属性
|
|
45
|
+
#initializeProperties(config) {
|
|
46
|
+
const { source = 'Node.js', eventLog = 'APPLICATION' } = config;
|
|
47
|
+
this.source = source, this.eventLog = eventLog;
|
|
48
|
+
this.#usePowerShellForAudit = false, this.#checkPowerShellAvailable();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 同步检测PowerShell是否可用
|
|
52
|
+
#checkPowerShellAvailable() {
|
|
53
|
+
try {
|
|
54
|
+
execSync('powershell -Command "exit 0"', { stdio: 'ignore' }), this.#usePowerShellForAudit = true;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.#usePowerShellForAudit = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 获取当前事件日志名称(大写)
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
get eventLog() {
|
|
65
|
+
return this.#logname.toUpperCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 设置事件日志名称,仅允许 APPLICATION, SYSTEM, SECURITY
|
|
70
|
+
* @param {string} value - 事件日志名称
|
|
71
|
+
*/
|
|
72
|
+
set eventLog(value) {
|
|
73
|
+
if (value) this.#logname = eventLogs.includes(value.toUpperCase()) ? value.toUpperCase() : 'APPLICATION';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* info方法的别名
|
|
78
|
+
* @returns {(message: string, code?: number, callback?: (error?: Error|null) => void) => Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
get information() {
|
|
81
|
+
return this.info.bind(this);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* warn方法的别名
|
|
86
|
+
* @returns {(message: string, code?: number, callback?: (error?: Error|null) => void) => Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
get warning() {
|
|
89
|
+
return this.warn.bind(this);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 判断是否需要使用PowerShell写入日志
|
|
94
|
+
* @private
|
|
95
|
+
* @param {string} logType - 日志类型
|
|
96
|
+
* @param {number} eventId - 事件ID
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
#shouldUsePowerShell(logType, eventId) {
|
|
100
|
+
// 审计类型或事件ID超过1000时使用PowerShell
|
|
101
|
+
const isAuditType = logType === 'SUCCESSAUDIT' || logType === 'FAILUREAUDIT', isEventIdOverLimit = eventId > 1000;
|
|
102
|
+
return this.#usePowerShellForAudit && (isAuditType || isEventIdOverLimit);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 将消息写入日志;如果日志不存在,则创建;
|
|
107
|
+
* @private
|
|
108
|
+
* @param {string} [log='APPLICATION'] - 日志名称
|
|
109
|
+
* @param {string} [src='未知应用程序'] - 事件来源
|
|
110
|
+
* @param {string} [type='INFORMATION'] - 日志类型
|
|
111
|
+
* @param {string} msg - 消息内容
|
|
112
|
+
* @param {number} [id=1000] - 事件ID
|
|
113
|
+
* @param {(error?: Error|null) => void} [callback] - 完成后的回调函数
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
async #write(log = 'APPLICATION', src = '未知应用程序', type = 'INFORMATION', msg, id = 1000, callback) {
|
|
117
|
+
if (!msg || msg.trim().length === 0) return;
|
|
118
|
+
|
|
119
|
+
const pMsg = msg.replace(/\r\n|\n\r|\r|\n/g, "\f"), // 替换换行符
|
|
120
|
+
vLog = eventLogs.includes(log.toUpperCase()) ? log : 'APPLICATION',
|
|
121
|
+
vType = validTypes.includes(type.toUpperCase()) ? type : 'INFORMATION',
|
|
122
|
+
vId = parseInt(id) || 1000, vSrc = src.trim();
|
|
123
|
+
|
|
124
|
+
let command;
|
|
125
|
+
// 判断是否需要使用PowerShell
|
|
126
|
+
if (this.#shouldUsePowerShell(vType, vId)) {
|
|
127
|
+
// 使用PowerShell的Write-EventLog命令
|
|
128
|
+
const entryTypeMap = { 'ERROR': 'Error', 'WARNING': 'Warning', 'SUCCESSAUDIT': 'SuccessAudit', 'FAILUREAUDIT': 'FailureAudit' },
|
|
129
|
+
entryType = entryTypeMap[vType] || 'Information', escapedMsg = pMsg.replace(/"/g, '""'),
|
|
130
|
+
powHad = 'powershell -Command "Write-EventLog -LogName';
|
|
131
|
+
command = `${powHad} '${vLog}' -Source '${vSrc}' -EventId ${vId} -EntryType ${entryType} -Message \\\"${escapedMsg}\\\""`;
|
|
132
|
+
} else {
|
|
133
|
+
const eventCreateId = Math.min(Math.max(1, vId), 1000); // 限制在1-1000范围内
|
|
134
|
+
command = `eventcreate /L ${vLog} /T ${vType} /SO "${vSrc}" /D "${pMsg}" /ID ${eventCreateId}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 执行命令
|
|
138
|
+
try {
|
|
139
|
+
await execAsync(command), callback?.();
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (isPermissionError(error?.message)) await this.#elevateCommand(command, callback);
|
|
142
|
+
else {
|
|
143
|
+
callback?.(error);
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 使用提升权限执行命令
|
|
151
|
+
* @private
|
|
152
|
+
* @param {string} command - 要执行的命令
|
|
153
|
+
* @param {(error?: Error|null) => void} [callback] - 完成回调
|
|
154
|
+
* @returns {Promise<void>}
|
|
155
|
+
*/
|
|
156
|
+
async #elevateCommand(command, callback) {
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
elevate(command, error => {
|
|
159
|
+
if (error) callback?.(error), reject(error);
|
|
160
|
+
else callback?.(), resolve();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 记录一条信息性消息
|
|
167
|
+
* >查看定义:@see {@link info}
|
|
168
|
+
* @param {string} message - 日志消息的内容
|
|
169
|
+
* @param {number} [code=1000] - 分配给消息的事件代码
|
|
170
|
+
* @param {(error?: Error|null) => void} [callback] - 消息记录后运行的可选回调函数
|
|
171
|
+
* @returns {Promise<void>}
|
|
172
|
+
*/
|
|
173
|
+
async info(message, code = 1000, callback) {
|
|
174
|
+
await this.#write(this.eventLog, this.source, 'INFORMATION', message, code, callback);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 记录一条警告消息
|
|
179
|
+
* >查看定义:@see {@link warn}
|
|
180
|
+
* @param {string} message - 日志消息的内容
|
|
181
|
+
* @param {number} [code=1000] - 分配给消息的事件代码
|
|
182
|
+
* @param {(error?: Error|null) => void} [callback] - 消息记录后运行的可选回调函数
|
|
183
|
+
* @returns {Promise<void>}
|
|
184
|
+
*/
|
|
185
|
+
async warn(message, code = 1000, callback) {
|
|
186
|
+
await this.#write(this.eventLog, this.source, 'WARNING', message, code, callback);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 记录一条错误消息
|
|
191
|
+
* >查看定义:@see {@link error}
|
|
192
|
+
* @param {string} message - 日志消息的内容
|
|
193
|
+
* @param {number} [code=1000] - 分配给消息的事件代码
|
|
194
|
+
* @param {(error?: Error|null) => void} [callback] - 消息记录后运行的可选回调函数
|
|
195
|
+
* @returns {Promise<void>}
|
|
196
|
+
*/
|
|
197
|
+
async error(message, code = 1000, callback) {
|
|
198
|
+
await this.#write(this.eventLog, this.source, 'ERROR', message, code, callback);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 记录一条审计成功消息
|
|
203
|
+
* >查看定义:@see {@link auditSuccess}
|
|
204
|
+
* @param {string} message - 日志消息的内容
|
|
205
|
+
* @param {number} [code=1000] - 分配给消息的事件代码
|
|
206
|
+
* @param {(error?: Error|null) => void} [callback] - 消息记录后运行的可选回调函数
|
|
207
|
+
* @returns {Promise<void>}
|
|
208
|
+
*/
|
|
209
|
+
async auditSuccess(message, code = 1000, callback) {
|
|
210
|
+
await this.#write(this.eventLog, this.source, 'SUCCESSAUDIT', message, code, callback);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* 记录一条审计失败消息
|
|
215
|
+
* >查看定义:@see {@link auditFailure}
|
|
216
|
+
* @param {string} message - 日志消息的内容
|
|
217
|
+
* @param {number} [code=1000] - 分配给消息的事件代码
|
|
218
|
+
* @param {(error?: Error|null) => void} [callback] - 消息记录后运行的可选回调函数
|
|
219
|
+
* @returns {Promise<void>}
|
|
220
|
+
*/
|
|
221
|
+
async auditFailure(message, code = 1000, callback) {
|
|
222
|
+
await this.#write(this.eventLog, this.source, 'FAILUREAUDIT', message, code, callback);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { EventLogger };
|
package/lib/shared.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 共享模块,导出在多个文件中使用的常用函数和模块
|
|
3
|
+
* >查看定义:@see {@link exec}、{@link execSync}、{@link fork}、{@link promisify}、{@link path}、{@link fs}、{@link EventEmitter}
|
|
4
|
+
*/
|
|
5
|
+
import { exec, execSync, fork } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import { EventEmitter } from 'events';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 检查错误是否为权限不足的错误
|
|
14
|
+
* >查看定义:@see {@link isPermissionError}
|
|
15
|
+
* @param {string|Error} error - 错误信息或错误对象
|
|
16
|
+
* @returns {boolean} 是否为权限错误
|
|
17
|
+
*/
|
|
18
|
+
const isPermissionError = error => {
|
|
19
|
+
const errorMessage = error?.message || error?.toString() || String(error),
|
|
20
|
+
permissionErrors = ['拒绝访问', 'Access is denied', '权限不足', 'Insufficient privileges', 'AccessDenied', '需要提升权限',
|
|
21
|
+
'requires elevation', 'Administrator', 'admin', '权限被拒绝'],
|
|
22
|
+
lowerMessage = errorMessage.toLowerCase();
|
|
23
|
+
return permissionErrors.some(keyword => lowerMessage.includes(keyword.toLowerCase()));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 获取当前模块所在目录的路径(类似 CommonJS 的 __dirname)
|
|
28
|
+
* >查看定义:@see {@link getDirname}
|
|
29
|
+
* @param {string} metaUrl - 传入 `import.meta.url`
|
|
30
|
+
* @returns {string} 当前模块的目录路径
|
|
31
|
+
*/
|
|
32
|
+
const getDirname = metaUrl => path.dirname(fileURLToPath(metaUrl));
|
|
33
|
+
|
|
34
|
+
export { exec, execSync, fork, promisify, path, fs, EventEmitter, isPermissionError, getDirname };
|
package/lib/winsw.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fs, path, getDirname } from './shared.js';
|
|
2
|
+
import xmlBuilder from 'xml';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const __dirname = getDirname(import.meta.url);
|
|
6
|
+
/**
|
|
7
|
+
* @method generateXml 生成 winsw 配置文件的 XML;
|
|
8
|
+
* >查看定义:@see {@link generateXml}
|
|
9
|
+
* @param {Object} config - 配置对象,必须包含以下必需属性:
|
|
10
|
+
* - *id* 服务的标识符,只能包含字母和数字,不能有空格
|
|
11
|
+
* - *name* 服务的描述性名称
|
|
12
|
+
* - *execPath* Node.js 可执行文件的绝对路径(如:C:\Program Files\nodejs\node.exe)
|
|
13
|
+
* - *script* Node.js 服务器脚本的绝对路径(包装脚本)
|
|
14
|
+
* - *workingdirectory* 服务运行的工作目录
|
|
15
|
+
* @param {string} [config.description] - 在服务管理器中显示的描述信息
|
|
16
|
+
* @param {string|string[]} [config.nodeOptions] - Node.js 选项(数组或空格分隔的字符串)
|
|
17
|
+
* @param {string} [config.wrapperArgs] - 传递给包装脚本的额外参数
|
|
18
|
+
* @param {string} [config.logmode] - 日志模式(老式):'rotate'(默认), 'reset', 'roll', 'append'
|
|
19
|
+
* @param {Object} [config.logging] - 日志配置对象,可替代 logmode
|
|
20
|
+
* @param {string} [config.logging.mode] - 日志模式(现代),支持 'append', 'reset', 'none', 'roll-by-time', 'roll-by-size'
|
|
21
|
+
* @param {string} [config.logging.pattern] - roll-by-time 模式的时间格式模式(默认:'yyyyMMdd')
|
|
22
|
+
* @param {number} [config.logging.sizeThreshold] - roll-by-size 模式的大小阈值(默认:10240)
|
|
23
|
+
* @param {number} [config.logging.keepFiles] - roll-by-size 模式保留的文件数(默认:8)
|
|
24
|
+
* @param {string} [config.logpath] - 日志存储目录的绝对路径(默认:当前目录)
|
|
25
|
+
* @param {string|string[]} [config.dependencies] - 进程依赖项的逗号分隔列表或数组
|
|
26
|
+
* @param {{ name: string, value: string }[]|Object<string, string>} [config.env] - 环境变量配置(键/值对象或对象数组)
|
|
27
|
+
* @param {Object} [config.logOnAs] - 服务登录凭据配置
|
|
28
|
+
* @param {string} [config.logOnAs.account] - 登录账户名
|
|
29
|
+
* @param {string} [config.logOnAs.password] - 登录密码
|
|
30
|
+
* @param {string} [config.logOnAs.domain] - 账户所在域
|
|
31
|
+
* @param {boolean} [config.allowServiceLogon] - 是否允许服务登录
|
|
32
|
+
* @param {boolean} [config.stopparentfirst] - 是否先停止父进程
|
|
33
|
+
* @param {number} [config.stoptimeout] - 停止超时时间(秒)
|
|
34
|
+
* @returns {string} 生成的 XML 字符串
|
|
35
|
+
* @throws {string} 当缺少必需配置时抛出错误
|
|
36
|
+
*/
|
|
37
|
+
const generateXml = config => {
|
|
38
|
+
const {
|
|
39
|
+
id, name, script, description = '', nodeOptions, wrapperArgs, logmode = 'rotate', logging, logpath, dependencies,
|
|
40
|
+
env, logOnAs, allowServiceLogon, workingdirectory, stopparentfirst, stoptimeout, execPath
|
|
41
|
+
} = config;
|
|
42
|
+
|
|
43
|
+
// 验证必需配置项是否存在
|
|
44
|
+
if (!id || !name || !script || !execPath || !workingdirectory) throw "id,name,script,execPath,workingdirectory为必需配置项";
|
|
45
|
+
|
|
46
|
+
let xml = [{ id }, { name }, { description }, { executable: execPath }]; // 基础 XML 结构
|
|
47
|
+
|
|
48
|
+
// 多值处理
|
|
49
|
+
const multi = (tag, input, splitter = ',') => {
|
|
50
|
+
if (!input) return;
|
|
51
|
+
const items = Array.isArray(input) ? input : input.split(splitter); // 标准化为数组
|
|
52
|
+
items.forEach(val => xml.push({ [tag]: String(val).trim() })); // 添加到 XML 结构
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
multi('argument', nodeOptions, ' '); // 添加 Node.js 选项参数
|
|
56
|
+
xml.push({ argument: script.trim() }); // 添加主脚本路径参数
|
|
57
|
+
multi('argument', wrapperArgs, ' '); // 添加包装脚本参数
|
|
58
|
+
|
|
59
|
+
// 日志配置处理:优先使用 logging 对象,其次使用 logmode
|
|
60
|
+
if (logging) {
|
|
61
|
+
const { mode = 'append', pattern = 'yyyyMMdd', sizeThreshold = 10240, keepFiles = 8 } = logging, logContent = [{ _attr: { mode } }];
|
|
62
|
+
if (mode === 'roll-by-time') logContent.push({ pattern }); // 按时间滚动日志
|
|
63
|
+
else if (mode === 'roll-by-size') logContent.push({ sizeThreshold }), logContent.push({ keepFiles }); // 按大小滚动日志
|
|
64
|
+
xml.push({ log: logContent });
|
|
65
|
+
}
|
|
66
|
+
else xml.push({ logmode });
|
|
67
|
+
|
|
68
|
+
if (logpath) xml.push({ logpath }); // 添加日志路径
|
|
69
|
+
if (stopparentfirst) xml.push({ stopparentprocessfirst: stopparentfirst }); // 添加停止父进程优先选项
|
|
70
|
+
if (stoptimeout) xml.push({ stoptimeout: `${stoptimeout} sec` }); // 添加停止超时设置
|
|
71
|
+
|
|
72
|
+
multi('depend', dependencies); // 添加依赖项
|
|
73
|
+
env && xml.push(...[].concat(env).map(({ name, value }) => ({ env: { _attr: { name, value } } }))); // 添加环境变量
|
|
74
|
+
|
|
75
|
+
// 添加服务登录凭据
|
|
76
|
+
if (logOnAs) {
|
|
77
|
+
const { domain = 'NT AUTHORITY', account = 'LocalSystem', password = '' } = logOnAs,
|
|
78
|
+
serviceaccount = [{ domain }, { user: account }, { password }];
|
|
79
|
+
if (allowServiceLogon) serviceaccount.push({ allowservicelogon: 'true' }); // 允许服务登录
|
|
80
|
+
xml.push({ serviceaccount });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
xml.push({ workingdirectory }); // 添加工作目录
|
|
84
|
+
return xmlBuilder({ service: xml }, { indent: '\t' }).replace(/\n/g, '\r\n'); // 格式化为 Windows 风格的换行符
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @method createExe 创建 winsw 可执行文件及其配置文件
|
|
89
|
+
* >查看定义:@see {@link createExe}
|
|
90
|
+
* @param {string} name - 可执行文件名称(不含扩展名);
|
|
91
|
+
* @param {string} dir - 输出目录(默认:当前工作目录)
|
|
92
|
+
* @param {() => void} [callback] - 完成后的回调函数
|
|
93
|
+
* @description 将 winsw.exe 的安装版本复制为根据服务id重命名的特定版本,
|
|
94
|
+
* 并复制 .exe.config 文件以支持 .NET 4+ 运行时环境;
|
|
95
|
+
*/
|
|
96
|
+
const createExe = (name, dir, callback) => {
|
|
97
|
+
if (!name || !dir) throw "createExe 方法需要 name 和 dir 两个参数!";
|
|
98
|
+
|
|
99
|
+
const binaryOptions = { encoding: 'binary' }, basePath = path.join(__dirname, '../bin/winsw'),
|
|
100
|
+
files = [{ src: 'winsw.exe', dest: `${name}.exe` }, { src: 'winsw.exe.config', dest: `${name}.exe.config` }];
|
|
101
|
+
|
|
102
|
+
// 复制文件到目标目录
|
|
103
|
+
files.forEach(file => {
|
|
104
|
+
const srcPath = path.join(basePath, file.src), destPath = path.join(dir, file.dest);
|
|
105
|
+
fs.writeFileSync(destPath, fs.readFileSync(srcPath, binaryOptions), binaryOptions);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
callback?.();
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export { generateXml, createExe };
|