@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/lib/eventlog.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { execPromise, execSync, isPermissionError } from './shared.js';
2
2
  import { elevate } from './binaries.js';
3
3
 
4
- const eventLogs = ['APPLICATION', 'SYSTEM', 'SECURITY'], validTypes = ['ERROR', 'WARNING', 'INFORMATION', 'SUCCESSAUDIT', 'FAILUREAUDIT'];
4
+ const eventLogs = ['APPLICATION', 'SYSTEM', 'SECURITY'],
5
+ validTypes = ['ERROR', 'WARNING', 'INFORMATION', 'SUCCESSAUDIT', 'FAILUREAUDIT'];
5
6
 
6
7
  /**
7
8
  * 事件日志记录器类,用于向Windows事件查看器写入日志
8
9
  * - 提供记录信息、警告、错误和审计成功、审计失败事件的方法
9
- * >查看定义:@see {@link EventLogger}、{@link info}、{@link warn}、{@link error}、{@link auditSuccess}、{@link auditFailure}
10
+ * >查看定义:@see {@link EventLogger}、{@link info}、{@link warn}、{@link error}、{@link auditSuccess}、
11
+ * {@link auditFailure}
10
12
  * @class EventLogger
11
13
  * @example
12
14
  * // 配置示例
@@ -28,9 +30,9 @@ const eventLogs = ['APPLICATION', 'SYSTEM', 'SECURITY'], validTypes = ['ERROR',
28
30
  class EventLogger {
29
31
  /**
30
32
  * @constructor
31
- * @param {string|Object} [config] - 配置字符串(作为 source)或配置对象
33
+ * @param {string|Object} [config] - 配置字符串(作为 source)或配置对象
32
34
  * @param {string} [config.source='Node.js'] - 事件源名称
33
- * @param {string} [config.eventLog='APPLICATION'] - 事件日志名称(APPLICATION, SYSTEM, SECURITY)
35
+ * @param {string} [config.eventLog='APPLICATION'] - 事件日志名称(APPLICATION, SYSTEM, SECURITY)
34
36
  */
