@link-assistant/hive-mind 1.78.4 → 1.78.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.78.5
4
+
5
+ ### Patch Changes
6
+
7
+ - b3d6588: Remove Hive Mind's npm global prefix workaround now that use-m handles non-writable npm global roots upstream.
8
+
3
9
  ## 1.78.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.78.4",
3
+ "version": "1.78.5",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -1,21 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { ensureWritableNpmGlobalPrefix } from './npm-global-prefix.lib.mjs';
4
-
5
3
  const defaultFetchUseMCode = async () => (await fetch('https://unpkg.com/use-m/use.js')).text();
6
4
 
7
5
  /**
8
- * Load the use-m bootstrap after npm's global prefix has been made safe for
9
- * use-m's Node resolver.
6
+ * Load the shared use-m bootstrap.
10
7
  *
11
8
  * @param {object} [options]
12
- * @param {(message: string) => void} [options.log]
13
9
  * @param {() => Promise<string>} [options.fetchUseMCode]
14
10
  * @returns {Promise<Function>} The global use-m `use` function.
15
11
  */
16
12
  export const ensureUseM = async (options = {}) => {
17
- const { log = message => console.log(message), fetchUseMCode = defaultFetchUseMCode } = options;
18
- await ensureWritableNpmGlobalPrefix({ log });
13
+ const { fetchUseMCode = defaultFetchUseMCode } = options;
19
14
  if (typeof globalThis.use === 'undefined') {
20
15
  globalThis.use = (await eval(await fetchUseMCode())).use;
21
16
  }
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Ensure npm's global install directory is writable before `use-m` runs.
5
- *
6
- * Issue #1897: `use-m` loads runtime dependencies (command-stream, getenv,
7
- * yargs, …) by shelling out to `npm install -g <alias>@npm:<pkg>@latest`.
8
- * npm installs into the global prefix reported by `npm root -g`. When the
9
- * CLI is launched with a system-wide Node.js whose global `node_modules`
10
- * directory is owned by root (e.g. `/opt/node-v24.16.0-linux-x64/lib/node_modules`),
11
- * the install fails with `EACCES: permission denied` and the whole process
12
- * crashes at the very first `use()` call with an unhandled error:
13
- *
14
- * Error: Failed to install command-stream@latest globally.
15
- * ... npm error code EACCES
16
- * ... npm error syscall rename
17
- * ... npm error path /opt/node-.../lib/node_modules/command-stream-v-latest
18
- *
19
- * This commonly happens when the package was installed with one runtime
20
- * (e.g. `bun add -g`, which writes to a user-owned `~/.bun/...`) but launched
21
- * under a system Node whose global prefix needs root.
22
- *
23
- * The fix mirrors npm's own documented recommendation for EACCES errors:
24
- * point the global prefix at a user-writable directory. We detect the
25
- * non-writable prefix up front and redirect `npm_config_prefix` (which both
26
- * `npm install -g` and `npm root -g` honour) to `~/.npm-global`, so use-m's
27
- * install succeeds without sudo. When the prefix is already writable we do
28
- * nothing — the common case stays a no-op with no extra `npm` spawn.
29
- */
30
-
31
- import { access, mkdir } from 'node:fs/promises';
32
- import { constants as fsConstants } from 'node:fs';
33
- import { dirname, join } from 'node:path';
34
- import { homedir } from 'node:os';
35
- import { exec } from 'node:child_process';
36
- import { promisify } from 'node:util';
37
-
38
- const execAsync = promisify(exec);
39
-
40
- /**
41
- * Derive the likely global `node_modules` path from the Node binary location
42
- * without spawning npm. For a standard POSIX layout the node binary lives at
43
- * `<prefix>/bin/node` and global packages at `<prefix>/lib/node_modules`.
44
- *
45
- * @param {string} execPath - Absolute path to the running node binary.
46
- * @returns {string} Candidate global `node_modules` directory.
47
- */
48
- export const deriveGlobalNodeModules = execPath => join(dirname(dirname(execPath)), 'lib', 'node_modules');
49
-
50
- /**
51
- * Check whether a directory is writable, walking up to the nearest existing
52
- * ancestor when the leaf does not yet exist (npm would create it on install).
53
- *
54
- * @param {string} startDir - Directory to test.
55
- * @param {(path: string, mode: number) => Promise<void>} accessFn - fs.access.
56
- * @returns {Promise<boolean>}
57
- */
58
- export const isPathWritable = async (startDir, accessFn = access) => {
59
- let current = startDir;
60
- for (;;) {
61
- try {
62
- await accessFn(current, fsConstants.W_OK);
63
- return true;
64
- } catch (error) {
65
- if (error && error.code === 'ENOENT') {
66
- const parent = dirname(current);
67
- if (parent === current) return false; // reached filesystem root
68
- current = parent;
69
- continue;
70
- }
71
- // EACCES / EPERM / anything else → treat as not writable.
72
- return false;
73
- }
74
- }
75
- };
76
-
77
- const queryNpmRoot = async runner => {
78
- try {
79
- const { stdout } = await runner('npm root -g');
80
- const value = String(stdout).trim();
81
- return value || null;
82
- } catch {
83
- return null;
84
- }
85
- };
86
-
87
- /**
88
- * Detect a non-writable npm global prefix and, if found, redirect global
89
- * installs to a user-writable directory by setting `npm_config_prefix`.
90
- *
91
- * Idempotent and dependency-injectable for tests. Returns an object describing
92
- * what happened: `{ changed: boolean, reason: string, prefix?, previousRoot? }`.
93
- *
94
- * @param {object} [options]
95
- * @param {Record<string, string>} [options.env=process.env] - Environment to mutate.
96
- * @param {string} [options.execPath=process.execPath] - Running node binary path.
97
- * @param {string} [options.platform=process.platform] - OS platform.
98
- * @param {string} [options.home] - User home directory.
99
- * @param {Function} [options.accessFn=fs.access]
100
- * @param {Function} [options.mkdirFn=fs.mkdir]
101
- * @param {Function} [options.runner=execAsync] - Runs a shell command, returns {stdout}.
102
- * @param {(message: string) => void} [options.log] - Informational logger.
103
- * @returns {Promise<{changed: boolean, reason: string, prefix?: string, previousRoot?: string, error?: Error}>}
104
- */
105
- export const ensureWritableNpmGlobalPrefix = async (options = {}) => {
106
- const { env = process.env, execPath = process.execPath, platform = process.platform, home = homedir(), accessFn = access, mkdirFn = mkdir, runner = execAsync, log = () => {}, isBunRuntime = typeof Bun !== 'undefined', isDenoRuntime = typeof Deno !== 'undefined' } = options;
107
-
108
- // Windows' global layout differs (`<prefix>/node_modules`, AppData), and the
109
- // EACCES scenario this guards against is POSIX-specific. Skip to avoid false
110
- // positives that would needlessly relocate the prefix.
111
- if (platform === 'win32') return { changed: false, reason: 'win32' };
112
-
113
- // This workaround only protects use-m's Node/npm resolver. Bun and Deno use
114
- // different install paths and should not have npm configuration changed.
115
- if (isBunRuntime) return { changed: false, reason: 'bun-runtime' };
116
- if (isDenoRuntime) return { changed: false, reason: 'deno-runtime' };
117
-
118
- // Respect an explicitly configured prefix — the user (or a parent process)
119
- // already chose where global installs go.
120
- if (env.npm_config_prefix || env.NPM_CONFIG_PREFIX) return { changed: false, reason: 'preset' };
121
-
122
- // Fast path: derive the likely global node_modules from the node binary and
123
- // check writability without spawning npm. Most installs land here.
124
- const derived = deriveGlobalNodeModules(execPath);
125
- if (await isPathWritable(derived, accessFn)) {
126
- return { changed: false, reason: 'writable' };
127
- }
128
-
129
- // The cheap heuristic says non-writable. Confirm against the authoritative
130
- // path npm/use-m actually use before changing anything (handles custom prefixes).
131
- const authoritative = (await queryNpmRoot(runner)) || derived;
132
- if (await isPathWritable(authoritative, accessFn)) {
133
- return { changed: false, reason: 'writable' };
134
- }
135
-
136
- // Global prefix is genuinely not writable by the current user (issue #1897).
137
- if (!home) {
138
- return { changed: false, reason: 'no-home' };
139
- }
140
-
141
- const prefix = join(home, '.npm-global');
142
- try {
143
- // npm installs into `<prefix>/lib/node_modules`; create it so the very
144
- // first `npm install -g` does not have to.
145
- await mkdirFn(join(prefix, 'lib', 'node_modules'), { recursive: true });
146
- } catch (error) {
147
- return { changed: false, reason: 'mkdir-failed', error };
148
- }
149
-
150
- env.npm_config_prefix = prefix;
151
- // Make globally-installed binaries from the new prefix resolvable too.
152
- const binDir = join(prefix, 'bin');
153
- const pathParts = String(env.PATH || '').split(':');
154
- if (!pathParts.includes(binDir)) {
155
- env.PATH = env.PATH ? `${binDir}:${env.PATH}` : binDir;
156
- }
157
-
158
- log(`ℹ️ npm global directory (${authoritative}) is not writable; redirecting global installs to ${prefix} (issue #1897).`);
159
- return { changed: true, reason: 'redirected', prefix, previousRoot: authoritative };
160
- };