dsh-multi-router 0.3.8 → 0.4.1
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 +13 -0
- package/README.md +55 -5
- package/lib/{chunk-WIDXMBQ2.js → chunk-DZYRIZW2.js} +469 -29
- package/lib/chunk-DZYRIZW2.js.map +7 -0
- package/lib/client.js +228 -8
- package/lib/client.js.map +2 -2
- package/lib/host.js +4 -2
- package/lib/index.js +4 -2
- package/lib/index.js.map +2 -2
- package/lib/manager.js +1 -1
- package/package.json +21 -13
- package/lib/chunk-WIDXMBQ2.js.map +0 -7
|
@@ -34,13 +34,13 @@ import {
|
|
|
34
34
|
import { createHash, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
35
35
|
|
|
36
36
|
// package.json
|
|
37
|
-
var version = "0.
|
|
37
|
+
var version = "0.4.1";
|
|
38
38
|
|
|
39
39
|
// src/manager.mjs
|
|
40
|
-
import { readFile, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as
|
|
40
|
+
import { readFile as readFile2, mkdir as mkdir2, mkdtemp as mkdtemp2, rm as rm3 } from "node:fs/promises";
|
|
41
41
|
import { watch } from "node:fs";
|
|
42
|
-
import { dirname as dirname3, join as
|
|
43
|
-
import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
42
|
+
import { dirname as dirname3, join as join4, relative as relative2, resolve } from "node:path";
|
|
43
|
+
import { withFileLock as withFileLock2 } from "@deepseek-ai/dsh-atomic-write";
|
|
44
44
|
|
|
45
45
|
// upstream/qoder/src/status-paths.ts
|
|
46
46
|
var QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
|
|
@@ -233,6 +233,400 @@ async function prepareMigration(manager, raw, portable, mode) {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
// src/plugin-updates.mjs
|
|
237
|
+
import { readFile, stat, rm as rm2 } from "node:fs/promises";
|
|
238
|
+
import { join as join3 } from "node:path";
|
|
239
|
+
import { randomUUID } from "node:crypto";
|
|
240
|
+
import { createRequire } from "node:module";
|
|
241
|
+
import { pathToFileURL } from "node:url";
|
|
242
|
+
import { parse as parseYaml } from "yaml";
|
|
243
|
+
import { withFileLock, writeFileAtomic as writeFileAtomic2 } from "@deepseek-ai/dsh-atomic-write";
|
|
244
|
+
|
|
245
|
+
// upstream/workbuddy/src/update.ts
|
|
246
|
+
var WORKBUDDY_REPOSITORY_URL = "https://github.com/corrinehu/dsh-workbuddy-connect";
|
|
247
|
+
var WORKBUDDY_UPDATE_PACKAGE_NAME = "dsh-workbuddy-connect";
|
|
248
|
+
var WORKBUDDY_UPDATE_NPM_METADATA_URL = `https://registry.npmjs.org/-/package/${WORKBUDDY_UPDATE_PACKAGE_NAME}/dist-tags`;
|
|
249
|
+
var WORKBUDDY_UPDATE_RELEASES_API_URL = `${WORKBUDDY_REPOSITORY_URL.replace("https://github.com", "https://api.github.com/repos")}/releases?per_page=100`;
|
|
250
|
+
var WORKBUDDY_RELEASE_PAGE_BASE = `${WORKBUDDY_REPOSITORY_URL}/releases/tag/`;
|
|
251
|
+
var WORKBUDDY_UPDATE_MAX_METADATA_BYTES = 64 * 1024;
|
|
252
|
+
var WORKBUDDY_UPDATE_MAX_RELEASES_BYTES = 1024 * 1024;
|
|
253
|
+
function parseWorkBuddyVersion(raw) {
|
|
254
|
+
if (typeof raw !== "string") return void 0;
|
|
255
|
+
const normalized = raw.startsWith("v") ? raw.slice(1) : raw;
|
|
256
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.exec(normalized);
|
|
257
|
+
if (match === null) return void 0;
|
|
258
|
+
const rawPrerelease = match[4] === void 0 ? [] : match[4].split(".");
|
|
259
|
+
if (rawPrerelease.some((identifier) => /^\d+$/u.test(identifier) && !/^(0|[1-9]\d*)$/u.test(identifier))) return void 0;
|
|
260
|
+
const prerelease = rawPrerelease.map((identifier) => /^(0|[1-9]\d*)$/u.test(identifier) ? Number(identifier) : identifier);
|
|
261
|
+
if (prerelease.some((identifier) => typeof identifier === "number" && !Number.isSafeInteger(identifier))) return void 0;
|
|
262
|
+
const parsed = {
|
|
263
|
+
major: Number(match[1]),
|
|
264
|
+
minor: Number(match[2]),
|
|
265
|
+
patch: Number(match[3]),
|
|
266
|
+
prerelease
|
|
267
|
+
};
|
|
268
|
+
return [parsed.major, parsed.minor, parsed.patch].every(Number.isSafeInteger) ? parsed : void 0;
|
|
269
|
+
}
|
|
270
|
+
function compareIdentifiers(left, right) {
|
|
271
|
+
if (typeof left === "number" && typeof right === "number") return left < right ? -1 : left > right ? 1 : 0;
|
|
272
|
+
if (typeof left === "number") return -1;
|
|
273
|
+
if (typeof right === "number") return 1;
|
|
274
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
275
|
+
}
|
|
276
|
+
function compareWorkBuddyVersions(left, right) {
|
|
277
|
+
const a = parseWorkBuddyVersion(left);
|
|
278
|
+
const b = parseWorkBuddyVersion(right);
|
|
279
|
+
if (a === void 0 || b === void 0) throw new TypeError("invalid WorkBuddy Connect version");
|
|
280
|
+
for (const [aPart, bPart] of [[a.major, b.major], [a.minor, b.minor], [a.patch, b.patch]]) {
|
|
281
|
+
if (aPart !== bPart) return aPart < bPart ? -1 : 1;
|
|
282
|
+
}
|
|
283
|
+
if (a.prerelease.length === 0 && b.prerelease.length !== 0) return 1;
|
|
284
|
+
if (a.prerelease.length !== 0 && b.prerelease.length === 0) return -1;
|
|
285
|
+
for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
|
|
286
|
+
const aPart = a.prerelease[index];
|
|
287
|
+
const bPart = b.prerelease[index];
|
|
288
|
+
if (aPart === void 0) return -1;
|
|
289
|
+
if (bPart === void 0) return 1;
|
|
290
|
+
const comparison = compareIdentifiers(aPart, bPart);
|
|
291
|
+
if (comparison !== 0) return comparison;
|
|
292
|
+
}
|
|
293
|
+
return 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/plugin-updates.mjs
|
|
297
|
+
var packageName = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
298
|
+
var safeRef = /^[a-zA-Z0-9._/-]{1,200}$/;
|
|
299
|
+
function parsePluginSource(name, spec) {
|
|
300
|
+
if (!packageName.test(name) || typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || spec.trim() !== spec || /[\x00-\x1f]/.test(spec)) return { kind: "unsupported" };
|
|
301
|
+
const git = /^(?:github:|git\+https:\/\/github\.com\/|https:\/\/github\.com\/|git\+ssh:\/\/git@github\.com\/|git@github\.com:)?([\w.-]+)\/([\w.-]+?)(?:\.git)?(?:#(.+))?$/.exec(spec);
|
|
302
|
+
if (git && !spec.startsWith("@") && !spec.startsWith("file:") && !spec.startsWith("link:")) {
|
|
303
|
+
const [, owner, repo, ref] = git;
|
|
304
|
+
if (owner === "." || owner === ".." || repo === "." || repo === ".." || ref && (!safeRef.test(ref) || ref.includes(".."))) return { kind: "unsupported" };
|
|
305
|
+
return { kind: "github", owner, repo, ref, source: `github:${owner}/${repo}${ref ? "#" + ref : ""}` };
|
|
306
|
+
}
|
|
307
|
+
if (/^(?:file:|link:|workspace:|https?:|git|\/|\.)/.test(spec)) return { kind: "unsupported" };
|
|
308
|
+
const alias = /^npm:((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
|
|
309
|
+
const actual = alias?.[1] ?? name, range = alias?.[2] ?? spec;
|
|
310
|
+
if (!packageName.test(actual) || !/^[a-zA-Z0-9.*^~<>=|+ -]{1,160}$/.test(range)) return { kind: "unsupported" };
|
|
311
|
+
const channel = /^[a-zA-Z][\w.-]*$/.test(range);
|
|
312
|
+
return { kind: "npm", package: actual, alias: !!alias, channel, tag: channel ? range : "latest", source: spec };
|
|
313
|
+
}
|
|
314
|
+
function githubCommit(value) {
|
|
315
|
+
if (typeof value !== "string") return void 0;
|
|
316
|
+
return /(?:\/tar\.gz\/|\/tarball\/|#)([a-f0-9]{40})(?:\b|\()/i.exec(value)?.[1]?.toLowerCase();
|
|
317
|
+
}
|
|
318
|
+
async function jsonFile(file) {
|
|
319
|
+
try {
|
|
320
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
321
|
+
} catch {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async function locks(dir) {
|
|
326
|
+
let pnpm;
|
|
327
|
+
try {
|
|
328
|
+
pnpm = parseYaml(await readFile(join3(dir, "pnpm-lock.yaml"), "utf8"), { maxAliasCount: 20 });
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
const npm = await jsonFile(join3(dir, "package-lock.json"));
|
|
332
|
+
const times = await Promise.all(["pnpm-lock.yaml", "package-lock.json"].map((name) => stat(join3(dir, name)).then((s) => s.mtimeMs).catch(() => 0)));
|
|
333
|
+
return { pnpm, npm, preferNpm: times[1] > times[0] };
|
|
334
|
+
}
|
|
335
|
+
async function publicJson(url, fetchImpl, signal) {
|
|
336
|
+
const res = await fetchImpl(url, {
|
|
337
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(12e3)]),
|
|
338
|
+
headers: { Accept: "application/json", "User-Agent": "dsh-multi-router-plugin-updates" },
|
|
339
|
+
redirect: "error"
|
|
340
|
+
});
|
|
341
|
+
if (!res.ok) throw Error(res.status === 403 || res.status === 429 ? "\u8FDC\u7AEF\u9650\u6D41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" : res.status === 404 ? "\u4ED3\u5E93 / \u7248\u672C\u4E0D\u5B58\u5728\uFF0C\u6216\u9700\u8981\u79C1\u6709\u4ED3\u5E93\u6388\u6743" : `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF08HTTP ${res.status}\uFF09`);
|
|
342
|
+
let size = 0, chunks = [];
|
|
343
|
+
for await (const chunk of res.body) {
|
|
344
|
+
size += chunk.byteLength;
|
|
345
|
+
if (size > 256 * 1024) throw Error("\u66F4\u65B0\u5143\u6570\u636E\u8FC7\u5927");
|
|
346
|
+
chunks.push(Buffer.from(chunk));
|
|
347
|
+
}
|
|
348
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
349
|
+
}
|
|
350
|
+
var short = (sha) => sha?.slice(0, 8);
|
|
351
|
+
async function registryVersion(service, profile, spec, signal) {
|
|
352
|
+
const require2 = createRequire(profile.installAnchor);
|
|
353
|
+
const { viewProfilePackage } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
|
|
354
|
+
const { registryPlan } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
|
|
355
|
+
for (const registry of registryPlan(void 0, await service.registries())) {
|
|
356
|
+
const answer = await viewProfilePackage(profile.dir, spec, { ...profile.packageManager, registry, timeoutMs: 12e3, signal });
|
|
357
|
+
if (signal.aborted) throw Error("\u68C0\u67E5\u5DF2\u53D6\u6D88");
|
|
358
|
+
if (answer.exitCode !== 0) continue;
|
|
359
|
+
const parsed = JSON.parse(answer.stdout), metadata = Array.isArray(parsed) ? parsed.at(-1) : parsed;
|
|
360
|
+
if (metadata?.version) return metadata;
|
|
361
|
+
}
|
|
362
|
+
throw Error("npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7F51\u7EDC\u3001registry \u8BBE\u7F6E\u6216\u5305\u7684\u8BBF\u95EE\u6743\u9650");
|
|
363
|
+
}
|
|
364
|
+
async function refreshGitPlugin(service, profile, row, options2, signal) {
|
|
365
|
+
const require2 = createRequire(profile.installAnchor);
|
|
366
|
+
const { runProfilePnpm } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/operations")).href);
|
|
367
|
+
const { registryPlan, attributeFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager/registry")).href);
|
|
368
|
+
const { classifyInstallFailure } = await import(pathToFileURL(require2.resolve("@deepseek-ai/dsh-plugin-manager")).href);
|
|
369
|
+
const plan = registryPlan(options2.registry, await service.registries());
|
|
370
|
+
return withFileLock(join3(profile.dir, "package.json"), async () => {
|
|
371
|
+
const files = await Promise.all(["package.json", "pnpm-lock.yaml"].map(async (name) => {
|
|
372
|
+
const path = join3(profile.dir, name);
|
|
373
|
+
try {
|
|
374
|
+
return { path, content: await readFile(path, "utf8") };
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if (e.code !== "ENOENT") throw e;
|
|
377
|
+
return { path };
|
|
378
|
+
}
|
|
379
|
+
}));
|
|
380
|
+
const restore = () => Promise.all(files.map((f) => f.content === void 0 ? rm2(f.path, { force: true }) : writeFileAtomic2(f.path, f.content, { mode: 384 })));
|
|
381
|
+
if (JSON.parse(files[0].content).dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
382
|
+
let result;
|
|
383
|
+
try {
|
|
384
|
+
for (const registry of plan) {
|
|
385
|
+
signal.throwIfAborted();
|
|
386
|
+
const run = await runProfilePnpm({ ...profile, profile: profile.name }, ["update", row.name, ...registry ? [`--registry=${registry}`] : []], {
|
|
387
|
+
execution: "service",
|
|
388
|
+
...profile.packageManager,
|
|
389
|
+
signal,
|
|
390
|
+
outputBytes: 16384,
|
|
391
|
+
idleTimeoutMs: 6e5,
|
|
392
|
+
lookupTimeoutMs: 2e4,
|
|
393
|
+
activateNewBundles: false
|
|
394
|
+
});
|
|
395
|
+
if (run.exitCode === 0 && !run.timedOut && !signal.aborted) return { packageResult: run, application: "restart-required" };
|
|
396
|
+
const kind = classifyInstallFailure({ log: run.output, timedOut: run.timedOut });
|
|
397
|
+
result = {
|
|
398
|
+
packageResult: { ...run, kind },
|
|
399
|
+
application: signal.aborted ? "cancelled" : "failed",
|
|
400
|
+
...run.incompatible?.length ? { error: { code: "incompatible-version" } } : {}
|
|
401
|
+
};
|
|
402
|
+
await restore();
|
|
403
|
+
if (signal.aborted || attributeFailure(kind, run.output, { kind: "git", host: "github.com" }) !== "registry") break;
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
} catch (e) {
|
|
407
|
+
await restore();
|
|
408
|
+
throw e;
|
|
409
|
+
}
|
|
410
|
+
}, { waitMs: 12e4 });
|
|
411
|
+
}
|
|
412
|
+
var PluginUpdates = class {
|
|
413
|
+
constructor({ service = () => void 0, profile = () => void 0, fetchImpl = (...args) => fetch(...args), lookup = registryVersion, refreshGit = refreshGitPlugin } = {}) {
|
|
414
|
+
this.service = service;
|
|
415
|
+
this.profile = profile;
|
|
416
|
+
this.fetchImpl = fetchImpl;
|
|
417
|
+
this.rows = [];
|
|
418
|
+
this.abort = new AbortController();
|
|
419
|
+
this.lookup = lookup;
|
|
420
|
+
this.refreshGit = refreshGit;
|
|
421
|
+
}
|
|
422
|
+
state() {
|
|
423
|
+
return {
|
|
424
|
+
available: !!this.service()?.listBundles && !!this.profile()?.dir,
|
|
425
|
+
checkedAt: this.checkedAt,
|
|
426
|
+
rows: this.rows.map(({ spec, ...row }) => ({ ...row, source: row.source.kind === "unsupported" ? { kind: "unsupported" } : row.source })),
|
|
427
|
+
job: this.job,
|
|
428
|
+
error: this.error
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async list() {
|
|
432
|
+
const service = this.service(), profile = this.profile();
|
|
433
|
+
if (!service?.listBundles || !profile?.dir) {
|
|
434
|
+
this.rows = [];
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const [manifest, bundles, lock] = await Promise.all([jsonFile(join3(profile.dir, "package.json")), service.listBundles(), locks(profile.dir)]);
|
|
438
|
+
if (!manifest) throw Error("\u5F53\u524D DSH \u914D\u7F6E\u7684\u63D2\u4EF6\u6E05\u5355\u65E0\u6CD5\u8BFB\u53D6");
|
|
439
|
+
this.rows = bundles.filter((b) => b.installed || b.version).map((b) => {
|
|
440
|
+
const spec = manifest.dependencies?.[b.name];
|
|
441
|
+
const source = !spec || b.removable === false && !b.readOnlyReason ? { kind: "builtin" } : parsePluginSource(b.name, spec);
|
|
442
|
+
const npm = lock.npm?.packages?.[`node_modules/${b.name}`]?.resolved;
|
|
443
|
+
const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[b.name]?.version;
|
|
444
|
+
const installed = lock.preferNpm ? npm ?? pnpm : pnpm ?? npm;
|
|
445
|
+
const previous = this.rows.find((r) => r.name === b.name && r.spec === spec && r.version === b.version && r.commit === githubCommit(installed));
|
|
446
|
+
return {
|
|
447
|
+
...previous,
|
|
448
|
+
name: b.name,
|
|
449
|
+
version: b.version,
|
|
450
|
+
enabled: b.enabled,
|
|
451
|
+
spec,
|
|
452
|
+
source,
|
|
453
|
+
commit: githubCommit(installed),
|
|
454
|
+
status: previous?.status ?? (source.kind === "builtin" ? "builtin" : source.kind === "unsupported" ? "unsupported" : "unchecked"),
|
|
455
|
+
...source.kind === "builtin" ? { message: "DSH \u81EA\u5E26 / \u53D7\u4FDD\u62A4\u7EC4\u4EF6\uFF0C\u8BF7\u901A\u8FC7\u66F4\u65B0 DeepSeek Harness \u5347\u7EA7" } : {},
|
|
456
|
+
...source.kind === "unsupported" ? { message: "\u672C\u5730\u6587\u4EF6 / \u94FE\u63A5\u6765\u6E90\uFF0C\u8BF7\u901A\u8FC7\u539F\u5B89\u88C5\u65B9\u5F0F\u66F4\u65B0" } : {}
|
|
457
|
+
};
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async checkRow(row) {
|
|
461
|
+
try {
|
|
462
|
+
if (row.source.kind === "npm") {
|
|
463
|
+
const spec = `${row.source.package}@${row.source.tag}`;
|
|
464
|
+
const found = await this.lookup(this.service(), this.profile(), spec, this.abort.signal);
|
|
465
|
+
if (!found.version || found.problem && found.problem !== "already-installed") throw Error(found.problem === "not-found" ? "npm \u4E2D\u672A\u627E\u5230\u8BE5\u5305\u6216\u9891\u9053" : "npm \u67E5\u8BE2\u5931\u8D25\uFF1B\u8BF7\u68C0\u67E5 DSH \u7684\u7F51\u7EDC\u4E0E registry \u8BBE\u7F6E");
|
|
466
|
+
if (!parseWorkBuddyVersion(found.version) || row.version && !parseWorkBuddyVersion(row.version)) throw Error("\u65E0\u6CD5\u6BD4\u8F83\u7248\u672C\u53F7");
|
|
467
|
+
row.latestVersion = found.version;
|
|
468
|
+
row.target = `${row.name}@${row.source.alias ? `npm:${row.source.package}@` : ""}${row.source.channel ? row.source.tag : found.version}`;
|
|
469
|
+
row.status = !row.version || compareWorkBuddyVersions(found.version, row.version) > 0 ? "available" : "current";
|
|
470
|
+
row.message = !row.version ? "\u5B89\u88C5\u6587\u4EF6\u4E0D\u53EF\u8BFB\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u8BE5\u6765\u6E90\u4FEE\u590D" : row.status === "current" ? "\u5F53\u524D\u7248\u672C\u5DF2\u662F\u8BE5\u9891\u9053\u6700\u65B0\uFF0C\u6216\u9AD8\u4E8E\u8BE5\u9891\u9053" : void 0;
|
|
471
|
+
} else if (row.source.kind === "github") {
|
|
472
|
+
const { owner, repo, ref } = row.source;
|
|
473
|
+
const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/commits/${encodeURIComponent(ref ?? "HEAD")}`;
|
|
474
|
+
const found = await publicJson(url, this.fetchImpl, this.abort.signal);
|
|
475
|
+
if (!/^[a-f0-9]{40}$/i.test(found.sha)) throw Error("GitHub \u8FD4\u56DE\u65E0\u6548\u63D0\u4EA4\u4FE1\u606F");
|
|
476
|
+
row.latestCommit = found.sha.toLowerCase();
|
|
477
|
+
row.target = `${row.name}@${row.spec}`;
|
|
478
|
+
row.status = row.commit === row.latestCommit ? "current" : "available";
|
|
479
|
+
row.message = row.commit ? row.status === "available" ? `\u63D0\u4EA4 ${short(row.commit)} \u2192 ${short(row.latestCommit)}` : "\u8BE5\u5206\u652F / \u6807\u7B7E\u7684\u63D0\u4EA4\u5DF2\u540C\u6B65" : "\u65E0\u6CD5\u8BFB\u53D6\u672C\u5730 Git \u63D0\u4EA4\uFF0C\u53EF\u91CD\u65B0\u5B89\u88C5\u5DF2\u68C0\u67E5\u7684\u6765\u6E90";
|
|
480
|
+
if (ref && /^[a-f0-9]{7,40}$/i.test(ref)) {
|
|
481
|
+
row.status = "pinned";
|
|
482
|
+
row.message = "\u5B89\u88C5\u6765\u6E90\u56FA\u5B9A\u5728 Git \u63D0\u4EA4\uFF1B\u4FDD\u7559\u6B64\u63D0\u4EA4\uFF0C\u4E0D\u81EA\u52A8\u5207\u6362\u5206\u652F";
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
row.checkedAt = Date.now();
|
|
486
|
+
row.checkedEnabled = row.enabled;
|
|
487
|
+
} catch (error) {
|
|
488
|
+
row.status = "failed";
|
|
489
|
+
row.message = error.message;
|
|
490
|
+
delete row.target;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
start(kind, task) {
|
|
494
|
+
if (this.flight) throw Error("\u5DF2\u6709\u63D2\u4EF6\u64CD\u4F5C\u6B63\u5728\u6267\u884C\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210");
|
|
495
|
+
this.job = { id: randomUUID(), kind, state: "running", startedAt: Date.now(), results: [], total: 0 };
|
|
496
|
+
this.error = void 0;
|
|
497
|
+
this.flight = Promise.resolve().then(() => task(this.job)).then(() => {
|
|
498
|
+
this.job.state = this.job.cancelRequested ? "cancelled" : "done";
|
|
499
|
+
}, (e) => {
|
|
500
|
+
this.job.state = this.job.cancelRequested ? "cancelled" : "failed";
|
|
501
|
+
this.job.message = e.message;
|
|
502
|
+
}).finally(() => {
|
|
503
|
+
this.job.finishedAt = Date.now();
|
|
504
|
+
this.flight = void 0;
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
check() {
|
|
508
|
+
this.start("check", async (job) => {
|
|
509
|
+
await this.list();
|
|
510
|
+
const rows = this.rows.filter((r) => ["npm", "github"].includes(r.source.kind));
|
|
511
|
+
job.total = rows.length;
|
|
512
|
+
let next = 0;
|
|
513
|
+
await Promise.all(Array.from({ length: Math.min(3, rows.length) }, async () => {
|
|
514
|
+
while (next < rows.length && !job.cancelRequested) {
|
|
515
|
+
const row = rows[next++];
|
|
516
|
+
await this.checkRow(row);
|
|
517
|
+
job.results.push({ name: row.name, status: row.status, message: row.message });
|
|
518
|
+
}
|
|
519
|
+
}));
|
|
520
|
+
this.checkedAt = Date.now();
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
async installRow(row, registry) {
|
|
524
|
+
const original = await jsonFile(join3(this.profile().dir, "package.json"));
|
|
525
|
+
if (original?.dependencies?.[row.name] !== row.spec) throw Error("\u5B89\u88C5\u6765\u6E90\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
526
|
+
const current = (await this.service().listBundles()).find((b) => b.name === row.name);
|
|
527
|
+
if (!current || current.enabled !== row.checkedEnabled || current.version !== row.version) throw Error("\u63D2\u4EF6\u7248\u672C\u6216\u542F\u7528\u72B6\u6001\u5DF2\u6539\u53D8\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5");
|
|
528
|
+
const options2 = { enabled: row.enabled, requestId: this.job.id, ...registry ? { registry } : {} };
|
|
529
|
+
let result;
|
|
530
|
+
if (row.source.kind === "github") {
|
|
531
|
+
this.gitAbort = new AbortController();
|
|
532
|
+
try {
|
|
533
|
+
result = await this.refreshGit(this.service(), this.profile(), row, options2, AbortSignal.any([this.abort.signal, this.gitAbort.signal]));
|
|
534
|
+
} finally {
|
|
535
|
+
this.gitAbort = void 0;
|
|
536
|
+
}
|
|
537
|
+
} else result = await this.service().installBundle(row.target, options2);
|
|
538
|
+
if (result.packageResult && result.packageResult.exitCode !== 0 || ["failed", "cancelled"].includes(result.application) || result.error) {
|
|
539
|
+
return {
|
|
540
|
+
name: row.name,
|
|
541
|
+
status: "failed",
|
|
542
|
+
application: result.application,
|
|
543
|
+
message: result.pendingBuilds?.length ? "\u4F9D\u8D56\u6784\u5EFA\u811A\u672C\u9700\u8981\u6388\u6743\uFF0C\u8BF7\u5230 DSH \u63D2\u4EF6\u5B89\u88C5\u9875\u5141\u8BB8\u8FD9\u4E9B\u811A\u672C\u540E\u91CD\u8BD5" : result.error?.code === "incompatible-version" ? "\u63D2\u4EF6\u4E0E\u5F53\u524D DSH \u4E0D\u517C\u5BB9\uFF0C\u8BF7\u5148\u5347\u7EA7 DSH \u6216\u9009\u62E9\u517C\u5BB9\u7248\u672C" : `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error?.code ?? result.packageResult?.kind ?? "unknown"}\uFF09\uFF1B\u8BF7\u5230 DSH \u63D2\u4EF6\u9875\u67E5\u770B\u5B89\u88C5\u65E5\u5FD7`,
|
|
544
|
+
pendingBuilds: result.pendingBuilds ?? [],
|
|
545
|
+
logPath: result.packageResult?.logPath
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const installed = (await this.service().listBundles()).find((b) => b.name === row.name);
|
|
549
|
+
if (row.source.kind === "npm" && installed?.version !== row.latestVersion) return {
|
|
550
|
+
name: row.name,
|
|
551
|
+
status: "failed",
|
|
552
|
+
message: `\u5B9E\u9645\u5B89\u88C5\u7248\u672C ${installed?.version ?? "\u672A\u77E5"} \u4E0E\u68C0\u67E5\u7248\u672C ${row.latestVersion} \u4E0D\u540C\uFF1B\u8BF7\u7A0D\u540E\u91CD\u65B0\u68C0\u67E5\uFF0C\u53EF\u80FD\u662F\u955C\u50CF\u5C1A\u672A\u540C\u6B65\u6216\u53D1\u884C\u65F6\u95F4\u9650\u5236`
|
|
553
|
+
};
|
|
554
|
+
if (row.source.kind === "github") {
|
|
555
|
+
const lock = await locks(this.profile().dir);
|
|
556
|
+
const pnpm = lock.pnpm?.importers?.["."]?.dependencies?.[row.name]?.version, npm = lock.npm?.packages?.[`node_modules/${row.name}`]?.resolved;
|
|
557
|
+
if (githubCommit(lock.preferNpm ? npm ?? pnpm : pnpm ?? npm) !== row.latestCommit) return {
|
|
558
|
+
name: row.name,
|
|
559
|
+
status: "failed",
|
|
560
|
+
restartRequired: true,
|
|
561
|
+
message: "\u5B9E\u9645 Git \u63D0\u4EA4\u4E0E\u68C0\u67E5\u7ED3\u679C\u4E0D\u4E00\u81F4\uFF0C\u8BF7\u91CD\u65B0\u68C0\u67E5\u540E\u91CD\u8BD5\uFF1B\u4E0D\u628A\u4ECD\u505C\u7559\u5728\u65E7\u9501\u6587\u4EF6\u7684\u5B89\u88C5\u62A5\u544A\u4E3A\u6210\u529F"
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return { name: row.name, status: "installed", application: result.application, message: result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH \u540E\u751F\u6548" : "\u5B89\u88C5\u5B8C\u6210", restartRequired: result.application === "restart-required" };
|
|
565
|
+
}
|
|
566
|
+
update(names, registry) {
|
|
567
|
+
if (!Array.isArray(names) || !names.length || names.length > 200 || names.some((n) => typeof n !== "string") || new Set(names).size !== names.length) throw Error("\u8BF7\u9009\u62E9\u8981\u66F4\u65B0\u7684\u63D2\u4EF6");
|
|
568
|
+
const selected = names.map((name) => this.rows.find((r) => r.name === name));
|
|
569
|
+
if (selected.some((r) => !r || !["available", "failed"].includes(r.status) || !r.target || Date.now() - r.checkedAt > 6e5)) throw Error("\u8BF7\u5148\u68C0\u67E5\u66F4\u65B0\uFF0C\u518D\u9009\u62E9\u53EF\u66F4\u65B0\u7684\u63D2\u4EF6\uFF08\u68C0\u67E5\u7ED3\u679C\u6709\u6548\u671F 10 \u5206\u949F\uFF09");
|
|
570
|
+
selected.sort((a, b) => Number(a.name === "dsh-multi-router") - Number(b.name === "dsh-multi-router"));
|
|
571
|
+
this.start("update", async (job) => {
|
|
572
|
+
job.total = selected.length;
|
|
573
|
+
for (const row of selected) {
|
|
574
|
+
if (job.cancelRequested) break;
|
|
575
|
+
job.current = row.name;
|
|
576
|
+
let result;
|
|
577
|
+
try {
|
|
578
|
+
result = await this.installRow(row, registry);
|
|
579
|
+
} catch (e) {
|
|
580
|
+
result = { name: row.name, status: "failed", message: e.message };
|
|
581
|
+
}
|
|
582
|
+
job.results.push(result);
|
|
583
|
+
row.status = result.status;
|
|
584
|
+
row.message = result.message;
|
|
585
|
+
}
|
|
586
|
+
delete job.current;
|
|
587
|
+
await this.list();
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
install(spec, registry) {
|
|
591
|
+
if (typeof spec !== "string" || spec.length > 600 || spec.startsWith("-") || /[\s\x00-\x1f]/.test(spec)) throw Error("\u8BF7\u8F93\u5165\u4E00\u4E2A npm \u5305\u540D\u6216 GitHub \u5B89\u88C5\u5730\u5740");
|
|
592
|
+
const named = /^((?:@[^/]+\/)?[^@]+)(?:@(.+))?$/.exec(spec);
|
|
593
|
+
const github = parsePluginSource("placeholder", spec);
|
|
594
|
+
const npm = named && parsePluginSource(named[1], named[2] || "latest");
|
|
595
|
+
if (github.kind !== "github" && npm?.kind !== "npm") throw Error("\u4EC5\u652F\u6301 npm \u5305\u540D / \u7248\u672C\u6216 github:\u7528\u6237/\u4ED3\u5E93");
|
|
596
|
+
this.start("install", async (job) => {
|
|
597
|
+
job.total = 1;
|
|
598
|
+
job.current = spec;
|
|
599
|
+
const result = await this.service().installBundle(spec, { requestId: job.id, ...registry ? { registry } : {} });
|
|
600
|
+
job.results.push({
|
|
601
|
+
name: result.bundle ?? spec,
|
|
602
|
+
status: result.error || ["failed", "cancelled"].includes(result.application) || result.packageResult && result.packageResult.exitCode !== 0 ? "failed" : "installed",
|
|
603
|
+
message: result.error ? `\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${result.error.code}\uFF09\uFF0C\u8BF7\u67E5\u770B DSH \u63D2\u4EF6\u5B89\u88C5\u65E5\u5FD7` : result.application === "restart-required" ? "\u5DF2\u5B89\u88C5\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F DSH" : "\u5B89\u88C5\u5B8C\u6210",
|
|
604
|
+
application: result.application,
|
|
605
|
+
pendingBuilds: result.pendingBuilds ?? [],
|
|
606
|
+
restartRequired: result.application === "restart-required"
|
|
607
|
+
});
|
|
608
|
+
delete job.current;
|
|
609
|
+
await this.list();
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
async action(input) {
|
|
613
|
+
if (!this.state().available) throw Error("\u5F53\u524D DSH \u672A\u63D0\u4F9B\u63D2\u4EF6\u7BA1\u7406\u63A5\u53E3\uFF0C\u8BF7\u5347\u7EA7 DSH \u540E\u91CD\u8BD5");
|
|
614
|
+
if (input.registry && !["https://registry.npmjs.org/", "https://registry.npmmirror.com/"].includes(input.registry)) throw Error("\u65E0\u6548\u7684 npm \u6765\u6E90");
|
|
615
|
+
if (input.action === "plugins.list") {
|
|
616
|
+
if (!this.flight) await this.list();
|
|
617
|
+
} else if (input.action === "plugins.check") this.check();
|
|
618
|
+
else if (input.action === "plugins.update") this.update(input.names, input.registry);
|
|
619
|
+
else if (input.action === "plugins.install") this.install(input.spec, input.registry);
|
|
620
|
+
else if (input.action === "plugins.cancel") {
|
|
621
|
+
if (this.job?.state === "running") {
|
|
622
|
+
this.job.cancelRequested = true;
|
|
623
|
+
if (this.gitAbort) this.gitAbort.abort();
|
|
624
|
+
else if (this.job.kind !== "check") await this.service().cancelInstall?.(this.job.id);
|
|
625
|
+
}
|
|
626
|
+
} else throw Error("\u672A\u77E5\u63D2\u4EF6\u64CD\u4F5C");
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
|
|
236
630
|
// src/manager.mjs
|
|
237
631
|
var MANAGEMENT_PATH = "/.well-known/dsh-multi-router";
|
|
238
632
|
var revisionOf = (raw) => createHash("sha256").update(JSON.stringify(raw)).digest("hex");
|
|
@@ -272,28 +666,35 @@ function modelInfo(row) {
|
|
|
272
666
|
}
|
|
273
667
|
var Manager = class {
|
|
274
668
|
constructor(file, router, onChange = () => {
|
|
275
|
-
}) {
|
|
669
|
+
}, pluginAccess = {}) {
|
|
276
670
|
this.file = file;
|
|
277
671
|
this.router = router;
|
|
278
672
|
this.onChange = onChange;
|
|
279
|
-
this.catalogFile =
|
|
673
|
+
this.catalogFile = join4(dirname3(file), "catalogs.json");
|
|
280
674
|
this.catalogs = /* @__PURE__ */ new Map();
|
|
281
675
|
this.catalogErrors = /* @__PURE__ */ new Map();
|
|
282
676
|
this.refreshes = /* @__PURE__ */ new Map();
|
|
283
677
|
this.closed = false;
|
|
284
678
|
this.monitorFlights = /* @__PURE__ */ new Map();
|
|
285
679
|
this.loginInfo = /* @__PURE__ */ new Map();
|
|
286
|
-
this.telemetry = new Telemetry(
|
|
680
|
+
this.telemetry = new Telemetry(join4(dirname3(file), "usage.json"));
|
|
681
|
+
this.plugins = new PluginUpdates(pluginAccess);
|
|
682
|
+
this.setupFile = join4(dirname3(file), "initial-setup.json");
|
|
287
683
|
router.telemetry = this.telemetry;
|
|
288
684
|
router.monitor = (a) => this.monitorAccount(a);
|
|
289
685
|
router.prepareFree = () => this.checkFreeModels(true);
|
|
290
686
|
router.freeModels = (account) => this.freeModels(account);
|
|
291
687
|
}
|
|
292
688
|
async load() {
|
|
293
|
-
this.raw = JSON.parse(await
|
|
689
|
+
this.raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
690
|
+
try {
|
|
691
|
+
this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
|
|
692
|
+
} catch {
|
|
693
|
+
this.setup = void 0;
|
|
694
|
+
}
|
|
294
695
|
this.config = validateConfig(this.raw, this.file);
|
|
295
696
|
try {
|
|
296
|
-
const saved = JSON.parse(await
|
|
697
|
+
const saved = JSON.parse(await readFile2(this.catalogFile, "utf8"));
|
|
297
698
|
for (const account of this.config.accounts) {
|
|
298
699
|
const item = saved[account.id];
|
|
299
700
|
if (item?.credentialFile === account.credentialFile && Array.isArray(item.models)) {
|
|
@@ -333,10 +734,11 @@ var Manager = class {
|
|
|
333
734
|
...discovered.status.map((s) => ({ ...s, checkedAt: Date.now() }))
|
|
334
735
|
];
|
|
335
736
|
if (this.closed || !discovered.accounts.length) return;
|
|
336
|
-
await
|
|
737
|
+
await withFileLock2(this.file, async () => {
|
|
337
738
|
if (this.closed) return;
|
|
338
|
-
let raw = JSON.parse(await
|
|
739
|
+
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
339
740
|
let changed = false;
|
|
741
|
+
const saved = /* @__PURE__ */ new Map();
|
|
340
742
|
for (const item of discovered.accounts) {
|
|
341
743
|
const key = localAccountKey(item.provider, item.document);
|
|
342
744
|
if (!force && raw.ignoredLocalAccounts?.includes(key)) continue;
|
|
@@ -353,6 +755,11 @@ var Manager = class {
|
|
|
353
755
|
} catch {
|
|
354
756
|
}
|
|
355
757
|
}
|
|
758
|
+
const id = existing?.id ?? `local_${createHash("sha256").update(key).digest("hex").slice(0, 20)}`;
|
|
759
|
+
const recordSaved = () => {
|
|
760
|
+
if (!saved.has(item.provider)) saved.set(item.provider, /* @__PURE__ */ new Set());
|
|
761
|
+
saved.get(item.provider).add(id);
|
|
762
|
+
};
|
|
356
763
|
let sameDevice = true;
|
|
357
764
|
if (existing && item.identityDocument) {
|
|
358
765
|
try {
|
|
@@ -362,13 +769,15 @@ var Manager = class {
|
|
|
362
769
|
sameDevice = false;
|
|
363
770
|
}
|
|
364
771
|
}
|
|
365
|
-
if (existing && sameDevice && current.credential.expiresAtMs >= item.document.credential.expiresAtMs)
|
|
366
|
-
|
|
367
|
-
|
|
772
|
+
if (existing && sameDevice && current.credential.expiresAtMs >= item.document.credential.expiresAtMs) {
|
|
773
|
+
recordSaved();
|
|
774
|
+
continue;
|
|
775
|
+
}
|
|
776
|
+
const base = join4(dirname3(this.file), "accounts");
|
|
368
777
|
await mkdir2(base, { recursive: true, mode: 448 });
|
|
369
|
-
const staging = await mkdtemp2(
|
|
778
|
+
const staging = await mkdtemp2(join4(base, `${id}-`));
|
|
370
779
|
try {
|
|
371
|
-
const credentialFile =
|
|
780
|
+
const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
|
|
372
781
|
await importAccount({ ...item, credentialFile, identityFile });
|
|
373
782
|
const paths = {
|
|
374
783
|
credentialFile: relative2(dirname3(this.file), credentialFile),
|
|
@@ -387,11 +796,12 @@ var Manager = class {
|
|
|
387
796
|
await writePrivateJson(this.file, next);
|
|
388
797
|
raw = next;
|
|
389
798
|
changed = true;
|
|
799
|
+
recordSaved();
|
|
390
800
|
this.router.clients.delete(id);
|
|
391
801
|
this.catalogs.delete(id);
|
|
392
802
|
this.catalogErrors.delete(id);
|
|
393
803
|
} catch (e) {
|
|
394
|
-
await
|
|
804
|
+
await rm3(staging, { recursive: true, force: true });
|
|
395
805
|
this.localDiscovery = this.localDiscovery.map((s) => s.provider === item.provider ? { ...s, state: "unreadable", code: "import_failed" } : s);
|
|
396
806
|
}
|
|
397
807
|
}
|
|
@@ -401,12 +811,25 @@ var Manager = class {
|
|
|
401
811
|
await Promise.all(this.config.accounts.map((a) => this.readLogin(a)));
|
|
402
812
|
this.publish();
|
|
403
813
|
}
|
|
814
|
+
this.localDiscovery = this.localDiscovery.map((s) => discovered.status.some((d) => d.provider === s.provider) ? { ...s, savedCount: saved.get(s.provider)?.size ?? 0 } : s);
|
|
404
815
|
}, { waitMs: 3e4 });
|
|
405
816
|
})().finally(() => {
|
|
406
817
|
this.localScan = void 0;
|
|
407
818
|
});
|
|
408
819
|
return this.localScan;
|
|
409
820
|
}
|
|
821
|
+
async finishInitialSetup() {
|
|
822
|
+
if (this.setup?.completedAt) return;
|
|
823
|
+
await withFileLock2(this.setupFile, async () => {
|
|
824
|
+
try {
|
|
825
|
+
this.setup = JSON.parse(await readFile2(this.setupFile, "utf8"));
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
if (this.setup?.completedAt) return;
|
|
829
|
+
this.setup = { completedAt: Date.now(), dismissed: false };
|
|
830
|
+
await writePrivateJson(this.setupFile, this.setup);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
410
833
|
async monitorAccount(account) {
|
|
411
834
|
if (this.monitorFlights.has(account.id)) return this.monitorFlights.get(account.id);
|
|
412
835
|
const promise = (async () => {
|
|
@@ -581,7 +1004,7 @@ var Manager = class {
|
|
|
581
1004
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id && a.credentialFile === account.credentialFile)) return;
|
|
582
1005
|
this.catalogs.set(account.id, { credentialFile: account.credentialFile, fetchedAt: Date.now(), pricingVersion: 1, models });
|
|
583
1006
|
this.catalogErrors.delete(account.id);
|
|
584
|
-
await
|
|
1007
|
+
await withFileLock2(this.catalogFile, () => writePrivateJson(this.catalogFile, Object.fromEntries(this.catalogs)));
|
|
585
1008
|
} catch (error) {
|
|
586
1009
|
if (this.closed || !this.config.accounts.some((a) => a.id === account.id)) return;
|
|
587
1010
|
this.catalogErrors.set(account.id, { status: Number(error.status ?? error.failure?.status) || 502, code: "catalog_fetch_failed", at: Date.now() });
|
|
@@ -597,6 +1020,8 @@ var Manager = class {
|
|
|
597
1020
|
pluginVersion: version,
|
|
598
1021
|
revision: this.revision,
|
|
599
1022
|
localDiscovery: this.localDiscovery ?? [],
|
|
1023
|
+
setup: this.setup,
|
|
1024
|
+
pluginUpdates: this.plugins.state(),
|
|
600
1025
|
telemetry: this.telemetry.state(),
|
|
601
1026
|
accounts: this.config.accounts.map((a) => ({
|
|
602
1027
|
id: a.id,
|
|
@@ -631,8 +1056,23 @@ var Manager = class {
|
|
|
631
1056
|
}
|
|
632
1057
|
async action(input) {
|
|
633
1058
|
if (!input || typeof input !== "object" || Array.isArray(input)) bad("\u8BF7\u6C42\u683C\u5F0F\u65E0\u6548");
|
|
1059
|
+
if (typeof input.action === "string" && input.action.startsWith("plugins.")) {
|
|
1060
|
+
try {
|
|
1061
|
+
await this.plugins.action(input);
|
|
1062
|
+
} catch (e) {
|
|
1063
|
+
bad(e.message);
|
|
1064
|
+
}
|
|
1065
|
+
return this.state();
|
|
1066
|
+
}
|
|
1067
|
+
if (input.action === "setup.dismiss") {
|
|
1068
|
+
await withFileLock2(this.setupFile, async () => {
|
|
1069
|
+
this.setup = { ...this.setup, completedAt: this.setup?.completedAt ?? Date.now(), dismissed: true };
|
|
1070
|
+
await writePrivateJson(this.setupFile, this.setup);
|
|
1071
|
+
});
|
|
1072
|
+
return this.state();
|
|
1073
|
+
}
|
|
634
1074
|
if (input.action === "account.export") {
|
|
635
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1075
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u63D0\u53D6\u51ED\u8BC1");
|
|
636
1076
|
const account = this.config.accounts.find((a) => a.id === input.id);
|
|
637
1077
|
if (!account) bad("\u8D26\u53F7\u4E0D\u5B58\u5728");
|
|
638
1078
|
let credential;
|
|
@@ -654,7 +1094,7 @@ var Manager = class {
|
|
|
654
1094
|
} };
|
|
655
1095
|
}
|
|
656
1096
|
if (input.action === "config.export") {
|
|
657
|
-
if (input.revision !== revisionOf(JSON.parse(await
|
|
1097
|
+
if (input.revision !== revisionOf(JSON.parse(await readFile2(this.file, "utf8")))) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u5BFC\u51FA");
|
|
658
1098
|
try {
|
|
659
1099
|
return { ...this.state(), exported: { filename: `dsh-multi-router-${input.includeCredentials ? "encrypted" : "settings"}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.json`, document: await exportConfiguration(this, input) } };
|
|
660
1100
|
} catch (e) {
|
|
@@ -701,8 +1141,8 @@ var Manager = class {
|
|
|
701
1141
|
await this.checkFreeModels(true);
|
|
702
1142
|
return this.state();
|
|
703
1143
|
}
|
|
704
|
-
return
|
|
705
|
-
let raw = JSON.parse(await
|
|
1144
|
+
return withFileLock2(this.file, async () => {
|
|
1145
|
+
let raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
706
1146
|
if (input.revision !== revisionOf(raw)) throw new ManagementError(409, "\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u540E\u518D\u4FDD\u5B58");
|
|
707
1147
|
raw.routes ??= structuredClone(this.config.routes);
|
|
708
1148
|
let staging;
|
|
@@ -725,10 +1165,10 @@ var Manager = class {
|
|
|
725
1165
|
}
|
|
726
1166
|
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(input.id ?? "") || !PROVIDERS.includes(input.provider)) bad("\u8D26\u53F7 ID \u6216\u4F9B\u5E94\u5546\u65E0\u6548");
|
|
727
1167
|
if (account && !replacing) bad("\u8D26\u53F7 ID \u5DF2\u5B58\u5728\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C ID");
|
|
728
|
-
const base =
|
|
1168
|
+
const base = join4(dirname3(this.file), "accounts");
|
|
729
1169
|
await mkdir2(base, { recursive: true, mode: 448 });
|
|
730
|
-
staging = await mkdtemp2(
|
|
731
|
-
const credentialFile =
|
|
1170
|
+
staging = await mkdtemp2(join4(base, `${input.id}-`));
|
|
1171
|
+
const credentialFile = join4(staging, "credential.json"), identityFile = join4(staging, "identity.json");
|
|
732
1172
|
const added = {
|
|
733
1173
|
id: input.id,
|
|
734
1174
|
name: input.name || account?.name || input.id,
|
|
@@ -888,7 +1328,7 @@ var Manager = class {
|
|
|
888
1328
|
this.publish();
|
|
889
1329
|
await this.telemetry.persist();
|
|
890
1330
|
} finally {
|
|
891
|
-
if (staging) await
|
|
1331
|
+
if (staging) await rm3(staging, { recursive: true, force: true });
|
|
892
1332
|
}
|
|
893
1333
|
return this.state();
|
|
894
1334
|
}, { waitMs: 3e4 }).then(async (state) => {
|
|
@@ -916,8 +1356,8 @@ var Manager = class {
|
|
|
916
1356
|
if (filename && String(filename) !== this.file.split(/[\\/]/).pop()) return;
|
|
917
1357
|
clearTimeout(timer);
|
|
918
1358
|
timer = setTimeout(() => {
|
|
919
|
-
void
|
|
920
|
-
const raw = JSON.parse(await
|
|
1359
|
+
void withFileLock2(this.file, async () => {
|
|
1360
|
+
const raw = JSON.parse(await readFile2(this.file, "utf8"));
|
|
921
1361
|
if (this.closed || revisionOf(raw) === this.revision) return;
|
|
922
1362
|
const config = validateConfig(raw, this.file);
|
|
923
1363
|
this.raw = raw;
|
|
@@ -984,4 +1424,4 @@ export {
|
|
|
984
1424
|
Manager,
|
|
985
1425
|
managementHandler
|
|
986
1426
|
};
|
|
987
|
-
//# sourceMappingURL=chunk-
|
|
1427
|
+
//# sourceMappingURL=chunk-DZYRIZW2.js.map
|