dsh-hot-reload 0.2.1 → 0.2.2
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 +19 -0
- package/README.md +1 -1
- package/lib/index.js +56 -5
- package/package.json +2 -2
- /package/{README.zh.md → README-zh.md} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to `dsh-hot-reload` are documented here. This project
|
|
4
4
|
follows [semantic versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## 0.2.2
|
|
7
|
+
|
|
8
|
+
Reloads now re-import a plugin's whole package, not just its entry module.
|
|
9
|
+
Config and API unchanged.
|
|
10
|
+
|
|
11
|
+
**Fixes**
|
|
12
|
+
|
|
13
|
+
- **Multi-file plugins reload as one unit.** The reload used to invalidate only
|
|
14
|
+
the entry URL and re-import it. Under a hoisted linker (`nodeLinker: hoisted`)
|
|
15
|
+
a version bump rewrites the package's files in place, so the entry's relative
|
|
16
|
+
imports (`./routes.js` and friends) resolved to the same URLs and hit the
|
|
17
|
+
stale cache — the running plugin mixed new entry code with old dependency code,
|
|
18
|
+
which for a plugin like `dshmarket` surfaced as a crash looking for a file the
|
|
19
|
+
new version had deleted. The reload now invalidates every cached module under
|
|
20
|
+
the package's own directory (in both the ESM `loadCache` and the CJS
|
|
21
|
+
`require.cache`) before re-importing, so the entry and its in-package imports
|
|
22
|
+
come back together. Shared dependencies live outside that directory and are
|
|
23
|
+
left alone.
|
|
24
|
+
|
|
6
25
|
## 0.2.1
|
|
7
26
|
|
|
8
27
|
Code-review fixes to the 0.2.0 notification surfaces. No config or API changes,
|
package/README.md
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// dsh-hot-reload — live-reload upgraded dsh plugins without restarting dsh.
|
|
2
2
|
//
|
|
3
3
|
// It watches the profile's pnpm-lock.yaml; when an already-loaded plugin
|
|
4
|
-
// package's version changes, it invalidates that
|
|
5
|
-
// the new code, and swaps the
|
|
4
|
+
// package's version changes, it invalidates that package's cached modules
|
|
5
|
+
// (entry plus in-package imports), re-imports the new code, and swaps the
|
|
6
|
+
// running plugin fiber in place — the same
|
|
6
7
|
// technique cordis-plugin-hmr uses, but reaching into node_modules (which HMR
|
|
7
8
|
// deliberately ignores).
|
|
8
9
|
//
|
|
@@ -29,8 +30,8 @@
|
|
|
29
30
|
import { watch } from "chokidar";
|
|
30
31
|
import { readFileSync, existsSync } from "node:fs";
|
|
31
32
|
import { createRequire } from "node:module";
|
|
32
|
-
import { fileURLToPath } from "node:url";
|
|
33
|
-
import { join } from "node:path";
|
|
33
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
34
|
+
import { dirname, join } from "node:path";
|
|
34
35
|
|
|
35
36
|
export const name = "dsh-hot-reload";
|
|
36
37
|
|
|
@@ -272,6 +273,49 @@ export function apply(ctx, config = {}) {
|
|
|
272
273
|
} catch {}
|
|
273
274
|
}
|
|
274
275
|
|
|
276
|
+
/** Directory, as a trailing-slash file:// URL, of the nearest ancestor that
|
|
277
|
+
* has a package.json — the package this module belongs to. A version bump
|
|
278
|
+
* rewrites every file inside it in place under a hoisted linker, so a reload
|
|
279
|
+
* must drop the whole directory, not just the entry. Returns null when no
|
|
280
|
+
* package.json is found (defensive; a node_modules package always has one). */
|
|
281
|
+
function packageRootUrlOf(url) {
|
|
282
|
+
let dir = dirname(fileURLToPath(url));
|
|
283
|
+
for (;;) {
|
|
284
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
285
|
+
const href = pathToFileURL(dir).href;
|
|
286
|
+
return href.endsWith("/") ? href : `${href}/`;
|
|
287
|
+
}
|
|
288
|
+
const parent = dirname(dir);
|
|
289
|
+
if (parent === dir) return null;
|
|
290
|
+
dir = parent;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Invalidate every cached module under one package directory — the entry and
|
|
295
|
+
* its transitive in-package imports. Re-importing only the entry would leave
|
|
296
|
+
* `./routes.js` and friends on their stale cache entries (the URLs are
|
|
297
|
+
* unchanged under a hoisted linker), so the running plugin would mix old and
|
|
298
|
+
* new code. Shared dependencies live OUTSIDE this directory and are left
|
|
299
|
+
* alone: re-importing them would be wasteful and would fork shared classes. */
|
|
300
|
+
function invalidateTree(rootUrl) {
|
|
301
|
+
if (!rootUrl) return;
|
|
302
|
+
// ESM loadCache.
|
|
303
|
+
try {
|
|
304
|
+
for (const u of Map.prototype.keys.call(internal.loadCache)) {
|
|
305
|
+
if (typeof u === "string" && u.startsWith(rootUrl)) {
|
|
306
|
+
Map.prototype.delete.call(internal.loadCache, u);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
} catch {}
|
|
310
|
+
// CJS: modules imported via import() also land in the require cache on Node 24.
|
|
311
|
+
try {
|
|
312
|
+
const rootPath = fileURLToPath(rootUrl);
|
|
313
|
+
for (const fp of Object.keys(cjsRequire.cache)) {
|
|
314
|
+
if (fp.startsWith(rootPath)) delete cjsRequire.cache[fp];
|
|
315
|
+
}
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
318
|
+
|
|
275
319
|
/** Reload one loaded entry's module in place. Throws on failure, after rolling
|
|
276
320
|
* the old plugin back — except when teardown began mid-reload, where it drops
|
|
277
321
|
* the new plugin and does NOT roll back (the dying context disposes what is
|
|
@@ -290,7 +334,14 @@ export function apply(ctx, config = {}) {
|
|
|
290
334
|
const url = await resolveUrl(specifier, parentURL);
|
|
291
335
|
if (!url) throw new Error(`could not resolve ${specifier}`);
|
|
292
336
|
|
|
293
|
-
|
|
337
|
+
// Drop the whole package directory, not just the entry: under a hoisted
|
|
338
|
+
// linker a version bump rewrites files in place (same URLs), so the entry's
|
|
339
|
+
// relative imports would otherwise re-hit the stale cache and mix old and
|
|
340
|
+
// new code. Falls back to the single-URL invalidation only when no
|
|
341
|
+
// package.json is found.
|
|
342
|
+
const rootUrl = packageRootUrlOf(url);
|
|
343
|
+
if (rootUrl) invalidateTree(rootUrl);
|
|
344
|
+
else invalidate(url);
|
|
294
345
|
|
|
295
346
|
// Read the version as close to the import as possible: this is what the
|
|
296
347
|
// fresh module actually is, and the only value safe to commit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hot-reload",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Live-reload upgraded DeepSeek Harness (dsh) plugins without restarting dsh \u2014 the running plugin is swapped in place, and a reload that fails rolls back to the working old version and asks for a manual restart.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"lib",
|
|
37
37
|
"cordis.patch.yml",
|
|
38
38
|
"README.md",
|
|
39
|
-
"README
|
|
39
|
+
"README-zh.md",
|
|
40
40
|
"CHANGELOG.md",
|
|
41
41
|
"LICENSE"
|
|
42
42
|
],
|
|
File without changes
|