35
37
  constructor(config = {}) {
36
38
  if (typeof config === 'string') config = { source: config };
@@ -57,7 +59,7 @@ class EventLogger {
57
59
  }
58
60
 
59
61
  /**
60
- * 获取当前事件日志名称(大写)
62
+ * 获取当前事件日志名称(大写)
61
63
  * @returns {string}
62
64
  */
63
65
  get eventLog() {
@@ -124,10 +126,14 @@ class EventLogger {
124
126
  // 判断是否需要使用PowerShell
125
127
  if (this.#shouldUsePowerShell(vType, vId)) {
126
128
  // 使用PowerShell的Write-EventLog命令
127
- const entryTypeMap = { 'ERROR': 'Error', 'WARNING': 'Warning', 'SUCCESSAUDIT': 'SuccessAudit', 'FAILUREAUDIT': 'FailureAudit' },
129
+ const entryTypeMap = {
130
+ 'ERROR': 'Error', 'WARNING': 'Warning',
131
+ 'SUCCESSAUDIT': 'SuccessAudit', 'FAILUREAUDIT': 'FailureAudit'
132
+ },
128
133
  entryType = entryTypeMap[vType] || 'Information', escapedMsg = pMsg.replace(/"/g, '""'),
129
134
  powHad = 'powershell -Command "Write-EventLog -LogName';
130
- command = `${powHad} '${vLog}' -Source '${vSrc}' -EventId ${vId} -EntryType ${entryType} -Message \\\"${escapedMsg}\\\""`;
135
+ command = `${powHad} '${vLog}' -Source '${vSrc}' -EventId ${vId}` +
136
+ ` -EntryType ${entryType} -Message \\\"${escapedMsg}\\\""`;
131
137
  } else {
132
138
  const eventCreateId = Math.min(Math.max(1, vId), 1000); // 限制在1-1000范围内
133
139
  command = `eventcreate /L ${vLog} /T ${vType} /SO "${vSrc}" /D "${pMsg}" /ID ${eventCreateId}`;
package/lib/shared.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 共享模块,导出在多个文件中使用的常用函数和模块
3
- * >查看定义:@see {@link exec}、{@link execSync}、{@link fork}、{@link promisify}、{@link path}、{@link fs}、{@link EventEmitter}
3
+ * >查看定义:@see {@link exec}、{@link execSync}、{@link fork}、{@link promisify}、{@link path}、
4
+ * {@link fs}、{@link EventEmitter}
4
5
  */
5
6
  import { exec, execSync, fork } from 'child_process';
6
7
  import { promisify } from 'util';
@@ -24,14 +25,15 @@ const execPromise = promisify(exec);
24
25
  */
25
26
  const isPermissionError = error => {
26
27
  const errorMessage = error?.message || error?.toString() || String(error),
27
- permissionErrors = ['拒绝访问', 'Access is denied', '权限不足', 'Insufficient privileges', 'AccessDenied', '需要提升权限',
28
+ permissionErrors = ['拒绝访问', 'Access is denied', '权限不足', 'Insufficient privileges',
29
+ 'AccessDenied', '需要提升权限',
28
30
  'requires elevation', 'Administrator', 'admin', '权限被拒绝'],
29
31
  lowerMessage = errorMessage.toLowerCase();
30
32
  return permissionErrors.some(keyword => lowerMessage.includes(keyword.toLowerCase()));
31
33
  }
32
34
 
33
35
  /**
34
- * 获取当前模块所在目录的路径(类似 CommonJS 的 __dirname)
36
+ * 获取当前模块所在目录的路径(类似 CommonJS 的 __dirname)
35
37
  * >查看定义:@see {@link getDirname}
36
38
  * @param {string} metaUrl - 传入 `import.meta.url`
37
39
  * @returns {string} 当前模块的目录路径
package/lib/winsw.js CHANGED
@@ -6,31 +6,31 @@ const __dirname = getDirname(import.meta.url);
6
6
  /**
7
7
  * @method generateXml 生成 winsw 配置文件的 XML;
8
8
  * >查看定义:@see {@link generateXml}
9
- * @param {Object} config - 配置对象,必须包含以下必需属性:
9
+ * @param {Object} config - 配置对象,必须包含以下必需属性:
10
10
  * - *id* 服务的标识符,只能包含字母和数字,不能有空格
11
11
  * - *name* 服务的描述性名称
12
- * - *execPath* Node.js 可执行文件的绝对路径(如:C:\Program Files\nodejs\node.exe)
13
- * - *script* Node.js 服务器脚本的绝对路径(包装脚本)
12
+ * - *execPath* Node.js 可执行文件的绝对路径(如:C:\Program Files\nodejs\node.exe)
13
+ * - *script* Node.js 服务器脚本的绝对路径(包装脚本)
14
14
  * - *workingdirectory* 服务运行的工作目录
15
15
  * @param {string} [config.description] - 在服务管理器中显示的描述信息
16
- * @param {string|string[]} [config.nodeOptions] - Node.js 选项(数组或空格分隔的字符串)
16
+ * @param {string|string[]} [config.nodeOptions] - Node.js 选项(数组或空格分隔的字符串)
17
17
  * @param {string} [config.wrapperArgs] - 传递给包装脚本的额外参数
18
18
  * @param {string} [config.logmode] - 日志模式(老式):'rotate'(默认), 'reset', 'roll', 'append'
19
19
  * @param {Object} [config.logging] - 日志配置对象,可替代 logmode
20
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] - 日志存储目录的绝对路径(默认:当前目录)
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
25
  * @param {string|string[]} [config.dependencies] - 进程依赖项的逗号分隔列表或数组
26
- * @param {{ name: string, value: string }[]|Object<string, string>} [config.env] - 环境变量配置(键/值对象或对象数组)
26
+ * @param {{ name: string, value: string }[]|Object<string, string>} [config.env] - 环境变量配置(键/值对象或对象数组)
27
27
  * @param {Object} [config.logOnAs] - 服务登录凭据配置
28
28
  * @param {string} [config.logOnAs.account] - 登录账户名
29
29
  * @param {string} [config.logOnAs.password] - 登录密码
30
30
  * @param {string} [config.logOnAs.domain] - 账户所在域
31
31
  * @param {boolean} [config.allowServiceLogon] - 是否允许服务登录
32
32
  * @param {boolean} [config.stopparentfirst] - 是否先停止父进程
33
- * @param {number} [config.stoptimeout] - 停止超时时间(秒)
33
+ * @param {number} [config.stoptimeout] - 停止超时时间(秒)
34
34
  * @returns {string} 生成的 XML 字符串
35
35
  * @throws {string} 当缺少必需配置时抛出错误
36
36
  */
@@ -41,7 +41,8 @@ const generateXml = config => {
41
41
  } = config;
42
42
 
43
43
  // 验证必需配置项是否存在
44
- if (!id || !name || !script || !execPath || !workingdirectory) throw "id,name,script,execPath,workingdirectory为必需配置项";
44
+ if (!id || !name || !script || !execPath || !workingdirectory)
45
+ throw "id,name,script,execPath,workingdirectory为必需配置项";
45
46
 
46
47
  let xml = [{ id }, { name }, { description }, { executable: execPath }]; // 基础 XML 结构
47
48
 
@@ -58,7 +59,8 @@ const generateXml = config => {
58
59
 
59
60
  // 日志配置处理:优先使用 logging 对象,其次使用 logmode
60
61
  if (logging) {
61
- const { mode = 'append', pattern = 'yyyyMMdd', sizeThreshold = 10240, keepFiles = 8 } = logging, logContent = [{ _attr: { mode } }];
62
+ const { mode = 'append', pattern = 'yyyyMMdd', sizeThreshold = 10240, keepFiles = 8 } = logging,
63
+ logContent = [{ _attr: { mode } }];
62
64
  if (mode === 'roll-by-time') logContent.push({ pattern }); // 按时间滚动日志
63
65
  else if (mode === 'roll-by-size') logContent.push({ sizeThreshold }), logContent.push({ keepFiles }); // 按大小滚动日志
64
66
  xml.push({ log: logContent });
@@ -88,7 +90,7 @@ const generateXml = config => {
88
90
  * @method createExe 创建 winsw 可执行文件及其配置文件
89
91
  * >查看定义:@see {@link createExe}
90
92
  * @param {string} name - 可执行文件名称(不含扩展名);
91
- * @param {string} dir - 输出目录(默认:当前工作目录)
93
+ * @param {string} dir - 输出目录(默认:当前工作目录)
92
94
  * @param {() => void} [callback] - 完成后的回调函数
93
95
  * @description 将 winsw.exe 的安装版本复制为根据服务id重命名的特定版本,
94
96
  * 并复制 .exe.config 文件以支持 .NET 4+ 运行时环境;
package/lib/wrapper.js CHANGED
@@ -8,14 +8,14 @@
8
8
  * 4. 重启次数限制和频率控制
9
9
  * 5. 支持通过事件日志或控制台输出日志
10
10
  *
11
- * 命令行参数:
12
- * -f, --file: 要运行的脚本绝对路径(必需)
11
+ * 命令行参数:
12
+ * -f, --file: 要运行的脚本绝对路径(必需)
13
13
  * -d, --cwd: 子进程工作目录
14
- * -l, --log: 日志描述名称(必需)
15
- * -e, --eventlog: 事件日志容器(APPLICATION或SYSTEM)
16
- * -m, --maxretries: 最大重启次数(-1表示无限制)
14
+ * -l, --log: 日志描述名称(必需)
15
+ * -e, --eventlog: 事件日志容器(APPLICATION或SYSTEM)
16
+ * -m, --maxretries: 最大重启次数(-1表示无限制)
17
17
  * -r, --maxrestarts: 时间窗口内最大重启次数
18
- * -w, --wait: 重启等待时间(秒)
18
+ * -w, --wait: 重启等待时间(秒)
19
19
  * -g, --grow: 等待时间增长系数
20
20
  * -a, --abortonerror: 错误时是否终止
21
21
  * -s, --stopparentfirst: 是否允许优雅退出
@@ -27,7 +27,7 @@ import yargs from 'yargs/yargs';
27
27
  import { hideBin } from 'yargs/helpers';
28
28
  import { EventLogger } from './eventlog.js';
29
29
 
30
- // 时间窗口(秒),默认时间窗口内最大重启次数,默认重启等待时间(秒),默认等待时间增长系数
30
+ // 时间窗口(秒),默认时间窗口内最大重启次数,默认重启等待时间(秒),默认等待时间增长系数
31
31
  const timeWindow = 60, defaultMaxRestarts = 3, defaultWaitTime = 1, defaultGrowth = 0.25,
32
32
  booleanChoices = ['y', 'n', 'yes', 'no', 'true', 'false'],
33
33
  /**
@@ -104,7 +104,7 @@ const timeWindow = 60, defaultMaxRestarts = 3, defaultWaitTime = 1, defaultGrowt
104
104
  alias: 'm',
105
105
  type: 'number',
106
106
  default: -1,
107
- description: '进程自动重启的最大次数(-1表示无限制)'
107
+ description: '进程自动重启的最大次数(-1表示无限制)'
108
108
  })
109
109
  .option('maxrestarts', {
110
110
  alias: 'r',
@@ -126,7 +126,7 @@ const timeWindow = 60, defaultMaxRestarts = 3, defaultWaitTime = 1, defaultGrowt
126
126
  alias: 'g',
127
127
  type: 'number',
128
128
  default: defaultGrowth,
129
- description: '等待时间增长的增长百分比(0-1之间)',
129
+ description: '等待时间增长的增长百分比(0-1之间)',
130
130
  check: growth => {
131
131
  if (growth < 0 || growth > 1) throw new Error('增长率必须在0到1之间');
132
132
  return true;
@@ -150,7 +150,7 @@ const timeWindow = 60, defaultMaxRestarts = 3, defaultWaitTime = 1, defaultGrowt
150
150
  })
151
151
  .option('scriptoptions', {
152
152
  type: 'string',
153
- description: '要传递给脚本的选项(空格分隔)',
153
+ description: '要传递给脚本的选项(空格分隔)',
154
154
  default: '',
155
155
  coerce: parseQuotedOption
156
156
  }).help().parse();
@@ -223,7 +223,7 @@ const prepareScriptArgs = () => {
223
223
 
224
224
  // 检查总重启次数限制
225
225
  if (argv.maxretries >= 0 && restartAttempts > argv.maxretries)
226
- logger.error(`重启次数过多。${scriptPath} 将不会被重启,已超过最大重启次数 ${argv.maxretries}`), process.exit(1);
226
+ logger.error(`重启次数过多;${scriptPath} 将不会被重启,已超过最大重启次数 ${argv.maxretries}`), process.exit(1);
227
227
 
228
228
  launchProcess('warn', `在意外退出后 ${waitTime} 毫秒重启: 尝试次数=${restartAttempts}`);
229
229
  }, waitTime);
@@ -233,7 +233,7 @@ const prepareScriptArgs = () => {
233
233
 
234
234
  /**
235
235
  * 启动子进程
236
- * @param {string} logLevel - 日志级别(info, warn, error等)
236
+ * @param {string} logLevel - 日志级别(info, warn, error等)
237
237
  * @param {string} message - 日志消息
238
238
  * @returns {void}
239
239
  */
@@ -273,7 +273,7 @@ const prepareScriptArgs = () => {
273
273
  logger.warn(exitMessage);
274
274
  // 检查是否需要因错误而终止
275
275
  if (code !== 0 && (argv.abortonerror === 'yes' || argv.abortonerror === 'y'))
276
- logger.error(`${scriptPath} 以错误代码 ${code} 退出,终止包装器进程`), process.exit(code || 1);
276
+ logger.error(`${scriptPath} 以错误代码 ${code} 退出,终止包装器进程`), process.exit(code || 1);
277
277
  else if (isForceKill) process.exit(0);
278
278
 
279
279
  childProcess = null, monitorChildProcess(); // 重新监控并可能重启
@@ -306,10 +306,10 @@ const prepareScriptArgs = () => {
306
306
  // 注册进程终止事件处理程序
307
307
  process.on('exit', terminateChildProcess);
308
308
  process.on('SIGINT', () => {
309
- logger.info('收到 SIGINT 信号,正在终止...'), terminateChildProcess();
309
+ logger.info('收到 SIGINT 信号,正在终止...'), terminateChildProcess();
310
310
  });
311
311
  process.on('SIGTERM', () => {
312
- logger.info('收到 SIGTERM 信号,正在终止...'), terminateChildProcess();
312
+ logger.info('收到 SIGTERM 信号,正在终止...'), terminateChildProcess();
313
313
  });
314
314
 
315
315
  // 处理未捕获异常
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flun/windows",
3
- "version": "3.0.10",
3
+ "version": "4.0.0",
4
4
  "description": "支持 Windows 服务、事件日志、UAC 以及多种与操作系统交互的辅助方法",
5
5
  "keywords": [
6
6
  "windows",
@@ -23,12 +23,15 @@
23
23
  ".": "./index.js"
24
24
  },
25
25
  "main": "index.js",
26
+ "bin": {
27
+ "windows": "bin/cli.js"
28
+ },
26
29
  "files": [
27
30
  "lib",
28
31
  "bin",
29
32
  "index.d.ts",
30
33
  "index.js",
31
- "sevWin.js",
34
+ "win-service.config.js",
32
35
  "copy-files.js",
33
36
  "CHANGELOG.md",
34
37
  "README.md",
@@ -36,6 +39,7 @@
36
39
  ],
37
40
  "preferGlobal": true,
38
41
  "dependencies": {
42
+ "chalk": "^6.0.1",
39
43
  "xml": "1.0.1",
40
44
  "yargs": "^18.2.0"
41
45
  },
@@ -47,7 +51,7 @@
47
51
  "type": "git",
48
52
  "url": "git+https://github.com/OpenFlun/windows.git"
49
53
  },
50
- "homepage": "https://www.npmjs.com/package/@flun/windows#readme",
54
+ "homepage": "https://github.com/OpenFlun/windows#readme",
51
55
  "bugs": {
52
56
  "url": "https://github.com/OpenFlun/windows/issues"
53
57
  },
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Windows 服务配置模板
3
+ * 带 * 的为必填项,其余可删除或按需保留;
4
+ * 本配置既支持命令行方式(如 windows install),
5
+ * 也支持在函数方式中导入:import config from './win-service.config.js';
6
+ * 具体用法见 README 的「命令行」与「函数方式」章节;
7
+ *
8
+ * 关于 quiet:默认 false,各方法执行后会在终端打印结果(如"服务安装完成;");
9
+ * 若本包嵌入到自己的生产项目,终端输出会混入宿主日志,此时设为 true,
10
+ * 库只发事件不打印,由宿主自行监听 install/start/stop/uninstall/error 等事件;
11
+ * 命令行方式恒为 quiet,不受此字段影响;
12
+ */
13
+ export default {
14
+ // * 服务名称,用于服务管理与注册表标识;不能含 <>:"\/|?*
15
+ name: 'MyApp',
16
+ // * 启动服务的入口脚本绝对路径
17
+ script: 'C:\\path\\to\\main.js',
18
+
19
+ // 服务描述,显示在服务管理器中
20
+ description: 'Node.js 服务',
21
+ // 传递给 node 进程的选项,可为数组或空格分隔的字符串
22
+ nodeOptions: ['--harmony'],
23
+ // 传递给脚本的选项
24
+ scriptOptions: '',
25
+ // node 可执行文件路径,默认取当前进程的 execPath
26
+ execPath: process.execPath,
27
+ // 服务工作目录,默认取当前工作目录
28
+ workingDirectory: process.cwd(),
29
+
30
+ // 无响应/故障前的最大重试次数,默认 null(不启用)
31
+ maxRetries: null,
32
+ // 60 秒内最大重启次数,0 表示不启用,默认 3
33
+ maxRestarts: 3,
34
+ // 停止服务的超时秒数,默认 30
35
+ stoptimeout: 30,
36
+ // 脚本停止后等待重启的秒数,默认 1
37
+ wait: 1,
38
+ // 重启等待时间的增长百分比,默认 0.25
39
+ grow: 0.25,
40
+ // 脚本运行错误时是否退出进程,默认 false
41
+ abortOnError: false,
42
+ // 是否先停止父进程,默认 false
43
+ stopparentfirst: false,
44
+
45
+ // 日志文件路径,默认与可执行文件同目录
46
+ logpath: null,
47
+ // 日志模式:rotate(默认)、truncate、append
48
+ logmode: 'rotate',
49
+
50
+ // 环境变量,形式为 { name, value } 或同构数组
51
+ env: { name: 'NODE_ENV', value: 'production' },
52
+
53
+ // 日志配置对象,可替代 logmode;含 mode、pattern、sizeThreshold、keepFiles
54
+ logging: undefined,
55
+
56
+ // 服务登录凭据;默认使用 LocalSystem
57
+ logOnAs: {
58
+ account: undefined,
59
+ password: null,
60
+ domain: process.env.COMPUTERNAME,
61
+ mungeCredentialsAfterInstall: true,
62
+ },
63
+ // 是否允许服务登录,默认 undefined
64
+ allowServiceLogon: undefined,
65
+
66
+ // 为 true 时不打印执行结果,只发事件;嵌入生产项目时建议开启
67
+ quiet: false,
68
+ };
package/sevWin.js DELETED
@@ -1,18 +0,0 @@
1
- import { Service } from '@flun/windows'; // 可选参数(EventLogger,elevate, sudo, isAdminUser, kill, list)
2
-
3
- const serviceName = 'TestApp', scriptPath = 'D:\\test\\dev.js', // 请根据实际路径修改
4
- svc = new Service({
5
- name: serviceName,
6
- description: 'Node.js 开发服务器',
7
- script: scriptPath,
8
- nodeOptions: ['--harmony', '--max-old-space-size=4096'],
9
- env: { name: "NODE_ENV", value: "production" },
10
- });
11
-
12
- const installService = () => {
13
- console.log('🚀 开始安装服务...');
14
- svc.on('install', () => svc.start());
15
- svc.on('start', () => console.log('✅ 安装成功,服务已启动!!'));
16
- svc.install();
17
- }
18
- installService();