@remcp/remcp 0.2.54 → 0.2.55
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/package.json +1 -1
- package/src/cli/service.mjs +86 -3
- package/src/cli/update.mjs +2 -2
package/package.json
CHANGED
package/src/cli/service.mjs
CHANGED
|
@@ -29,9 +29,92 @@ export function npmGlobalInstall(...specs) {
|
|
|
29
29
|
run(npm.command, [...npm.args, 'install', '--global', ...specs, '--no-audit', '--no-fund', '--loglevel=error']);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
function pathEntryExists(file) {
|
|
33
|
+
try {
|
|
34
|
+
fs.lstatSync(file);
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function globalPackagePath(prefix, packageName) {
|
|
42
|
+
const name = String(packageName || '').trim();
|
|
43
|
+
const parts = name.split('/');
|
|
44
|
+
const validUnscoped = parts.length === 1 && parts[0] && !parts[0].startsWith('@') && parts[0] !== '.' && parts[0] !== '..';
|
|
45
|
+
const validScoped = parts.length === 2 && /^@[^/]+$/.test(parts[0]) && parts[1] && parts[1] !== '.' && parts[1] !== '..';
|
|
46
|
+
if (!validUnscoped && !validScoped) throw new Error(`Invalid npm package name for update: ${name || '(empty)'}`);
|
|
47
|
+
return path.join(prefix, 'lib', 'node_modules', ...parts);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function transactionalMacGlobalInstall({ resolved, prefix, packageNames, specs }) {
|
|
51
|
+
prefix = String(prefix || '').trim();
|
|
52
|
+
if (!path.isAbsolute(prefix)) throw new Error(`npm global prefix must be absolute for a macOS update: ${prefix || '(empty)'}`);
|
|
53
|
+
const backupRoot = path.join(prefix, `.remcp-update-backup-${process.pid}-${Date.now()}`);
|
|
54
|
+
const moved = [];
|
|
55
|
+
const moveAside = (source, label) => {
|
|
56
|
+
if (!pathEntryExists(source)) return;
|
|
57
|
+
const backup = path.join(backupRoot, label);
|
|
58
|
+
fs.renameSync(source, backup);
|
|
59
|
+
moved.push({ source, backup });
|
|
60
|
+
};
|
|
61
|
+
const restore = originalError => {
|
|
62
|
+
let rollbackError = null;
|
|
63
|
+
for (const item of [...moved].reverse()) {
|
|
64
|
+
try {
|
|
65
|
+
if (pathEntryExists(item.source)) fs.rmSync(item.source, { recursive:true, force:true });
|
|
66
|
+
fs.mkdirSync(path.dirname(item.source), { recursive:true });
|
|
67
|
+
fs.renameSync(item.backup, item.source);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
rollbackError ??= error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
try { fs.rmSync(backupRoot, { recursive:true, force:true }); } catch (error) { rollbackError ??= error; }
|
|
73
|
+
if (rollbackError) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`ReMCP update failed and the previous macOS installation could not be fully restored: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
|
|
76
|
+
{ cause:originalError },
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
fs.mkdirSync(prefix, { recursive:true });
|
|
82
|
+
fs.mkdirSync(backupRoot, { recursive:false, mode:0o700 });
|
|
83
|
+
try {
|
|
84
|
+
const uniqueNames = [...new Set(packageNames.map(name => String(name || '').trim()).filter(Boolean))];
|
|
85
|
+
uniqueNames.forEach((name, index) => moveAside(globalPackagePath(prefix, name), `package-${index}`));
|
|
86
|
+
if (uniqueNames.includes(PACKAGE_NAME)) moveAside(path.join(prefix, 'bin', 'remcp'), 'bin-remcp');
|
|
87
|
+
|
|
88
|
+
run(resolved.command, [...resolved.args, 'install', '--global', ...specs, '--no-audit', '--no-fund', '--loglevel=error']);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
restore(error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
fs.rmSync(backupRoot, { recursive:true, force:true });
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(`ReMCP update: could not remove macOS rollback backup ${backupRoot}: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function npmGlobalUpdate(packageNames, ...specs) {
|
|
102
|
+
if (servicePlatform() !== 'darwin') {
|
|
103
|
+
npmGlobalInstall(...specs);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const prefix = globalPrefix();
|
|
107
|
+
transactionalMacGlobalInstall({ resolved:npm, prefix, packageNames, specs });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function npmGlobalUpdateForNode(nodePath, packageNames, ...specs) {
|
|
33
111
|
const resolved = resolveNpm({ nodePath, home, platform:servicePlatform() });
|
|
34
|
-
|
|
112
|
+
if (servicePlatform() !== 'darwin') {
|
|
113
|
+
run(resolved.command, [...resolved.args, 'install', '--global', ...specs, '--no-audit', '--no-fund', '--loglevel=error']);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const prefix = output(resolved.command, [...resolved.args, 'prefix', '--global']);
|
|
117
|
+
transactionalMacGlobalInstall({ resolved, prefix, packageNames, specs });
|
|
35
118
|
}
|
|
36
119
|
|
|
37
120
|
export function quoteSystemd(value) {
|
|
@@ -337,7 +420,7 @@ export function syncKnownInstallations(config, ...specs) {
|
|
|
337
420
|
seen.add(resolvedNode);
|
|
338
421
|
const required = Boolean(candidate?.service) || resolvedNode === serviceInfo.nodePath;
|
|
339
422
|
try {
|
|
340
|
-
|
|
423
|
+
npmGlobalUpdateForNode(resolvedNode, [PACKAGE_NAME, config.runtime.packageName], ...specs);
|
|
341
424
|
results.push({ nodePath:resolvedNode, ok:true, required });
|
|
342
425
|
} catch (error) {
|
|
343
426
|
const message = error instanceof Error ? error.message : String(error);
|
package/src/cli/update.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { PACKAGE_NAME, VERSION } from '../version.mjs';
|
|
|
11
11
|
import { loadConfig, saveConfig } from './config.mjs';
|
|
12
12
|
import { installedVersion } from './doctor.mjs';
|
|
13
13
|
import { linuxServiceFile, macServiceFile, officialOrigin } from './env.mjs';
|
|
14
|
-
import { currentInstallationInfo, ensureServiceIfRecorded, installationVersionsAtCliPath,
|
|
14
|
+
import { currentInstallationInfo, ensureServiceIfRecorded, installationVersionsAtCliPath, npmGlobalUpdate, persistentServiceExpected, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, syncKnownInstallations } from './service.mjs';
|
|
15
15
|
|
|
16
16
|
const UPDATE_DISCOVERY_TIMEOUT_MS = 5000;
|
|
17
17
|
|
|
@@ -242,7 +242,7 @@ export async function updateCommand(flags) {
|
|
|
242
242
|
|
|
243
243
|
const before = { cli: VERSION, runtime: installedVersion(cfg.runtime.packageName) };
|
|
244
244
|
console.log(`Updating ReMCP to ${targets.clientSpec} with ${targets.runtimeSpec}…`);
|
|
245
|
-
|
|
245
|
+
npmGlobalUpdate([PACKAGE_NAME, cfg.runtime.packageName], targets.clientSpec, targets.runtimeSpec);
|
|
246
246
|
|
|
247
247
|
// Converge every ReMCP installation this account has actually used on this machine. The canonical
|
|
248
248
|
// service installation is mandatory; secondary nvm/Hermes/Homebrew copies are best-effort and
|