memtrace 0.3.33 → 0.3.35

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/uninstall.js CHANGED
@@ -6,6 +6,17 @@ const path = require("path");
6
6
  const fs = require("fs");
7
7
  const { execSync, spawnSync } = require("child_process");
8
8
 
9
+ // claude-integration may not be present in old installs running this
10
+ // uninstall script from ~/.memtrace/. Resolve it lazily so the absence
11
+ // of the module is a soft no-op.
12
+ function loadClaudeIntegration() {
13
+ try {
14
+ return require("./lib/claude-integration");
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
9
20
  // ── Constants ─────────────────────────────────────────────────────────────────
10
21
 
11
22
  const PLUGIN_NAME = "memtrace-skills";
@@ -42,9 +53,13 @@ const SKILL_NAMES = [
42
53
  ];
43
54
 
44
55
  // ── Remove skills ───────────────────────────────────────────────────────────
56
+ //
57
+ // Each cleanup helper takes an optional `home` parameter so we can
58
+ // drive it against a tmp dir in tests. Production callers pass
59
+ // nothing and the helper defaults to the real home directory.
45
60
 
46
- function removeSkills() {
47
- const claudeSkillsDir = path.join(os.homedir(), ".claude", "skills");
61
+ function removeSkills(home = os.homedir()) {
62
+ const claudeSkillsDir = path.join(home, ".claude", "skills");
48
63
  let removed = 0;
49
64
 
50
65
  for (const name of SKILL_NAMES) {
@@ -62,15 +77,20 @@ function removeSkills() {
62
77
  if (removed > 0) {
63
78
  console.log(`memtrace: removed ${removed} skills from ${claudeSkillsDir}`);
64
79
  }
80
+ return removed;
65
81
  }
66
82
 
67
83
  // ── Settings.json helpers ────────────────────────────────────────────────────
68
84
 
69
- function readSettings() {
70
- const settingsFile = path.join(os.homedir(), ".claude", "settings.json");
71
- if (fs.existsSync(settingsFile)) {
85
+ function settingsPath(home = os.homedir()) {
86
+ return path.join(home, ".claude", "settings.json");
87
+ }
88
+
89
+ function readSettings(home = os.homedir()) {
90
+ const file = settingsPath(home);
91
+ if (fs.existsSync(file)) {
72
92
  try {
73
- return JSON.parse(fs.readFileSync(settingsFile, "utf-8"));
93
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
74
94
  } catch {
75
95
  return {};
76
96
  }
@@ -78,9 +98,8 @@ function readSettings() {
78
98
  return {};
79
99
  }
80
100
 
81
- function writeSettings(settings) {
82
- const settingsFile = path.join(os.homedir(), ".claude", "settings.json");
83
- fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + "\n");
101
+ function writeSettings(settings, home = os.homedir()) {
102
+ fs.writeFileSync(settingsPath(home), JSON.stringify(settings, null, 2) + "\n");
84
103
  }
85
104
 
86
105
  function isRecord(value) {
@@ -89,8 +108,8 @@ function isRecord(value) {
89
108
 
90
109
  // ── Remove MCP server ───────────────────────────────────────────────────────
91
110
 
92
- function removeMcpServer() {
93
- const settings = readSettings();
111
+ function removeMcpServer(home = os.homedir()) {
112
+ const settings = readSettings(home);
94
113
  let changed = false;
95
114
 
96
115
  if (isRecord(settings.mcpServers) && settings.mcpServers[MCP_SERVER_NAME]) {
@@ -102,13 +121,14 @@ function removeMcpServer() {
102
121
  console.log("memtrace: removed MCP server from ~/.claude/settings.json");
103
122
  }
104
123
 
105
- if (changed) writeSettings(settings);
124
+ if (changed) writeSettings(settings, home);
125
+ return changed;
106
126
  }
107
127
 
108
128
  // ── Remove plugin + marketplace ─────────────────────────────────────────────
109
129
 
110
- function removePlugin() {
111
- const settings = readSettings();
130
+ function removePlugin(home = os.homedir()) {
131
+ const settings = readSettings(home);
112
132
  let changed = false;
113
133
 
114
134
  if (isRecord(settings.enabledPlugins)) {
@@ -144,34 +164,38 @@ function removePlugin() {
144
164
  console.log(`memtrace: removed marketplace ${MARKETPLACE_NAME}`);
145
165
  }
146
166
 
147
- if (changed) writeSettings(settings);
167
+ if (changed) writeSettings(settings, home);
168
+ return changed;
148
169
  }
149
170
 
150
- function removeMarketplaceCaches() {
151
- const marketplacesRoot = path.join(os.homedir(), ".claude", "plugins", "marketplaces");
171
+ function removeMarketplaceCaches(home = os.homedir()) {
172
+ const marketplacesRoot = path.join(home, ".claude", "plugins", "marketplaces");
173
+ let removed = 0;
152
174
  for (const entry of MARKETPLACE_SETTING_KEYS) {
153
175
  const dir = path.join(marketplacesRoot, entry);
154
176
  if (fs.existsSync(dir)) {
155
177
  try {
156
178
  fs.rmSync(dir, { recursive: true, force: true });
157
179
  console.log(`memtrace: removed marketplace cache ${entry}`);
180
+ removed++;
158
181
  } catch (e) {
159
182
  console.warn(`memtrace: failed to remove marketplace cache ${entry}: ${e.message}`);
160
183
  }
161
184
  }
162
185
  }
186
+ return removed;
163
187
  }
164
188
 
165
- function removeInstalledPluginMetadata() {
166
- const installedPluginsFile = path.join(os.homedir(), ".claude", "plugins", "installed_plugins.json");
167
- if (!fs.existsSync(installedPluginsFile)) return;
189
+ function removeInstalledPluginMetadata(home = os.homedir()) {
190
+ const installedPluginsFile = path.join(home, ".claude", "plugins", "installed_plugins.json");
191
+ if (!fs.existsSync(installedPluginsFile)) return false;
168
192
  let data;
169
193
  try {
170
194
  data = JSON.parse(fs.readFileSync(installedPluginsFile, "utf-8"));
171
195
  } catch {
172
- return;
196
+ return false;
173
197
  }
174
- if (!isRecord(data.plugins)) return;
198
+ if (!isRecord(data.plugins)) return false;
175
199
 
176
200
  let changed = false;
177
201
  for (const key of Object.keys(data.plugins)) {
@@ -184,6 +208,19 @@ function removeInstalledPluginMetadata() {
184
208
  fs.writeFileSync(installedPluginsFile, JSON.stringify(data, null, 2) + "\n");
185
209
  console.log("memtrace: removed plugin metadata");
186
210
  }
211
+ return changed;
212
+ }
213
+
214
+ function removeMemtraceHomeDir(home = os.homedir()) {
215
+ const memtraceDir = path.join(home, ".memtrace");
216
+ if (!fs.existsSync(memtraceDir)) return false;
217
+ try {
218
+ fs.rmSync(memtraceDir, { recursive: true, force: true });
219
+ return true;
220
+ } catch (e) {
221
+ console.warn(`memtrace: failed to remove ${memtraceDir}: ${e.message}`);
222
+ return false;
223
+ }
187
224
  }
188
225
 
189
226
  // ── Try Claude CLI uninstall ────────────────────────────────────────────────
@@ -209,15 +246,30 @@ function tryClaudeCliUninstall() {
209
246
 
210
247
  // ── Main uninstall logic ───────────────────────────────────────────────────
211
248
 
249
+ function removeClaudeIntegration(home = os.homedir()) {
250
+ const ci = loadClaudeIntegration();
251
+ if (!ci) return;
252
+ try {
253
+ const summary = ci.uninstallFromFs({ claudeDir: path.join(home, ".claude") });
254
+ if (summary.removed.length > 0) {
255
+ console.log(`memtrace: cleaned Claude Code integration (${summary.removed.length} item(s))`);
256
+ }
257
+ } catch (e) {
258
+ console.warn(`memtrace: Claude Code integration cleanup failed: ${e.message}`);
259
+ }
260
+ }
261
+
212
262
  function legacyCleanup(options = {}) {
213
- try { removeSkills(); } catch (e) { console.warn(`memtrace: skill removal failed: ${e.message}`); }
263
+ const home = options.home || os.homedir();
264
+ try { removeSkills(home); } catch (e) { console.warn(`memtrace: skill removal failed: ${e.message}`); }
214
265
  if (!options.skipClaudeCli) {
215
266
  try { tryClaudeCliUninstall(); } catch { /* silent */ }
216
267
  }
217
- try { removePlugin(); } catch (e) { console.warn(`memtrace: plugin removal failed: ${e.message}`); }
218
- try { removeMcpServer(); } catch (e) { console.warn(`memtrace: MCP server removal failed: ${e.message}`); }
219
- try { removeMarketplaceCaches(); } catch (e) { console.warn(`memtrace: marketplace cache cleanup failed: ${e.message}`); }
220
- try { removeInstalledPluginMetadata(); } catch (e) { console.warn(`memtrace: installed plugin metadata cleanup failed: ${e.message}`); }
268
+ try { removePlugin(home); } catch (e) { console.warn(`memtrace: plugin removal failed: ${e.message}`); }
269
+ try { removeMcpServer(home); } catch (e) { console.warn(`memtrace: MCP server removal failed: ${e.message}`); }
270
+ try { removeMarketplaceCaches(home); } catch (e) { console.warn(`memtrace: marketplace cache cleanup failed: ${e.message}`); }
271
+ try { removeInstalledPluginMetadata(home); } catch (e) { console.warn(`memtrace: installed plugin metadata cleanup failed: ${e.message}`); }
272
+ removeClaudeIntegration(home);
221
273
  }
222
274
 
223
275
  function run() {
@@ -242,23 +294,39 @@ function run() {
242
294
  }
243
295
 
244
296
  // Always clean up ~/.memtrace/
245
- try {
246
- const memtraceDir = path.join(os.homedir(), ".memtrace");
247
- if (fs.existsSync(memtraceDir)) {
248
- fs.rmSync(memtraceDir, { recursive: true, force: true });
249
- console.log("memtrace: removed ~/.memtrace/");
250
- }
251
- } catch (e) {
252
- console.warn(`memtrace: failed to remove ~/.memtrace/: ${e.message}`);
297
+ if (removeMemtraceHomeDir()) {
298
+ console.log("memtrace: removed ~/.memtrace/");
253
299
  }
254
300
 
255
301
  console.log("memtrace: uninstall complete");
256
302
  }
257
303
 
258
- // Run when called directly (npm preuninstall hook) or when require()'d
304
+ // Exports for testability. legacyCleanup accepts `{ home }` so test
305
+ // suites can drive the full cleanup pipeline against a tmp dir.
306
+ module.exports = {
307
+ SKILL_NAMES,
308
+ PLUGIN_KEY,
309
+ PLUGIN_NAME,
310
+ MARKETPLACE_NAME,
311
+ MCP_SERVER_NAME,
312
+ MARKETPLACE_SETTING_KEYS,
313
+ MARKETPLACE_SETTING_CONTAINERS,
314
+ removeSkills,
315
+ readSettings,
316
+ writeSettings,
317
+ removeMcpServer,
318
+ removePlugin,
319
+ removeMarketplaceCaches,
320
+ removeInstalledPluginMetadata,
321
+ removeMemtraceHomeDir,
322
+ removeClaudeIntegration,
323
+ legacyCleanup,
324
+ };
325
+
326
+ // Run when called directly (npm preuninstall hook). When require()'d
327
+ // for tests, exports are available without running. The `memtrace
328
+ // uninstall` CLI invokes run() through bin/memtrace.js, not via this
329
+ // module's auto-execution.
259
330
  if (require.main === module) {
260
331
  run();
261
- } else {
262
- // Called via `memtrace uninstall` — run immediately
263
- run();
264
332
  }