@sfmc-bds/cli 0.2.4 → 0.3.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.
Files changed (41) hide show
  1. package/dist/command-surface.d.ts.map +1 -1
  2. package/dist/command-surface.js +5 -1
  3. package/dist/command-surface.js.map +2 -2
  4. package/dist/commands.d.ts.map +1 -1
  5. package/dist/commands.js +20 -79
  6. package/dist/commands.js.map +2 -2
  7. package/dist/help-text.d.ts.map +1 -1
  8. package/dist/help-text.js +2 -0
  9. package/dist/help-text.js.map +2 -2
  10. package/dist/i18n/locales/en.d.ts +33 -0
  11. package/dist/i18n/locales/en.d.ts.map +1 -1
  12. package/dist/i18n/locales/en.js +33 -0
  13. package/dist/i18n/locales/en.js.map +2 -2
  14. package/dist/i18n/locales/zh-CN.d.ts.map +1 -1
  15. package/dist/i18n/locales/zh-CN.js +33 -0
  16. package/dist/i18n/locales/zh-CN.js.map +2 -2
  17. package/dist/module-commands.d.ts.map +1 -1
  18. package/dist/module-commands.js +8 -0
  19. package/dist/module-commands.js.map +2 -2
  20. package/dist/module-update/index.d.ts +18 -0
  21. package/dist/module-update/index.d.ts.map +1 -0
  22. package/dist/module-update/index.js +223 -0
  23. package/dist/module-update/index.js.map +7 -0
  24. package/dist/repl.d.ts.map +1 -1
  25. package/dist/repl.js +11 -0
  26. package/dist/repl.js.map +2 -2
  27. package/dist/services.d.ts +29 -1
  28. package/dist/services.d.ts.map +1 -1
  29. package/dist/services.js +87 -3
  30. package/dist/services.js.map +2 -2
  31. package/dist/wizard.d.ts.map +1 -1
  32. package/dist/wizard.js +2 -0
  33. package/dist/wizard.js.map +2 -2
  34. package/package.json +1 -1
  35. package/scripts/module-install/fetch-module.mjs +164 -76
  36. package/scripts/module-install/lib/module-update-config.mjs +83 -0
  37. package/scripts/module-install/lib/module-update.mjs +507 -0
  38. package/scripts/module-install/lib/pins.mjs +436 -0
  39. package/scripts/module-install/lib/registry-cache.mjs +83 -0
  40. package/scripts/module-install/lib/semver.mjs +124 -0
  41. package/scripts/module-install/test/module-update.test.mjs +182 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * 模块自动更新的纯逻辑与失败还原。
