@michengai/dsh-skills-manager 1.2.0 → 1.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 +12 -0
- package/CHANGELOG.zh-CN.md +12 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/client.js +5 -5
- package/lib/plugin-updater.js +171 -3
- package/package.json +12 -10
package/lib/plugin-updater.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -212,6 +212,162 @@ async function status(options, target) {
|
|
|
212
212
|
canAutoUpdate: target.canAutoUpdate
|
|
213
213
|
};
|
|
214
214
|
}
|
|
215
|
+
function releaseAgeSpec(packageSpec) {
|
|
216
|
+
const at = packageSpec.lastIndexOf("@");
|
|
217
|
+
if (at <= 0) return void 0;
|
|
218
|
+
const name = packageSpec.slice(0, at);
|
|
219
|
+
const version = packageSpec.slice(at + 1);
|
|
220
|
+
if (!isPackageName(name) || !isReleaseVersion(version)) return void 0;
|
|
221
|
+
return { name, version };
|
|
222
|
+
}
|
|
223
|
+
function isPackageName(name) {
|
|
224
|
+
return /^@[^/\s]+\/[^/\s]+$/.test(name) || /^[^@/\s]+$/.test(name);
|
|
225
|
+
}
|
|
226
|
+
function isReleaseVersion(version) {
|
|
227
|
+
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version);
|
|
228
|
+
}
|
|
229
|
+
function unreadableReleaseAgeExclude() {
|
|
230
|
+
return new Error("\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\u65E0\u6CD5\u5B89\u5168\u5408\u5E76\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\uFF0C\u907F\u514D\u8FD9\u4E2A profile \u4E4B\u540E\u65E0\u6CD5\u5B89\u88C5\u63D2\u4EF6\u3002");
|
|
231
|
+
}
|
|
232
|
+
function excludeEntry(indent, name, versions, comment = "") {
|
|
233
|
+
const entry = `${indent}- '${name}@${versions.join(" || ")}'`;
|
|
234
|
+
return comment === "" ? entry : `${entry} ${comment}`;
|
|
235
|
+
}
|
|
236
|
+
function parseExcludeLine(line) {
|
|
237
|
+
const dash = line.indexOf("-");
|
|
238
|
+
if (!/^\s+-/.test(line) || dash < 0) return void 0;
|
|
239
|
+
const indent = line.slice(0, dash);
|
|
240
|
+
const raw = line.slice(dash + 1).trim();
|
|
241
|
+
let body = raw;
|
|
242
|
+
let comment = "";
|
|
243
|
+
if (raw.startsWith("'") || raw.startsWith('"')) {
|
|
244
|
+
const quote = raw[0] ?? "'";
|
|
245
|
+
const end = raw.lastIndexOf(quote);
|
|
246
|
+
if (end <= 0) return void 0;
|
|
247
|
+
const rest = raw.slice(end + 1).trim();
|
|
248
|
+
if (rest !== "" && !rest.startsWith("#")) return void 0;
|
|
249
|
+
if (rest.startsWith("#")) comment = rest;
|
|
250
|
+
body = raw.slice(1, end);
|
|
251
|
+
} else if (raw.includes("#")) return void 0;
|
|
252
|
+
if (body.includes("#")) return void 0;
|
|
253
|
+
const versionAt = body.lastIndexOf("@");
|
|
254
|
+
if (versionAt > 0) {
|
|
255
|
+
const name = body.slice(0, versionAt);
|
|
256
|
+
const versions = body.slice(versionAt + 1).split(" || ");
|
|
257
|
+
if (isPackageName(name) && versions.every(isReleaseVersion)) return { indent, name, versions, comment };
|
|
258
|
+
return void 0;
|
|
259
|
+
}
|
|
260
|
+
if (isPackageName(body) || /^@[^/\s]+\/\*$/.test(body)) return { indent, name: body, versions: void 0, comment };
|
|
261
|
+
return void 0;
|
|
262
|
+
}
|
|
263
|
+
function mergeMinimumReleaseAgeExclude(yaml, packageSpec) {
|
|
264
|
+
const spec = releaseAgeSpec(packageSpec);
|
|
265
|
+
if (spec === void 0) throw new Error("\u66F4\u65B0\u7248\u672C\u65E0\u6CD5\u5199\u5165\u53D1\u5E03\u5E74\u9F84\u767D\u540D\u5355\uFF0C\u5DF2\u505C\u6B62\u66F4\u65B0\u3002");
|
|
266
|
+
const eol = yaml.includes("\r\n") ? "\r\n" : "\n";
|
|
267
|
+
const text = yaml.replaceAll("\r\n", "\n");
|
|
268
|
+
const next = mergeReleaseAgeText(text, spec.name, spec.version);
|
|
269
|
+
return eol === "\r\n" ? next.replaceAll("\n", "\r\n") : next;
|
|
270
|
+
}
|
|
271
|
+
function mergeReleaseAgeText(yaml, name, version) {
|
|
272
|
+
if (/minimumReleaseAgeExclude:[^\n\r]/.test(yaml)) throw unreadableReleaseAgeExclude();
|
|
273
|
+
const marker = /(?:^|\n)minimumReleaseAgeExclude:[ \t]*(?:\n|$)/.exec(yaml);
|
|
274
|
+
if (marker === null) {
|
|
275
|
+
const base = yaml.endsWith("\n") || yaml.length === 0 ? yaml : `${yaml}
|
|
276
|
+
`;
|
|
277
|
+
const gap = base.length === 0 || base.endsWith("\n\n") ? "" : "\n";
|
|
278
|
+
return `${base}${gap}minimumReleaseAgeExclude:
|
|
279
|
+
- '${name}@${version}'
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
const keyAt = (marker.index ?? 0) + (marker[0].startsWith("\n") ? 1 : 0);
|
|
283
|
+
let cursor = keyAt + "minimumReleaseAgeExclude:".length;
|
|
284
|
+
while (cursor < yaml.length && (yaml[cursor] === " " || yaml[cursor] === " ")) cursor += 1;
|
|
285
|
+
if (yaml[cursor] !== "\n") throw unreadableReleaseAgeExclude();
|
|
286
|
+
const bodyStart = cursor + 1;
|
|
287
|
+
const lines = [];
|
|
288
|
+
let bodyEnd = bodyStart;
|
|
289
|
+
while (bodyEnd <= yaml.length) {
|
|
290
|
+
const nextNl = yaml.indexOf("\n", bodyEnd);
|
|
291
|
+
const lineEnd = nextNl === -1 ? yaml.length : nextNl;
|
|
292
|
+
const line = yaml.slice(bodyEnd, lineEnd);
|
|
293
|
+
const ended = line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]/.test(line);
|
|
294
|
+
if (ended) break;
|
|
295
|
+
if (line.trim() !== "" && !/^[ \t]*#/.test(line) && !/^[ \t]+-/.test(line)) throw unreadableReleaseAgeExclude();
|
|
296
|
+
lines.push(line);
|
|
297
|
+
if (nextNl === -1) {
|
|
298
|
+
bodyEnd = yaml.length;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
bodyEnd = nextNl + 1;
|
|
302
|
+
if (bodyEnd === yaml.length) break;
|
|
303
|
+
}
|
|
304
|
+
let ours;
|
|
305
|
+
const rewritten = lines.map((line, index) => {
|
|
306
|
+
if (line.trim() === "" || /^[ \t]*#/.test(line)) return line;
|
|
307
|
+
const parsed = parseExcludeLine(line);
|
|
308
|
+
if (parsed === void 0) throw unreadableReleaseAgeExclude();
|
|
309
|
+
if (parsed.name !== name) return line;
|
|
310
|
+
if (ours === void 0) {
|
|
311
|
+
ours = { index, indent: parsed.indent, versions: parsed.versions, comment: parsed.comment };
|
|
312
|
+
return line;
|
|
313
|
+
}
|
|
314
|
+
if (ours.versions !== void 0 && parsed.versions !== void 0) ours.versions = [...ours.versions, ...parsed.versions.filter((item) => !ours?.versions?.includes(item))];
|
|
315
|
+
return "";
|
|
316
|
+
});
|
|
317
|
+
if (ours?.versions === void 0 && ours !== void 0) return yaml;
|
|
318
|
+
const indent = ours?.indent ?? lines.find((line) => /^[ \t]+-/.test(line))?.match(/^([ \t]+)/)?.[1] ?? " ";
|
|
319
|
+
const versions = ours?.versions === void 0 ? [version] : ours.versions.includes(version) ? ours.versions : [...ours.versions, version];
|
|
320
|
+
const entry = excludeEntry(indent, name, versions, ours?.comment ?? "");
|
|
321
|
+
if (ours === void 0) rewritten.push(entry);
|
|
322
|
+
else rewritten[ours.index] = entry;
|
|
323
|
+
const body = rewritten.filter((line) => line !== "").join("\n");
|
|
324
|
+
const prefix = yaml.slice(0, bodyStart);
|
|
325
|
+
const suffix = yaml.slice(bodyEnd);
|
|
326
|
+
const joined = `${prefix}${body}${body.length === 0 || body.endsWith("\n") ? "" : "\n"}${suffix}`;
|
|
327
|
+
return joined.endsWith("\n") ? joined : `${joined}
|
|
328
|
+
`;
|
|
329
|
+
}
|
|
330
|
+
async function guardReleaseAgeExclude(profileDir, packageSpec) {
|
|
331
|
+
const file = resolve(profileDir, "pnpm-workspace.yaml");
|
|
332
|
+
const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
|
|
333
|
+
mergeMinimumReleaseAgeExclude(current, packageSpec);
|
|
334
|
+
}
|
|
335
|
+
async function replaceTextFile(file, next) {
|
|
336
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
337
|
+
const backup = `${file}.${process.pid}.bak`;
|
|
338
|
+
await writeFile(tmp, next, "utf8");
|
|
339
|
+
try {
|
|
340
|
+
await rename(tmp, file);
|
|
341
|
+
return;
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
344
|
+
if (code !== "EEXIST" && code !== "EPERM" && code !== "EACCES") {
|
|
345
|
+
await unlink(tmp).catch(() => {
|
|
346
|
+
});
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
await unlink(backup).catch(() => {
|
|
351
|
+
});
|
|
352
|
+
await rename(file, backup);
|
|
353
|
+
try {
|
|
354
|
+
await rename(tmp, file);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
await rename(backup, file).catch(() => {
|
|
357
|
+
});
|
|
358
|
+
await unlink(tmp).catch(() => {
|
|
359
|
+
});
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
await unlink(backup).catch(() => {
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async function recordReleaseAgeExclude(profileDir, packageSpec) {
|
|
366
|
+
const file = resolve(profileDir, "pnpm-workspace.yaml");
|
|
367
|
+
const current = existsSync(file) ? await readFile(file, "utf8") : "packages:\n - .\n";
|
|
368
|
+
const next = mergeMinimumReleaseAgeExclude(current, packageSpec);
|
|
369
|
+
if (next !== current) await replaceTextFile(file, next);
|
|
370
|
+
}
|
|
215
371
|
async function runCliInstall(target, packageSpec) {
|
|
216
372
|
if (target.cliEntry === void 0) throw new Error("\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002");
|
|
217
373
|
await new Promise((resolvePromise, reject) => {
|
|
@@ -245,12 +401,15 @@ async function runCliInstall(target, packageSpec) {
|
|
|
245
401
|
}
|
|
246
402
|
async function install(target, packageSpec) {
|
|
247
403
|
if (target.desktopPnpm !== void 0) {
|
|
404
|
+
await guardReleaseAgeExclude(target.profileDir, packageSpec);
|
|
248
405
|
const handle = target.desktopPnpm.runPlugin(["add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], target.profileDir);
|
|
249
406
|
const result = await handle.done;
|
|
250
407
|
if (result.exitCode !== 0) throw new Error(`\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(result.exitCode)}\u3002`);
|
|
408
|
+
await recordReleaseAgeExclude(target.profileDir, packageSpec);
|
|
251
409
|
return;
|
|
252
410
|
}
|
|
253
411
|
if (target.packageManager !== void 0) {
|
|
412
|
+
await guardReleaseAgeExclude(target.profileDir, packageSpec);
|
|
254
413
|
const manager = target.packageManager;
|
|
255
414
|
await new Promise((resolvePromise, reject) => {
|
|
256
415
|
const child = spawn(manager.command, [...manager.args, "add", "--config.minimumReleaseAge=0", packageSpec, "--registry=https://registry.npmjs.org/"], {
|
|
@@ -280,14 +439,21 @@ async function install(target, packageSpec) {
|
|
|
280
439
|
else reject(new Error(detail.trim() || `\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${String(code)}`));
|
|
281
440
|
});
|
|
282
441
|
});
|
|
442
|
+
await recordReleaseAgeExclude(target.profileDir, packageSpec);
|
|
283
443
|
return;
|
|
284
444
|
}
|
|
285
445
|
if (target.pluginManager !== void 0) {
|
|
446
|
+
await guardReleaseAgeExclude(target.profileDir, packageSpec);
|
|
286
447
|
const result = await target.pluginManager.installBundle(packageSpec);
|
|
287
|
-
if (result.application === "applied" || result.application === "restart-required" || result.application === "overridden")
|
|
448
|
+
if (result.application === "applied" || result.application === "restart-required" || result.application === "overridden") {
|
|
449
|
+
await recordReleaseAgeExclude(target.profileDir, packageSpec);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
288
452
|
throw new Error(result.error?.message || result.packageResult?.output || "\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u670D\u52A1\u7AEF\u65E5\u5FD7\u3002");
|
|
289
453
|
}
|
|
290
|
-
|
|
454
|
+
await guardReleaseAgeExclude(target.profileDir, packageSpec);
|
|
455
|
+
await runCliInstall(target, packageSpec);
|
|
456
|
+
await recordReleaseAgeExclude(target.profileDir, packageSpec);
|
|
291
457
|
}
|
|
292
458
|
function json(response, statusCode, value) {
|
|
293
459
|
response.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -360,6 +526,8 @@ export {
|
|
|
360
526
|
isDshCliEntry,
|
|
361
527
|
isNewerVersion,
|
|
362
528
|
isTrustedUpdateRequest,
|
|
529
|
+
mergeMinimumReleaseAgeExclude,
|
|
530
|
+
recordReleaseAgeExclude,
|
|
363
531
|
registerPluginUpdater,
|
|
364
532
|
resolveUpdateRuntime,
|
|
365
533
|
shouldNotifyParent
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -35,14 +35,15 @@
|
|
|
35
35
|
"client": {
|
|
36
36
|
"inject": [
|
|
37
37
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
38
|
-
"@deepseek-ai/dsh-client-locale"
|
|
38
|
+
"@deepseek-ai/dsh-client-locale",
|
|
39
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
39
40
|
],
|
|
40
41
|
"platform": "web"
|
|
41
42
|
}
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build": "node scripts/build.mjs",
|
|
45
|
-
"test": "npm run typecheck && npm run build && node test/build-test.mjs && node test/route-lifecycle.test.mjs && node test/repositories.test.mjs && node test/repository-install-targets.test.mjs && node test/workspace-target.test.mjs && node test/workspace-routes.test.mjs && node test/workspace-list.test.mjs && node test/destination-picker.test.mjs && node test/workspace-project-page.test.mjs && node test/repository-ui.test.mjs && node test/catalog-skill.test.mjs && node test/skills-sh-catalog.test.mjs && node test/clawhub-catalog.test.mjs && node --test test/catalog-state.test.mjs test/catalog-lifecycle.test.mjs && node test/theme-select.test.mjs && node --test test/antd-theme.browser.test.mjs && node test/host-fallback.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/disabled-external-native.test.mjs && node test/categories.test.mjs && node test/categories-ui.test.mjs && node --experimental-strip-types --test test/settings-nav-icon.test.mjs && node test/readonly-discovery.test.mjs && node test/project-root-skills.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
|
|
46
|
+
"test": "node scripts/check-committed-lib.mjs && npm run typecheck && npm run build && node test/build-test.mjs && node test/route-lifecycle.test.mjs && node test/repositories.test.mjs && node test/repository-install-targets.test.mjs && node test/workspace-target.test.mjs && node test/workspace-routes.test.mjs && node test/workspace-list.test.mjs && node test/destination-picker.test.mjs && node test/workspace-project-page.test.mjs && node test/repository-ui.test.mjs && node test/catalog-skill.test.mjs && node test/skills-sh-catalog.test.mjs && node test/clawhub-catalog.test.mjs && node --test test/catalog-state.test.mjs test/catalog-lifecycle.test.mjs && node test/theme-select.test.mjs && node --test test/antd-theme.browser.test.mjs && node test/host-fallback.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/disabled-external-native.test.mjs && node test/categories.test.mjs && node test/categories-ui.test.mjs && node --experimental-strip-types --test test/settings-nav-icon.test.mjs && node test/readonly-discovery.test.mjs && node test/project-root-skills.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
|
|
46
47
|
"verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node test/pack-test.mjs",
|
|
47
48
|
"pack:check": "npm pack --dry-run --json",
|
|
48
49
|
"prepack": "npm run typecheck && npm run build",
|
|
@@ -56,13 +57,14 @@
|
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|
|
58
59
|
"@deepseek-ai/cordis": ">=4.0.1 <5.0.0",
|
|
59
|
-
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
60
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-skill": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-web-app": "0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1",
|
|
66
68
|
"react": "^18.2.0",
|
|
67
69
|
"react-dom": "^18.2.0"
|
|
68
70
|
},
|