pczzz 0.0.4 → 0.0.8
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/LICENSE +1 -1
- package/README.md +18 -24
- package/dist/cli.js +17 -15
- package/dist/menu.js +1 -1
- package/dist/shutdown.js +3 -3
- package/dist/store.js +1 -1
- package/package.json +5 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,33 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pczzz
|
|
2
2
|
|
|
3
3
|
定时关机命令行工具
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install -g
|
|
8
|
+
npm install -g pczzz
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
### 命令行直接指定时间
|
|
11
|
+
## 用法
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
pczzz 1s # 1 秒后关机
|
|
15
|
+
pczzz 20m # 20 分钟后关机
|
|
16
|
+
pczzz 1h # 1 小时后关机
|
|
17
|
+
pczzz 1h30m # 组合单位:1 小时 30 分钟后
|
|
18
|
+
pczzz 90 # 纯数字按秒计
|
|
19
|
+
pczzz 30秒 # 中文单位也可以
|
|
22
20
|
```
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### 交互式菜单
|
|
27
|
-
|
|
28
|
-
直接运行 `pcoff`(不带参数):
|
|
22
|
+
或者不带参数执行 `pczzz`
|
|
29
23
|
|
|
30
|
-
```
|
|
24
|
+
```bash
|
|
31
25
|
你想多久关机?
|
|
32
26
|
1. 30 秒
|
|
33
27
|
2. 20 分钟
|
|
@@ -44,7 +38,7 @@ pcoff 30秒 # 中文单位也可以
|
|
|
44
38
|
### 取消关机
|
|
45
39
|
|
|
46
40
|
```bash
|
|
47
|
-
|
|
41
|
+
pczzz cancel
|
|
48
42
|
```
|
|
49
43
|
|
|
50
44
|
或在菜单中选择「取消当前关机」。
|
|
@@ -53,9 +47,9 @@ pcoff cancel
|
|
|
53
47
|
|
|
54
48
|
## 重复安排检测
|
|
55
49
|
|
|
56
|
-
安排新的关机前,
|
|
50
|
+
安排新的关机前,pczzz 会检查是否已存在定时关机任务:
|
|
57
51
|
|
|
58
|
-
1. **自己的记录**(`~/.
|
|
52
|
+
1. **自己的记录**(`~/.pczzz/task.json`):能显示确切的剩余时间,例如
|
|
59
53
|
「当前已安排 1 小时 29 分钟 后关机(本工具于 23:01:35 设置)。要重新安排吗?(Y/N)」
|
|
60
54
|
2. **系统级检测(仅 Windows)**:即使任务不是本工具设置的(比如手动执行过 `shutdown -s -t 3600`),也能检测到。Windows 在已有计划关机时再次执行 `shutdown` 会返回错误码 1190,据此弹出「要取消旧任务并重新安排吗?」的询问。外来任务拿不到剩余时间,只能提示存在。
|
|
61
55
|
|
|
@@ -66,18 +60,18 @@ pcoff cancel
|
|
|
66
60
|
| 平台 | 精度 | 权限 | 说明 |
|
|
67
61
|
| ------------- | ------ | ----------------------------- | ----------------------------------------------------- |
|
|
68
62
|
| Windows | 秒级 | 无需管理员 | 完整支持,含系统级检测 |
|
|
69
|
-
| macOS / Linux | 分钟级 | 需要 root(`sudo
|
|
63
|
+
| macOS / Linux | 分钟级 | 需要 root(`sudo pczzz ...`) | `shutdown` 命令仅支持分钟;无系统级检测,依赖记录文件 |
|
|
70
64
|
|
|
71
65
|
## 开发
|
|
72
66
|
|
|
73
67
|
```bash
|
|
74
68
|
npm install
|
|
75
69
|
npm run build # tsc 编译到 dist/
|
|
76
|
-
npm test # 冒烟测试(
|
|
77
|
-
npm link # 本地全局试用
|
|
70
|
+
npm test # 冒烟测试(PCZZZ_DRY_RUN=1,不会真的关机)
|
|
71
|
+
npm link # 本地全局试用 pczzz 命令
|
|
78
72
|
```
|
|
79
73
|
|
|
80
|
-
测试钩子:`
|
|
74
|
+
测试钩子:`PCZZZ_DRY_RUN=1` 跳过真实的 shutdown 调用;`PCZZZ_HOME` 指定记录文件目录(默认 `~/.pczzz`)。
|
|
81
75
|
|
|
82
76
|
## 发布
|
|
83
77
|
|
package/dist/cli.js
CHANGED
|
@@ -6,12 +6,12 @@ import { abortShutdown, scheduleShutdown } from './shutdown.js';
|
|
|
6
6
|
import { Prompt } from './menu.js';
|
|
7
7
|
import { bold, cyan, dim, green, red, yellow } from './ui.js';
|
|
8
8
|
const { version: VERSION } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
9
|
-
const HELP = `
|
|
9
|
+
const HELP = `pczzz — 定时关机小工具
|
|
10
10
|
|
|
11
11
|
用法:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
pczzz 打开交互式菜单
|
|
13
|
+
pczzz <时间> 指定时间后关机,例如: pczzz 30s
|
|
14
|
+
pczzz cancel 取消当前已安排的定时关机
|
|
15
15
|
|
|
16
16
|
时间格式(可自由组合):
|
|
17
17
|
30s 30 秒后 20m 20 分钟后 1h 1 小时后
|
|
@@ -24,15 +24,15 @@ const HELP = `pcoff — 定时关机小工具
|
|
|
24
24
|
|
|
25
25
|
说明:
|
|
26
26
|
· 关机计划由操作系统执行,退出本程序或关闭窗口不会取消它。
|
|
27
|
-
· 取消方式:
|
|
27
|
+
· 取消方式: pczzz cancel,或菜单中的「取消当前关机」。
|
|
28
28
|
· Windows 完整支持(秒级精度,无需管理员权限)。
|
|
29
|
-
· macOS / Linux: shutdown 需要 root 权限(sudo
|
|
29
|
+
· macOS / Linux: shutdown 需要 root 权限(sudo pczzz ...),且只能按分钟安排。`;
|
|
30
30
|
function fail(message) {
|
|
31
31
|
console.error(message);
|
|
32
32
|
process.exit(1);
|
|
33
33
|
}
|
|
34
34
|
function exitGracefully() {
|
|
35
|
-
process.stdout.write('\n已退出。已设置的定时关机仍会执行;取消请运行
|
|
35
|
+
process.stdout.write('\n已退出。已设置的定时关机仍会执行;取消请运行 pczzz cancel。\n');
|
|
36
36
|
process.exit(0);
|
|
37
37
|
}
|
|
38
38
|
/** 安排成功后的提示 + 倒计时。倒计时期间 Ctrl+C 随时退出,不影响系统层面的关机。 */
|
|
@@ -45,7 +45,7 @@ async function printScheduled(seconds) {
|
|
|
45
45
|
console.log(yellow(`(记录文件写入失败,不影响已安排的关机:${err instanceof Error ? err.message : String(err)})`));
|
|
46
46
|
}
|
|
47
47
|
console.log(green(`✅ 已安排在本机 ${formatTimeOfDay(fireAt)} 自动关机(${formatDuration(seconds)} 后)。`));
|
|
48
|
-
console.log(dim(' 关机由系统执行,关闭本窗口不影响;取消请运行
|
|
48
|
+
console.log(dim(' 关机由系统执行,关闭本窗口不影响;取消请运行 pczzz cancel。'));
|
|
49
49
|
startCountdown(seconds);
|
|
50
50
|
}
|
|
51
51
|
function startCountdown(seconds) {
|
|
@@ -68,7 +68,7 @@ function startCountdown(seconds) {
|
|
|
68
68
|
* 返回 false 表示用户选择保留旧计划。
|
|
69
69
|
*
|
|
70
70
|
* 检测分两层:
|
|
71
|
-
* 1. 记录文件(~/.
|
|
71
|
+
* 1. 记录文件(~/.pczzz/task.json):本工具自己设置的、尚未到期的任务,能给出剩余时间;
|
|
72
72
|
* 2. Windows 系统探测:shutdown 若因错误码 1190 失败,说明系统里另有一个计划中的关机
|
|
73
73
|
* (外来任务或重启后记录失效),此时询问用户是否取消旧的再排新的。
|
|
74
74
|
*/
|
|
@@ -78,7 +78,7 @@ async function scheduleWithCheck(prompt, seconds) {
|
|
|
78
78
|
const remaining = Math.ceil((Date.parse(own.fireAt) - Date.now()) / 1000);
|
|
79
79
|
const replace = await prompt.confirm(`当前已安排 ${bold(formatDuration(remaining))} 后关机(本工具于 ${formatTimeOfDay(new Date(own.createdAt))} 设置)。要重新安排吗?`);
|
|
80
80
|
if (!replace) {
|
|
81
|
-
console.log(dim('已保留原计划;取消请运行
|
|
81
|
+
console.log(dim('已保留原计划;取消请运行 pczzz cancel。'));
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -91,7 +91,7 @@ async function scheduleWithCheck(prompt, seconds) {
|
|
|
91
91
|
if (!own) {
|
|
92
92
|
const replace = await prompt.confirm('检测到系统已有一个定时关机任务(可能由其他方式设置,剩余时间未知)。要取消它并按新时间安排吗?');
|
|
93
93
|
if (!replace) {
|
|
94
|
-
console.log(dim('已保留现有计划;取消请运行
|
|
94
|
+
console.log(dim('已保留现有计划;取消请运行 pczzz cancel。'));
|
|
95
95
|
return false;
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -105,7 +105,7 @@ async function scheduleWithCheck(prompt, seconds) {
|
|
|
105
105
|
}
|
|
106
106
|
let message = red(`安排关机失败:${res.stderr || '未知错误'}`);
|
|
107
107
|
if (process.platform !== 'win32' && /root|permission|permitt|not allowed/i.test(res.stderr)) {
|
|
108
|
-
message += dim('\n(macOS/Linux 的 shutdown 需要 root 权限,可尝试:sudo
|
|
108
|
+
message += dim('\n(macOS/Linux 的 shutdown 需要 root 权限,可尝试:sudo pczzz ...)');
|
|
109
109
|
}
|
|
110
110
|
fail(message);
|
|
111
111
|
}
|
|
@@ -167,7 +167,7 @@ async function main() {
|
|
|
167
167
|
return;
|
|
168
168
|
}
|
|
169
169
|
if (argv.includes('-v') || argv.includes('--version')) {
|
|
170
|
-
console.log(`
|
|
170
|
+
console.log(`pczzz v${VERSION}`);
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
173
|
if (argv.includes('cancel') || argv.includes('-c')) {
|
|
@@ -181,11 +181,13 @@ async function main() {
|
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
183
183
|
if (argv.length > 1) {
|
|
184
|
-
fail(red('参数太多,一次只接受一个时间。') +
|
|
184
|
+
fail(red('参数太多,一次只接受一个时间。') +
|
|
185
|
+
dim('\n用法:pczzz 30s / 20m / 1h;不带参数打开菜单;pczzz cancel 取消。'));
|
|
185
186
|
}
|
|
186
187
|
const parsed = parseDuration(argv[0] ?? '');
|
|
187
188
|
if (!parsed) {
|
|
188
|
-
fail(red(`无法识别的时间:“${argv[0] ?? ''}”`) +
|
|
189
|
+
fail(red(`无法识别的时间:“${argv[0] ?? ''}”`) +
|
|
190
|
+
dim('\n示例:30s / 20m / 1h / 1h30m;纯数字按秒计。详见 pczzz -h。'));
|
|
189
191
|
}
|
|
190
192
|
if (parsed.totalSeconds < 1)
|
|
191
193
|
fail(red('时间至少 1 秒。'));
|
package/dist/menu.js
CHANGED
|
@@ -30,7 +30,7 @@ export class Prompt {
|
|
|
30
30
|
});
|
|
31
31
|
// readline 处于原始模式时 Ctrl+C 只会触发 'SIGINT' 事件,不会杀掉进程,需要自己接住
|
|
32
32
|
this.rl.on('SIGINT', () => {
|
|
33
|
-
stdout.write('\n已退出。已设置的定时关机仍会执行;取消请运行
|
|
33
|
+
stdout.write('\n已退出。已设置的定时关机仍会执行;取消请运行 pczzz cancel。\n');
|
|
34
34
|
process.exit(0);
|
|
35
35
|
});
|
|
36
36
|
}
|
package/dist/shutdown.js
CHANGED
|
@@ -31,12 +31,12 @@ function isAlreadyScheduled(code, stderr) {
|
|
|
31
31
|
return Number(code) === WERR_SHUTDOWN_IS_SCHEDULED || stderr.includes('1190');
|
|
32
32
|
}
|
|
33
33
|
export async function scheduleShutdown(seconds) {
|
|
34
|
-
if (process.env.
|
|
34
|
+
if (process.env.PCZZZ_DRY_RUN === '1') {
|
|
35
35
|
return { ok: true, alreadyScheduled: false, stderr: '' };
|
|
36
36
|
}
|
|
37
37
|
try {
|
|
38
38
|
if (process.platform === 'win32') {
|
|
39
|
-
await exec('shutdown', ['/s', '/t', String(Math.round(seconds)), '/c', '
|
|
39
|
+
await exec('shutdown', ['/s', '/t', String(Math.round(seconds)), '/c', 'pczzz 定时关机'], {
|
|
40
40
|
windowsHide: true,
|
|
41
41
|
encoding: 'buffer',
|
|
42
42
|
});
|
|
@@ -54,7 +54,7 @@ export async function scheduleShutdown(seconds) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
export async function abortShutdown() {
|
|
57
|
-
if (process.env.
|
|
57
|
+
if (process.env.PCZZZ_DRY_RUN === '1') {
|
|
58
58
|
return { ok: true, nothingPending: false, stderr: '' };
|
|
59
59
|
}
|
|
60
60
|
const args = process.platform === 'win32' ? ['/a'] : ['-c'];
|
package/dist/store.js
CHANGED
|
@@ -2,7 +2,7 @@ import { promises as fs } from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
function taskFile() {
|
|
5
|
-
const base = process.env.
|
|
5
|
+
const base = process.env.PCZZZ_HOME ?? path.join(os.homedir(), '.pczzz');
|
|
6
6
|
return path.join(base, 'task.json');
|
|
7
7
|
}
|
|
8
8
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pczzz",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Timed shutdown command tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist"
|
|
16
16
|
],
|
|
17
|
+
"author": "chengxudong",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"registry": "https://registry.npmjs.org/"
|
|
20
|
+
},
|
|
17
21
|
"engines": {
|
|
18
22
|
"node": ">=20"
|
|
19
23
|
},
|