@jonaskahn/relo 0.1.0 → 0.1.1-beta.23.d85c5105

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/lib/conflict.mjs CHANGED
@@ -3,8 +3,10 @@
3
3
  // installers. Two installs would still fight over the name, the home
4
4
  // directory, and the ports, so the install refuses when a command this
5
5
  // package did not install answers PATH.
6
- import { accessSync, constants, realpathSync } from 'node:fs';
6
+ import { accessSync, constants, existsSync, realpathSync } from 'node:fs';
7
7
  import { delimiter, join, resolve, sep } from 'node:path';
8
+ import { homedir } from 'node:os';
9
+ import { execFileSync } from 'node:child_process';
8
10
 
9
11
  // candidates lists the relo commands a PATH resolves, in order. Windows
10
12
  // installs the app's command as relo.cmd, a shim that starts the GUI
@@ -47,6 +49,53 @@ export function findConflictingRelo({
47
49
  return null;
48
50
  }
49
51
 
52
+ // appBundles lists where the macOS Relo.app may live. The bundle installs
53
+ // no command on PATH, so the PATH search below never sees it, and a stacked
54
+ // install would fight it over the home directory and the ports.
55
+ export function appBundles({ home = homedir(), applications = '/Applications' } = {}) {
56
+ return [join(applications, 'Relo.app'), join(home, 'Applications', 'Relo.app')];
57
+ }
58
+
59
+ // findReloApp returns the first Relo.app bundle on this Mac, or null. The
60
+ // locations and the existence check are parameters so the search can be
61
+ // tested against fixture directories.
62
+ export function findReloApp({
63
+ nodePlatform = process.platform,
64
+ home,
65
+ applications,
66
+ exists = existsSync
67
+ } = {}) {
68
+ if (nodePlatform !== 'darwin') return null;
69
+ for (const app of appBundles({ home, applications })) {
70
+ if (exists(app)) return app;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ const UNINSTALL_KEYS = [
76
+ 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Relo',
77
+ 'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Relo',
78
+ 'HKLM\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Relo'
79
+ ];
80
+
81
+ // findSetupInstall reports whether Relo-Setup registered an install. The
82
+ // installer normally adds its shim directory to PATH, which the search above
83
+ // already sees; the registry covers the install that skipped the PATH edit.
84
+ // A registry that cannot be read reads as no install.
85
+ export function findSetupInstall({ nodePlatform = process.platform, query = queryUninstallKey } = {}) {
86
+ if (nodePlatform !== 'win32') return false;
87
+ return UNINSTALL_KEYS.some((key) => query(key));
88
+ }
89
+
90
+ function queryUninstallKey(key) {
91
+ try {
92
+ execFileSync('reg', ['query', key], { stdio: 'ignore' });
93
+ return true;
94
+ } catch {
95
+ return false;
96
+ }
97
+ }
98
+
50
99
  // insideDir reports whether candidate resolves inside dir, following a link
51
100
  // the way the npm bin shim points into the installed package. Both sides
52
101
  // resolve, because either path may itself sit under a link. A path that
package/lib/install.mjs CHANGED
@@ -19,7 +19,7 @@ import { fileURLToPath } from 'node:url';
19
19
 
20
20
  import { expect, parse } from './checksums.mjs';
21
21
  import { askStart, quiesce, reloHome } from './daemon.mjs';
22
- import { findConflictingRelo } from './conflict.mjs';
22
+ import { findConflictingRelo, findReloApp, findSetupInstall } from './conflict.mjs';
23
23
  import { fetchBytes } from './download.mjs';
24
24
  import { target } from './platform.mjs';
25
25
  import { assets, checksumsUrl, prereleaseTag, selectBundle, tagFromArgs, url } from './release.mjs';
@@ -77,6 +77,21 @@ try {
77
77
  ' remove that install first to install this package, or keep using that install instead'
78
78
  );
79
79
  }
80
+ // Relo.app installs no command on PATH, so the search above never sees
81
+ // it; Relo-Setup without its PATH entry is invisible the same way.
82
+ const app = findReloApp();
83
+ if (app) {
84
+ throw new Error(
85
+ `Relo.app is already installed at ${app}\n` +
86
+ ' remove that install first to install this package, or keep using that install instead'
87
+ );
88
+ }
89
+ if (findSetupInstall()) {
90
+ throw new Error(
91
+ 'Relo is already installed by Relo-Setup.\n' +
92
+ ' remove that install first to install this package, or keep using that install instead'
93
+ );
94
+ }
80
95
  const destination = await fromRelease(tagFromArgs(process.argv.slice(2)) ?? prereleaseTag(publishedVersion));
81
96
  const answer = await askStart();
82
97
  if (answer === 'yes') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonaskahn/relo",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-beta.23.d85c5105",
4
4
  "description": "One local proxy that lets Codex, Claude Code, Cursor and Grok, etc. Talk to any model, any provider with no config changes",
5
5
  "license": "AGPL-3.0-only",
6
6
  "type": "module",