3
+ * 不访问网络,索引和 dist-tag 都由测试注入。
4
+ */
5
+ import assert from "node:assert/strict";
6
+ import fs from "node:fs";
7
+ import os from "node:os";
8
+ import path from "node:path";
9
+ import test from "node:test";
10
+ import { compareSemver, isMajorBump, satisfiesSdk } from "../lib/semver.mjs";
11
+ import { decideModuleUpdate, orderUpgrades, applyModuleUpgrades, planModuleUpdates } from "../lib/module-update.mjs";
12
+ import {
13
+ inferPinDraft,
14
+ reconcileDefaultAuto,
15
+ setModulePinAuto,
16
+ shouldPreserveLock,
17
+ writePins,
18
+ } from "../lib/pins.mjs";
19
+
20
+ test("semver 比较与 SDK 范围", () => {
21
+ assert.equal(compareSemver("1.2.3", "1.2.4"), -1);
22
+ assert.equal(compareSemver("1.2.3", "1.2.3"), 0);
23
+ assert.equal(compareSemver("1.2.3", "1.2.3-beta.1"), 1);
24
+ assert.equal(isMajorBump("1.2.0", "2.0.0"), true);
25
+ assert.equal(isMajorBump("0.2.0", "0.3.0"), false);
26
+ assert.equal(satisfiesSdk("0.2.5", ">=0.2.2"), true);
27
+ assert.equal(satisfiesSdk("0.2.1", ">=0.2.2"), false);
28
+ assert.equal(satisfiesSdk("0.2.5", "^0.2.3"), true);
29
+ assert.equal(satisfiesSdk("0.3.0", "^0.2.3"), false);
30
+ });
31
+
32
+ test("decideModuleUpdate 按来源和版本取舍", () => {
33
+ const base = {
34
+ auto: true,
35
+ autoLocked: false,
36
+ source: "npm",
37
+ installedVersion: "1.2.0",
38
+ targetVersion: "1.3.0",
39
+ allowMajor: false,
40
+ sdkRange: null,
41
+ hostSdk: "0.2.5",
42
+ offline: false,
43
+ targetReason: null,
44
+ };
45
+ assert.equal(decideModuleUpdate(base).action, "upgrade");
46
+ assert.equal(decideModuleUpdate({ ...base, targetVersion: "2.0.0" }).reason, "major");
47
+ assert.equal(decideModuleUpdate({ ...base, targetVersion: "2.0.0", allowMajor: true }).action, "upgrade");
48
+ assert.equal(decideModuleUpdate({ ...base, auto: false }).reason, "auto-off");
49
+ assert.equal(decideModuleUpdate({ ...base, source: "link", autoLocked: true }).reason, "local-source");
50
+ assert.equal(decideModuleUpdate({ ...base, targetVersion: "1.2.0" }).reason, "up-to-date");
51
+ assert.equal(decideModuleUpdate({ ...base, offline: true, targetVersion: null }).reason, "registry-offline");
52
+ assert.equal(
53
+ decideModuleUpdate({ ...base, sdkRange: ">=0.3.0", hostSdk: "0.2.5" }).reason,
54
+ "sdk"
55
+ );
56
+ });
57
+
58
+ test("升级顺序让被依赖的模块先走", () => {
59
+ const ordered = orderUpgrades([
60
+ { id: "chat", fromVersion: "1.0.0", toVersion: "1.1.0", spec: "npm:a@1.1.0", requires: ["economy"] },
61
+ { id: "economy", fromVersion: "1.0.0", toVersion: "1.1.0", spec: "npm:b@1.1.0", requires: [] },
62
+ ]);
63
+ assert.deepEqual(ordered.map((item) => item.id), ["economy", "chat"]);
64
+ });
65
+
66
+ test("pin 草稿与默认 auto", () => {
67
+ assert.equal(shouldPreserveLock(true, true), true);
68
+ assert.equal(shouldPreserveLock(true, false), false);
69
+ assert.equal(shouldPreserveLock(false, true), false);
70
+ const linked = inferPinDraft("dir:D:/mod", true);
71
+ assert.equal(linked.autoLocked, true);
72
+ assert.equal(linked.auto, false);
73
+ const versioned = inferPinDraft("npm:@sfmc-bds/module-chat@0.2.2", false);
74
+ assert.equal(versioned.auto, false);
75
+ assert.equal(versioned.channel, "index");
76
+ const opened = reconcileDefaultAuto(
77
+ {
78
+ ...versioned,
79
+ installedVersion: "0.2.2",
80
+ manifestId: "chat",
81
+ autoSetBy: "default",
82
+ lastCheckedAt: null,
83
+ lastAppliedAt: null,
84
+ lastError: null,
85
+ },
86
+ { npm: "@sfmc-bds/module-chat", version: "0.2.3" }
87
+ );
88
+ assert.equal(opened.auto, true);
89
+ assert.equal(opened.channel, "index");
90
+ const kept = reconcileDefaultAuto({ ...opened, auto: false, autoSetBy: "user" }, {
91
+ npm: "@sfmc-bds/module-chat",
92
+ version: "0.2.3",
93
+ });
94
+ assert.equal(kept.auto, false);
95
+ });
96
+
97
+ test("计划只升级索引里更高的补丁,并跳过 major", async () => {
98
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-mod-update-"));
99
+ const pkg = path.join(root, "modules", "packages", "chat");
100
+ fs.mkdirSync(path.join(pkg, "sapi"), { recursive: true });
101
+ fs.writeFileSync(path.join(pkg, "package.json"), JSON.stringify({ name: "@sfmc-bds/module-chat", version: "1.0.0" }));
102
+ fs.writeFileSync(
103
+ path.join(pkg, "sapi", "manifest.json"),
104
+ JSON.stringify({ id: "chat", configKey: "chat", requires: [] })
105
+ );
106
+ const plan = await planModuleUpdates({
107
+ root,
108
+ hostSdk: "0.2.5",
109
+ fetchIndex: async () => ({
110
+ index: { chat: { npm: "@sfmc-bds/module-chat", version: "1.1.0", sdk: ">=0.2.2" } },
111
+ offline: false,
112
+ }),
113
+ fetchDistTag: async () => {
114
+ throw new Error("indexed module should not query dist-tag");
115
+ },
116
+ });
117
+ assert.equal(plan.upgrades.length, 1);
118
+ assert.equal(plan.upgrades[0].toVersion, "1.1.0");
119
+ assert.equal(plan.upgrades[0].spec, "npm:@sfmc-bds/module-chat@1.1.0");
120
+
121
+ const major = await planModuleUpdates({
122
+ root,
123
+ hostSdk: "0.2.5",
124
+ fetchIndex: async () => ({
125
+ index: { chat: { npm: "@sfmc-bds/module-chat", version: "2.0.0", sdk: ">=0.2.2" } },
126
+ offline: false,
127
+ }),
128
+ fetchDistTag: async () => null,
129
+ });
130
+ assert.equal(major.upgrades.length, 0);
131
+ assert.equal(major.skipped.some((item) => item.reason === "major"), true);
132
+ });
133
+
134
+ test("换包失败时还原原来的目录", async () => {
135
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-mod-restore-"));
136
+ const pkg = path.join(root, "modules", "packages", "demo");
137
+ fs.mkdirSync(pkg, { recursive: true });
138
+ fs.writeFileSync(path.join(pkg, "marker.txt"), "old");
139
+ const result = await applyModuleUpgrades(
140
+ [{ id: "demo", fromVersion: "1.0.0", toVersion: "1.1.0", spec: "npm:x@1.1.0", requires: [] }],
141
+ {
142
+ root,
143
+ failMode: "continue",
144
+ install: async (id) => {
145
+ const dir = path.join(root, "modules", "packages", id);
146
+ fs.mkdirSync(dir, { recursive: true });
147
+ fs.writeFileSync(path.join(dir, "marker.txt"), "new");
148
+ throw new Error("boom");
149
+ },
150
+ }
151
+ );
152
+ assert.equal(result.failed.length, 1);
153
+ assert.equal(fs.readFileSync(path.join(pkg, "marker.txt"), "utf8"), "old");
154
+ });
155
+
156
+ test("本地 pin 不能打开自动更新", () => {
157
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-mod-pin-"));
158
+ fs.mkdirSync(path.join(root, "modules", "packages", "demo"), { recursive: true });
159
+ writePins(root, {
160
+ version: 1,
161
+ modules: {
162
+ demo: {
163
+ source: "link",
164
+ channel: "none",
165
+ spec: "",
166
+ installedVersion: "1.0.0",
167
+ manifestId: "demo",
168
+ auto: false,
169
+ autoLocked: true,
170
+ autoSetBy: "default",
171
+ lastCheckedAt: null,
172
+ lastAppliedAt: null,
173
+ lastError: null,
174
+ },
175
+ },
176
+ });
177
+ const locked = setModulePinAuto(root, "demo", true);
178
+ assert.equal(locked.ok, false);
179
+ assert.equal(locked.code, "locked");
180
+ const off = setModulePinAuto(root, "demo", false);
181
+ assert.equal(off.ok, true);
182
+ });