@link-assistant/hive-mind 2.10.3 → 2.10.4
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 +6 -0
- package/package.json +1 -1
- package/src/use-m-bootstrap.lib.mjs +8 -1
- package/src/use-with-retry.lib.mjs +37 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.10.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f1d5026: Recover use-m dependency imports when an installed package is missing an internal module or alias cleanup hits a transient filesystem race by removing the incomplete alias with bounded retries and reloading it. The CDN bootstrap fallback is also repinned from use-m 8.13.8 to 8.14.4 so a CDN outage no longer downgrades dependency loading to a use-m without corrupt-alias recovery.
|
|
8
|
+
|
|
3
9
|
## 2.10.3
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
import { wrapUseWithRetry } from './use-with-retry.lib.mjs';
|
|
4
4
|
|
|
5
5
|
export const USE_M_BOOTSTRAP_URL = 'https://unpkg.com/use-m/use.js';
|
|
6
|
-
|
|
6
|
+
// Issue #2113: the fallback is only reached when unpkg cannot serve the `latest`
|
|
7
|
+
// bundle, but until now it pinned 8.13.8 — the last release *without* any
|
|
8
|
+
// corrupt-alias self-healing. A CDN hiccup therefore silently downgraded every
|
|
9
|
+
// dependency import to the least resilient loader available. 8.14.4 is the first
|
|
10
|
+
// release that both repairs corrupt aliases (8.14.3, use-m #66/#67) and removes
|
|
11
|
+
// them with a retry budget (8.14.4, use-m #68), so the degraded path now keeps
|
|
12
|
+
// upstream recovery instead of losing it.
|
|
13
|
+
export const USE_M_BOOTSTRAP_FALLBACK_URL = 'https://unpkg.com/use-m@8.14.4/use.js';
|
|
7
14
|
|
|
8
15
|
const isMissingUseMBundle = code => /^Not found: \/use-m@[^/]+\/use\.js\s*$/.test(code.trim());
|
|
9
16
|
|
|
@@ -16,13 +16,21 @@
|
|
|
16
16
|
* 3. Node throws `Invalid package config <dir>/package.json.` with
|
|
17
17
|
* `code: 'ERR_INVALID_PACKAGE_CONFIG'` — the package.json itself
|
|
18
18
|
* is corrupt/truncated and cannot even be parsed (issue #1712).
|
|
19
|
+
* 4. Node throws `ERR_MODULE_NOT_FOUND` for a file imported by the package
|
|
20
|
+
* entry point — npm left an incomplete package tree (issue #2113).
|
|
21
|
+
* 5. use-m's self-heal throws `Failed to remove corrupt npm alias ...`
|
|
22
|
+
* because a concurrent filesystem mutation made recursive removal fail
|
|
23
|
+
* with a retryable code such as `ENOTEMPTY` (issue #2113).
|
|
19
24
|
*
|
|
20
|
-
* The recovery is identical for all
|
|
25
|
+
* The recovery is identical for all five: delete the broken alias install
|
|
21
26
|
* directory and ask use-m to re-fetch. A clean reinstall almost always
|
|
22
27
|
* succeeds. This helper centralises that retry so every call site picks
|
|
23
28
|
* it up.
|
|
24
29
|
*/
|
|
25
30
|
|
|
31
|
+
const ALIAS_CLEANUP_ERROR = /^Failed to remove (?:corrupt|incomplete) npm alias '([^']+)'\.$/;
|
|
32
|
+
const RETRYABLE_RM_CODES = new Set(['EBUSY', 'EMFILE', 'ENFILE', 'ENOTEMPTY', 'EPERM']);
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* @param {(specifier: string) => Promise<unknown>} use - the use-m loader.
|
|
28
36
|
* @param {string} specifier - the npm specifier to load (e.g. `'getenv'`).
|
|
@@ -94,8 +102,9 @@ export const useWithRetry = async (use, specifier, options = {}) => {
|
|
|
94
102
|
// Remember the file so the next attempt can bypass Node's poisoned
|
|
95
103
|
// module cache if use-m hands us the same path again.
|
|
96
104
|
cleanedImportPath = /Failed to import module from '/.test(error?.message ?? '') ? corruptedPath : null;
|
|
97
|
-
} catch {
|
|
105
|
+
} catch (cleanupError) {
|
|
98
106
|
// Best-effort cleanup; fall through to retry regardless.
|
|
107
|
+
log(`cleanup of ${resolveAliasDir(corruptedPath)} failed: ${cleanupError?.message}`);
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
}
|
|
@@ -128,9 +137,23 @@ export const isCorruptInstallError = error => {
|
|
|
128
137
|
// resolve/import logic gets a chance to run.
|
|
129
138
|
if (error?.code === 'ERR_INVALID_PACKAGE_CONFIG') return true;
|
|
130
139
|
if (cause?.code === 'ERR_INVALID_PACKAGE_CONFIG') return true;
|
|
140
|
+
// Mode 4 (issue #2113): the installed entry point exists, but one of the
|
|
141
|
+
// files it imports does not. Restrict this to use-m's import wrapper so an
|
|
142
|
+
// unrelated application-level ERR_MODULE_NOT_FOUND is not retried.
|
|
143
|
+
const message = typeof error?.message === 'string' ? error.message : '';
|
|
144
|
+
if (/^Failed to import module from '/.test(message) && cause?.code === 'ERR_MODULE_NOT_FOUND') return true;
|
|
145
|
+
// Mode 5 (issue #2113): use-m@8.14.3 added its own alias self-heal, but its
|
|
146
|
+
// recursive rm used Node's default maxRetries=0, so a concurrent mutation
|
|
147
|
+
// escaped as ENOTEMPTY (or another rm-retry code). use-m@8.14.4 fixed this
|
|
148
|
+
// upstream (use-m #68) by giving removePackageAlias its own retry budget.
|
|
149
|
+
// The downstream guard is deliberately kept: the bootstrap can still land on
|
|
150
|
+
// an older use-m (a pinned CDN fallback, a preinstalled global, or a cached
|
|
151
|
+
// bundle), and even 8.14.4 rethrows this exact wrapper once its own budget is
|
|
152
|
+
// exhausted — in which case a fresh removal plus reinstall is still the right
|
|
153
|
+
// recovery.
|
|
154
|
+
if (ALIAS_CLEANUP_ERROR.test(message) && RETRYABLE_RM_CODES.has(cause?.code)) return true;
|
|
131
155
|
// Mode 2 (also seen on hosted CI): npm install completes but the package
|
|
132
156
|
// tree is incomplete, so use-m can't resolve the entry point.
|
|
133
|
-
const message = typeof error?.message === 'string' ? error.message : '';
|
|
134
157
|
if (/^Failed to resolve the path to /.test(message)) return true;
|
|
135
158
|
// Fallback string match for ERR_INVALID_PACKAGE_CONFIG (in case the error
|
|
136
159
|
// bubbles through use-m without preserving the `code` property).
|
|
@@ -150,7 +173,10 @@ export const extractCorruptedFilePath = error => {
|
|
|
150
173
|
// extract the package.json path so the caller's cleanup() walks up to
|
|
151
174
|
// the alias dir.
|
|
152
175
|
const invalidConfigMatch = message.match(/Invalid package config (\S+?package\.json)/);
|
|
153
|
-
|
|
176
|
+
if (invalidConfigMatch) return invalidConfigMatch[1];
|
|
177
|
+
// Mode 5 (issue #2113): use-m already reports the whole alias directory.
|
|
178
|
+
const cleanupMatch = message.match(ALIAS_CLEANUP_ERROR);
|
|
179
|
+
return cleanupMatch ? cleanupMatch[1] : null;
|
|
154
180
|
};
|
|
155
181
|
|
|
156
182
|
/**
|
|
@@ -176,11 +202,15 @@ export const resolveAliasDir = corruptedPath => {
|
|
|
176
202
|
return segments.slice(0, -1).join('/') || corruptedPath;
|
|
177
203
|
};
|
|
178
204
|
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
205
|
+
export const removeAliasWithRetry = async (path, options = {}) => {
|
|
206
|
+
const remove = options.rm ?? (await import('node:fs/promises')).rm;
|
|
207
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
208
|
+
const retryDelay = options.retryDelay ?? 100;
|
|
209
|
+
await remove(path, { recursive: true, force: true, maxRetries, retryDelay });
|
|
182
210
|
};
|
|
183
211
|
|
|
212
|
+
const defaultCleanup = removeAliasWithRetry;
|
|
213
|
+
|
|
184
214
|
// Cache-busting import: a query string makes Node treat the URL as a distinct
|
|
185
215
|
// module, so the freshly reinstalled file is evaluated instead of the cached
|
|
186
216
|
// SyntaxError from the corrupt one.
|