@koishi-ce/plugin-market 1.0.0
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/dist/index.css +2 -0
- package/dist/index.js +6 -0
- package/lib/assets/message.zh-CN-B_nH77kB.yml +28 -0
- package/lib/assets/schema.zh-CN-CLU01Lrj.yml +10 -0
- package/lib/index.d.ts +165 -0
- package/lib/index.mjs +513 -0
- package/package.json +88 -0
- package/src/browser/index.ts +26 -0
- package/src/browser/market.ts +21 -0
- package/src/index.ts +2 -0
- package/src/node/deps.ts +28 -0
- package/src/node/index.ts +229 -0
- package/src/node/installer.ts +388 -0
- package/src/node/locales/message.de-DE.yml +25 -0
- package/src/node/locales/message.en-US.yml +25 -0
- package/src/node/locales/message.fr-FR.yml +25 -0
- package/src/node/locales/message.ja-JP.yml +25 -0
- package/src/node/locales/message.ru-RU.yml +25 -0
- package/src/node/locales/message.zh-CN.yml +28 -0
- package/src/node/locales/message.zh-TW.yml +25 -0
- package/src/node/locales/schema.de-DE.yml +9 -0
- package/src/node/locales/schema.en-US.yml +9 -0
- package/src/node/locales/schema.fr-FR.yml +9 -0
- package/src/node/locales/schema.ja-JP.yml +9 -0
- package/src/node/locales/schema.ru-RU.yml +9 -0
- package/src/node/locales/schema.zh-CN.yml +10 -0
- package/src/node/locales/schema.zh-TW.yml +9 -0
- package/src/node/market.ts +149 -0
- package/src/shared/index.ts +73 -0
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { Logger, Schema, Service, Time, defineProperty, pick, valueMap } from "@koishi-ce/koishi";
|
|
4
|
+
import { compare, gt, satisfies, valid } from "semver";
|
|
5
|
+
import { DataService } from "@koishi-ce/console";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import Scanner from "@koishi-ce/registry";
|
|
8
|
+
import spawn from "execa";
|
|
9
|
+
import getRegistry from "get-registry";
|
|
10
|
+
import pMap from "p-map";
|
|
11
|
+
import messageZhCN from "./assets/message.zh-CN-B_nH77kB.yml";
|
|
12
|
+
import schemaZhCN from "./assets/schema.zh-CN-CLU01Lrj.yml";
|
|
13
|
+
//#region \0rolldown/runtime.js
|
|
14
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/node/deps.ts
|
|
17
|
+
var DependencyProvider = class extends DataService {
|
|
18
|
+
constructor(ctx) {
|
|
19
|
+
super(ctx, "dependencies", { authority: 4 });
|
|
20
|
+
}
|
|
21
|
+
async get() {
|
|
22
|
+
return this.ctx.installer.getDeps();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var RegistryProvider = class extends DataService {
|
|
26
|
+
constructor(ctx) {
|
|
27
|
+
super(ctx, "registry", { authority: 4 });
|
|
28
|
+
}
|
|
29
|
+
async get() {
|
|
30
|
+
return this.ctx.installer.fullCache;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/node/installer.ts
|
|
35
|
+
const logger$1 = new Logger("market");
|
|
36
|
+
const whichPMRuns = createRequire(import.meta.url)("which-pm-runs");
|
|
37
|
+
const levelMap = {
|
|
38
|
+
info: "info",
|
|
39
|
+
warning: "debug",
|
|
40
|
+
error: "warn"
|
|
41
|
+
};
|
|
42
|
+
function loadManifest(name) {
|
|
43
|
+
const filename = __require.resolve(`${name}/package.json`);
|
|
44
|
+
const meta = JSON.parse(readFileSync(filename, "utf8"));
|
|
45
|
+
meta.dependencies ||= {};
|
|
46
|
+
defineProperty(meta, "$workspace", !filename.includes("node_modules"));
|
|
47
|
+
return meta;
|
|
48
|
+
}
|
|
49
|
+
function getVersions(versions) {
|
|
50
|
+
return Object.fromEntries(versions.map((item) => [item.version, pick(item, [
|
|
51
|
+
"peerDependencies",
|
|
52
|
+
"peerDependenciesMeta",
|
|
53
|
+
"deprecated"
|
|
54
|
+
])]).sort(([a], [b]) => compare(b, a)));
|
|
55
|
+
}
|
|
56
|
+
var Installer = class extends Service {
|
|
57
|
+
fullCache = {};
|
|
58
|
+
tempCache = {};
|
|
59
|
+
pkgTasks = {};
|
|
60
|
+
agent = whichPMRuns();
|
|
61
|
+
manifest;
|
|
62
|
+
flushData;
|
|
63
|
+
config;
|
|
64
|
+
constructor(ctx, config) {
|
|
65
|
+
super(ctx, "installer");
|
|
66
|
+
this.config = config;
|
|
67
|
+
this.manifest = loadManifest(this.cwd);
|
|
68
|
+
this.flushData = ctx.throttle(() => {
|
|
69
|
+
ctx.get("console")?.broadcast("market/registry", this.tempCache);
|
|
70
|
+
this.tempCache = {};
|
|
71
|
+
}, 500);
|
|
72
|
+
}
|
|
73
|
+
get cwd() {
|
|
74
|
+
return this.ctx.baseDir;
|
|
75
|
+
}
|
|
76
|
+
async start() {
|
|
77
|
+
const { endpoint, timeout } = this.config;
|
|
78
|
+
this.endpoint = endpoint ?? await getRegistry();
|
|
79
|
+
const options = {};
|
|
80
|
+
if (this.endpoint) options.endpoint = this.endpoint;
|
|
81
|
+
if (timeout !== void 0) options.timeout = timeout;
|
|
82
|
+
this.http = this.ctx.http.extend(options);
|
|
83
|
+
}
|
|
84
|
+
resolveName(name) {
|
|
85
|
+
if (name.startsWith("@koishijs/plugin-")) return [name];
|
|
86
|
+
if (name.match(/(^|\/)koishi-plugin-/)) return [name];
|
|
87
|
+
if (name[0] === "@") {
|
|
88
|
+
const [left, right] = name.split("/");
|
|
89
|
+
return [`${left}/koishi-plugin-${right}`];
|
|
90
|
+
} else return [`@koishijs/plugin-${name}`, `koishi-plugin-${name}`];
|
|
91
|
+
}
|
|
92
|
+
async findVersion(names) {
|
|
93
|
+
return (await Promise.all(names.map(async (name) => {
|
|
94
|
+
try {
|
|
95
|
+
const [latest] = Object.entries(await this.getPackage(name));
|
|
96
|
+
if (!latest) return void 0;
|
|
97
|
+
return { [name]: latest[0] };
|
|
98
|
+
} catch {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}))).find((entry) => entry !== void 0);
|
|
102
|
+
}
|
|
103
|
+
async _getPackage(name) {
|
|
104
|
+
try {
|
|
105
|
+
const registry = await this.http.get(`/${name}`);
|
|
106
|
+
const versions = getVersions(Object.values(registry.versions).filter((remote) => {
|
|
107
|
+
if (name === "koishi") return satisfies(remote.version, "4");
|
|
108
|
+
return !Scanner.isPlugin(name) || Scanner.isCompatible("4", remote);
|
|
109
|
+
}));
|
|
110
|
+
this.fullCache[name] = this.tempCache[name] = versions;
|
|
111
|
+
this.flushData();
|
|
112
|
+
return versions;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
logger$1.warn(error);
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
setPackage(name, versions) {
|
|
119
|
+
this.fullCache[name] = this.tempCache[name] = getVersions(versions);
|
|
120
|
+
this.flushData();
|
|
121
|
+
this.pkgTasks[name] = Promise.resolve(this.fullCache[name]);
|
|
122
|
+
}
|
|
123
|
+
getPackage(name) {
|
|
124
|
+
return this.pkgTasks[name] ||= this._getPackage(name);
|
|
125
|
+
}
|
|
126
|
+
async _getDeps() {
|
|
127
|
+
const result = valueMap(this.manifest.dependencies, (request) => {
|
|
128
|
+
return { request: request.replace(/^[~^]/, "") };
|
|
129
|
+
});
|
|
130
|
+
await pMap(Object.keys(result), async (name) => {
|
|
131
|
+
const dep = result[name];
|
|
132
|
+
if (!dep) return;
|
|
133
|
+
try {
|
|
134
|
+
const meta = loadManifest(name);
|
|
135
|
+
dep.resolved = meta.version;
|
|
136
|
+
dep.workspace = meta.$workspace;
|
|
137
|
+
if (meta.$workspace) return;
|
|
138
|
+
} catch {}
|
|
139
|
+
if (!valid(dep.request)) dep.invalid = true;
|
|
140
|
+
const versions = await this.getPackage(name);
|
|
141
|
+
if (versions) dep.latest = Object.keys(versions)[0];
|
|
142
|
+
}, { concurrency: 10 });
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
getDeps() {
|
|
146
|
+
return this.depTask ||= this._getDeps();
|
|
147
|
+
}
|
|
148
|
+
refreshData() {
|
|
149
|
+
this.ctx.get("console")?.refresh("registry");
|
|
150
|
+
this.ctx.get("console")?.refresh("packages");
|
|
151
|
+
}
|
|
152
|
+
refresh(refresh = false) {
|
|
153
|
+
this.pkgTasks = {};
|
|
154
|
+
this.fullCache = {};
|
|
155
|
+
this.tempCache = {};
|
|
156
|
+
this.depTask = this._getDeps();
|
|
157
|
+
if (!refresh) return;
|
|
158
|
+
this.refreshData();
|
|
159
|
+
}
|
|
160
|
+
async exec(args) {
|
|
161
|
+
const name = this.agent?.name ?? "npm";
|
|
162
|
+
const useJson = name === "yarn" && (this.agent?.version ?? "1") >= "2";
|
|
163
|
+
if (name !== "yarn") args.unshift("install");
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
if (useJson) args.push("--json");
|
|
166
|
+
const child = spawn(name, args, { cwd: this.cwd });
|
|
167
|
+
child.on("exit", (code) => resolve(code ?? -1));
|
|
168
|
+
child.on("error", () => resolve(-1));
|
|
169
|
+
let stderr = "";
|
|
170
|
+
child.stderr?.on("data", (data) => {
|
|
171
|
+
data = stderr + data.toString();
|
|
172
|
+
const lines = data.split("\n");
|
|
173
|
+
stderr = lines.pop() ?? "";
|
|
174
|
+
for (const line of lines) logger$1.warn(line);
|
|
175
|
+
});
|
|
176
|
+
let stdout = "";
|
|
177
|
+
child.stdout?.on("data", (data) => {
|
|
178
|
+
data = stdout + data.toString();
|
|
179
|
+
const lines = data.split("\n");
|
|
180
|
+
stdout = lines.pop() ?? "";
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (!useJson || line[0] !== "{") {
|
|
183
|
+
logger$1.info(line);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const { type, data } = JSON.parse(line);
|
|
188
|
+
const level = type in levelMap ? levelMap[type] : null;
|
|
189
|
+
(level ? logger$1[level] : logger$1.info)(data);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
logger$1.warn(line);
|
|
192
|
+
logger$1.warn(error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async override(deps) {
|
|
199
|
+
const filename = resolve(this.cwd, "package.json");
|
|
200
|
+
for (const key in deps) if (deps[key]) this.manifest.dependencies[key] = deps[key];
|
|
201
|
+
else delete this.manifest.dependencies[key];
|
|
202
|
+
this.manifest.dependencies = Object.fromEntries(Object.entries(this.manifest.dependencies).sort((a, b) => a[0].localeCompare(b[0])));
|
|
203
|
+
await Bun.write(filename, `${JSON.stringify(this.manifest, null, 2)}\n`);
|
|
204
|
+
}
|
|
205
|
+
_install() {
|
|
206
|
+
const args = [];
|
|
207
|
+
if (this.endpoint) args.push("--registry", this.endpoint);
|
|
208
|
+
return this.exec(args);
|
|
209
|
+
}
|
|
210
|
+
_getLocalDeps(override) {
|
|
211
|
+
return valueMap(override, (request, name) => {
|
|
212
|
+
const dep = { request };
|
|
213
|
+
try {
|
|
214
|
+
const meta = loadManifest(name);
|
|
215
|
+
dep.resolved = meta.version;
|
|
216
|
+
dep.workspace = meta.$workspace;
|
|
217
|
+
} catch {}
|
|
218
|
+
return dep;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async install(deps, forced) {
|
|
222
|
+
const localDeps = this._getLocalDeps(deps);
|
|
223
|
+
await this.override(deps);
|
|
224
|
+
let shouldInstall = forced === true;
|
|
225
|
+
for (const name in deps) {
|
|
226
|
+
const request = deps[name];
|
|
227
|
+
const local = localDeps[name];
|
|
228
|
+
if (local?.workspace || request && local?.resolved && satisfies(local.resolved, request, { includePrerelease: true })) continue;
|
|
229
|
+
shouldInstall = true;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
if (shouldInstall) {
|
|
233
|
+
const code = await this._install();
|
|
234
|
+
if (code) return code;
|
|
235
|
+
}
|
|
236
|
+
this.refresh();
|
|
237
|
+
const newDeps = await this.getDeps();
|
|
238
|
+
for (const name in localDeps) {
|
|
239
|
+
const local = localDeps[name];
|
|
240
|
+
const newDep = newDeps[name];
|
|
241
|
+
if (!local || !newDep || local.workspace) continue;
|
|
242
|
+
if (newDep.resolved === local.resolved) continue;
|
|
243
|
+
try {
|
|
244
|
+
if (!(__require.resolve(name) in __require.cache)) continue;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
logger$1.error(error);
|
|
247
|
+
}
|
|
248
|
+
this.ctx.loader.fullReload();
|
|
249
|
+
}
|
|
250
|
+
this.refreshData();
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
static Config = Schema.object({
|
|
254
|
+
endpoint: Schema.string().role("link"),
|
|
255
|
+
timeout: Schema.number().role("time").default(Time.second * 5)
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/shared/index.ts
|
|
260
|
+
const logger = new Logger("market");
|
|
261
|
+
var MarketProvider = class extends DataService {
|
|
262
|
+
_task = null;
|
|
263
|
+
_timestamp = 0;
|
|
264
|
+
_error;
|
|
265
|
+
constructor(ctx) {
|
|
266
|
+
super(ctx, "market", { authority: 4 });
|
|
267
|
+
ctx.console.addListener("market/refresh", () => this.start(true), { authority: 4 });
|
|
268
|
+
ctx.on("console/connection", async (client) => {
|
|
269
|
+
if (!ctx.console.clients[client.id]) return;
|
|
270
|
+
if (Date.now() - this._timestamp <= Time.hour * 12) return;
|
|
271
|
+
if (await this.ctx.serial("console/intercept", client, { authority: 4 })) return;
|
|
272
|
+
this.start();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
start(_refresh = false) {
|
|
276
|
+
this._task = null;
|
|
277
|
+
this._error = null;
|
|
278
|
+
this._timestamp = Date.now();
|
|
279
|
+
this.refresh();
|
|
280
|
+
}
|
|
281
|
+
async prepare() {
|
|
282
|
+
return this._task ||= this.collect().catch((error) => {
|
|
283
|
+
logger.warn(error);
|
|
284
|
+
this._error = error;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/node/market.ts
|
|
290
|
+
var MarketProvider$1 = class extends MarketProvider {
|
|
291
|
+
http;
|
|
292
|
+
failed = [];
|
|
293
|
+
scanner;
|
|
294
|
+
fullCache = {};
|
|
295
|
+
tempCache = {};
|
|
296
|
+
flushData;
|
|
297
|
+
config;
|
|
298
|
+
constructor(ctx, config) {
|
|
299
|
+
super(ctx);
|
|
300
|
+
this.config = config;
|
|
301
|
+
if (config.endpoint) this.http = ctx.http.extend(config);
|
|
302
|
+
this.flushData = ctx.throttle(() => {
|
|
303
|
+
ctx.console.broadcast("market/patch", {
|
|
304
|
+
data: this.tempCache,
|
|
305
|
+
failed: this.failed.length,
|
|
306
|
+
total: this.scanner.total,
|
|
307
|
+
progress: this.scanner.progress
|
|
308
|
+
});
|
|
309
|
+
this.tempCache = {};
|
|
310
|
+
}, 500);
|
|
311
|
+
}
|
|
312
|
+
async start(refresh = false) {
|
|
313
|
+
this.failed = [];
|
|
314
|
+
this.fullCache = {};
|
|
315
|
+
this.tempCache = {};
|
|
316
|
+
if (refresh) this.ctx.installer.refresh(true);
|
|
317
|
+
await this.prepare();
|
|
318
|
+
super.start();
|
|
319
|
+
}
|
|
320
|
+
async collect() {
|
|
321
|
+
const { timeout } = this.config;
|
|
322
|
+
const registry = this.ctx.installer.http;
|
|
323
|
+
this.failed = [];
|
|
324
|
+
this.scanner = new Scanner(registry.get);
|
|
325
|
+
if (this.http) {
|
|
326
|
+
const result = await this.http.get("");
|
|
327
|
+
this.scanner.objects = result.objects.filter((object) => !object.ignored);
|
|
328
|
+
this.scanner.total = this.scanner.objects.length;
|
|
329
|
+
if (result.version !== void 0) this.scanner.version = result.version;
|
|
330
|
+
} else await this.scanner.collect(timeout !== void 0 ? { timeout } : {});
|
|
331
|
+
if (!this.scanner.version) {
|
|
332
|
+
let isNpmmirror = false;
|
|
333
|
+
try {
|
|
334
|
+
isNpmmirror = new URL(registry.config.endpoint ?? "").origin === "https://registry.npmmirror.com";
|
|
335
|
+
} catch {}
|
|
336
|
+
this.scanner.analyze({
|
|
337
|
+
version: "4",
|
|
338
|
+
onFailure: (name, reason) => {
|
|
339
|
+
this.failed.push(name);
|
|
340
|
+
if (isNpmmirror) {
|
|
341
|
+
if (this.ctx.http.isError(reason) && reason.response?.status === 404) {}
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
onRegistry: (registry, versions) => {
|
|
345
|
+
this.ctx.installer.setPackage(registry.name, versions);
|
|
346
|
+
},
|
|
347
|
+
onSuccess: (object, _versions) => {
|
|
348
|
+
object.package.links ||= { npm: `${registry.config.endpoint?.replace("registry.", "www.") ?? ""}/package/${object.package.name}` };
|
|
349
|
+
this.fullCache[object.package.name] = this.tempCache[object.package.name] = object;
|
|
350
|
+
},
|
|
351
|
+
after: () => this.flushData()
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async get() {
|
|
356
|
+
await this.prepare();
|
|
357
|
+
if (this._error) return {
|
|
358
|
+
data: {},
|
|
359
|
+
failed: 0,
|
|
360
|
+
total: 0,
|
|
361
|
+
progress: 0
|
|
362
|
+
};
|
|
363
|
+
const gravatar = process.env["GRAVATAR_MIRROR"];
|
|
364
|
+
return this.scanner.version ? {
|
|
365
|
+
registry: this.ctx.installer.endpoint,
|
|
366
|
+
data: Object.fromEntries(this.scanner.objects.map((item) => [item.package.name, item])),
|
|
367
|
+
failed: 0,
|
|
368
|
+
total: this.scanner.total,
|
|
369
|
+
progress: this.scanner.total,
|
|
370
|
+
gravatar
|
|
371
|
+
} : {
|
|
372
|
+
registry: this.ctx.installer.endpoint,
|
|
373
|
+
data: this.fullCache,
|
|
374
|
+
failed: this.failed.length,
|
|
375
|
+
total: this.scanner.total,
|
|
376
|
+
progress: this.scanner.progress,
|
|
377
|
+
gravatar
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
static Config = Schema.object({
|
|
381
|
+
endpoint: Schema.string().role("link"),
|
|
382
|
+
timeout: Schema.number().role("time").default(Time.second * 30),
|
|
383
|
+
proxyAgent: Schema.string().role("link")
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/node/index.ts
|
|
388
|
+
const name = "market";
|
|
389
|
+
const inject = ["http"];
|
|
390
|
+
const usage = `
|
|
391
|
+
如果插件市场页面提示「无法连接到插件市场」,则可以选择一个 Koishi 社区提供的镜像地址,填入下方对应的配置项中。
|
|
392
|
+
|
|
393
|
+
## 插件市场(填入 search.endpoint)
|
|
394
|
+
|
|
395
|
+
- Koishi(全球):https://registry.koishi.chat/index.json
|
|
396
|
+
- [t4wefan](https://k.ilharp.cc/2611)(大陆):https://registry.koishi.t4wefan.pub/index.json
|
|
397
|
+
- [Lipraty](https://k.ilharp.cc/3530)(大陆):https://koi.nyan.zone/registry/index.json
|
|
398
|
+
- [itzdrli](https://k.ilharp.cc/9975)(全球):https://kp.itzdrli.cc
|
|
399
|
+
- [Q78KG](https://k.ilharp.cc/10042)(全球):https://koishi-registry.yumetsuki.moe/index.json
|
|
400
|
+
|
|
401
|
+
要浏览更多社区镜像,请访问 [Koishi 论坛上的镜像一览](https://k.ilharp.cc/4000)。`;
|
|
402
|
+
const Config = Schema.object({
|
|
403
|
+
registry: Installer.Config,
|
|
404
|
+
search: MarketProvider$1.Config
|
|
405
|
+
}).i18n({ "zh-CN": schemaZhCN });
|
|
406
|
+
function apply(ctx, config) {
|
|
407
|
+
if (!ctx.loader?.writable) return ctx.logger("app").warn("@koishijs/plugin-market is only available for json/yaml config file");
|
|
408
|
+
ctx.plugin(Installer, config.registry);
|
|
409
|
+
ctx.inject(["installer"], (ctx) => {
|
|
410
|
+
ctx.i18n.define("zh-CN", messageZhCN);
|
|
411
|
+
ctx.command("plugin.install <name>", { authority: 4 }).alias(".i").action(async ({ session }, name) => {
|
|
412
|
+
if (!session) return;
|
|
413
|
+
if (!name) return session.text(".expect-name");
|
|
414
|
+
const names = ctx.installer.resolveName(name);
|
|
415
|
+
const deps = await ctx.installer.getDeps();
|
|
416
|
+
if (names.find((name) => deps[name])) return session.text(".already-installed");
|
|
417
|
+
const result = await ctx.installer.findVersion(names);
|
|
418
|
+
if (!result) return session.text(".not-found");
|
|
419
|
+
ctx.loader.envData.message = {
|
|
420
|
+
...pick(session, [
|
|
421
|
+
"sid",
|
|
422
|
+
"channelId",
|
|
423
|
+
"guildId",
|
|
424
|
+
"isDirect"
|
|
425
|
+
]),
|
|
426
|
+
content: session.text(".success")
|
|
427
|
+
};
|
|
428
|
+
await ctx.installer.install(result);
|
|
429
|
+
ctx.loader.envData.message = null;
|
|
430
|
+
return session.text(".success");
|
|
431
|
+
});
|
|
432
|
+
ctx.command("plugin.uninstall <name>", { authority: 4 }).alias(".r").action(async ({ session }, name) => {
|
|
433
|
+
if (!session) return;
|
|
434
|
+
if (!name) return session.text(".expect-name");
|
|
435
|
+
const names = ctx.installer.resolveName(name);
|
|
436
|
+
const deps = await ctx.installer.getDeps();
|
|
437
|
+
const installed = names.find((name) => deps[name]);
|
|
438
|
+
if (!installed) return session.text(".not-installed");
|
|
439
|
+
await ctx.installer.install({ [installed]: null });
|
|
440
|
+
return session.text(".success");
|
|
441
|
+
});
|
|
442
|
+
ctx.command("plugin.upgrade [name...]", { authority: 4 }).alias(".update", ".up").option("self", "-s, --koishi").action(async ({ session, options }, ...names) => {
|
|
443
|
+
if (!session) return;
|
|
444
|
+
async function getPackages(names) {
|
|
445
|
+
if (!names.length) return Object.keys(deps);
|
|
446
|
+
const resolved = names.map((name) => {
|
|
447
|
+
return ctx.installer.resolveName(name).find((name) => deps[name]);
|
|
448
|
+
}).filter((name) => name !== void 0);
|
|
449
|
+
if (options?.self) resolved.push("koishi");
|
|
450
|
+
return resolved;
|
|
451
|
+
}
|
|
452
|
+
ctx.installer.refresh(true);
|
|
453
|
+
const deps = await ctx.installer.getDeps();
|
|
454
|
+
names = (await getPackages(names.filter((name) => !!name))).filter((name) => {
|
|
455
|
+
const { latest, resolved, invalid } = deps[name] ?? {};
|
|
456
|
+
if (latest === void 0 || resolved === void 0) return false;
|
|
457
|
+
try {
|
|
458
|
+
return !invalid && gt(latest, resolved);
|
|
459
|
+
} catch {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
if (!names.length) return session.text(".all-updated");
|
|
464
|
+
const output = names.map((name) => {
|
|
465
|
+
const { latest, resolved } = deps[name] ?? {};
|
|
466
|
+
return `${name}: ${resolved} -> ${latest}`;
|
|
467
|
+
});
|
|
468
|
+
output.unshift(session.text(".available"));
|
|
469
|
+
output.push(session.text(".prompt"));
|
|
470
|
+
await session.send(output.join("\n"));
|
|
471
|
+
const answer = (await session.prompt())?.trim();
|
|
472
|
+
if (answer !== "Y" && answer !== "y") return session.text(".cancelled");
|
|
473
|
+
ctx.loader.envData.message = {
|
|
474
|
+
...pick(session, [
|
|
475
|
+
"sid",
|
|
476
|
+
"channelId",
|
|
477
|
+
"guildId",
|
|
478
|
+
"isDirect"
|
|
479
|
+
]),
|
|
480
|
+
content: session.text(".success")
|
|
481
|
+
};
|
|
482
|
+
await ctx.installer.install(names.reduce((result, name) => {
|
|
483
|
+
const latest = deps[name]?.latest;
|
|
484
|
+
if (latest !== void 0) result[name] = latest;
|
|
485
|
+
return result;
|
|
486
|
+
}, {}));
|
|
487
|
+
ctx.loader.envData.message = null;
|
|
488
|
+
return session.text(".success");
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
ctx.inject(["console", "installer"], (ctx) => {
|
|
492
|
+
ctx.plugin(DependencyProvider);
|
|
493
|
+
ctx.plugin(RegistryProvider);
|
|
494
|
+
ctx.plugin(MarketProvider$1, config.search);
|
|
495
|
+
ctx.console.addEntry({
|
|
496
|
+
dev: resolve(__dirname, "../../client/index.ts"),
|
|
497
|
+
prod: resolve(__dirname, "../../dist")
|
|
498
|
+
});
|
|
499
|
+
ctx.console.addListener("market/install", async (deps, forced) => {
|
|
500
|
+
const code = await ctx.installer.install(deps, forced);
|
|
501
|
+
ctx.get("console")?.refresh("dependencies");
|
|
502
|
+
ctx.get("console")?.refresh("registry");
|
|
503
|
+
ctx.get("console")?.refresh("packages");
|
|
504
|
+
return code;
|
|
505
|
+
}, { authority: 4 });
|
|
506
|
+
ctx.console.addListener("market/registry", async (names) => {
|
|
507
|
+
const meta = await Promise.all(names.map((name) => ctx.installer.getPackage(name)));
|
|
508
|
+
return Object.fromEntries(meta.map((meta, index) => [names[index], meta]));
|
|
509
|
+
}, { authority: 4 });
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
export { Config, DependencyProvider, Installer, MarketProvider, RegistryProvider, apply, inject, name, usage };
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@koishi-ce/plugin-market",
|
|
3
|
+
"description": "Manage your bots and plugins with console",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.mjs",
|
|
7
|
+
"typings": "lib/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"lib",
|
|
10
|
+
"dist",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"contributors": [
|
|
14
|
+
"Shigma <shigma10826@gmail.com>",
|
|
15
|
+
"Oppenheymu <oppenheymu@gmail.com>"
|
|
16
|
+
],
|
|
17
|
+
"license": "AGPL-3.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Koishi-CE/koishi.git",
|
|
21
|
+
"directory": "plugins/webui/market"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Koishi-CE/koishi/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://koishi.chat/plugins/console/market.html",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"bot",
|
|
29
|
+
"chatbot",
|
|
30
|
+
"koishi",
|
|
31
|
+
"plugin",
|
|
32
|
+
"market",
|
|
33
|
+
"manager",
|
|
34
|
+
"server"
|
|
35
|
+
],
|
|
36
|
+
"koishi": {
|
|
37
|
+
"public": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"description": {
|
|
41
|
+
"en": "Manage your plugins with console",
|
|
42
|
+
"zh": "使用控制台安装、卸载、更新你的插件"
|
|
43
|
+
},
|
|
44
|
+
"service": {
|
|
45
|
+
"optional": [
|
|
46
|
+
"console"
|
|
47
|
+
],
|
|
48
|
+
"implements": [
|
|
49
|
+
"installer"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@koishijs/plugin-console": "^5.30.11",
|
|
55
|
+
"koishi": "^4.18.11"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"@koishijs/plugin-console": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@koishi-ce/client": "^1.0.0",
|
|
64
|
+
"@koishi-ce/loader": "^1.0.0",
|
|
65
|
+
"@koishi-ce/plugin-config": "^1.0.0",
|
|
66
|
+
"@koishijs/market": "^4.2.10",
|
|
67
|
+
"@types/semver": "^7.5.8",
|
|
68
|
+
"vue": "^3.5.12"
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@koishi-ce/console": "^1.0.0",
|
|
72
|
+
"@koishi-ce/registry": "^1.0.0",
|
|
73
|
+
"execa": "^5.1.1",
|
|
74
|
+
"get-registry": "^1.2.0",
|
|
75
|
+
"p-map": "^4.0.0",
|
|
76
|
+
"semver": "^7.6.3",
|
|
77
|
+
"which-pm-runs": "^1.1.0"
|
|
78
|
+
},
|
|
79
|
+
"exports": {
|
|
80
|
+
".": {
|
|
81
|
+
"source": "./src/index.ts",
|
|
82
|
+
"types": "./lib/index.d.ts",
|
|
83
|
+
"import": "./lib/index.mjs",
|
|
84
|
+
"default": "./lib/index.mjs"
|
|
85
|
+
},
|
|
86
|
+
"./package.json": "./package.json"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Context, Schema } from "@koishi-ce/koishi";
|
|
2
|
+
import MarketProvider from "./market.ts";
|
|
3
|
+
|
|
4
|
+
export * from "../shared/index.ts";
|
|
5
|
+
export * from "./market.ts";
|
|
6
|
+
|
|
7
|
+
export { MarketProvider };
|
|
8
|
+
|
|
9
|
+
export const filter = false;
|
|
10
|
+
export const name = "market";
|
|
11
|
+
export const inject = ["console"];
|
|
12
|
+
|
|
13
|
+
export type Config = Record<never, never>;
|
|
14
|
+
|
|
15
|
+
export const Config: Schema<Config> = Schema.object({});
|
|
16
|
+
|
|
17
|
+
export function apply(ctx: Context, _config: Config) {
|
|
18
|
+
ctx.plugin(MarketProvider);
|
|
19
|
+
|
|
20
|
+
const base = process.env["KOISHI_BASE"];
|
|
21
|
+
ctx.console.addEntry(
|
|
22
|
+
base
|
|
23
|
+
? [`${base}/dist/index.js`, `${base}/dist/style.css`]
|
|
24
|
+
: [import.meta.url.replace(/\/src\/[^/]+\/[^/]+$/, "/client/index.ts")],
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type {} from "@koishi-ce/plugin-config";
|
|
2
|
+
import { MarketProvider as BaseMarketProvider } from "../shared/index.ts";
|
|
3
|
+
|
|
4
|
+
export default class MarketProvider extends BaseMarketProvider {
|
|
5
|
+
async collect() {
|
|
6
|
+
return this.ctx.loader.market;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async get() {
|
|
10
|
+
const market = await this.prepare();
|
|
11
|
+
if (!market) return { data: {}, failed: 0, total: 0, progress: 0 };
|
|
12
|
+
return {
|
|
13
|
+
data: Object.fromEntries(
|
|
14
|
+
market.objects.map((item) => [item.package.name, item]),
|
|
15
|
+
),
|
|
16
|
+
failed: 0,
|
|
17
|
+
total: market.objects.length,
|
|
18
|
+
progress: market.objects.length,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/index.ts
ADDED
package/src/node/deps.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DataService } from "@koishi-ce/console";
|
|
2
|
+
import type { Context, Dict } from "@koishi-ce/koishi";
|
|
3
|
+
import type { DependencyMetaKey, RemotePackage } from "@koishi-ce/registry";
|
|
4
|
+
import type { Dependency } from "./installer.ts";
|
|
5
|
+
|
|
6
|
+
class DependencyProvider extends DataService<Dict<Dependency>> {
|
|
7
|
+
constructor(ctx: Context) {
|
|
8
|
+
super(ctx, "dependencies", { authority: 4 });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
override async get() {
|
|
12
|
+
return this.ctx.installer.getDeps();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class RegistryProvider extends DataService<
|
|
17
|
+
Dict<Dict<Pick<RemotePackage, DependencyMetaKey>>>
|
|
18
|
+
> {
|
|
19
|
+
constructor(ctx: Context) {
|
|
20
|
+
super(ctx, "registry", { authority: 4 });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override async get() {
|
|
24
|
+
return this.ctx.installer.fullCache;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { DependencyProvider, RegistryProvider };
|