flower-trellis 0.4.0 → 0.4.2
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 +91 -2
- package/enhancements/0.6/overrides/skills/trellis-finish-work.md +1 -3
- package/enhancements/0.6/overrides/workflow-states/in_progress-inline.md +5 -2
- package/enhancements/0.6/overrides/workflow-states/in_progress.md +4 -1
- package/enhancements/0.6/overrides/workflow-states/no_task.md +1 -1
- package/enhancements/0.6/overrides/workflow-states/planning-inline.md +1 -1
- package/enhancements/0.6/overrides/workflow-states/planning.md +1 -1
- package/enhancements/0.6/overrides/workflow.md +21 -15
- package/enhancements/MANIFEST.json +2 -2
- package/package.json +1 -1
- package/src/assets/flower_update_hook.py +135 -0
- package/src/cli.js +27 -0
- package/src/commands/self-check.js +19 -0
- package/src/commands/self-update.js +108 -0
- package/src/commands/update-check.js +86 -0
- package/src/lib/apply-enhancements.js +12 -2
- package/src/lib/claude-tweaks.js +98 -0
- package/src/lib/codex-tweaks.js +41 -18
- package/src/lib/flower-assets.js +29 -0
- package/src/lib/manifest.js +87 -2
- package/src/lib/self-check.js +352 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
manifestPath,
|
|
5
|
+
readManifest,
|
|
6
|
+
readUpdateCheck,
|
|
7
|
+
writeUpdateCheck,
|
|
8
|
+
} from "../lib/manifest.js";
|
|
9
|
+
|
|
10
|
+
const POLICIES = new Set(["off", "notify", "ask", "auto"]);
|
|
11
|
+
|
|
12
|
+
/** 读取 flag 后面的取值。 */
|
|
13
|
+
function optionValue(args, name) {
|
|
14
|
+
const index = args.indexOf(name);
|
|
15
|
+
if (index === -1) return null;
|
|
16
|
+
return args[index + 1] || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 确保目标是 Trellis 项目。 */
|
|
20
|
+
function assertTrellisProject(target) {
|
|
21
|
+
if (!fs.existsSync(path.join(target, ".trellis"))) {
|
|
22
|
+
throw new Error(`目标不是 Trellis 项目:${target}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* flower-trellis update-check:管理 manifest 内的启动更新检查策略。
|
|
28
|
+
*
|
|
29
|
+
* @param {object} ctx 见 cli.js 的 parse()
|
|
30
|
+
*/
|
|
31
|
+
export async function updateCheck(ctx) {
|
|
32
|
+
const args = ctx.passthrough;
|
|
33
|
+
const action = args.find((arg) => !arg.startsWith("-")) || "get";
|
|
34
|
+
assertTrellisProject(ctx.target);
|
|
35
|
+
|
|
36
|
+
if (action === "get") {
|
|
37
|
+
const manifest = readManifest(ctx.target);
|
|
38
|
+
console.log(`manifest: ${manifestPath(ctx.target)}`);
|
|
39
|
+
console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
|
|
40
|
+
if (!manifest) console.log(" · manifest 不存在,当前显示默认策略");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (action === "set") {
|
|
45
|
+
const policy = optionValue(args, "--policy");
|
|
46
|
+
const intervalText = optionValue(args, "--interval-hours");
|
|
47
|
+
const patch = {};
|
|
48
|
+
if (policy) {
|
|
49
|
+
if (!POLICIES.has(policy)) {
|
|
50
|
+
throw new Error("policy 只能是 off / notify / ask / auto");
|
|
51
|
+
}
|
|
52
|
+
patch.policy = policy;
|
|
53
|
+
patch.enabled = policy !== "off";
|
|
54
|
+
}
|
|
55
|
+
if (intervalText !== null) {
|
|
56
|
+
const intervalHours = Number(intervalText);
|
|
57
|
+
if (!Number.isFinite(intervalHours) || intervalHours < 0) {
|
|
58
|
+
throw new Error("--interval-hours 必须是非负数字");
|
|
59
|
+
}
|
|
60
|
+
patch.intervalHours = intervalHours;
|
|
61
|
+
}
|
|
62
|
+
if (!Object.keys(patch).length) {
|
|
63
|
+
throw new Error("update-check set 需要 --policy 或 --interval-hours");
|
|
64
|
+
}
|
|
65
|
+
writeUpdateCheck(ctx.target, patch);
|
|
66
|
+
console.log(" ✓ updateCheck 策略已更新");
|
|
67
|
+
console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (action === "disable") {
|
|
72
|
+
writeUpdateCheck(ctx.target, { enabled: false });
|
|
73
|
+
console.log(" ✓ 启动更新检查已关闭(policy 保留)");
|
|
74
|
+
console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action === "enable") {
|
|
79
|
+
writeUpdateCheck(ctx.target, { enabled: true });
|
|
80
|
+
console.log(" ✓ 启动更新检查已启用(policy 沿用)");
|
|
81
|
+
console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error("update-check 只支持 get / set / disable / enable");
|
|
86
|
+
}
|
|
@@ -5,6 +5,8 @@ import { copyScriptAssets } from "./copy-scripts.js";
|
|
|
5
5
|
import { injectWorkflow } from "./workflow-inject.js";
|
|
6
6
|
import { injectSkillOverrides } from "./skill-override-inject.js";
|
|
7
7
|
import { applyCodexTweaks } from "./codex-tweaks.js";
|
|
8
|
+
import { applyClaudeTweaks } from "./claude-tweaks.js";
|
|
9
|
+
import { copyFlowerAssets } from "./flower-assets.js";
|
|
8
10
|
import { readManifest, writeManifest } from "./manifest.js";
|
|
9
11
|
import { flowerVersion } from "./versions.js";
|
|
10
12
|
import { rmrf } from "./fs-utils.js";
|
|
@@ -63,8 +65,11 @@ export function applyEnhancements(target, opts = {}) {
|
|
|
63
65
|
variantDir,
|
|
64
66
|
skills,
|
|
65
67
|
);
|
|
66
|
-
const installed =
|
|
67
|
-
|
|
68
|
+
const { installed: flowerInstalled, paths: flowerPaths } = skills.length === 0
|
|
69
|
+
? copyFlowerAssets(target)
|
|
70
|
+
: { installed: [], paths: [] };
|
|
71
|
+
const installed = [...skillInstalled, ...scriptInstalled, ...flowerInstalled];
|
|
72
|
+
const newPaths = [...skillPaths, ...scriptPaths, ...flowerPaths];
|
|
68
73
|
const where = [];
|
|
69
74
|
if (newPaths.some((p) => p.startsWith(".claude/skills"))) where.push(".claude/skills");
|
|
70
75
|
if (newPaths.some((p) => p.startsWith(".agents/skills"))) where.push(".agents/skills");
|
|
@@ -152,5 +157,10 @@ export function applyEnhancements(target, opts = {}) {
|
|
|
152
157
|
console.log(` ✓ codex 调整:${seg};hooks.json 已合并 SessionStart;${dispatch}`);
|
|
153
158
|
}
|
|
154
159
|
|
|
160
|
+
const claude = applyClaudeTweaks(target);
|
|
161
|
+
if (claude.applied) {
|
|
162
|
+
console.log(" ✓ claude 调整:settings.json 已合并 startup 更新检查 hook");
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
return { variant, installed };
|
|
156
166
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FLOWER_UPDATE_HOOK_REL } from "./flower-assets.js";
|
|
4
|
+
|
|
5
|
+
const WORKFLOW_HOOK_SCRIPT = ".claude/hooks/inject-workflow-state.py";
|
|
6
|
+
const DEFAULT_COMMAND = `python3 ${FLOWER_UPDATE_HOOK_REL}`;
|
|
7
|
+
|
|
8
|
+
/** 容错读取 Claude settings JSON。 */
|
|
9
|
+
function readSettings(settingsPath) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
12
|
+
if (parsed && typeof parsed === "object") {
|
|
13
|
+
const hooks = parsed.hooks && typeof parsed.hooks === "object" ? parsed.hooks : {};
|
|
14
|
+
return { ...parsed, hooks };
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// 缺失或损坏时用空配置重建,避免后处理阻断 init/update。
|
|
18
|
+
}
|
|
19
|
+
return { hooks: {} };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 从 Claude UserPromptSubmit hook 推导 Python 命令前缀。 */
|
|
23
|
+
function flowerUpdateCommand(config) {
|
|
24
|
+
const groups = Array.isArray(config.hooks.UserPromptSubmit)
|
|
25
|
+
? config.hooks.UserPromptSubmit
|
|
26
|
+
: [];
|
|
27
|
+
for (const group of groups) {
|
|
28
|
+
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
29
|
+
for (const hook of hooks) {
|
|
30
|
+
if (hook?.type !== "command" || typeof hook.command !== "string") continue;
|
|
31
|
+
if (hook.command.includes(WORKFLOW_HOOK_SCRIPT)) {
|
|
32
|
+
return hook.command.replace(WORKFLOW_HOOK_SCRIPT, FLOWER_UPDATE_HOOK_REL);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return DEFAULT_COMMAND;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 判断 hook 列表里是否已有 flower update hook。 */
|
|
40
|
+
function hasFlowerHook(hooks) {
|
|
41
|
+
return hooks.some(
|
|
42
|
+
(hook) => hook?.type === "command" &&
|
|
43
|
+
typeof hook.command === "string" &&
|
|
44
|
+
hook.command.includes(FLOWER_UPDATE_HOOK_REL),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 合并 Claude startup hook,并确保 clear / compact 不运行 update hook。 */
|
|
49
|
+
function mergeClaudeHooks(settingsPath) {
|
|
50
|
+
const config = readSettings(settingsPath);
|
|
51
|
+
const groups = Array.isArray(config.hooks.SessionStart)
|
|
52
|
+
? config.hooks.SessionStart
|
|
53
|
+
: [];
|
|
54
|
+
const command = flowerUpdateCommand(config);
|
|
55
|
+
let startup = groups.find((group) => group?.matcher === "startup");
|
|
56
|
+
if (!startup) {
|
|
57
|
+
startup = { matcher: "startup", hooks: [] };
|
|
58
|
+
groups.unshift(startup);
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(startup.hooks)) startup.hooks = [];
|
|
61
|
+
|
|
62
|
+
let changed = false;
|
|
63
|
+
for (const group of groups) {
|
|
64
|
+
if (group === startup || !Array.isArray(group?.hooks)) continue;
|
|
65
|
+
const before = group.hooks.length;
|
|
66
|
+
group.hooks = group.hooks.filter(
|
|
67
|
+
(hook) => !(hook?.type === "command" &&
|
|
68
|
+
typeof hook.command === "string" &&
|
|
69
|
+
hook.command.includes(FLOWER_UPDATE_HOOK_REL)),
|
|
70
|
+
);
|
|
71
|
+
if (group.hooks.length !== before) changed = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!hasFlowerHook(startup.hooks)) {
|
|
75
|
+
startup.hooks.push({ type: "command", command, timeout: 8 });
|
|
76
|
+
changed = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
config.hooks.SessionStart = groups;
|
|
80
|
+
const desired = JSON.stringify(config, null, 2) + "\n";
|
|
81
|
+
const current = fs.existsSync(settingsPath) ? fs.readFileSync(settingsPath, "utf8") : "";
|
|
82
|
+
if (current === desired) return changed;
|
|
83
|
+
fs.writeFileSync(settingsPath, desired);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Claude Code 平台后处理入口。仅当 `.claude/` 存在时执行。
|
|
89
|
+
*
|
|
90
|
+
* @param {string} target 目标项目根
|
|
91
|
+
* @returns {{applied:boolean,settingsWritten?:boolean}}
|
|
92
|
+
*/
|
|
93
|
+
export function applyClaudeTweaks(target) {
|
|
94
|
+
const claudeDir = path.join(target, ".claude");
|
|
95
|
+
if (!fs.existsSync(claudeDir)) return { applied: false };
|
|
96
|
+
const settingsWritten = mergeClaudeHooks(path.join(claudeDir, "settings.json"));
|
|
97
|
+
return { applied: true, settingsWritten };
|
|
98
|
+
}
|
package/src/lib/codex-tweaks.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { FLOWER_UPDATE_HOOK_REL } from "./flower-assets.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* flower-trellis 对 Trellis 生成的 codex 配置的定制后处理。
|
|
@@ -239,23 +240,40 @@ function sessionStartCommand(config) {
|
|
|
239
240
|
return `python3 -X utf8 ${SESSION_START_SCRIPT}`;
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
/**
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
243
|
+
/** 从 Trellis SessionStart 命令推导 flower update hook 命令。 */
|
|
244
|
+
function flowerUpdateCommand(config) {
|
|
245
|
+
const command = sessionStartCommand(config);
|
|
246
|
+
if (command.includes(SESSION_START_SCRIPT)) {
|
|
247
|
+
return command.replace(SESSION_START_SCRIPT, FLOWER_UPDATE_HOOK_REL);
|
|
248
|
+
}
|
|
249
|
+
return `python3 -X utf8 ${FLOWER_UPDATE_HOOK_REL}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** 判断 hooks 数组里是否已有指定命令。 */
|
|
253
|
+
function hasCommand(hooks, commandNeedle) {
|
|
254
|
+
return hooks.some(
|
|
255
|
+
(hook) => hook?.type === "command" &&
|
|
256
|
+
typeof hook.command === "string" &&
|
|
257
|
+
hook.command.includes(commandNeedle),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 向 SessionStart 合并单个 command hook,保留既有自定义 hook。 */
|
|
262
|
+
function ensureSessionStartCommand(groups, command, timeout, needle) {
|
|
263
|
+
for (const group of groups) {
|
|
264
|
+
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
265
|
+
if (hasCommand(hooks, needle)) return false;
|
|
266
|
+
}
|
|
267
|
+
groups.push({
|
|
268
|
+
hooks: [
|
|
269
|
+
{
|
|
270
|
+
type: "command",
|
|
271
|
+
command,
|
|
272
|
+
timeout,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
return true;
|
|
259
277
|
}
|
|
260
278
|
|
|
261
279
|
/**
|
|
@@ -269,7 +287,12 @@ function sessionStartHook(config) {
|
|
|
269
287
|
*/
|
|
270
288
|
function mergeHooks(hooksPath) {
|
|
271
289
|
const config = readHooksConfig(hooksPath);
|
|
272
|
-
config.hooks.SessionStart
|
|
290
|
+
const groups = Array.isArray(config.hooks.SessionStart)
|
|
291
|
+
? config.hooks.SessionStart
|
|
292
|
+
: [];
|
|
293
|
+
ensureSessionStartCommand(groups, sessionStartCommand(config), 30, SESSION_START_SCRIPT);
|
|
294
|
+
ensureSessionStartCommand(groups, flowerUpdateCommand(config), 8, FLOWER_UPDATE_HOOK_REL);
|
|
295
|
+
config.hooks.SessionStart = groups;
|
|
273
296
|
|
|
274
297
|
const desired = JSON.stringify(config, null, 2) + "\n";
|
|
275
298
|
const current = fs.existsSync(hooksPath) ? fs.readFileSync(hooksPath, "utf8") : "";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { copyPath } from "./fs-utils.js";
|
|
3
|
+
import { PKG_ROOT } from "./paths.js";
|
|
4
|
+
|
|
5
|
+
/** flower 自有启动更新 hook 脚本名。 */
|
|
6
|
+
export const FLOWER_UPDATE_HOOK = "flower_update_hook.py";
|
|
7
|
+
|
|
8
|
+
/** flower 自有启动更新 hook 在目标项目内的相对路径。 */
|
|
9
|
+
export const FLOWER_UPDATE_HOOK_REL = `.trellis/scripts/${FLOWER_UPDATE_HOOK}`;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 复制 flower 自有脚本资产。
|
|
13
|
+
*
|
|
14
|
+
* 这些脚本属于 flower-trellis 自身能力,不从 skill-garden 快照同步,避免和
|
|
15
|
+
* `enhancements/<variant>/scripts` 的边界混淆。
|
|
16
|
+
*
|
|
17
|
+
* @param {string} target 目标项目根
|
|
18
|
+
* @returns {{installed:string[],paths:string[]}} 已安装资产和 manifest 路径
|
|
19
|
+
*/
|
|
20
|
+
export function copyFlowerAssets(target) {
|
|
21
|
+
copyPath(
|
|
22
|
+
path.join(PKG_ROOT, "src", "assets", FLOWER_UPDATE_HOOK),
|
|
23
|
+
path.join(target, ...FLOWER_UPDATE_HOOK_REL.split("/")),
|
|
24
|
+
);
|
|
25
|
+
return {
|
|
26
|
+
installed: [`script:${FLOWER_UPDATE_HOOK}`],
|
|
27
|
+
paths: [FLOWER_UPDATE_HOOK_REL],
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/lib/manifest.js
CHANGED
|
@@ -11,6 +11,16 @@ import path from "node:path";
|
|
|
11
11
|
* 放在 .trellis/ 下,随项目的 Trellis 生命周期存在(uninstall 删 .trellis 时一并消失)。
|
|
12
12
|
*/
|
|
13
13
|
const MANIFEST_REL = path.join(".trellis", ".flower-manifest.json");
|
|
14
|
+
const UPDATE_POLICIES = new Set(["off", "notify", "ask", "auto"]);
|
|
15
|
+
const DEFAULT_UPDATE_CHECK = {
|
|
16
|
+
enabled: true,
|
|
17
|
+
policy: "ask",
|
|
18
|
+
intervalHours: 24,
|
|
19
|
+
lastCheckedAt: null,
|
|
20
|
+
lastRemote: null,
|
|
21
|
+
lastStatus: null,
|
|
22
|
+
lastErrorCode: null,
|
|
23
|
+
};
|
|
14
24
|
|
|
15
25
|
/** manifest 文件的绝对路径。 */
|
|
16
26
|
export function manifestPath(target) {
|
|
@@ -26,7 +36,82 @@ export function readManifest(target) {
|
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
|
|
29
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* 归一化启动更新检查配置。
|
|
41
|
+
*
|
|
42
|
+
* 用户策略字段启用保守默认值;缓存字段只保留结构化摘要,避免把网络错误细节写入项目。
|
|
43
|
+
*
|
|
44
|
+
* @param {object|null|undefined} value 原始 updateCheck 字段
|
|
45
|
+
* @returns {{enabled:boolean,policy:string,intervalHours:number,lastCheckedAt:string|null,lastRemote:object|null,lastStatus:string|null,lastErrorCode:string|null}} 归一化后的 updateCheck
|
|
46
|
+
*/
|
|
47
|
+
export function normalizeUpdateCheck(value) {
|
|
48
|
+
const raw = value && typeof value === "object" ? value : {};
|
|
49
|
+
const policy = UPDATE_POLICIES.has(raw.policy) ? raw.policy : DEFAULT_UPDATE_CHECK.policy;
|
|
50
|
+
const interval = Number(raw.intervalHours);
|
|
51
|
+
const intervalHours = Number.isFinite(interval) && interval >= 0
|
|
52
|
+
? interval
|
|
53
|
+
: DEFAULT_UPDATE_CHECK.intervalHours;
|
|
54
|
+
const lastRemote = raw.lastRemote && typeof raw.lastRemote === "object"
|
|
55
|
+
? {
|
|
56
|
+
latest: typeof raw.lastRemote.latest === "string" ? raw.lastRemote.latest : null,
|
|
57
|
+
beta: typeof raw.lastRemote.beta === "string" ? raw.lastRemote.beta : null,
|
|
58
|
+
}
|
|
59
|
+
: null;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_UPDATE_CHECK.enabled,
|
|
63
|
+
policy,
|
|
64
|
+
intervalHours,
|
|
65
|
+
lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : null,
|
|
66
|
+
lastRemote,
|
|
67
|
+
lastStatus: typeof raw.lastStatus === "string" ? raw.lastStatus : null,
|
|
68
|
+
lastErrorCode: typeof raw.lastErrorCode === "string" ? raw.lastErrorCode : null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 读取 manifest 里的启动更新检查配置。
|
|
74
|
+
*
|
|
75
|
+
* @param {string} target 目标项目根
|
|
76
|
+
* @returns {ReturnType<typeof normalizeUpdateCheck>} 归一化后的配置
|
|
77
|
+
*/
|
|
78
|
+
export function readUpdateCheck(target) {
|
|
79
|
+
return normalizeUpdateCheck(readManifest(target)?.updateCheck);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 写入启动更新检查配置,保留 manifest 其它安装清单字段。
|
|
84
|
+
*
|
|
85
|
+
* @param {string} target 目标项目根
|
|
86
|
+
* @param {object} patch 要合并进 updateCheck 的字段
|
|
87
|
+
* @returns {object} 写入后的 manifest
|
|
88
|
+
*/
|
|
89
|
+
export function writeUpdateCheck(target, patch) {
|
|
90
|
+
const current = readManifest(target) || {};
|
|
91
|
+
const updateCheck = normalizeUpdateCheck({
|
|
92
|
+
...normalizeUpdateCheck(current.updateCheck),
|
|
93
|
+
...(patch || {}),
|
|
94
|
+
});
|
|
95
|
+
const next = { ...current, updateCheck };
|
|
96
|
+
fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
|
|
97
|
+
return next;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 写入 manifest。
|
|
102
|
+
*
|
|
103
|
+
* 若调用方没有显式传入 `updateCheck`,保留目标项目已有策略与缓存,避免全装重写时覆盖用户选择。
|
|
104
|
+
*
|
|
105
|
+
* @param {string} target 目标项目根
|
|
106
|
+
* @param {object} data manifest 新内容
|
|
107
|
+
*/
|
|
30
108
|
export function writeManifest(target, data) {
|
|
31
|
-
|
|
109
|
+
const current = readManifest(target);
|
|
110
|
+
const next = { ...data };
|
|
111
|
+
if (Object.prototype.hasOwnProperty.call(data, "updateCheck")) {
|
|
112
|
+
next.updateCheck = normalizeUpdateCheck(data.updateCheck);
|
|
113
|
+
} else {
|
|
114
|
+
next.updateCheck = normalizeUpdateCheck(current?.updateCheck);
|
|
115
|
+
}
|
|
116
|
+
fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
|
|
32
117
|
}
|