@delta-comic/plugin 2.1.0 → 3.0.0-next.4
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/global-B4SQiBzS.mjs +1446 -0
- package/dist/global-B4SQiBzS.mjs.map +1 -0
- package/dist/lib/index.d.mts +927 -0
- package/dist/lib/index.mjs +535 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/native-CHH-Olpj.mjs +63 -0
- package/dist/native-CHH-Olpj.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/dist/runtime-DQbAfFDr.mjs +663 -0
- package/dist/runtime-DQbAfFDr.mjs.map +1 -0
- package/dist/storage-DMKDlPzl.mjs +351 -0
- package/dist/storage-DMKDlPzl.mjs.map +1 -0
- package/dist/vite/index.d.mts +33 -0
- package/dist/vite/index.mjs +81 -0
- package/dist/vite/index.mjs.map +1 -0
- package/package.json +30 -37
- package/README.md +0 -42
- package/dist/core-BF7YwnuZ.js +0 -2
- package/dist/core-Czlf2w3A.js +0 -521
- package/dist/core-Czlf2w3A.js.map +0 -1
- package/dist/index.css +0 -3
- package/dist/index.js +0 -6764
- package/dist/index.js.map +0 -1
- package/dist/lib/config.d.ts +0 -71
- package/dist/lib/depends.d.ts +0 -7
- package/dist/lib/driver/booter.d.ts +0 -4
- package/dist/lib/driver/core.d.ts +0 -67
- package/dist/lib/driver/icon.d.ts +0 -3
- package/dist/lib/driver/index.d.ts +0 -4
- package/dist/lib/driver/init/booter/0_configSetter.d.ts +0 -8
- package/dist/lib/driver/init/booter/10_apiTest.d.ts +0 -9
- package/dist/lib/driver/init/booter/20_resourceTest.d.ts +0 -8
- package/dist/lib/driver/init/booter/30_boot.d.ts +0 -8
- package/dist/lib/driver/init/booter/40_auth.d.ts +0 -8
- package/dist/lib/driver/init/booter/50_otherProcess.d.ts +0 -8
- package/dist/lib/driver/init/booter/utils.d.ts +0 -3
- package/dist/lib/driver/init/installer/10_normalUrl.d.ts +0 -12
- package/dist/lib/driver/init/installer/20_local.d.ts +0 -12
- package/dist/lib/driver/init/installer/30_dev.d.ts +0 -12
- package/dist/lib/driver/init/installer/40_github.d.ts +0 -12
- package/dist/lib/driver/init/installer/9999_awesome.d.ts +0 -12
- package/dist/lib/driver/init/loader/1_userscript.d.ts +0 -11
- package/dist/lib/driver/init/loader/2_zip.d.ts +0 -11
- package/dist/lib/driver/init/utils.d.ts +0 -29
- package/dist/lib/driver/install.d.ts +0 -14
- package/dist/lib/driver/loader.d.ts +0 -5
- package/dist/lib/driver/store.d.ts +0 -37
- package/dist/lib/env/Inject.vue.d.ts +0 -18
- package/dist/lib/env/index.d.ts +0 -12
- package/dist/lib/global.d.ts +0 -26
- package/dist/lib/index.d.ts +0 -6
- package/dist/lib/plugin/api.d.ts +0 -8
- package/dist/lib/plugin/auth.d.ts +0 -15
- package/dist/lib/plugin/content.d.ts +0 -8
- package/dist/lib/plugin/index.d.ts +0 -58
- package/dist/lib/plugin/otherProgress.d.ts +0 -4
- package/dist/lib/plugin/resource.d.ts +0 -5
- package/dist/lib/plugin/search.d.ts +0 -75
- package/dist/lib/plugin/share.d.ts +0 -26
- package/dist/lib/plugin/subscribe.d.ts +0 -15
- package/dist/lib/plugin/user.d.ts +0 -47
- package/dist/pack.tgz +0 -0
|
@@ -0,0 +1,1446 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
+
import { l as releasePluginObjectUrls, o as isTauriRuntime, u as removePluginFiles } from "./storage-DMKDlPzl.mjs";
|
|
3
|
+
import { isFunction } from "es-toolkit/compat";
|
|
4
|
+
import { db, useConfig } from "@delta-comic/db";
|
|
5
|
+
import { computed, h, markRaw, ref, shallowReactive, shallowRef } from "vue";
|
|
6
|
+
import { SourcedKeyMap, uni } from "@delta-comic/model";
|
|
7
|
+
import { createDownloadMessage, environmentRegistry } from "@delta-comic/ui";
|
|
8
|
+
import { SharedFunction } from "@delta-comic/utils";
|
|
9
|
+
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from "lz-string";
|
|
10
|
+
import { logger } from "@delta-comic/logger";
|
|
11
|
+
import { Mutex, isString } from "es-toolkit";
|
|
12
|
+
//#region lib/plugin/index.ts
|
|
13
|
+
/**
|
|
14
|
+
* 这仅是个辅助定义的函数,没有副作用
|
|
15
|
+
*/
|
|
16
|
+
const definePlugin = (config) => {
|
|
17
|
+
if (isFunction(config)) return config;
|
|
18
|
+
return () => config;
|
|
19
|
+
};
|
|
20
|
+
/** Defines a trusted plugin that is bundled with the application. */
|
|
21
|
+
const defineInnerPlugin = (definition) => definition;
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region lib/configPointer.ts
|
|
24
|
+
var ConfigPointer = class {
|
|
25
|
+
pluginName;
|
|
26
|
+
config;
|
|
27
|
+
configName;
|
|
28
|
+
key;
|
|
29
|
+
constructor(pluginName, config, configName) {
|
|
30
|
+
this.pluginName = pluginName;
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.configName = configName;
|
|
33
|
+
this.key = Symbol.for(`config:${pluginName}`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region lib/features/core/config.ts
|
|
38
|
+
const coreConfig = new ConfigPointer("core", {
|
|
39
|
+
recordHistory: {
|
|
40
|
+
type: "switch",
|
|
41
|
+
defaultValue: true,
|
|
42
|
+
info: "settings.core.recordHistory"
|
|
43
|
+
},
|
|
44
|
+
showAIProject: {
|
|
45
|
+
type: "switch",
|
|
46
|
+
defaultValue: true,
|
|
47
|
+
info: "settings.core.showAiWorks"
|
|
48
|
+
},
|
|
49
|
+
darkMode: {
|
|
50
|
+
type: "radio",
|
|
51
|
+
defaultValue: "system",
|
|
52
|
+
info: "settings.core.theme.title",
|
|
53
|
+
comp: "select",
|
|
54
|
+
selects: [
|
|
55
|
+
{
|
|
56
|
+
label: "settings.core.theme.light",
|
|
57
|
+
value: "light"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: "settings.core.theme.dark",
|
|
61
|
+
value: "dark"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
label: "settings.core.systemDefault",
|
|
65
|
+
value: "system"
|
|
66
|
+
}
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
language: {
|
|
70
|
+
type: "radio",
|
|
71
|
+
defaultValue: "system",
|
|
72
|
+
info: "settings.core.language.title",
|
|
73
|
+
comp: "select",
|
|
74
|
+
selects: [
|
|
75
|
+
{
|
|
76
|
+
label: "settings.core.language.zhCN",
|
|
77
|
+
value: "zh-CN"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
label: "settings.core.language.zhTW",
|
|
81
|
+
value: "zh-TW"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
label: "settings.core.language.enUS",
|
|
85
|
+
value: "en-US"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: "settings.core.systemDefault",
|
|
89
|
+
value: "system"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
},
|
|
93
|
+
easilyTitle: {
|
|
94
|
+
type: "switch",
|
|
95
|
+
defaultValue: false,
|
|
96
|
+
info: "settings.core.simplifiedTitle"
|
|
97
|
+
},
|
|
98
|
+
githubToken: {
|
|
99
|
+
type: "string",
|
|
100
|
+
defaultValue: "",
|
|
101
|
+
info: "settings.core.githubToken.title",
|
|
102
|
+
placeholder: "settings.core.githubToken.placeholder"
|
|
103
|
+
},
|
|
104
|
+
receivePerReleaseUpdate: {
|
|
105
|
+
type: "switch",
|
|
106
|
+
defaultValue: false,
|
|
107
|
+
info: "settings.core.prereleaseUpdates"
|
|
108
|
+
},
|
|
109
|
+
cloudEnabled: {
|
|
110
|
+
type: "switch",
|
|
111
|
+
defaultValue: false,
|
|
112
|
+
info: "settings.core.cloud.enabled"
|
|
113
|
+
},
|
|
114
|
+
cloudServerUrl: {
|
|
115
|
+
type: "string",
|
|
116
|
+
defaultValue: "",
|
|
117
|
+
info: "settings.core.cloud.serverUrl",
|
|
118
|
+
placeholder: "settings.core.cloud.serverUrlPlaceholder"
|
|
119
|
+
},
|
|
120
|
+
installOverride: {
|
|
121
|
+
type: "pairs",
|
|
122
|
+
defaultValue: [],
|
|
123
|
+
info: "settings.core.installOverride",
|
|
124
|
+
required: true
|
|
125
|
+
}
|
|
126
|
+
}, "settings.core.title");
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region lib/config.ts
|
|
129
|
+
var ConfigStore = class {
|
|
130
|
+
form = shallowReactive(/* @__PURE__ */ new Map());
|
|
131
|
+
darkMode = computed(() => {
|
|
132
|
+
if (!this.$isExistConfig(coreConfig)) return this.isSystemDark;
|
|
133
|
+
switch (this.$load(coreConfig).data.value.darkMode) {
|
|
134
|
+
case "light": return false;
|
|
135
|
+
case "dark": return true;
|
|
136
|
+
default: return this.isSystemDark;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
isSystemDark = globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
|
|
140
|
+
get isDark() {
|
|
141
|
+
return this.darkMode.value;
|
|
142
|
+
}
|
|
143
|
+
constructor() {
|
|
144
|
+
this.$registerConfig(coreConfig);
|
|
145
|
+
}
|
|
146
|
+
$load(pointer) {
|
|
147
|
+
const value = this.form.get(pointer.key);
|
|
148
|
+
if (!value) throw new Error(`not found config by plugin "${pointer.pluginName}"`);
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
$loadApp() {
|
|
152
|
+
return this.$load(coreConfig);
|
|
153
|
+
}
|
|
154
|
+
$isExistConfig(pointer) {
|
|
155
|
+
return this.form.has(pointer.key);
|
|
156
|
+
}
|
|
157
|
+
$registerConfig(pointer) {
|
|
158
|
+
const registered = this.form.get(pointer.key);
|
|
159
|
+
if (registered) return registered;
|
|
160
|
+
const store = useConfig(pointer.pluginName, pointer.config);
|
|
161
|
+
const saved = {
|
|
162
|
+
form: pointer.config,
|
|
163
|
+
data: store,
|
|
164
|
+
name: pointer.configName,
|
|
165
|
+
ready: store.ready
|
|
166
|
+
};
|
|
167
|
+
this.form.set(pointer.key, saved);
|
|
168
|
+
return saved;
|
|
169
|
+
}
|
|
170
|
+
/** @deprecated Use `$registerConfig`. */
|
|
171
|
+
$resignerConfig(pointer) {
|
|
172
|
+
return this.$registerConfig(pointer);
|
|
173
|
+
}
|
|
174
|
+
$unregisterConfig(pointer) {
|
|
175
|
+
this.form.delete(pointer.key);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
let configStore;
|
|
179
|
+
const useConfig$1 = () => configStore ??= new ConfigStore();
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region lib/depends.ts
|
|
182
|
+
const exposeSymbol = (name) => Symbol.for(`expose:${name}`);
|
|
183
|
+
const declareDepType = (name) => exposeSymbol(name);
|
|
184
|
+
var DependencyRegistry = class {
|
|
185
|
+
exposes;
|
|
186
|
+
constructor(exposes = /* @__PURE__ */ new Map()) {
|
|
187
|
+
this.exposes = exposes;
|
|
188
|
+
}
|
|
189
|
+
provide(define, value) {
|
|
190
|
+
this.exposes.set(define, value);
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
require(define) {
|
|
194
|
+
if (!this.exposes.has(define)) {
|
|
195
|
+
const name = Symbol.keyFor(define) ?? define.description ?? define.toString();
|
|
196
|
+
throw new Error(`not found plugin expose "${name}"`);
|
|
197
|
+
}
|
|
198
|
+
return this.exposes.get(define);
|
|
199
|
+
}
|
|
200
|
+
has(define) {
|
|
201
|
+
return this.exposes.has(define);
|
|
202
|
+
}
|
|
203
|
+
delete(define) {
|
|
204
|
+
return this.exposes.delete(define);
|
|
205
|
+
}
|
|
206
|
+
clear() {
|
|
207
|
+
this.exposes.clear();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const pluginExposes = /* @__PURE__ */ new Map();
|
|
211
|
+
const defaultDependencyRegistry = new DependencyRegistry(pluginExposes);
|
|
212
|
+
const createDependencyRegistry = () => new DependencyRegistry();
|
|
213
|
+
const provide = (define, value) => defaultDependencyRegistry.provide(define, value);
|
|
214
|
+
const require = (define) => defaultDependencyRegistry.require(define);
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region lib/i18n.ts
|
|
217
|
+
const messageKeyPrefix = "i18n:";
|
|
218
|
+
const unsafeKeys = /* @__PURE__ */ new Set([
|
|
219
|
+
"__proto__",
|
|
220
|
+
"constructor",
|
|
221
|
+
"prototype"
|
|
222
|
+
]);
|
|
223
|
+
const mergeMessages = (target, source) => {
|
|
224
|
+
if (!source) return target;
|
|
225
|
+
for (const [key, value] of Object.entries(source)) {
|
|
226
|
+
if (unsafeKeys.has(key)) continue;
|
|
227
|
+
if (typeof value === "string") {
|
|
228
|
+
target[key] = value;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const current = target[key];
|
|
232
|
+
target[key] = mergeMessages(typeof current === "object" ? { ...current } : {}, value);
|
|
233
|
+
}
|
|
234
|
+
return target;
|
|
235
|
+
};
|
|
236
|
+
var PluginI18nRegistry = class {
|
|
237
|
+
adapter;
|
|
238
|
+
baseMessages = {};
|
|
239
|
+
pluginMessages = /* @__PURE__ */ new Map();
|
|
240
|
+
install(adapter, baseMessages) {
|
|
241
|
+
this.adapter = adapter;
|
|
242
|
+
this.baseMessages = baseMessages;
|
|
243
|
+
this.refresh(this.locales());
|
|
244
|
+
}
|
|
245
|
+
register(plugin, messages) {
|
|
246
|
+
const previous = this.pluginMessages.get(plugin);
|
|
247
|
+
this.pluginMessages.delete(plugin);
|
|
248
|
+
this.pluginMessages.set(plugin, messages);
|
|
249
|
+
this.refresh(/* @__PURE__ */ new Set([...Object.keys(previous ?? {}), ...Object.keys(messages)]));
|
|
250
|
+
}
|
|
251
|
+
remove(plugin) {
|
|
252
|
+
const messages = this.pluginMessages.get(plugin);
|
|
253
|
+
if (!messages) return;
|
|
254
|
+
this.pluginMessages.delete(plugin);
|
|
255
|
+
this.refresh(new Set(Object.keys(messages)));
|
|
256
|
+
}
|
|
257
|
+
translate(key, params) {
|
|
258
|
+
return this.adapter?.translate?.(key, params) ?? key;
|
|
259
|
+
}
|
|
260
|
+
compose(locale) {
|
|
261
|
+
const message = mergeMessages({}, this.baseMessages[locale]);
|
|
262
|
+
for (const messages of this.pluginMessages.values()) mergeMessages(message, messages[locale]);
|
|
263
|
+
return message;
|
|
264
|
+
}
|
|
265
|
+
locales() {
|
|
266
|
+
const locales = new Set(Object.keys(this.baseMessages));
|
|
267
|
+
for (const messages of this.pluginMessages.values()) for (const locale of Object.keys(messages)) locales.add(locale);
|
|
268
|
+
return locales;
|
|
269
|
+
}
|
|
270
|
+
refresh(locales) {
|
|
271
|
+
if (!this.adapter) return;
|
|
272
|
+
for (const locale of locales) this.adapter.setLocaleMessage(locale, this.compose(locale));
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
const pluginI18n = new PluginI18nRegistry();
|
|
276
|
+
const pluginMessageKey = (key) => `${messageKeyPrefix}${key}`;
|
|
277
|
+
const translatePluginText = (value) => value.startsWith(messageKeyPrefix) ? pluginI18n.translate(value.slice(5)) : value;
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region lib/driver/store.ts
|
|
280
|
+
const pluginStoreLogger = logger.scoped("plugin:store");
|
|
281
|
+
var PluginStore = class {
|
|
282
|
+
plugins = shallowReactive(/* @__PURE__ */ new Map());
|
|
283
|
+
/** Plugins whose complete boot pipeline has completed successfully. */
|
|
284
|
+
ready = shallowReactive(/* @__PURE__ */ new Set());
|
|
285
|
+
revision = shallowRef(0);
|
|
286
|
+
pluginNames = shallowReactive(/* @__PURE__ */ new Map());
|
|
287
|
+
/**
|
|
288
|
+
* Returns true only when every registered booter has completed successfully.
|
|
289
|
+
*
|
|
290
|
+
* `plugins.has()` intentionally is not used here: the config is inserted before
|
|
291
|
+
* the first booter runs and can therefore describe a partially initialized plugin.
|
|
292
|
+
*/
|
|
293
|
+
$isLoaded(plugin) {
|
|
294
|
+
return this.ready.has(plugin);
|
|
295
|
+
}
|
|
296
|
+
/** Starts a new load generation and invalidates the previous readiness state. */
|
|
297
|
+
$markLoading(plugin) {
|
|
298
|
+
this.ready.delete(plugin);
|
|
299
|
+
}
|
|
300
|
+
/** Marks a plugin ready after all booters have completed. */
|
|
301
|
+
$markReady(plugin) {
|
|
302
|
+
this.ready.add(plugin);
|
|
303
|
+
}
|
|
304
|
+
/** Removes all lifecycle state for a plugin. */
|
|
305
|
+
$markUnloaded(plugin) {
|
|
306
|
+
this.ready.delete(plugin);
|
|
307
|
+
}
|
|
308
|
+
async $refreshI18nNames() {
|
|
309
|
+
const names = await db.selectFrom("plugin").select(["pluginName", "displayName"]).execute();
|
|
310
|
+
this.pluginNames.clear();
|
|
311
|
+
for (const plugin of names) this.pluginNames.set(plugin.pluginName, plugin.displayName);
|
|
312
|
+
pluginStoreLogger.debug("plugin display names refreshed", { count: names.length });
|
|
313
|
+
}
|
|
314
|
+
$getI18nName(key) {
|
|
315
|
+
return translatePluginText(this.pluginNames.get(key) || key);
|
|
316
|
+
}
|
|
317
|
+
async $upsertArchives(archives) {
|
|
318
|
+
if (archives.length === 0) return;
|
|
319
|
+
await db.replaceInto("plugin").values(archives.map((archive) => ({
|
|
320
|
+
...archive,
|
|
321
|
+
meta: JSON.stringify(archive.meta)
|
|
322
|
+
}))).execute();
|
|
323
|
+
await this.$refreshI18nNames();
|
|
324
|
+
this.$touch();
|
|
325
|
+
pluginStoreLogger.info("plugin archives persisted", { count: archives.length });
|
|
326
|
+
}
|
|
327
|
+
$touch() {
|
|
328
|
+
this.revision.value++;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const pluginStore = new PluginStore();
|
|
332
|
+
const usePluginStore = () => pluginStore;
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region lib/driver/loadPlan.ts
|
|
335
|
+
const getDependencyNames = (plugin) => plugin.meta.require.map((dep) => dep.id);
|
|
336
|
+
const canonicalCycleKey = (cycle) => {
|
|
337
|
+
const nodes = cycle.slice(0, -1);
|
|
338
|
+
const rotations = nodes.map((_, index) => nodes.slice(index).concat(nodes.slice(0, index)));
|
|
339
|
+
rotations.sort((a, b) => a.join("\0").localeCompare(b.join("\0")));
|
|
340
|
+
return rotations[0].join("\0");
|
|
341
|
+
};
|
|
342
|
+
const findCyclePaths = (plugins) => {
|
|
343
|
+
const pluginNames = new Set(plugins.map((plugin) => plugin.pluginName));
|
|
344
|
+
const dependencies = new Map(plugins.map((plugin) => [plugin.pluginName, getDependencyNames(plugin).filter((dep) => pluginNames.has(dep))]));
|
|
345
|
+
const state = /* @__PURE__ */ new Map();
|
|
346
|
+
const path = [];
|
|
347
|
+
const cycleKeys = /* @__PURE__ */ new Set();
|
|
348
|
+
const cycles = [];
|
|
349
|
+
const visit = (pluginName) => {
|
|
350
|
+
state.set(pluginName, "visiting");
|
|
351
|
+
path.push(pluginName);
|
|
352
|
+
for (const dependencyName of dependencies.get(pluginName) ?? []) {
|
|
353
|
+
const dependencyState = state.get(dependencyName);
|
|
354
|
+
if (dependencyState === "visiting") {
|
|
355
|
+
const cycleStart = path.lastIndexOf(dependencyName);
|
|
356
|
+
if (cycleStart === -1) continue;
|
|
357
|
+
const cycle = path.slice(cycleStart).concat(dependencyName);
|
|
358
|
+
const key = canonicalCycleKey(cycle);
|
|
359
|
+
if (!cycleKeys.has(key)) {
|
|
360
|
+
cycleKeys.add(key);
|
|
361
|
+
cycles.push(cycle);
|
|
362
|
+
}
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (dependencyState !== "visited") visit(dependencyName);
|
|
366
|
+
}
|
|
367
|
+
path.pop();
|
|
368
|
+
state.set(pluginName, "visited");
|
|
369
|
+
};
|
|
370
|
+
for (const plugin of plugins) if (!state.has(plugin.pluginName)) visit(plugin.pluginName);
|
|
371
|
+
return cycles;
|
|
372
|
+
};
|
|
373
|
+
const planPluginLoadOrder = (plugins) => {
|
|
374
|
+
const nameToPlugin = new Map(plugins.map((plugin) => [plugin.pluginName, plugin]));
|
|
375
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
376
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
377
|
+
const missing = [];
|
|
378
|
+
for (const plugin of plugins) {
|
|
379
|
+
const installedDependencies = getDependencyNames(plugin).filter((dependencyName) => {
|
|
380
|
+
const isInstalled = nameToPlugin.has(dependencyName);
|
|
381
|
+
if (!isInstalled) missing.push({
|
|
382
|
+
pluginName: plugin.pluginName,
|
|
383
|
+
dependencyName
|
|
384
|
+
});
|
|
385
|
+
return isInstalled;
|
|
386
|
+
});
|
|
387
|
+
inDegree.set(plugin.pluginName, installedDependencies.length);
|
|
388
|
+
for (const dependencyName of installedDependencies) {
|
|
389
|
+
const dependents = adjacency.get(dependencyName) ?? [];
|
|
390
|
+
dependents.push(plugin.pluginName);
|
|
391
|
+
adjacency.set(dependencyName, dependents);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const queue = [...inDegree.entries()].filter(([, degree]) => degree === 0).map(([pluginName]) => pluginName);
|
|
395
|
+
const levels = [];
|
|
396
|
+
while (queue.length > 0) {
|
|
397
|
+
const levelSize = queue.length;
|
|
398
|
+
const level = [];
|
|
399
|
+
for (let index = 0; index < levelSize; index++) {
|
|
400
|
+
const pluginName = queue.shift();
|
|
401
|
+
if (!pluginName) continue;
|
|
402
|
+
const plugin = nameToPlugin.get(pluginName);
|
|
403
|
+
if (plugin) level.push(plugin);
|
|
404
|
+
for (const dependentName of adjacency.get(pluginName) ?? []) {
|
|
405
|
+
const newDegree = (inDegree.get(dependentName) ?? 0) - 1;
|
|
406
|
+
inDegree.set(dependentName, newDegree);
|
|
407
|
+
if (newDegree === 0) queue.push(dependentName);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (level.length > 0) levels.push(level);
|
|
411
|
+
}
|
|
412
|
+
const unresolved = plugins.filter((plugin) => (inDegree.get(plugin.pluginName) ?? 0) > 0);
|
|
413
|
+
return {
|
|
414
|
+
levels,
|
|
415
|
+
missing,
|
|
416
|
+
cycles: findCyclePaths(unresolved)
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
const formatPluginLoadPlanError = (plan, translate = (key, params) => pluginI18n.translate(key, params)) => {
|
|
420
|
+
const sections = [];
|
|
421
|
+
if (plan.missing.length > 0) {
|
|
422
|
+
const missing = plan.missing.map(({ pluginName, dependencyName }) => `${pluginName} -> ${dependencyName}`).join("\n");
|
|
423
|
+
sections.push(translate("plugin.runtime.errors.missingDependencies", { missing }));
|
|
424
|
+
}
|
|
425
|
+
if (plan.cycles.length > 0) {
|
|
426
|
+
const paths = plan.cycles.map((path) => path.join(" -> ")).join("\n");
|
|
427
|
+
sections.push(translate("plugin.runtime.errors.dependencyCycles", { paths }));
|
|
428
|
+
}
|
|
429
|
+
return sections.join("\n\n");
|
|
430
|
+
};
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region lib/features/core/feature.ts
|
|
433
|
+
var feature_default = defineInnerPlugin({
|
|
434
|
+
meta: {
|
|
435
|
+
kind: "preboot",
|
|
436
|
+
author: "Delta Comic",
|
|
437
|
+
description: pluginMessageKey("plugin.core.description"),
|
|
438
|
+
require: [],
|
|
439
|
+
name: {
|
|
440
|
+
display: pluginMessageKey("settings.core.title"),
|
|
441
|
+
id: "core"
|
|
442
|
+
},
|
|
443
|
+
version: {
|
|
444
|
+
plugin: "2.3.0",
|
|
445
|
+
supportCore: "*"
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
enabledByDefault: true,
|
|
449
|
+
config: () => ({
|
|
450
|
+
name: "core",
|
|
451
|
+
config: [coreConfig]
|
|
452
|
+
})
|
|
453
|
+
});
|
|
454
|
+
//#endregion
|
|
455
|
+
//#region lib/features/registry.ts
|
|
456
|
+
const rawDefinitions = /* #__PURE__ */ Object.assign({ "./core/feature.ts": feature_default });
|
|
457
|
+
const createBuiltInPluginRegistry = (definitions) => {
|
|
458
|
+
const registry = /* @__PURE__ */ new Map();
|
|
459
|
+
for (const definition of definitions) {
|
|
460
|
+
const pluginName = definition.meta.name.id;
|
|
461
|
+
if (!pluginName) throw new Error("built-in plugin id cannot be empty");
|
|
462
|
+
if (registry.has(pluginName)) throw new Error(`duplicate built-in plugin: ${pluginName}`);
|
|
463
|
+
registry.set(pluginName, definition);
|
|
464
|
+
}
|
|
465
|
+
return registry;
|
|
466
|
+
};
|
|
467
|
+
const builtInPluginRegistry = createBuiltInPluginRegistry(Object.values(rawDefinitions));
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region lib/driver/builtIn.ts
|
|
470
|
+
const builtInLogger = logger.scoped("plugin:builtin");
|
|
471
|
+
const BUILT_IN_PLUGIN_LOADER = "builtin";
|
|
472
|
+
const isBuiltInPluginName = (pluginName) => builtInPluginRegistry.has(pluginName);
|
|
473
|
+
const isBuiltInPlugin = (plugin) => plugin.loaderName === BUILT_IN_PLUGIN_LOADER;
|
|
474
|
+
const builtInDefinitionToArchive = (definition, enable = definition.enabledByDefault ?? true) => ({
|
|
475
|
+
displayName: definition.meta.name.display,
|
|
476
|
+
enable,
|
|
477
|
+
installerName: BUILT_IN_PLUGIN_LOADER,
|
|
478
|
+
installInput: "",
|
|
479
|
+
loaderName: BUILT_IN_PLUGIN_LOADER,
|
|
480
|
+
meta: definition.meta,
|
|
481
|
+
pluginName: definition.meta.name.id
|
|
482
|
+
});
|
|
483
|
+
/** Mirrors compile-time definitions into the plugin catalog while preserving their enable state. */
|
|
484
|
+
const synchronizeBuiltInPlugins = async () => {
|
|
485
|
+
builtInLogger.info("built-in plugin synchronization started", { definitionCount: builtInPluginRegistry.size });
|
|
486
|
+
const { db } = await import("@delta-comic/db");
|
|
487
|
+
await db.transaction().execute(async (trx) => {
|
|
488
|
+
const existing = await trx.selectFrom("plugin").selectAll().execute();
|
|
489
|
+
const existingByName = new Map(existing.map((plugin) => [plugin.pluginName, plugin]));
|
|
490
|
+
for (const [pluginName, definition] of builtInPluginRegistry) {
|
|
491
|
+
const current = existingByName.get(pluginName);
|
|
492
|
+
const archive = builtInDefinitionToArchive(definition, current?.enable);
|
|
493
|
+
const values = {
|
|
494
|
+
...archive,
|
|
495
|
+
meta: JSON.stringify(archive.meta)
|
|
496
|
+
};
|
|
497
|
+
if (current) await trx.updateTable("plugin").set(values).where("pluginName", "=", pluginName).execute();
|
|
498
|
+
else await trx.insertInto("plugin").values(values).execute();
|
|
499
|
+
}
|
|
500
|
+
const currentNames = [...builtInPluginRegistry.keys()];
|
|
501
|
+
let staleQuery = trx.deleteFrom("plugin").where("loaderName", "=", BUILT_IN_PLUGIN_LOADER);
|
|
502
|
+
if (currentNames.length > 0) staleQuery = staleQuery.where("pluginName", "not in", currentNames);
|
|
503
|
+
await staleQuery.execute();
|
|
504
|
+
});
|
|
505
|
+
builtInLogger.info("built-in plugin synchronization completed");
|
|
506
|
+
};
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region lib/driver/cleanup.ts
|
|
509
|
+
const removeSourcedEntries = (map, plugin) => {
|
|
510
|
+
for (const key of map.keys()) if (key.split(":", 1)[0] === plugin) map.delete(key);
|
|
511
|
+
};
|
|
512
|
+
const cleanupPlugin = (config) => {
|
|
513
|
+
const plugin = config.name;
|
|
514
|
+
const pluginStore = usePluginStore();
|
|
515
|
+
const errors = [];
|
|
516
|
+
const attempt = (action) => {
|
|
517
|
+
try {
|
|
518
|
+
action();
|
|
519
|
+
} catch (error) {
|
|
520
|
+
errors.push(error);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
attempt(() => pluginStore.$markUnloaded(plugin));
|
|
524
|
+
attempt(() => Global.tabbar.delete(plugin));
|
|
525
|
+
attempt(() => Global.categories.delete(plugin));
|
|
526
|
+
attempt(() => Global.barcode.delete(plugin));
|
|
527
|
+
attempt(() => Global.hotSearch.delete(plugin));
|
|
528
|
+
attempt(() => Global.levelboard.delete(plugin));
|
|
529
|
+
attempt(() => Global.topButton.delete(plugin));
|
|
530
|
+
attempt(() => Global.mainLists.delete(plugin));
|
|
531
|
+
attempt(() => Global.removeOwnedRegistrations(plugin));
|
|
532
|
+
attempt(() => pluginI18n.remove(plugin));
|
|
533
|
+
attempt(() => removeSourcedEntries(Global.share, plugin));
|
|
534
|
+
attempt(() => removeSourcedEntries(Global.shareToken, plugin));
|
|
535
|
+
attempt(() => removeSourcedEntries(Global.userActions, plugin));
|
|
536
|
+
attempt(() => removeSourcedEntries(Global.subscribes, plugin));
|
|
537
|
+
for (const [contentType, value] of Object.entries(config.content ?? {})) {
|
|
538
|
+
if (value.layout) attempt(() => uni.content.ContentPage.layouts.delete(contentType));
|
|
539
|
+
if (value.itemCard) attempt(() => uni.item.Item.itemCards.delete(contentType));
|
|
540
|
+
if (value.contentPage) attempt(() => uni.content.ContentPage.contentPages.delete(contentType));
|
|
541
|
+
if (value.downloadProvider) attempt(() => uni.content.ContentPage.downloadProviders.delete(contentType));
|
|
542
|
+
if (value.commentRow) attempt(() => uni.comment.Comment.commentRow.delete(contentType));
|
|
543
|
+
if (value.itemTranslator) attempt(() => uni.item.Item.itemTranslator.delete(contentType));
|
|
544
|
+
}
|
|
545
|
+
for (const type of config.resource?.types ?? []) {
|
|
546
|
+
attempt(() => uni.resource.Resource.fork.delete([plugin, type.type]));
|
|
547
|
+
attempt(() => uni.resource.Resource.precedenceFork.delete([plugin, type.type]));
|
|
548
|
+
}
|
|
549
|
+
for (const name of Object.keys(config.resource?.process ?? {})) attempt(() => uni.resource.Resource.processInstances.delete([plugin, name]));
|
|
550
|
+
if (config.user?.card) attempt(() => uni.user.User.userCards.delete(plugin));
|
|
551
|
+
if (config.user?.edit) attempt(() => uni.user.User.userEditorBase.delete(plugin));
|
|
552
|
+
for (const key of Object.keys(config.user?.authorIcon ?? {})) attempt(() => uni.item.Item.authorIcon.delete([plugin, key]));
|
|
553
|
+
for (const pointer of config.config ?? []) attempt(() => useConfig$1().$unregisterConfig(pointer));
|
|
554
|
+
attempt(() => defaultDependencyRegistry.delete(declareDepType(plugin)));
|
|
555
|
+
attempt(() => pluginStore.plugins.delete(plugin));
|
|
556
|
+
attempt(() => releasePluginObjectUrls(plugin));
|
|
557
|
+
if (typeof document !== "undefined") {
|
|
558
|
+
for (const node of document.querySelectorAll("style[data-plugin]")) if (node.dataset.plugin === plugin) attempt(() => node.remove());
|
|
559
|
+
}
|
|
560
|
+
if (errors.length > 0) throw new AggregateError(errors, `failed to completely clean plugin "${plugin}"`);
|
|
561
|
+
};
|
|
562
|
+
//#endregion
|
|
563
|
+
//#region lib/driver/extensions.ts
|
|
564
|
+
var OrderedExtensionRegistry = class {
|
|
565
|
+
direction;
|
|
566
|
+
values = shallowReactive(new Array());
|
|
567
|
+
entries = [];
|
|
568
|
+
sequence = 0;
|
|
569
|
+
constructor(direction) {
|
|
570
|
+
this.direction = direction;
|
|
571
|
+
}
|
|
572
|
+
register(extension, options = {}, owner) {
|
|
573
|
+
const entry = {
|
|
574
|
+
extension,
|
|
575
|
+
order: options.order ?? 0,
|
|
576
|
+
owner,
|
|
577
|
+
sequence: this.sequence++
|
|
578
|
+
};
|
|
579
|
+
this.entries.push(entry);
|
|
580
|
+
this.refresh();
|
|
581
|
+
return () => {
|
|
582
|
+
const index = this.entries.indexOf(entry);
|
|
583
|
+
if (index < 0) return;
|
|
584
|
+
this.entries.splice(index, 1);
|
|
585
|
+
this.refresh();
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
removeOwner(owner) {
|
|
589
|
+
let changed = false;
|
|
590
|
+
for (let index = this.entries.length - 1; index >= 0; index--) {
|
|
591
|
+
if (this.entries[index].owner !== owner) continue;
|
|
592
|
+
this.entries.splice(index, 1);
|
|
593
|
+
changed = true;
|
|
594
|
+
}
|
|
595
|
+
if (changed) this.refresh();
|
|
596
|
+
}
|
|
597
|
+
refresh() {
|
|
598
|
+
const activeByName = /* @__PURE__ */ new Map();
|
|
599
|
+
for (const entry of this.entries) activeByName.set(entry.extension.name, entry);
|
|
600
|
+
const direction = this.direction === "ascending" ? 1 : -1;
|
|
601
|
+
const active = [...activeByName.values()].sort((left, right) => (left.order - right.order) * direction || left.sequence - right.sequence).map((entry) => entry.extension);
|
|
602
|
+
this.values.splice(0, this.values.length, ...active);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
var PluginRuntimeExtensions = class {
|
|
606
|
+
booters = new OrderedExtensionRegistry("ascending");
|
|
607
|
+
installers = new OrderedExtensionRegistry("descending");
|
|
608
|
+
loaders = new OrderedExtensionRegistry("ascending");
|
|
609
|
+
owners = [];
|
|
610
|
+
registerBooter(booter, options) {
|
|
611
|
+
return this.booters.register(booter, options, this.owners.at(-1));
|
|
612
|
+
}
|
|
613
|
+
registerInstaller(installer, options) {
|
|
614
|
+
return this.installers.register(installer, options, this.owners.at(-1));
|
|
615
|
+
}
|
|
616
|
+
registerLoader(loader, options) {
|
|
617
|
+
return this.loaders.register(loader, options, this.owners.at(-1));
|
|
618
|
+
}
|
|
619
|
+
removeOwner(owner) {
|
|
620
|
+
this.booters.removeOwner(owner);
|
|
621
|
+
this.installers.removeOwner(owner);
|
|
622
|
+
this.loaders.removeOwner(owner);
|
|
623
|
+
}
|
|
624
|
+
async withOwner(owner, action) {
|
|
625
|
+
this.owners.push(owner);
|
|
626
|
+
try {
|
|
627
|
+
return await action();
|
|
628
|
+
} finally {
|
|
629
|
+
this.owners.pop();
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
const runtimeExtensions = new PluginRuntimeExtensions();
|
|
634
|
+
const registerBooter = runtimeExtensions.registerBooter.bind(runtimeExtensions);
|
|
635
|
+
const registerInstaller = runtimeExtensions.registerInstaller.bind(runtimeExtensions);
|
|
636
|
+
const registerLoader = runtimeExtensions.registerLoader.bind(runtimeExtensions);
|
|
637
|
+
//#endregion
|
|
638
|
+
//#region lib/driver/booter.ts
|
|
639
|
+
var booter_exports = /* @__PURE__ */ __exportAll({
|
|
640
|
+
bootPlugin: () => bootPlugin,
|
|
641
|
+
booters: () => booters
|
|
642
|
+
});
|
|
643
|
+
const pluginBooterLogger = logger.scoped("plugin:booter");
|
|
644
|
+
const booters = runtimeExtensions.booters.values;
|
|
645
|
+
const bootPlugin = async (cfg, info) => {
|
|
646
|
+
const store = usePluginStore();
|
|
647
|
+
store.$markLoading(cfg.name);
|
|
648
|
+
try {
|
|
649
|
+
store.plugins.set(cfg.name, markRaw(cfg));
|
|
650
|
+
const env = {};
|
|
651
|
+
for (const booter of booters) {
|
|
652
|
+
const msIndex = info.value[cfg.name].steps.length;
|
|
653
|
+
info.value[cfg.name].steps[msIndex] = {
|
|
654
|
+
name: booter.name,
|
|
655
|
+
description: ""
|
|
656
|
+
};
|
|
657
|
+
info.value[cfg.name].progress = {
|
|
658
|
+
stepsIndex: msIndex,
|
|
659
|
+
status: "process"
|
|
660
|
+
};
|
|
661
|
+
pluginBooterLogger.debug("plugin boot step started", {
|
|
662
|
+
plugin: cfg.name,
|
|
663
|
+
step: msIndex
|
|
664
|
+
});
|
|
665
|
+
await booter.call(cfg, (meta) => {
|
|
666
|
+
if (isString(meta)) info.value[cfg.name].steps[msIndex].description = meta;
|
|
667
|
+
else {
|
|
668
|
+
if (meta.description) info.value[cfg.name].steps[msIndex].description = meta.description;
|
|
669
|
+
if (meta.name) info.value[cfg.name].steps[msIndex].name = meta.name;
|
|
670
|
+
}
|
|
671
|
+
}, env);
|
|
672
|
+
}
|
|
673
|
+
info.value[cfg.name].progress.status = "done";
|
|
674
|
+
store.$markReady(cfg.name);
|
|
675
|
+
pluginBooterLogger.info("plugin boot pipeline completed", { plugin: cfg.name });
|
|
676
|
+
} catch (error) {
|
|
677
|
+
store.$markUnloaded(cfg.name);
|
|
678
|
+
pluginBooterLogger.error("plugin boot pipeline failed", { plugin: cfg.name }, error);
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
//#endregion
|
|
683
|
+
//#region lib/driver/loader.ts
|
|
684
|
+
var loader_exports = /* @__PURE__ */ __exportAll({
|
|
685
|
+
bootConfig: () => bootConfig,
|
|
686
|
+
bootResolvedConfig: () => bootResolvedConfig,
|
|
687
|
+
createPluginLoadingInfo: () => createPluginLoadingInfo,
|
|
688
|
+
ensurePluginLoadingInfo: () => ensurePluginLoadingInfo,
|
|
689
|
+
loadAllPlugins: () => loadAllPlugins,
|
|
690
|
+
loadPlugin: () => loadPlugin,
|
|
691
|
+
loadPluginConfig: () => loadPluginConfig,
|
|
692
|
+
loaders: () => loaders,
|
|
693
|
+
markPluginLoadError: () => markPluginLoadError
|
|
694
|
+
});
|
|
695
|
+
const pluginLoaderLogger = logger.scoped("plugin:loader");
|
|
696
|
+
const loaders = runtimeExtensions.loaders.values;
|
|
697
|
+
const loadLocks = {};
|
|
698
|
+
const getLoadLock = (pluginName) => loadLocks[pluginName] ??= new Mutex();
|
|
699
|
+
const createPluginLoadingInfo = () => ({
|
|
700
|
+
progress: {
|
|
701
|
+
status: "wait",
|
|
702
|
+
stepsIndex: 0
|
|
703
|
+
},
|
|
704
|
+
steps: [{
|
|
705
|
+
name: pluginMessageKey("plugin.runtime.steps.waiting.title"),
|
|
706
|
+
description: pluginMessageKey("plugin.runtime.steps.waiting.description")
|
|
707
|
+
}]
|
|
708
|
+
});
|
|
709
|
+
const ensurePluginLoadingInfo = (info, pluginName) => info.value[pluginName] ??= createPluginLoadingInfo();
|
|
710
|
+
const markPluginLoadError = (info, pluginName, error) => {
|
|
711
|
+
const loadingInfo = ensurePluginLoadingInfo(info, pluginName);
|
|
712
|
+
loadingInfo.progress = {
|
|
713
|
+
...loadingInfo.progress,
|
|
714
|
+
errorReason: error instanceof Error ? error.message : String(error),
|
|
715
|
+
status: "error"
|
|
716
|
+
};
|
|
717
|
+
};
|
|
718
|
+
/** 解析 config factory 并启动插件;任何中途失败都保留原始错误并执行回滚。 */
|
|
719
|
+
const bootConfig = async (configFactory, info, options = {}) => {
|
|
720
|
+
const { expectedName, rollback } = options;
|
|
721
|
+
if (expectedName) ensurePluginLoadingInfo(info, expectedName);
|
|
722
|
+
let cfg;
|
|
723
|
+
try {
|
|
724
|
+
cfg = configFactory({
|
|
725
|
+
safe: true,
|
|
726
|
+
platform: isTauriRuntime() ? "tauri" : "web"
|
|
727
|
+
});
|
|
728
|
+
if (expectedName && cfg.name !== expectedName) throw new Error(`plugin name mismatch: ${expectedName} / ${cfg.name}`);
|
|
729
|
+
await bootResolvedConfig(cfg, info);
|
|
730
|
+
return cfg;
|
|
731
|
+
} catch (error) {
|
|
732
|
+
const progressName = expectedName ?? cfg?.name;
|
|
733
|
+
if (progressName) markPluginLoadError(info, progressName, error);
|
|
734
|
+
if (rollback) try {
|
|
735
|
+
await rollback(cfg, expectedName);
|
|
736
|
+
} catch (rollbackError) {
|
|
737
|
+
pluginLoaderLogger.error("plugin boot rollback failed", { plugin: progressName }, rollbackError);
|
|
738
|
+
}
|
|
739
|
+
throw error;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
const bootResolvedConfig = async (cfg, info) => {
|
|
743
|
+
ensurePluginLoadingInfo(info, cfg.name);
|
|
744
|
+
await bootPlugin(cfg, info);
|
|
745
|
+
};
|
|
746
|
+
const loadPluginConfig = async (meta) => {
|
|
747
|
+
const lock = getLoadLock(meta.pluginName);
|
|
748
|
+
try {
|
|
749
|
+
await lock.acquire();
|
|
750
|
+
pluginLoaderLogger.debug("loading plugin config", {
|
|
751
|
+
loader: meta.loaderName,
|
|
752
|
+
plugin: meta.pluginName
|
|
753
|
+
});
|
|
754
|
+
if (isBuiltInPlugin(meta)) return builtInPluginRegistry.get(meta.pluginName)?.config;
|
|
755
|
+
const loader = loaders.find((value) => value.name === meta.loaderName);
|
|
756
|
+
if (!loader) throw new Error(pluginI18n.translate("plugin.runtime.errors.loaderMissing", {
|
|
757
|
+
loader: meta.loaderName,
|
|
758
|
+
plugin: meta.pluginName
|
|
759
|
+
}));
|
|
760
|
+
return await loader.load(meta);
|
|
761
|
+
} finally {
|
|
762
|
+
lock.release();
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
const loadPlugin = async (meta, info) => {
|
|
766
|
+
pluginLoaderLogger.info("plugin boot started", { plugin: meta.pluginName });
|
|
767
|
+
ensurePluginLoadingInfo(info, meta.pluginName);
|
|
768
|
+
try {
|
|
769
|
+
const configFactory = await loadPluginConfig(meta);
|
|
770
|
+
if (!configFactory) throw new Error(pluginI18n.translate("plugin.runtime.errors.defaultExportMissing", { plugin: meta.pluginName }));
|
|
771
|
+
await bootConfig(configFactory, info, {
|
|
772
|
+
expectedName: meta.pluginName,
|
|
773
|
+
rollback: (config) => cleanupPlugin(config ?? { name: meta.pluginName })
|
|
774
|
+
});
|
|
775
|
+
pluginLoaderLogger.info("plugin boot completed", { plugin: meta.pluginName });
|
|
776
|
+
} catch (error) {
|
|
777
|
+
pluginLoaderLogger.error("plugin boot failed", { plugin: meta.pluginName }, error);
|
|
778
|
+
markPluginLoadError(info, meta.pluginName, error);
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
const loadAllPlugins = () => Promise.resolve().then(() => runtime_exports).then(({ pluginRuntime }) => pluginRuntime.loadNormal());
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region lib/driver/runtimePlan.ts
|
|
785
|
+
const pluginKind = (plugin) => plugin.meta.kind ?? "normal";
|
|
786
|
+
const selectPluginsForPhase = (plugins, kind, startupPrebootNames = new Set(plugins.filter((plugin) => pluginKind(plugin) === "preboot").map((plugin) => plugin.pluginName)), activePrebootNames = startupPrebootNames) => {
|
|
787
|
+
const selected = plugins.filter((plugin) => kind === "preboot" ? startupPrebootNames.has(plugin.pluginName) : !startupPrebootNames.has(plugin.pluginName));
|
|
788
|
+
if (kind === "preboot") return selected;
|
|
789
|
+
return selected.map((plugin) => ({
|
|
790
|
+
...plugin,
|
|
791
|
+
meta: {
|
|
792
|
+
...plugin.meta,
|
|
793
|
+
require: plugin.meta.require.filter((dependency) => !activePrebootNames.has(dependency.id))
|
|
794
|
+
}
|
|
795
|
+
}));
|
|
796
|
+
};
|
|
797
|
+
const failedDependencies = (plugin, failedPluginNames) => plugin.meta.require.filter((dependency) => failedPluginNames.has(dependency.id)).map((v) => v.id);
|
|
798
|
+
const filterPluginsBySelection = (plugins, pluginNames) => pluginNames ? plugins.filter((plugin) => pluginNames.has(plugin.pluginName)) : plugins;
|
|
799
|
+
//#endregion
|
|
800
|
+
//#region lib/driver/runtime.ts
|
|
801
|
+
var runtime_exports = /* @__PURE__ */ __exportAll({ pluginRuntime: () => pluginRuntime });
|
|
802
|
+
const pluginRuntimeLogger = logger.scoped("plugin:runtime");
|
|
803
|
+
const recoveryKey = "delta-comic:preboot-recovery";
|
|
804
|
+
const reasonText = (error) => error instanceof Error ? error.message : String(error);
|
|
805
|
+
const createLoadingInfo = () => ({
|
|
806
|
+
progress: {
|
|
807
|
+
status: "wait",
|
|
808
|
+
stepsIndex: 0
|
|
809
|
+
},
|
|
810
|
+
steps: [{
|
|
811
|
+
name: pluginMessageKey("plugin.runtime.steps.waiting.title"),
|
|
812
|
+
description: pluginMessageKey("plugin.runtime.steps.waiting.description")
|
|
813
|
+
}]
|
|
814
|
+
});
|
|
815
|
+
var PluginRuntime = class {
|
|
816
|
+
active = /* @__PURE__ */ new Map();
|
|
817
|
+
activePrebootNames = /* @__PURE__ */ new Set();
|
|
818
|
+
prebootCleanups = /* @__PURE__ */ new Map();
|
|
819
|
+
preparedPreboot = /* @__PURE__ */ new Map();
|
|
820
|
+
startupPluginNames = /* @__PURE__ */ new Set();
|
|
821
|
+
startupPrebootNames = /* @__PURE__ */ new Set();
|
|
822
|
+
builtInSynchronization;
|
|
823
|
+
enabledPrebootNames = [];
|
|
824
|
+
normalOperation;
|
|
825
|
+
startupSnapshotReady = false;
|
|
826
|
+
prebootActivated = false;
|
|
827
|
+
get activeNormalPluginNames() {
|
|
828
|
+
return [...this.active.entries()].filter(([, plugin]) => plugin.kind === "normal").map(([name]) => name);
|
|
829
|
+
}
|
|
830
|
+
loadNormal(options = {}) {
|
|
831
|
+
if (this.normalOperation) throw new Error("normal plugins are already loading");
|
|
832
|
+
if (this.preparedPreboot.size > 0 && !this.prebootActivated) throw new Error("preboot plugins have not finished activating");
|
|
833
|
+
if ([...this.active.values()].some((plugin) => plugin.kind === "normal")) throw new Error("normal plugins are already active; use reloadNormal()");
|
|
834
|
+
const progress = ref({});
|
|
835
|
+
pluginRuntimeLogger.info("normal plugin load requested", { selectedCount: options.pluginNames?.length });
|
|
836
|
+
const operation = this.loadKind("normal", progress, options);
|
|
837
|
+
this.trackNormalOperation(operation);
|
|
838
|
+
return {
|
|
839
|
+
operation,
|
|
840
|
+
progress
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
reloadNormal(options = {}) {
|
|
844
|
+
if (this.normalOperation) throw new Error("normal plugins are already loading");
|
|
845
|
+
const progress = ref({});
|
|
846
|
+
pluginRuntimeLogger.info("normal plugin reload requested", { selectedCount: options.pluginNames?.length });
|
|
847
|
+
const operation = (async () => {
|
|
848
|
+
const unloadErrors = await this.unloadKind("normal");
|
|
849
|
+
await this.loadKind("normal", progress, options);
|
|
850
|
+
if (unloadErrors.length > 0) throw new AggregateError(unloadErrors, "some plugins failed to unload cleanly");
|
|
851
|
+
})();
|
|
852
|
+
this.trackNormalOperation(operation);
|
|
853
|
+
return {
|
|
854
|
+
operation,
|
|
855
|
+
progress
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
async preparePreboot(app) {
|
|
859
|
+
pluginRuntimeLogger.info("preboot plugin preparation started");
|
|
860
|
+
await this.ensureBuiltInPlugins();
|
|
861
|
+
await pluginStore.$refreshI18nNames();
|
|
862
|
+
const allPlugins = await db.selectFrom("plugin").selectAll().execute();
|
|
863
|
+
this.captureStartupSnapshot(allPlugins);
|
|
864
|
+
const plugins = selectPluginsForPhase(allPlugins.filter((plugin) => plugin.enable), "preboot", this.startupPrebootNames, this.activePrebootNames);
|
|
865
|
+
this.enabledPrebootNames = plugins.map((plugin) => plugin.pluginName);
|
|
866
|
+
pluginRuntimeLogger.debug("preboot plugins selected", { count: plugins.length });
|
|
867
|
+
if (plugins.length === 0) {
|
|
868
|
+
this.prebootActivated = true;
|
|
869
|
+
return { reloadRequired: false };
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const plan = planPluginLoadOrder(plugins);
|
|
873
|
+
const planError = formatPluginLoadPlanError(plan);
|
|
874
|
+
if (planError) throw new Error(planError);
|
|
875
|
+
for (const level of plan.levels) for (const archive of level) await Global.withRegistrationOwner(archive.pluginName, async () => {
|
|
876
|
+
const platform = isTauriRuntime() ? "tauri" : "web";
|
|
877
|
+
const factory = await loadPluginConfig(archive);
|
|
878
|
+
if (!factory) throw new Error(`plugin entry has no default export: ${archive.pluginName}`);
|
|
879
|
+
const config = factory({
|
|
880
|
+
safe: true,
|
|
881
|
+
platform
|
|
882
|
+
});
|
|
883
|
+
if (config.name !== archive.pluginName) throw new Error(`plugin name mismatch: ${archive.pluginName} / ${config.name}`);
|
|
884
|
+
const configStore = useConfig$1();
|
|
885
|
+
await Promise.all((config.config ?? []).map((pointer) => configStore.$registerConfig(pointer).ready));
|
|
886
|
+
const cleanup = await config.onPreboot?.({
|
|
887
|
+
app,
|
|
888
|
+
platform,
|
|
889
|
+
safe: true
|
|
890
|
+
});
|
|
891
|
+
if (cleanup) this.prebootCleanups.set(config.name, cleanup);
|
|
892
|
+
this.preparedPreboot.set(config.name, config);
|
|
893
|
+
});
|
|
894
|
+
pluginRuntimeLogger.info("preboot plugin preparation completed", { count: plugins.length });
|
|
895
|
+
return { reloadRequired: false };
|
|
896
|
+
} catch (error) {
|
|
897
|
+
pluginRuntimeLogger.error("preboot plugin preparation failed", error);
|
|
898
|
+
return await this.failPreboot(this.enabledPrebootNames, error);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
async activatePreboot(progress = ref({})) {
|
|
902
|
+
if (this.prebootActivated) return { reloadRequired: false };
|
|
903
|
+
try {
|
|
904
|
+
pluginRuntimeLogger.info("preboot plugin activation started", { count: this.preparedPreboot.size });
|
|
905
|
+
for (const [name, config] of this.preparedPreboot) {
|
|
906
|
+
await Global.withRegistrationOwner(name, async () => {
|
|
907
|
+
await bootResolvedConfig(config, progress);
|
|
908
|
+
});
|
|
909
|
+
this.active.set(name, {
|
|
910
|
+
config,
|
|
911
|
+
kind: "preboot"
|
|
912
|
+
});
|
|
913
|
+
this.activePrebootNames.add(name);
|
|
914
|
+
this.preparedPreboot.delete(name);
|
|
915
|
+
}
|
|
916
|
+
this.preparedPreboot.clear();
|
|
917
|
+
this.prebootActivated = true;
|
|
918
|
+
pluginRuntimeLogger.info("preboot plugin activation completed");
|
|
919
|
+
return { reloadRequired: false };
|
|
920
|
+
} catch (error) {
|
|
921
|
+
pluginRuntimeLogger.error("preboot plugin activation failed", error);
|
|
922
|
+
return await this.failPreboot(this.enabledPrebootNames, error);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
async uninstall(pluginName) {
|
|
926
|
+
pluginRuntimeLogger.info("plugin uninstall started", { plugin: pluginName });
|
|
927
|
+
const archive = await db.selectFrom("plugin").selectAll().where("pluginName", "=", pluginName).executeTakeFirst();
|
|
928
|
+
if (!archive) return;
|
|
929
|
+
if (isBuiltInPlugin(archive)) throw new Error(`built-in plugin "${pluginName}" cannot be uninstalled`);
|
|
930
|
+
const errors = [];
|
|
931
|
+
const active = this.active.get(pluginName);
|
|
932
|
+
if (active) errors.push(...await this.unloadOne(pluginName, active, true));
|
|
933
|
+
else {
|
|
934
|
+
let config;
|
|
935
|
+
try {
|
|
936
|
+
await Global.withRegistrationOwner(pluginName, async () => {
|
|
937
|
+
config = (await loadPluginConfig(archive))?.({
|
|
938
|
+
safe: true,
|
|
939
|
+
platform: isTauriRuntime() ? "tauri" : "web"
|
|
940
|
+
});
|
|
941
|
+
await config?.onUninstall?.();
|
|
942
|
+
});
|
|
943
|
+
} catch (error) {
|
|
944
|
+
errors.push(error);
|
|
945
|
+
}
|
|
946
|
+
if (config) try {
|
|
947
|
+
cleanupPlugin(config);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
errors.push(error);
|
|
950
|
+
}
|
|
951
|
+
else Global.removeOwnedRegistrations(pluginName);
|
|
952
|
+
}
|
|
953
|
+
await removePluginFiles(pluginName);
|
|
954
|
+
await db.deleteFrom("plugin").where("pluginName", "=", pluginName).execute();
|
|
955
|
+
await pluginStore.$refreshI18nNames();
|
|
956
|
+
pluginStore.$touch();
|
|
957
|
+
pluginRuntimeLogger.info("plugin uninstall completed", {
|
|
958
|
+
cleanupErrorCount: errors.length,
|
|
959
|
+
plugin: pluginName
|
|
960
|
+
});
|
|
961
|
+
if (errors.length > 0) throw new AggregateError(errors, `plugin "${pluginName}" was removed with cleanup errors`);
|
|
962
|
+
}
|
|
963
|
+
readRecovery() {
|
|
964
|
+
try {
|
|
965
|
+
const value = globalThis.localStorage?.getItem(recoveryKey);
|
|
966
|
+
if (!value) return null;
|
|
967
|
+
return JSON.parse(value);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
pluginRuntimeLogger.warn("failed to read preboot recovery", error);
|
|
970
|
+
return null;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
clearRecovery() {
|
|
974
|
+
try {
|
|
975
|
+
globalThis.localStorage?.removeItem(recoveryKey);
|
|
976
|
+
} catch (error) {
|
|
977
|
+
pluginRuntimeLogger.warn("failed to clear preboot recovery", error);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
trackNormalOperation(operation) {
|
|
981
|
+
this.normalOperation = operation;
|
|
982
|
+
operation.then(() => {
|
|
983
|
+
this.normalOperation = void 0;
|
|
984
|
+
pluginRuntimeLogger.info("normal plugin operation completed");
|
|
985
|
+
}, (error) => {
|
|
986
|
+
this.normalOperation = void 0;
|
|
987
|
+
pluginRuntimeLogger.error("normal plugin operation failed", error);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
ensureBuiltInPlugins() {
|
|
991
|
+
return this.builtInSynchronization ??= import("./runtime-DQbAfFDr.mjs").then(async ({ registerCoreRuntimeExtensions }) => {
|
|
992
|
+
registerCoreRuntimeExtensions();
|
|
993
|
+
await synchronizeBuiltInPlugins();
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
captureStartupSnapshot(plugins) {
|
|
997
|
+
if (!this.startupSnapshotReady) this.startupPrebootNames.clear();
|
|
998
|
+
for (const plugin of plugins) {
|
|
999
|
+
if (this.startupPluginNames.has(plugin.pluginName)) continue;
|
|
1000
|
+
this.startupPluginNames.add(plugin.pluginName);
|
|
1001
|
+
if (pluginKind(plugin) === "preboot") this.startupPrebootNames.add(plugin.pluginName);
|
|
1002
|
+
}
|
|
1003
|
+
this.startupSnapshotReady = true;
|
|
1004
|
+
}
|
|
1005
|
+
async loadKind(kind, progress, options) {
|
|
1006
|
+
await this.ensureBuiltInPlugins();
|
|
1007
|
+
await pluginStore.$refreshI18nNames();
|
|
1008
|
+
const allPlugins = await db.selectFrom("plugin").where("enable", "is", true).selectAll().execute();
|
|
1009
|
+
this.captureStartupSnapshot(allPlugins);
|
|
1010
|
+
const plugins = filterPluginsBySelection(selectPluginsForPhase(allPlugins, kind, this.startupPrebootNames, this.activePrebootNames), options.pluginNames ? new Set(options.pluginNames) : void 0);
|
|
1011
|
+
const plan = planPluginLoadOrder(plugins);
|
|
1012
|
+
pluginRuntimeLogger.info("plugin load plan created", {
|
|
1013
|
+
levelCount: plan.levels.length,
|
|
1014
|
+
pluginCount: plugins.length
|
|
1015
|
+
});
|
|
1016
|
+
const planError = formatPluginLoadPlanError(plan);
|
|
1017
|
+
if (planError) throw new Error(planError);
|
|
1018
|
+
const failed = /* @__PURE__ */ new Set();
|
|
1019
|
+
for (const level of plan.levels) for (const archive of level) {
|
|
1020
|
+
const name = archive.pluginName;
|
|
1021
|
+
progress.value[name] ??= createLoadingInfo();
|
|
1022
|
+
const blockedBy = failedDependencies(archive, failed);
|
|
1023
|
+
if (blockedBy.length > 0) {
|
|
1024
|
+
failed.add(name);
|
|
1025
|
+
progress.value[name].progress = {
|
|
1026
|
+
errorReason: pluginI18n.translate("plugin.runtime.errors.blockedDependencies", { plugins: blockedBy.join(", ") }),
|
|
1027
|
+
status: "error",
|
|
1028
|
+
stepsIndex: 0
|
|
1029
|
+
};
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
1032
|
+
let config;
|
|
1033
|
+
try {
|
|
1034
|
+
await Global.withRegistrationOwner(name, async () => {
|
|
1035
|
+
const factory = await loadPluginConfig(archive);
|
|
1036
|
+
if (!factory) throw new Error(`plugin entry has no default export: ${name}`);
|
|
1037
|
+
config = factory({
|
|
1038
|
+
safe: globalThis.window?.$$safe$$ ?? true,
|
|
1039
|
+
platform: isTauriRuntime() ? "tauri" : "web"
|
|
1040
|
+
});
|
|
1041
|
+
if (config.name !== name) throw new Error(`plugin name mismatch: ${name} / ${config.name}`);
|
|
1042
|
+
await bootResolvedConfig(config, progress);
|
|
1043
|
+
});
|
|
1044
|
+
this.active.set(name, {
|
|
1045
|
+
config,
|
|
1046
|
+
kind
|
|
1047
|
+
});
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
failed.add(name);
|
|
1050
|
+
if (config) try {
|
|
1051
|
+
cleanupPlugin(config);
|
|
1052
|
+
} catch (cleanupError) {
|
|
1053
|
+
pluginRuntimeLogger.error("plugin rollback failed", { plugin: name }, cleanupError);
|
|
1054
|
+
}
|
|
1055
|
+
else Global.removeOwnedRegistrations(name);
|
|
1056
|
+
const current = progress.value[name] ?? createLoadingInfo();
|
|
1057
|
+
progress.value[name] = {
|
|
1058
|
+
...current,
|
|
1059
|
+
progress: {
|
|
1060
|
+
...current.progress,
|
|
1061
|
+
errorReason: reasonText(error),
|
|
1062
|
+
status: "error"
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
pluginRuntimeLogger.error("plugin load failed", { plugin: name }, error);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
async unloadKind(kind) {
|
|
1070
|
+
const entries = [...this.active.entries()].filter(([, active]) => active.kind === kind);
|
|
1071
|
+
const errors = [];
|
|
1072
|
+
pluginRuntimeLogger.info("plugin unload phase started", {
|
|
1073
|
+
count: entries.length,
|
|
1074
|
+
kind
|
|
1075
|
+
});
|
|
1076
|
+
for (const [name, active] of entries.reverse()) errors.push(...await this.unloadOne(name, active, false));
|
|
1077
|
+
pluginRuntimeLogger.info("plugin unload phase completed", {
|
|
1078
|
+
errorCount: errors.length,
|
|
1079
|
+
kind
|
|
1080
|
+
});
|
|
1081
|
+
return errors;
|
|
1082
|
+
}
|
|
1083
|
+
async unloadOne(name, active, uninstall) {
|
|
1084
|
+
const errors = [];
|
|
1085
|
+
const attempt = async (action) => {
|
|
1086
|
+
if (!action) return;
|
|
1087
|
+
try {
|
|
1088
|
+
await action();
|
|
1089
|
+
} catch (error) {
|
|
1090
|
+
errors.push(error);
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
await attempt(active.config.onUnload?.bind(active.config));
|
|
1094
|
+
if (uninstall) await attempt(active.config.onUninstall?.bind(active.config));
|
|
1095
|
+
await attempt(this.prebootCleanups.get(name));
|
|
1096
|
+
try {
|
|
1097
|
+
cleanupPlugin(active.config);
|
|
1098
|
+
} catch (error) {
|
|
1099
|
+
errors.push(error);
|
|
1100
|
+
}
|
|
1101
|
+
this.prebootCleanups.delete(name);
|
|
1102
|
+
this.preparedPreboot.delete(name);
|
|
1103
|
+
this.active.delete(name);
|
|
1104
|
+
this.activePrebootNames.delete(name);
|
|
1105
|
+
return errors;
|
|
1106
|
+
}
|
|
1107
|
+
async failPreboot(pluginNames, error) {
|
|
1108
|
+
const cleanupErrors = [];
|
|
1109
|
+
cleanupErrors.push(...await this.unloadKind("preboot"));
|
|
1110
|
+
for (const [name, config] of [...this.preparedPreboot.entries()].reverse()) {
|
|
1111
|
+
try {
|
|
1112
|
+
await this.prebootCleanups.get(name)?.();
|
|
1113
|
+
} catch (cleanupError) {
|
|
1114
|
+
cleanupErrors.push(cleanupError);
|
|
1115
|
+
}
|
|
1116
|
+
try {
|
|
1117
|
+
cleanupPlugin(config);
|
|
1118
|
+
} catch (cleanupError) {
|
|
1119
|
+
cleanupErrors.push(cleanupError);
|
|
1120
|
+
}
|
|
1121
|
+
this.prebootCleanups.delete(name);
|
|
1122
|
+
}
|
|
1123
|
+
this.preparedPreboot.clear();
|
|
1124
|
+
this.activePrebootNames.clear();
|
|
1125
|
+
let disabled = true;
|
|
1126
|
+
try {
|
|
1127
|
+
if (pluginNames.length > 0) await db.updateTable("plugin").set({ enable: false }).where("pluginName", "in", pluginNames).execute();
|
|
1128
|
+
} catch (disableError) {
|
|
1129
|
+
disabled = false;
|
|
1130
|
+
cleanupErrors.push(disableError);
|
|
1131
|
+
}
|
|
1132
|
+
const reasons = [reasonText(error), ...cleanupErrors.map(reasonText)];
|
|
1133
|
+
const recovery = {
|
|
1134
|
+
failedAt: Date.now(),
|
|
1135
|
+
plugins: pluginNames,
|
|
1136
|
+
reason: reasons.join("\n")
|
|
1137
|
+
};
|
|
1138
|
+
try {
|
|
1139
|
+
globalThis.localStorage?.setItem(recoveryKey, JSON.stringify(recovery));
|
|
1140
|
+
} catch (storageError) {
|
|
1141
|
+
pluginRuntimeLogger.warn("failed to persist preboot recovery", storageError);
|
|
1142
|
+
}
|
|
1143
|
+
this.prebootActivated = true;
|
|
1144
|
+
return { reloadRequired: disabled };
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
const pluginRuntime = new PluginRuntime();
|
|
1148
|
+
//#endregion
|
|
1149
|
+
//#region lib/driver/extensionTypes.ts
|
|
1150
|
+
var PluginInstaller = class {};
|
|
1151
|
+
var PluginLoader = class {};
|
|
1152
|
+
var PluginBooter = class {};
|
|
1153
|
+
//#endregion
|
|
1154
|
+
//#region lib/driver/install.ts
|
|
1155
|
+
var install_exports = /* @__PURE__ */ __exportAll({
|
|
1156
|
+
installDepends: () => installDepends,
|
|
1157
|
+
installFilePlugin: () => installFilePlugin,
|
|
1158
|
+
installPlugin: () => installPlugin,
|
|
1159
|
+
installers: () => installers,
|
|
1160
|
+
uninstallPlugin: () => uninstallPlugin,
|
|
1161
|
+
updatePlugin: () => updatePlugin
|
|
1162
|
+
});
|
|
1163
|
+
const pluginInstallLogger = logger.scoped("plugin:install");
|
|
1164
|
+
const clampProgress = (progress) => Math.min(100, Math.max(0, progress));
|
|
1165
|
+
const installers = runtimeExtensions.installers.values;
|
|
1166
|
+
const installDepends = (m, meta, installedPlugins) => m.createLoading(pluginI18n.translate("plugin.progress.dependencies.checking"), async (v) => {
|
|
1167
|
+
v.retryable = true;
|
|
1168
|
+
let count = 0;
|
|
1169
|
+
const plugins = installedPlugins ?? new Set((await db.selectFrom("plugin").select("pluginName").execute()).map((v) => v.pluginName));
|
|
1170
|
+
const overrides = useConfig$1().$loadApp().data.value.installOverride;
|
|
1171
|
+
for (const { id, download } of meta.require) {
|
|
1172
|
+
if (plugins.has(id) || !download) continue;
|
|
1173
|
+
pluginInstallLogger.info("installing missing plugin dependency", {
|
|
1174
|
+
dependency: id,
|
|
1175
|
+
plugin: meta.name.id
|
|
1176
|
+
});
|
|
1177
|
+
v.description = pluginI18n.translate("plugin.progress.dependencies.installing", { id });
|
|
1178
|
+
const downloadCommend = overrides.find((c) => c.key == id)?.value ?? download;
|
|
1179
|
+
await installPlugin(downloadCommend);
|
|
1180
|
+
count++;
|
|
1181
|
+
}
|
|
1182
|
+
v.description = pluginI18n.translate("plugin.progress.dependencies.complete", { count });
|
|
1183
|
+
});
|
|
1184
|
+
const installPluginFile = async (m, file, installerName, installInput, __installedPlugins) => {
|
|
1185
|
+
pluginInstallLogger.info("plugin archive installation started", { installer: installerName });
|
|
1186
|
+
const meta = await m.createProgress(pluginI18n.translate("plugin.progress.installing"), async (v) => {
|
|
1187
|
+
v.retryable = true;
|
|
1188
|
+
const loader = loaders.filter((ins) => ins.canInstall(file)).at(-1);
|
|
1189
|
+
if (!loader) throw new Error(pluginI18n.translate("plugin.install.errors.noInstaller"));
|
|
1190
|
+
v.description = loader.name;
|
|
1191
|
+
v.progress = 0;
|
|
1192
|
+
const meta = await loader.install(file, { report(progress) {
|
|
1193
|
+
if (progress.description) v.description = progress.description;
|
|
1194
|
+
if (typeof progress.progress === "number") v.progress = clampProgress(progress.progress);
|
|
1195
|
+
} });
|
|
1196
|
+
if (isBuiltInPluginName(meta.name.id)) throw new Error(pluginI18n.translate("plugin.install.errors.reservedId", { id: meta.name.id }));
|
|
1197
|
+
v.description = pluginI18n.translate("plugin.progress.persisting");
|
|
1198
|
+
v.progress = 95;
|
|
1199
|
+
await pluginStore.$upsertArchives([{
|
|
1200
|
+
displayName: meta.name.display,
|
|
1201
|
+
enable: true,
|
|
1202
|
+
installerName,
|
|
1203
|
+
installInput,
|
|
1204
|
+
loaderName: loader.name,
|
|
1205
|
+
meta,
|
|
1206
|
+
pluginName: meta.name.id
|
|
1207
|
+
}]);
|
|
1208
|
+
v.progress = 100;
|
|
1209
|
+
return meta;
|
|
1210
|
+
});
|
|
1211
|
+
pluginInstallLogger.info("plugin archive installed", { plugin: meta.name.id });
|
|
1212
|
+
await installDepends(m, meta, __installedPlugins);
|
|
1213
|
+
};
|
|
1214
|
+
const installPlugin = (input, __installedPlugins) => createDownloadMessage(pluginI18n.translate("plugin.progress.downloadTitle", { input }), async (m) => {
|
|
1215
|
+
const [file, installer] = await m.createLoading(pluginI18n.translate("plugin.progress.downloading"), async (v) => {
|
|
1216
|
+
v.retryable = true;
|
|
1217
|
+
let dlCommend = input;
|
|
1218
|
+
for (;;) {
|
|
1219
|
+
const installer = installers.filter((ins) => ins.isMatched(dlCommend)).at(0);
|
|
1220
|
+
if (!installer) throw new Error(pluginI18n.translate("plugin.install.errors.noDownloader", { input: dlCommend }));
|
|
1221
|
+
v.description = installer.name;
|
|
1222
|
+
const meta = await installer.download(dlCommend);
|
|
1223
|
+
if (isString(meta)) dlCommend = meta;
|
|
1224
|
+
else return [meta, installer];
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
await installPluginFile(m, file, installer.name, input, __installedPlugins);
|
|
1228
|
+
});
|
|
1229
|
+
const installFilePlugin = (file, __installedPlugins) => createDownloadMessage(pluginI18n.translate("plugin.progress.installTitle", { file: file.name }), async (m) => {
|
|
1230
|
+
await installPluginFile(m, file, "", "", __installedPlugins);
|
|
1231
|
+
});
|
|
1232
|
+
const updatePlugin = async (pluginMeta, __installedPlugins) => {
|
|
1233
|
+
if (isBuiltInPlugin(pluginMeta)) throw new Error(pluginI18n.translate("plugin.install.errors.builtInUpdate"));
|
|
1234
|
+
return createDownloadMessage(pluginI18n.translate("plugin.progress.updateTitle", { plugin: pluginMeta.pluginName }), async (m) => {
|
|
1235
|
+
pluginInstallLogger.info("plugin update started", { plugin: pluginMeta.pluginName });
|
|
1236
|
+
const file = await m.createLoading(pluginI18n.translate("plugin.progress.updating"), async (v) => {
|
|
1237
|
+
v.retryable = true;
|
|
1238
|
+
let installerName = pluginMeta.installerName;
|
|
1239
|
+
for (;;) {
|
|
1240
|
+
const installer = installers.find((v) => v.name == installerName);
|
|
1241
|
+
if (!installer) throw new Error(pluginI18n.translate("plugin.install.errors.noDownloader"));
|
|
1242
|
+
v.description = installer.name;
|
|
1243
|
+
const file = await installer.update(pluginMeta);
|
|
1244
|
+
if (isString(file)) installerName = file;
|
|
1245
|
+
else return file;
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
1248
|
+
const meta = await m.createProgress(pluginI18n.translate("plugin.progress.installing"), async (v) => {
|
|
1249
|
+
v.retryable = true;
|
|
1250
|
+
const loader = loaders.find((v) => v.name == pluginMeta.loaderName);
|
|
1251
|
+
if (!loader) throw new Error(pluginI18n.translate("plugin.install.errors.noInstaller"));
|
|
1252
|
+
v.description = loader.name;
|
|
1253
|
+
v.progress = 0;
|
|
1254
|
+
const meta = await loader.install(file, { report(progress) {
|
|
1255
|
+
if (progress.description) v.description = progress.description;
|
|
1256
|
+
if (typeof progress.progress === "number") v.progress = clampProgress(progress.progress);
|
|
1257
|
+
} });
|
|
1258
|
+
v.progress = 100;
|
|
1259
|
+
return meta;
|
|
1260
|
+
});
|
|
1261
|
+
await pluginStore.$upsertArchives([{
|
|
1262
|
+
...pluginMeta,
|
|
1263
|
+
meta
|
|
1264
|
+
}]);
|
|
1265
|
+
await installDepends(m, meta, __installedPlugins);
|
|
1266
|
+
pluginInstallLogger.info("plugin update completed", { plugin: pluginMeta.pluginName });
|
|
1267
|
+
});
|
|
1268
|
+
};
|
|
1269
|
+
const uninstallPlugin = (pluginName) => pluginRuntime.uninstall(pluginName);
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region lib/driver/icon.ts
|
|
1272
|
+
const OfflineShareRound = () => h("svg", {
|
|
1273
|
+
"xmlns": "http://www.w3.org/2000/svg",
|
|
1274
|
+
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
|
1275
|
+
"viewBox": "0 0 24 24"
|
|
1276
|
+
}, [
|
|
1277
|
+
h("path", {
|
|
1278
|
+
d: "M5 5c-.55 0-1 .45-1 1v15c0 1.1.9 2 2 2h9c.55 0 1-.45 1-1s-.45-1-1-1H6V6c0-.55-.45-1-1-1z",
|
|
1279
|
+
fill: "currentColor"
|
|
1280
|
+
}),
|
|
1281
|
+
h("path", {
|
|
1282
|
+
d: "M18 1h-8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2zm0 14h-8V5h8v10z",
|
|
1283
|
+
fill: "currentColor"
|
|
1284
|
+
}),
|
|
1285
|
+
h("path", {
|
|
1286
|
+
d: "M12.5 10.25h2v.54c0 .45.54.67.85.35l1.29-1.29c.2-.2.2-.51 0-.71l-1.29-1.29a.5.5 0 0 0-.85.35v.54H12c-.55 0-1 .45-1 1v1.5c0 .41.34.75.75.75s.75-.34.75-.75v-.99z",
|
|
1287
|
+
fill: "currentColor"
|
|
1288
|
+
})
|
|
1289
|
+
]);
|
|
1290
|
+
const TagOutlined = () => h("svg", {
|
|
1291
|
+
"xmlns": "http://www.w3.org/2000/svg",
|
|
1292
|
+
"xmlns:xlink": "http://www.w3.org/1999/xlink",
|
|
1293
|
+
"viewBox": "0 0 1024 1024"
|
|
1294
|
+
}, [h("path", {
|
|
1295
|
+
d: "M938 458.8l-29.6-312.6c-1.5-16.2-14.4-29-30.6-30.6L565.2 86h-.4c-3.2 0-5.7 1-7.6 2.9L88.9 557.2a9.96 9.96 0 0 0 0 14.1l363.8 363.8c1.9 1.9 4.4 2.9 7.1 2.9s5.2-1 7.1-2.9l468.3-468.3c2-2.1 3-5 2.8-8zM459.7 834.7L189.3 564.3L589 164.6L836 188l23.4 247l-399.7 399.7zM680 256c-48.5 0-88 39.5-88 88s39.5 88 88 88s88-39.5 88-88s-39.5-88-88-88zm0 120c-17.7 0-32-14.3-32-32s14.3-32 32-32s32 14.3 32 32s-14.3 32-32 32z",
|
|
1296
|
+
fill: "currentColor"
|
|
1297
|
+
})]);
|
|
1298
|
+
//#endregion
|
|
1299
|
+
//#region lib/global.ts
|
|
1300
|
+
var _Global = class {
|
|
1301
|
+
share = shallowReactive(SourcedKeyMap.createReactive());
|
|
1302
|
+
shareToken = shallowReactive(SourcedKeyMap.createReactive());
|
|
1303
|
+
userActions = shallowReactive(SourcedKeyMap.createReactive());
|
|
1304
|
+
subscribes = shallowReactive(SourcedKeyMap.createReactive());
|
|
1305
|
+
globalNodes = shallowReactive(new Array());
|
|
1306
|
+
globalNodeOwners = /* @__PURE__ */ new Map();
|
|
1307
|
+
registrationOwners = [];
|
|
1308
|
+
/**
|
|
1309
|
+
* Associates global registrations made by a plugin with that plugin. The runtime
|
|
1310
|
+
* deliberately loads plugins serially while this ambient scope is active.
|
|
1311
|
+
*/
|
|
1312
|
+
async withRegistrationOwner(plugin, action) {
|
|
1313
|
+
this.registrationOwners.push(plugin);
|
|
1314
|
+
try {
|
|
1315
|
+
return await runtimeExtensions.withOwner(plugin, () => environmentRegistry.withOwner(plugin, action));
|
|
1316
|
+
} finally {
|
|
1317
|
+
this.registrationOwners.pop();
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
get registrationOwner() {
|
|
1321
|
+
return this.registrationOwners.at(-1);
|
|
1322
|
+
}
|
|
1323
|
+
addGlobalNode(component, plugin = this.registrationOwner) {
|
|
1324
|
+
this.globalNodes.push(component);
|
|
1325
|
+
if (plugin) this.globalNodeOwners.set(component, plugin);
|
|
1326
|
+
return component;
|
|
1327
|
+
}
|
|
1328
|
+
/** Removes only registrations owned by the requested plugin. */
|
|
1329
|
+
removeOwnedRegistrations(plugin) {
|
|
1330
|
+
for (let index = this.globalNodes.length - 1; index >= 0; index--) {
|
|
1331
|
+
const component = this.globalNodes[index];
|
|
1332
|
+
if (this.globalNodeOwners.get(component) !== plugin) continue;
|
|
1333
|
+
this.globalNodes.splice(index, 1);
|
|
1334
|
+
this.globalNodeOwners.delete(component);
|
|
1335
|
+
}
|
|
1336
|
+
environmentRegistry.removeOwner(plugin);
|
|
1337
|
+
runtimeExtensions.removeOwner(plugin);
|
|
1338
|
+
}
|
|
1339
|
+
tabbar = shallowReactive(/* @__PURE__ */ new Map());
|
|
1340
|
+
addTabbar(plugin, ...tabbar) {
|
|
1341
|
+
const old = this.tabbar.get(plugin) ?? [];
|
|
1342
|
+
this.tabbar.set(plugin, old.concat(tabbar));
|
|
1343
|
+
}
|
|
1344
|
+
categories = shallowReactive(/* @__PURE__ */ new Map());
|
|
1345
|
+
addCategories(plugin, ...categories) {
|
|
1346
|
+
const old = this.categories.get(plugin) ?? [];
|
|
1347
|
+
this.categories.set(plugin, old.concat(categories));
|
|
1348
|
+
}
|
|
1349
|
+
barcode = shallowReactive(/* @__PURE__ */ new Map());
|
|
1350
|
+
addBarcode(plugin, ...barcode) {
|
|
1351
|
+
const old = this.barcode.get(plugin) ?? [];
|
|
1352
|
+
this.barcode.set(plugin, old.concat(barcode));
|
|
1353
|
+
}
|
|
1354
|
+
hotSearch = shallowReactive(/* @__PURE__ */ new Map());
|
|
1355
|
+
addHotSearch(plugin, ...providers) {
|
|
1356
|
+
const old = this.hotSearch.get(plugin) ?? [];
|
|
1357
|
+
this.hotSearch.set(plugin, old.concat(providers));
|
|
1358
|
+
}
|
|
1359
|
+
levelboard = shallowReactive(/* @__PURE__ */ new Map());
|
|
1360
|
+
addLevelboard(plugin, ...levelboard) {
|
|
1361
|
+
const old = this.levelboard.get(plugin) ?? [];
|
|
1362
|
+
this.levelboard.set(plugin, old.concat(levelboard));
|
|
1363
|
+
}
|
|
1364
|
+
topButton = shallowReactive(/* @__PURE__ */ new Map());
|
|
1365
|
+
addTopButton(plugin, ...topButton) {
|
|
1366
|
+
const old = this.topButton.get(plugin) ?? [];
|
|
1367
|
+
this.topButton.set(plugin, old.concat(topButton));
|
|
1368
|
+
}
|
|
1369
|
+
mainLists = shallowReactive(/* @__PURE__ */ new Map());
|
|
1370
|
+
addMainList(plugin, ...mainLists) {
|
|
1371
|
+
const old = this.mainLists.get(plugin) ?? [];
|
|
1372
|
+
this.mainLists.set(plugin, old.concat(mainLists));
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const Global = new _Global();
|
|
1376
|
+
Global.share.set(["core", "token"], {
|
|
1377
|
+
filter: (page) => !!page.preload,
|
|
1378
|
+
icon: TagOutlined,
|
|
1379
|
+
key: "token",
|
|
1380
|
+
name: pluginMessageKey("plugin.share.copyToken"),
|
|
1381
|
+
async call(page) {
|
|
1382
|
+
const item = page.preload?.toJSON();
|
|
1383
|
+
if (!item) throw new Error("Not found preload in content. Maybe not fetch detail?");
|
|
1384
|
+
const compressed = compressToEncodedURIComponent(JSON.stringify({
|
|
1385
|
+
item: {
|
|
1386
|
+
contentType: uni.content.ContentPage.contentPages.key.toString(item.contentType),
|
|
1387
|
+
ep: item.thisEp.id,
|
|
1388
|
+
name: item.title
|
|
1389
|
+
},
|
|
1390
|
+
plugin: page.plugin,
|
|
1391
|
+
id: page.id
|
|
1392
|
+
}));
|
|
1393
|
+
return { token: `[${item.title}](复制这条口令,打开Delta Comic)${compressed}` };
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
Global.share.set(["core", "native"], {
|
|
1397
|
+
filter: (page) => !!page.preload,
|
|
1398
|
+
icon: OfflineShareRound,
|
|
1399
|
+
key: "native",
|
|
1400
|
+
name: pluginMessageKey("plugin.share.native"),
|
|
1401
|
+
async call(page) {
|
|
1402
|
+
const item = page.preload?.toJSON();
|
|
1403
|
+
if (!item) throw new Error("Not found preload in content. Maybe not fetch detail?");
|
|
1404
|
+
const compressed = compressToEncodedURIComponent(JSON.stringify({
|
|
1405
|
+
item: {
|
|
1406
|
+
contentType: uni.content.ContentPage.contentPages.key.toString(item.contentType),
|
|
1407
|
+
ep: item.thisEp.id,
|
|
1408
|
+
name: item.title
|
|
1409
|
+
},
|
|
1410
|
+
plugin: page.plugin,
|
|
1411
|
+
id: page.id
|
|
1412
|
+
}));
|
|
1413
|
+
const token = `[${item.title}](复制这条口令,打开Delta Comic)${compressed}`;
|
|
1414
|
+
await navigator.share({
|
|
1415
|
+
title: pluginI18n.translate("plugin.share.nativeTitle"),
|
|
1416
|
+
text: token
|
|
1417
|
+
});
|
|
1418
|
+
return { token };
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
Global.shareToken.set(["core", "token"], {
|
|
1422
|
+
key: "token",
|
|
1423
|
+
name: pluginMessageKey("plugin.share.defaultToken"),
|
|
1424
|
+
patten(chipboard) {
|
|
1425
|
+
return /^\[.+\]\(复制这条口令,打开Delta Comic\).+/.test(chipboard);
|
|
1426
|
+
},
|
|
1427
|
+
show(chipboard) {
|
|
1428
|
+
const pluginStore = usePluginStore();
|
|
1429
|
+
const meta = JSON.parse(decompressFromEncodedURIComponent(chipboard.replace(/^\[.+\]/, "").replaceAll("(复制这条口令,打开Delta Comic)", "")));
|
|
1430
|
+
return {
|
|
1431
|
+
title: pluginI18n.translate("plugin.share.tokenTitle"),
|
|
1432
|
+
detail: pluginI18n.translate("plugin.share.tokenDetail", {
|
|
1433
|
+
item: meta.item.name,
|
|
1434
|
+
plugin: pluginStore.$getI18nName(meta.plugin)
|
|
1435
|
+
}),
|
|
1436
|
+
onNegative() {},
|
|
1437
|
+
onPositive() {
|
|
1438
|
+
return SharedFunction.call("routeToContent", uni.content.ContentPage.contentPages.key.toJSON(meta.item.contentType), meta.id, meta.item.ep);
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
//#endregion
|
|
1444
|
+
export { pluginI18n as A, require as B, findCyclePaths as C, pluginStore as D, PluginStore as E, declareDepType as F, definePlugin as G, useConfig$1 as H, defaultDependencyRegistry as I, exposeSymbol as L, translatePluginText as M, DependencyRegistry as N, usePluginStore as O, createDependencyRegistry as P, pluginExposes as R, synchronizeBuiltInPlugins as S, planPluginLoadOrder as T, ConfigPointer as U, ConfigStore as V, defineInnerPlugin as W, runtimeExtensions as _, PluginLoader as a, isBuiltInPlugin as b, filterPluginsBySelection as c, loader_exports as d, booter_exports as f, registerLoader as g, registerInstaller as h, PluginInstaller as i, pluginMessageKey as j, PluginI18nRegistry as k, pluginKind as l, registerBooter as m, install_exports as n, pluginRuntime as o, PluginRuntimeExtensions as p, PluginBooter as r, failedDependencies as s, Global as t, selectPluginsForPhase as u, BUILT_IN_PLUGIN_LOADER as v, formatPluginLoadPlanError as w, isBuiltInPluginName as x, builtInDefinitionToArchive as y, provide as z };
|
|
1445
|
+
|
|
1446
|
+
//# sourceMappingURL=global-B4SQiBzS.mjs.map
|