dsh-enhancement-suite 0.1.1 → 0.1.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/bin/dsh-enhance.mjs +169 -14
- package/package.json +1 -1
package/bin/dsh-enhance.mjs
CHANGED
|
@@ -84,18 +84,30 @@ function findDsh() {
|
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** Quote one argument for a Windows cmd line (no shell interpolation). */
|
|
88
|
+
function quoteArg(arg) {
|
|
89
|
+
return '"' + String(arg).replace(/"/g, '\\"') + '"';
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
/**
|
|
88
93
|
* Spawn one subprocess with an argument array (never a shell string).
|
|
89
|
-
* On Windows
|
|
90
|
-
*
|
|
94
|
+
* On Windows a .cmd shim (dsh.cmd / pnpm.cmd) is executed through the
|
|
95
|
+
* shell; the command line is built here with per-argument quoting, so
|
|
96
|
+
* Node's `args + shell:true` deprecation (DEP0190) is not triggered.
|
|
91
97
|
*/
|
|
92
|
-
function run(bin, args, { capture = false } = {}) {
|
|
98
|
+
function run(bin, args, { capture = false, cwd } = {}) {
|
|
93
99
|
return new Promise((resolve) => {
|
|
94
|
-
const
|
|
95
|
-
shell: process.platform === "win32",
|
|
100
|
+
const opts = {
|
|
96
101
|
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
97
102
|
windowsHide: true,
|
|
98
|
-
|
|
103
|
+
...(cwd ? { cwd } : {}),
|
|
104
|
+
};
|
|
105
|
+
let child;
|
|
106
|
+
if (process.platform === "win32") {
|
|
107
|
+
child = spawn([bin, ...args].map(quoteArg).join(" "), { ...opts, shell: true });
|
|
108
|
+
} else {
|
|
109
|
+
child = spawn(bin, args, opts);
|
|
110
|
+
}
|
|
99
111
|
let stdout = "";
|
|
100
112
|
let stderr = "";
|
|
101
113
|
if (capture) {
|
|
@@ -127,6 +139,88 @@ function installedVersion(pkg, profile) {
|
|
|
127
139
|
return installedManifest(pkg, profile)?.version ?? null;
|
|
128
140
|
}
|
|
129
141
|
|
|
142
|
+
const dryRunMode = () => process.env.DSH_ENHANCE_DRY_RUN === "1";
|
|
143
|
+
|
|
144
|
+
/** Locate pnpm on PATH (pnpm.cmd / pnpm.exe / pnpm). */
|
|
145
|
+
function findPnpm() {
|
|
146
|
+
const names = process.platform === "win32" ? ["pnpm.cmd", "pnpm.exe", "pnpm"] : ["pnpm"];
|
|
147
|
+
const entries = (process.env.PATH ?? "").split(process.platform === "win32" ? ";" : ":").filter(Boolean);
|
|
148
|
+
for (const dir of entries) {
|
|
149
|
+
for (const name of names) {
|
|
150
|
+
const candidate = join(dir, name);
|
|
151
|
+
if (existsSync(candidate)) return candidate;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Latest published version of a package from the npm registry (no auth). */
|
|
158
|
+
async function latestVersion(pkg) {
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}`, {
|
|
161
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
162
|
+
signal: AbortSignal.timeout(15000),
|
|
163
|
+
});
|
|
164
|
+
if (!res.ok) return null;
|
|
165
|
+
const data = await res.json();
|
|
166
|
+
return (data && data["dist-tags"] && data["dist-tags"].latest) || null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function profileJson(profile) {
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(readFileSync(join(profileDir(profile), "package.json"), "utf8"));
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Set dependencies[pkg] to `^version` in the profile manifest so pnpm
|
|
182
|
+
* resolves the freshly published version (plain `pnpm update` keeps the
|
|
183
|
+
* lockfile resolution when the old version still satisfies the range).
|
|
184
|
+
* Returns true when the manifest would be / was changed.
|
|
185
|
+
*/
|
|
186
|
+
function bumpProfileRange(profile, pkg, version) {
|
|
187
|
+
const json = profileJson(profile);
|
|
188
|
+
if (!json || !json.dependencies || typeof json.dependencies[pkg] !== "string") return false;
|
|
189
|
+
const want = `^${version}`;
|
|
190
|
+
if (json.dependencies[pkg] === want) return false;
|
|
191
|
+
if (dryRunMode()) return true;
|
|
192
|
+
json.dependencies[pkg] = want;
|
|
193
|
+
writeFileSync(join(profileDir(profile), "package.json"), JSON.stringify(json, null, 2) + "\n", "utf8");
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Make sure `${pkg}@${version}` appears in pnpm-workspace.yaml's
|
|
199
|
+
* minimumReleaseAgeExclude (pnpm 11 blocks freshly published packages until
|
|
200
|
+
* they reach a minimum release age). Returns true when the file changed.
|
|
201
|
+
*/
|
|
202
|
+
function ensureReleaseAgeExclude(profile, pkg, version) {
|
|
203
|
+
const patch = join(profileDir(profile), "pnpm-workspace.yaml");
|
|
204
|
+
if (!existsSync(patch)) return false;
|
|
205
|
+
const entry = `${pkg}@${version}`;
|
|
206
|
+
const src = readFileSync(patch, "utf8");
|
|
207
|
+
if (src.includes(entry)) return false;
|
|
208
|
+
if (dryRunMode()) return true;
|
|
209
|
+
const lines = src.split("\n");
|
|
210
|
+
const idx = lines.findIndex((l) => l.trim() === "minimumReleaseAgeExclude:");
|
|
211
|
+
let next;
|
|
212
|
+
if (idx >= 0) {
|
|
213
|
+
let insertAt = idx + 1;
|
|
214
|
+
while (insertAt < lines.length && /^[ \t]+- /.test(lines[insertAt])) insertAt += 1;
|
|
215
|
+
lines.splice(insertAt, 0, ` - ${entry}`);
|
|
216
|
+
next = lines.join("\n");
|
|
217
|
+
} else {
|
|
218
|
+
next = src.replace(/\s*$/, "") + `\nminimumReleaseAgeExclude:\n - ${entry}\n`;
|
|
219
|
+
}
|
|
220
|
+
writeFileSync(patch, next, "utf8");
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
130
224
|
/** Bundle plugins are auto-mounted by their own dsh.bundle layer. */
|
|
131
225
|
function isBundlePackage(pkg, profile) {
|
|
132
226
|
const manifest = installedManifest(pkg, profile);
|
|
@@ -313,10 +407,21 @@ async function cmdInstall(profile, only, dryRun) {
|
|
|
313
407
|
const failures = [];
|
|
314
408
|
for (const p of plugins) {
|
|
315
409
|
info(`${p.emoji} ${p.name}`);
|
|
410
|
+
const latest = await latestVersion(p.name);
|
|
411
|
+
const installed = installedVersion(p.name, profile);
|
|
316
412
|
const addArgs = ["plugin", "--profile", profile, "add", p.name];
|
|
317
413
|
if (dryRun) {
|
|
414
|
+
ok(`npm latest: ${latest ?? "unknown (network?)"}; installed: ${installed ?? "not installed"}`);
|
|
415
|
+
if (latest && installed && installed !== latest) {
|
|
416
|
+
ok(`would bump range to ^${latest} (pnpm keeps stale lockfile versions otherwise)`);
|
|
417
|
+
ok(`would ensure minimumReleaseAgeExclude entry for ${p.name}@${latest}`);
|
|
418
|
+
}
|
|
318
419
|
ok(`would run: dsh ${addArgs.join(" ")}`);
|
|
319
420
|
} else {
|
|
421
|
+
if (latest && installed && installed !== latest) {
|
|
422
|
+
if (bumpProfileRange(profile, p.name, latest)) info(`range align: ^${installed} → ^${latest}`);
|
|
423
|
+
ensureReleaseAgeExclude(profile, p.name, latest);
|
|
424
|
+
}
|
|
320
425
|
const result = await run(dsh, addArgs);
|
|
321
426
|
if (result.code !== 0) {
|
|
322
427
|
const tail = (result.stderr || result.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
@@ -325,6 +430,25 @@ async function cmdInstall(profile, only, dryRun) {
|
|
|
325
430
|
continue;
|
|
326
431
|
}
|
|
327
432
|
ok(`installed via dsh plugin add`);
|
|
433
|
+
const after = installedVersion(p.name, profile);
|
|
434
|
+
if (latest && after !== latest) {
|
|
435
|
+
const pnpm = findPnpm();
|
|
436
|
+
if (!pnpm) {
|
|
437
|
+
bad(`${p.name}: still at ${after ?? "?"}, npm latest ${latest} — install pnpm and re-run`);
|
|
438
|
+
failures.push(`${p.name}: version mismatch (${after ?? "?"} ≠ ${latest})`);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
info(`installed ${after ?? "?"} ≠ latest ${latest} — running pnpm install as fallback`);
|
|
442
|
+
const pr = await run(pnpm, ["install"], { capture: true, cwd: profileDir(profile) });
|
|
443
|
+
const final = installedVersion(p.name, profile);
|
|
444
|
+
if (pr.code !== 0 || final !== latest) {
|
|
445
|
+
const tail = (pr.stderr || pr.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
446
|
+
bad(`${p.name}: still at ${final ?? "?"}, npm latest ${latest} (pnpm exit ${pr.code}): ${tail || "no output"}`);
|
|
447
|
+
failures.push(`${p.name}: version mismatch (${final ?? "?"} ≠ ${latest})`);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
ok(`pnpm install fallback: ${final}`);
|
|
451
|
+
}
|
|
328
452
|
}
|
|
329
453
|
const mount = ensureMounted(p.name, profile);
|
|
330
454
|
if (dryRun) {
|
|
@@ -365,21 +489,52 @@ async function cmdUpdate(profile, only, dryRun) {
|
|
|
365
489
|
info(`${p.emoji} ${p.name}: not installed — run "dsh-enhance install" first`);
|
|
366
490
|
continue;
|
|
367
491
|
}
|
|
492
|
+
const installed = installedVersion(p.name, profile);
|
|
493
|
+
info(`${p.emoji} ${p.name} (installed ${installed})`);
|
|
494
|
+
const latest = await latestVersion(p.name);
|
|
368
495
|
const args = ["plugin", "--profile", profile, "update", p.name];
|
|
369
496
|
if (dryRun) {
|
|
497
|
+
ok(`npm latest: ${latest ?? "unknown (network?)"}`);
|
|
498
|
+
if (latest && installed && installed !== latest) {
|
|
499
|
+
ok(`would bump range to ^${latest} (pnpm keeps stale lockfile versions otherwise)`);
|
|
500
|
+
ok(`would ensure minimumReleaseAgeExclude entry for ${p.name}@${latest}`);
|
|
501
|
+
}
|
|
370
502
|
ok(`would run: dsh ${args.join(" ")}`);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (latest && installed && installed !== latest) {
|
|
506
|
+
if (bumpProfileRange(profile, p.name, latest)) info(`range align: ^${installed} → ^${latest}`);
|
|
507
|
+
ensureReleaseAgeExclude(profile, p.name, latest);
|
|
508
|
+
}
|
|
509
|
+
const result = await run(dsh, args);
|
|
510
|
+
if (result.code !== 0) {
|
|
511
|
+
const tail = (result.stderr || result.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
512
|
+
bad(`${p.name} update failed (${result.code}): ${tail || result.error?.message || "unknown error"}`);
|
|
513
|
+
failures.push(`${p.name}: dsh plugin update exited ${result.code}`);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
let after = installedVersion(p.name, profile);
|
|
517
|
+
ok(`updated: ${after ?? "latest"}`);
|
|
518
|
+
if (latest && after !== latest) {
|
|
519
|
+
const pnpm = findPnpm();
|
|
520
|
+
if (!pnpm) {
|
|
521
|
+
bad(`${p.name}: still at ${after ?? "?"}, npm latest ${latest} — install pnpm and re-run`);
|
|
522
|
+
failures.push(`${p.name}: version mismatch (${after ?? "?"} ≠ ${latest})`);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
info(`installed ${after ?? "?"} ≠ latest ${latest} — running pnpm install as fallback`);
|
|
526
|
+
const pr = await run(pnpm, ["install"], { capture: true, cwd: profileDir(profile) });
|
|
527
|
+
after = installedVersion(p.name, profile);
|
|
528
|
+
if (pr.code !== 0 || after !== latest) {
|
|
529
|
+
const tail = (pr.stderr || pr.stdout || "").trim().split("\n").slice(-3).join(" ");
|
|
530
|
+
bad(`${p.name}: still at ${after ?? "?"}, npm latest ${latest} (pnpm exit ${pr.code}): ${tail || "no output"}`);
|
|
531
|
+
failures.push(`${p.name}: version mismatch (${after ?? "?"} ≠ ${latest})`);
|
|
377
532
|
continue;
|
|
378
533
|
}
|
|
379
|
-
ok(`
|
|
534
|
+
ok(`pnpm install fallback: ${after}`);
|
|
380
535
|
}
|
|
381
536
|
const mount = ensureMounted(p.name, profile);
|
|
382
|
-
if (
|
|
537
|
+
if (mount.changed) ok(mount.note);
|
|
383
538
|
}
|
|
384
539
|
head("");
|
|
385
540
|
if (failures.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-enhancement-suite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Enhancement suite for DeepSeek Harness — the official installer and registry for Scorp1o117's Vision, Soul/Persona, Memory & Marketplace plugins.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|