cron-up 26.9.3 → 26.9.5
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/README.md +8 -7
- package/package.json +1 -1
- package/src/paths.mjs +9 -4
- package/src/patrol.mjs +2 -2
- package/src/service.mjs +96 -19
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ cron-up install --roots ~/code,~/work --interval 300 --lead 600
|
|
|
48
48
|
|
|
49
49
|
`--roots` 填写存放 agent 工作目录的父目录(逗号分隔)。不填时默认扫描家目录下两层以内(`~/` 自身、`~/*` 与 `~/*/*`),并自动跳过 `Library`、`Documents` 等系统目录和 `.git`、`node_modules`、`.cache` 等目录;建议显式指定,范围越小越安全、越快。使用默认范围安装时 install 会再打印一次安全提示:今后 clone 进家目录的任何仓库都会进入巡检,其中的周期任务会被自动续期永久化(原理见「安全说明」)。
|
|
50
50
|
|
|
51
|
-
launchd 的环境变量极少(PATH 只有 `/usr/bin:/bin:/usr/sbin:/sbin`),npm 全局命令那个 `#!/usr/bin/env node` 壳在它下面找不到 node
|
|
51
|
+
launchd 的环境变量极少(PATH 只有 `/usr/bin:/bin:/usr/sbin:/sbin`),npm 全局命令那个 `#!/usr/bin/env node` 壳在它下面找不到 node。因此安装时把**跟随默认版本的 bin 目录**(fnm 是 `~/.local/share/fnm/aliases/default/bin`——`fnm default` 切换时这个 symlink 自己跟着切;volta 是 `~/.volta/bin`;Homebrew/官网是系统 bin 目录)写进一个自有的启动脚本(`cron-up-service`)的 PATH,脚本直接 `exec cron-up run`——cron-up 与 node 每轮从 PATH 解析,**npm 升级、fnm/volta 切换默认版本都自动跟随,无需重跑 install**,且 node 与包永远同版本。切换默认版本后只需在新版本里装过一次 `npm i -g cron-up`,忘了装的话总览页会提示。例外:**nvm 的 default 只是个文本别名**(要解析到具体版本目录,没有跟随切换的 symlink),其启动脚本写死绝对路径,切换默认版本后需重跑 `cron-up install`,总览会检测路径失效与版本分叉。自建启动脚本的另一个原因是显示名:macOS 系统设置「允许在后台」按 launchd 拉起的第一个可执行文件给条目归组,直连 node 会显示在「Node.js Foundation」(node 的签名者)名下,指向自有脚本则显示 **cron-up-service**。
|
|
52
52
|
|
|
53
53
|
### 自动续期(默认开启)
|
|
54
54
|
|
|
@@ -68,8 +68,8 @@ cron-up install --force --no-auto-renew
|
|
|
68
68
|
|---|---|
|
|
69
69
|
| 命令行与代码 | npm 全局目录(位置因安装方式而异,如 fnm 是 `~/.local/share/fnm/aliases/default/lib/node_modules/cron-up/`,命令在同级 `bin/cron-up`) |
|
|
70
70
|
| launchd 配置 | `~/Library/LaunchAgents/local.cron-up.plist` |
|
|
71
|
-
| 启动脚本 |
|
|
72
|
-
| 配置文件 |
|
|
71
|
+
| 启动脚本 | `~/.local/share/cron-up/cron-up-service`(launchd 拉起的入口,内含 node 与包入口的绝对路径;后台列表显示名即来自它) |
|
|
72
|
+
| 配置文件 | `~/.local/share/cron-up/config.json` |
|
|
73
73
|
| 运行状态 | 同目录下的 `state.json` |
|
|
74
74
|
| 日志 | `~/Library/Logs/cron-up/`(巡检日志;各后台会话的界面记录在 `sessions/` 子目录下,该目录权限 700、日志文件 600) |
|
|
75
75
|
|
|
@@ -127,7 +127,7 @@ cron-up spawn 的预热会话进程环境里带 `CRON_UP_SESSION=1`(值严格
|
|
|
127
127
|
- **修改扫描范围**:直接编辑配置文件的 `roots`、`maxDepth`,最多 5 分钟后的下一轮巡检自动生效,无需重新加载服务。
|
|
128
128
|
- **开关自动续期**:编辑配置文件的 `autoRenew`(`true`/`false`)同样在下一轮巡检生效;或运行 `cron-up install --force --auto-renew`(`--no-auto-renew` 关闭)。想立即处理一次而不改长期开关,用 `cron-up renew`。
|
|
129
129
|
- **修改巡检间隔或提前量**:重新运行 `cron-up install --force --interval 600`,会重新生成 launchd 配置。`--force` 只更新命令行里显式给出的字段,其余配置(如 `roots`)保留;路径参数建议都加引号书写,例如 `--roots "$HOME/code,$HOME/work"`,避免 shell 对逗号后的波浪号不展开。
|
|
130
|
-
- **升级**:`npm i -g cron-up@latest
|
|
130
|
+
- **升级**:`npm i -g cron-up@latest`,下一轮巡检自然跑新版;fnm/volta 下切换默认 Node 版本同样自动跟随(在新版本里装过一次 `npm i -g cron-up` 即可,忘了装总览会提示),全程无需重跑 install。**例外**:nvm 的启动脚本写死绝对路径,切换默认版本后重跑一次 `cron-up install`。从 26.9.4 及更早升级后建议重跑一次 `cron-up install --force` 把启动脚本切到 PATH 形态(26.9.4 及更早还会顺带把数据目录从 `~/Library/Application Support/cron-up` 迁到 `~/.local/share/cron-up`);不重跑的话旧形态照常巡检。版本号按「年.月.序号」:两位年份、月份、月内发布序号(序号每月重置),如 2026 年 9 月第一次发布是 `26.9.1`,同月再发是 `26.9.2`,到 10 月是 `26.10.1`。发布方式:先把 package.json 的 version 改成新版本并合入 main,再打同名 `v<version>` tag(如 `v26.9.1`);GitHub Actions 会跑通测试(Node 22/24)、校验 package.json 版本与 tag 一致后 publish,版本不符直接失败,杜绝「代码是 26.9.2、tag 叫 26.9.3」这类漂移。push 到 main 的每次提交也会先跑一遍测试(ci workflow)。
|
|
131
131
|
- **配置校验**:手改配置时字段类型写错(`roots` 不是数组、数值字段写成字符串或布尔、`autoRenew` 不是布尔)不会静默回退默认值——巡检会以退出码 2 响亮失败并在总览页列出具体字段,修好或重跑 `cron-up install --force`(坏字段回默认,其余保留)。软问题只警告不中止:提前量小于间隔(可能来不及在任务前启动会话)、扫描目录不存在。无参数的 `cron-up` 总览随时也会显示这些问题;其中 interval 显示的是 launchd **实际生效**的间隔,手动改过 config 但与实际不一致时会提示需要重新 install。
|
|
132
132
|
- **路径大小写**:macOS 默认文件系统大小写不敏感,而 `realpath` 并不规范化大小写。扫描入口会把 `roots` 认回磁盘上的真实写法,`cron-up logs` 给绝对路径时也一样——大小写敲错不再是「警告一声然后匹配失效」,而是直接按同一路径工作。
|
|
133
133
|
- **提前量与间隔的关系**:请保持 `leadSeconds ≥ intervalSeconds`,否则极端情况下任务会在两轮巡检之间到期而来不及启动。默认 600 ≥ 300 有一倍余量。`--lead 0` 是合法取值(表示不提前,只靠错过补执行兜底),会如实写入配置;`--interval 0` 非法,安装时直接报错中止。
|
|
@@ -163,9 +163,10 @@ cron-up spawn 的预热会话进程环境里带 `CRON_UP_SESSION=1`(值严格
|
|
|
163
163
|
| 现象 | 排查方法 |
|
|
164
164
|
|---|---|
|
|
165
165
|
| 总览提示「冷却中」 | 运行 `cron-up logs <目录名>` 查看启动界面:要么停在信任确认或工具授权提问(日志里能看到提问界面),要么 shell 环境缺少命令(日志里是 `command not found`) |
|
|
166
|
-
| 总览提示「Node 或入口路径已失效」 | Node
|
|
167
|
-
| 总览提示「启动脚本已失效 / 内容无法解读」 |
|
|
168
|
-
|
|
|
166
|
+
| 总览提示「Node 或入口路径已失效」 | 写死路径形态(nvm 或旧版脚本)下 Node 版本目录被删或移动,重跑 `cron-up install` 重新解析即可 |
|
|
167
|
+
| 总览提示「启动脚本已失效 / 内容无法解读」 | 启动脚本(`~/.local/share/cron-up/cron-up-service`)被删或手改,重跑 `cron-up install` 覆盖重生成 |
|
|
168
|
+
| 总览提示「PATH 目录里找不到 cron-up」 | fnm/volta 切换了默认 Node 版本,新版本里还没装包:在新版本里 `npm i -g cron-up` 即可 |
|
|
169
|
+
| 总览提示「入口路径属于另一个 node 版本目录」 | 写死路径形态(nvm 或旧版脚本)下切换了默认 Node 版本,巡检在跑旧版本目录里的旧包;重跑 `cron-up install` 更新路径 |
|
|
169
170
|
| 日志里出现「卡死」 | 会话进程起来了但没能变成可用会话,绝大多数是停在信任确认或授权提问。先手动 `cd <目录> && claude` 走完确认,再等下一轮巡检 |
|
|
170
171
|
| 日志里出现「任务文件读不出 \<目录\>」 | 该目录的 `.claude/scheduled_tasks.json` 存在却解析不了(格式损坏),巡检跳过这一个目录,**其中的定时任务一个都不会执行**;其余目录照常。检查该文件 |
|
|
171
172
|
| 日志里出现「错误 \<目录\>」 | 巡检该目录时抛出未预期的异常,已按目录隔离、不影响其余目录。这通常意味着 cron-up 自身的 bug,请带上该行反馈 |
|
package/package.json
CHANGED
package/src/paths.mjs
CHANGED
|
@@ -16,7 +16,12 @@ export const TASK_REL = path.join('.claude', 'scheduled_tasks.json');
|
|
|
16
16
|
// HOME。getter 让 sessionLogDir/configPath/statePath 跟随替换后的前缀。
|
|
17
17
|
export const paths = {
|
|
18
18
|
home: HOME,
|
|
19
|
-
|
|
19
|
+
// 数据目录用 XDG 风格的 ~/.local/share/cron-up:config、state 与启动脚本
|
|
20
|
+
// 集中一处,purge 一删全清。26.9.3 及更早放在
|
|
21
|
+
// ~/Library/Application Support/cron-up,install 时自动搬家(见
|
|
22
|
+
// service.mjs 的 migrateLegacyDir)。
|
|
23
|
+
dataDir: path.join(HOME, '.local', 'share', 'cron-up'),
|
|
24
|
+
legacyAppSupport: path.join(HOME, 'Library', 'Application Support', 'cron-up'),
|
|
20
25
|
logDir: path.join(HOME, 'Library', 'Logs', 'cron-up'),
|
|
21
26
|
plistPath: path.join(HOME, 'Library', 'LaunchAgents', `${LABEL}.plist`),
|
|
22
27
|
sessionDir: path.join(HOME, '.claude', 'sessions'),
|
|
@@ -25,16 +30,16 @@ export const paths = {
|
|
|
25
30
|
return path.join(this.logDir, 'sessions');
|
|
26
31
|
},
|
|
27
32
|
get configPath() {
|
|
28
|
-
return path.join(this.
|
|
33
|
+
return path.join(this.dataDir, 'config.json');
|
|
29
34
|
},
|
|
30
35
|
get statePath() {
|
|
31
|
-
return path.join(this.
|
|
36
|
+
return path.join(this.dataDir, 'state.json');
|
|
32
37
|
},
|
|
33
38
|
// launchd 拉起的启动脚本。ProgramArguments[0] 直接指向它而非 node:
|
|
34
39
|
// macOS 后台项目列表按可执行文件名显示条目,直连 node 会归到「Node.js
|
|
35
40
|
// Foundation」名下(node 的签名者),自有脚本则显示脚本自己的名字。
|
|
36
41
|
get serviceScriptPath() {
|
|
37
|
-
return path.join(this.
|
|
42
|
+
return path.join(this.dataDir, 'cron-up-service');
|
|
38
43
|
},
|
|
39
44
|
};
|
|
40
45
|
|
package/src/patrol.mjs
CHANGED
|
@@ -58,8 +58,8 @@ function readLockPid(lockPath) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
export function acquireRunLock() {
|
|
61
|
-
fs.mkdirSync(deps.paths.
|
|
62
|
-
const lockPath = path.join(deps.paths.
|
|
61
|
+
fs.mkdirSync(deps.paths.dataDir, { recursive: true });
|
|
62
|
+
const lockPath = path.join(deps.paths.dataDir, 'run.lock');
|
|
63
63
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
64
64
|
const fd = tryCreateLock(lockPath);
|
|
65
65
|
if (fd !== null) {
|
package/src/service.mjs
CHANGED
|
@@ -135,7 +135,12 @@ function nvmNodePath(nvmDir) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// 返回 {nodePath, entryPath, manager}。注入 home/execPath/entryPath 仅为
|
|
138
|
-
// 测试在临时 HOME 里伪造各安装形态。
|
|
138
|
+
// 测试在临时 HOME 里伪造各安装形态。pathDirs 是该安装形态下「跟随默认版
|
|
139
|
+
// 本的 bin 目录」(fnm 的 aliases/default/bin——fnm default 切换时 symlink
|
|
140
|
+
// 跟着切;volta 的 ~/.volta/bin;brew/pkg 的系统 bin):启动脚本把它放进
|
|
141
|
+
// PATH 后 cron-up 与 node 都从 PATH 解析,npm 升级与切换默认版本自动跟
|
|
142
|
+
// 随。nvm 与 exec-path 兜底没有这种目录(nvm 的 default 是文本别名,要解
|
|
143
|
+
// 析到具体版本),pathDirs 为 null,启动脚本退回写死绝对路径。
|
|
139
144
|
export function resolveLauncher(opts = {}) {
|
|
140
145
|
const home = opts.home ?? paths.home;
|
|
141
146
|
const execPath = opts.execPath ?? process.execPath;
|
|
@@ -152,13 +157,21 @@ export function resolveLauncher(opts = {}) {
|
|
|
152
157
|
].filter(Boolean);
|
|
153
158
|
for (const d of fnmDirs) {
|
|
154
159
|
const nodePath = path.join(d, 'aliases', 'default', 'bin', 'node');
|
|
155
|
-
if (isExecutable(nodePath))
|
|
160
|
+
if (isExecutable(nodePath)) {
|
|
161
|
+
return {
|
|
162
|
+
nodePath, entryPath, manager: 'fnm',
|
|
163
|
+
pathDirs: [path.dirname(nodePath)],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
// 2. volta:~/.volta/bin/node 是稳定的版本无关 shim。
|
|
159
169
|
const voltaNode = path.join(home, '.volta', 'bin', 'node');
|
|
160
170
|
if (isExecutable(voltaNode)) {
|
|
161
|
-
return {
|
|
171
|
+
return {
|
|
172
|
+
nodePath: voltaNode, entryPath, manager: 'volta',
|
|
173
|
+
pathDirs: [path.dirname(voltaNode)],
|
|
174
|
+
};
|
|
162
175
|
}
|
|
163
176
|
|
|
164
177
|
// 3. nvm。
|
|
@@ -175,6 +188,7 @@ export function resolveLauncher(opts = {}) {
|
|
|
175
188
|
nodePath: cand,
|
|
176
189
|
entryPath,
|
|
177
190
|
manager: cand.includes('homebrew') || isBrewCellar(cand) ? 'brew' : 'pkg',
|
|
191
|
+
pathDirs: [path.dirname(cand)],
|
|
178
192
|
};
|
|
179
193
|
}
|
|
180
194
|
}
|
|
@@ -219,15 +233,25 @@ function xmlUnescape(s) {
|
|
|
219
233
|
// macOS 的后台项目列表(系统设置 → 登录项与扩展 → 允许在后台)按
|
|
220
234
|
// ProgramArguments[0] 的文件名归组条目:直连 node 会与所有 node 系后台服务
|
|
221
235
|
// 合并显示在「Node.js Foundation」(node 的签名者)名下;指向自有脚本则
|
|
222
|
-
// 显示 cron-up-service
|
|
223
|
-
//
|
|
236
|
+
// 显示 cron-up-service。
|
|
237
|
+
// 有跟随默认版本的 bin 目录的安装形态(fnm/volta/brew/pkg)把目录放进
|
|
238
|
+
// PATH 后直接 exec cron-up run:npm 升级、切换默认版本都自动跟随,无需
|
|
239
|
+
// 重跑 install(node 与 cron-up 同目录,天然同版本)。nvm 与 exec-path
|
|
240
|
+
// 兜底没有这种目录,写死绝对路径,失效与版本分叉由总览检测兜底。
|
|
224
241
|
export function writeServiceScript(launcher) {
|
|
225
242
|
const script = deps.paths.serviceScriptPath;
|
|
226
243
|
fs.mkdirSync(path.dirname(script), { recursive: true });
|
|
244
|
+
let body;
|
|
245
|
+
if (launcher.pathDirs) {
|
|
246
|
+
const dirs = launcher.pathDirs.map((d) => `"${d}"`).join(':');
|
|
247
|
+
body = `export PATH=${dirs}:"$PATH"\nexec cron-up run\n`;
|
|
248
|
+
} else {
|
|
249
|
+
body = `exec "${launcher.nodePath}" "${launcher.entryPath}" run\n`;
|
|
250
|
+
}
|
|
227
251
|
fs.writeFileSync(script,
|
|
228
252
|
'#!/bin/bash\n'
|
|
229
253
|
+ '# 由 cron-up install 生成;重跑 cron-up install --force 覆盖重生成\n'
|
|
230
|
-
+
|
|
254
|
+
+ body);
|
|
231
255
|
fs.chmodSync(script, 0o755);
|
|
232
256
|
return script;
|
|
233
257
|
}
|
|
@@ -277,18 +301,45 @@ export function readPlistProgramArgs() {
|
|
|
277
301
|
}
|
|
278
302
|
|
|
279
303
|
// 从启动脚本内容里解出 [node 路径, 入口路径];脚本是我们生成的固定格
|
|
280
|
-
// 式,解不出(手改坏)返回 null。
|
|
281
|
-
|
|
304
|
+
// 式,解不出(手改坏)返回 null。PATH 形态的脚本没有写死的路径,返回
|
|
305
|
+
// 'path' 标记。
|
|
306
|
+
function readServiceScriptShape(scriptPath) {
|
|
282
307
|
let text;
|
|
283
308
|
try {
|
|
284
309
|
text = fs.readFileSync(scriptPath, 'utf-8');
|
|
285
310
|
} catch {
|
|
286
311
|
return null;
|
|
287
312
|
}
|
|
313
|
+
if (/^export PATH=.+"\$PATH"$/m.test(text)) return 'path';
|
|
288
314
|
const m = text.match(/^exec "(.+)" "(.+)" run$/m);
|
|
289
315
|
return m ? [m[1], m[2]] : null;
|
|
290
316
|
}
|
|
291
317
|
|
|
318
|
+
// PATH 形态健康检查:跟随默认版本的 bin 目录里要能找到 cron-up 与
|
|
319
|
+
// node。典型断点是 fnm/volta 切换默认版本后忘了在新版本里装包。
|
|
320
|
+
function pathResolveHealth(scriptPath) {
|
|
321
|
+
let text;
|
|
322
|
+
try {
|
|
323
|
+
text = fs.readFileSync(scriptPath, 'utf-8');
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const m = text.match(/^export PATH=(.+):"\$PATH"$/m);
|
|
328
|
+
if (!m) return null;
|
|
329
|
+
const dirs = m[1].split('":"').map((s) => s.replace(/"/g, ''));
|
|
330
|
+
const has = (name) => dirs.some((d) => isExecutable(path.join(d, name)));
|
|
331
|
+
if (!has('cron-up')) {
|
|
332
|
+
return `启动脚本的 PATH 目录(${dirs.join('、')})里找不到 cron-up:切换`
|
|
333
|
+
+ '默认 Node 版本后要在新版本里重新 npm i -g cron-up,或重跑 '
|
|
334
|
+
+ 'cron-up install';
|
|
335
|
+
}
|
|
336
|
+
if (!has('node')) {
|
|
337
|
+
return `启动脚本的 PATH 目录(${dirs.join('、')})里找不到 node,重跑 `
|
|
338
|
+
+ 'cron-up install 重新解析';
|
|
339
|
+
}
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
292
343
|
// fnm 与 nvm 的全局包装在版本化目录里(…/node-versions/v<X>/… 与
|
|
293
344
|
// …/versions/node/v<X>/…)。default 别名切换后 node 路径依然有效,但入口
|
|
294
345
|
// 路径仍指向装包时的旧版本目录——巡检会静默继续跑旧版 cron-up,直到旧版
|
|
@@ -326,9 +377,9 @@ function pathHealthAlert(nodePath, entryPath) {
|
|
|
326
377
|
return null;
|
|
327
378
|
}
|
|
328
379
|
|
|
329
|
-
// 总览页健康检查:plist
|
|
330
|
-
//
|
|
331
|
-
//
|
|
380
|
+
// 总览页健康检查:plist 指定的启动链若已断(脚本丢失、PATH 里解析不到
|
|
381
|
+
// cron-up/node、写死路径失效如 nvm 版本目录被清),launchd 每轮都会静默
|
|
382
|
+
// 拉不起来;写死路径的形态还检查 fnm/nvm 版本分叉。
|
|
332
383
|
export function launcherHealth() {
|
|
333
384
|
if (!fs.existsSync(deps.paths.plistPath)) return null;
|
|
334
385
|
const args = readPlistProgramArgs();
|
|
@@ -339,12 +390,11 @@ export function launcherHealth() {
|
|
|
339
390
|
return `launchd 配置中的启动脚本已失效(${path.basename(scriptPath)} `
|
|
340
391
|
+ '不存在),巡检当前拉不起来;请重新运行 cron-up install';
|
|
341
392
|
}
|
|
342
|
-
const
|
|
343
|
-
if (
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return pathHealthAlert(...paths);
|
|
393
|
+
const shape = readServiceScriptShape(scriptPath);
|
|
394
|
+
if (shape === 'path') return pathResolveHealth(scriptPath);
|
|
395
|
+
if (Array.isArray(shape)) return pathHealthAlert(...shape);
|
|
396
|
+
return `启动脚本 ${scriptPath} 内容无法解读(可能被手改),`
|
|
397
|
+
+ '重跑 cron-up install 覆盖重生成';
|
|
348
398
|
}
|
|
349
399
|
if (args.length === 3) {
|
|
350
400
|
// 旧版 plist(ProgramArguments 直写 node 与入口):升级后重跑
|
|
@@ -356,8 +406,34 @@ export function launcherHealth() {
|
|
|
356
406
|
|
|
357
407
|
// ---- install / uninstall ----
|
|
358
408
|
|
|
409
|
+
// 26.9.3 及更早把数据放在 ~/Library/Application Support/cron-up;新版统一
|
|
410
|
+
// 到 ~/.local/share/cron-up。整体搬家(config、state 随行),随后 install
|
|
411
|
+
// 照常重写脚本与 plist 并重启服务,用户无感。搬家只在 install 里发生:
|
|
412
|
+
// 只升级 npm 包而不跑 install 的话,旧目录与旧 plist 依旧配套、照常巡检。
|
|
413
|
+
function migrateLegacyDir() {
|
|
414
|
+
const legacy = deps.paths.legacyAppSupport;
|
|
415
|
+
if (!fs.existsSync(legacy) || fs.existsSync(deps.paths.dataDir)) return;
|
|
416
|
+
try {
|
|
417
|
+
fs.mkdirSync(path.dirname(deps.paths.dataDir), { recursive: true });
|
|
418
|
+
try {
|
|
419
|
+
fs.renameSync(legacy, deps.paths.dataDir);
|
|
420
|
+
} catch {
|
|
421
|
+
// rename 搬不动(目标异常等):退化为复制,旧目录留着无害。
|
|
422
|
+
fs.cpSync(legacy, deps.paths.dataDir, { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
deps.print(`已迁移数据目录:${legacy} → ${deps.paths.dataDir}`);
|
|
425
|
+
} catch {
|
|
426
|
+
// 迁移彻底失败必须当面说:不说的话 install 会按「全新安装」落默认
|
|
427
|
+
// 配置,用户收窄过的 roots 等手改就静默丢了。
|
|
428
|
+
deps.print(`提示:旧数据目录迁移失败(${legacy}),本次按新目录继续;`
|
|
429
|
+
+ '如旧目录里还有你改过的配置,请手动复制到 '
|
|
430
|
+
+ `${deps.paths.dataDir} 后重跑 cron-up install`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
359
434
|
export async function cmdInstall(args) {
|
|
360
|
-
|
|
435
|
+
migrateLegacyDir();
|
|
436
|
+
fs.mkdirSync(deps.paths.dataDir, { recursive: true });
|
|
361
437
|
fs.mkdirSync(deps.paths.sessionLogDir, { recursive: true });
|
|
362
438
|
try {
|
|
363
439
|
fs.chmodSync(deps.paths.sessionLogDir, 0o700);
|
|
@@ -497,7 +573,8 @@ export async function cmdUninstall(args) {
|
|
|
497
573
|
}
|
|
498
574
|
|
|
499
575
|
if (args.purge) {
|
|
500
|
-
for (const p of [deps.paths.
|
|
576
|
+
for (const p of [deps.paths.dataDir, deps.paths.legacyAppSupport,
|
|
577
|
+
deps.paths.logDir]) {
|
|
501
578
|
fs.rmSync(p, { recursive: true, force: true });
|
|
502
579
|
deps.print(`已删除 ${p}`);
|
|
503
580
|
}
|