@yumerijs/loader 3.1.3 → 3.1.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/index.d.ts +3 -1
- package/dist/index.js +41 -22
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class PluginLoader {
|
|
|
34
34
|
private configPath;
|
|
35
35
|
private pluginContexts;
|
|
36
36
|
private isDev;
|
|
37
|
+
/** Install every missing configured plugin without prompting. */
|
|
38
|
+
autoInstallMissingPlugins: boolean;
|
|
37
39
|
constructor(core?: Core, pluginsDir?: string);
|
|
38
40
|
/**
|
|
39
41
|
* Normalize both the legacy `{ "package-name": { ...config } }` form and
|
|
@@ -52,7 +54,7 @@ export declare class PluginLoader {
|
|
|
52
54
|
getContext(pluginName: string, injections?: Record<string, any>): Context;
|
|
53
55
|
unregall(pluginName: string): void;
|
|
54
56
|
loadConfig(configPath: string): Promise<void>;
|
|
55
|
-
loadPlugins(): Promise<
|
|
57
|
+
loadPlugins(): Promise<boolean>;
|
|
56
58
|
loadSinglePlugin(pluginName: string, triggerPendingCheck?: boolean, onlypending?: boolean, requireOptionalDependencies?: boolean): Promise<boolean>;
|
|
57
59
|
private _loadPendingPlugins;
|
|
58
60
|
unloadPlugin(pluginNameToUnload: string, ispending?: boolean): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,6 @@ import { Core, Logger, Context, fallback, Schema, I18n, resolvePluginModule } fr
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { execFile } from 'child_process';
|
|
6
|
-
import { createInterface } from 'readline/promises';
|
|
7
|
-
import { stdin as input, stdout as output } from 'process';
|
|
8
6
|
import { fileURLToPath } from 'url';
|
|
9
7
|
import * as yaml from 'js-yaml';
|
|
10
8
|
import * as chokidar from 'chokidar';
|
|
@@ -21,6 +19,8 @@ export class PluginLoader {
|
|
|
21
19
|
configPath = '';
|
|
22
20
|
pluginContexts = {};
|
|
23
21
|
isDev = false;
|
|
22
|
+
/** Install every missing configured plugin without prompting. */
|
|
23
|
+
autoInstallMissingPlugins = false;
|
|
24
24
|
constructor(core, pluginsDir = 'plugins') {
|
|
25
25
|
this.pluginsDir = pluginsDir;
|
|
26
26
|
this.core = core || new Core(this, undefined, false);
|
|
@@ -133,7 +133,7 @@ export class PluginLoader {
|
|
|
133
133
|
async loadPlugins() {
|
|
134
134
|
if (!this.config || typeof this.config.plugins !== 'object' || this.config.plugins === null) {
|
|
135
135
|
this.logger.info('No plugins configuration found. No plugins to load.');
|
|
136
|
-
return;
|
|
136
|
+
return false;
|
|
137
137
|
}
|
|
138
138
|
const allEntries = Object.keys(this.config.plugins)
|
|
139
139
|
.map(key => this.getPluginEntry(key.startsWith('~') ? key.substring(1) : key))
|
|
@@ -151,10 +151,14 @@ export class PluginLoader {
|
|
|
151
151
|
}
|
|
152
152
|
if (enabledPlugins.length === 0) {
|
|
153
153
|
this.logger.info('No enabled plugins found in configuration.');
|
|
154
|
-
return;
|
|
154
|
+
return false;
|
|
155
155
|
}
|
|
156
156
|
// Resolve and install every configured plugin before any plugin module is loaded.
|
|
157
|
-
await this.installMissingPluginModules(allEntries.filter(entry => entry.enabled));
|
|
157
|
+
const installedMissingPlugins = await this.installMissingPluginModules(allEntries.filter(entry => entry.enabled));
|
|
158
|
+
if (installedMissingPlugins) {
|
|
159
|
+
this.logger.info('Missing plugin packages were installed. A worker restart is required before loading plugins.');
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
158
162
|
// 第一阶段:将 optional 与 depend 一样处理,尽量让可选服务先完成加载并注入。
|
|
159
163
|
while (await this._loadPendingPlugins(true)) {
|
|
160
164
|
// Continue scanning until no plugin can satisfy all required and optional dependencies.
|
|
@@ -167,6 +171,7 @@ export class PluginLoader {
|
|
|
167
171
|
if (pendingPlugins.length > 0) {
|
|
168
172
|
this.logger.warn('Some plugins could not be loaded due to unresolved required dependencies:', pendingPlugins);
|
|
169
173
|
}
|
|
174
|
+
return false;
|
|
170
175
|
}
|
|
171
176
|
async loadSinglePlugin(pluginName, triggerPendingCheck = true, onlypending = false, requireOptionalDependencies = true) {
|
|
172
177
|
if (!this.pluginStatus[pluginName]) {
|
|
@@ -402,19 +407,13 @@ export class PluginLoader {
|
|
|
402
407
|
return 'npm';
|
|
403
408
|
}
|
|
404
409
|
async confirmPluginInstall(moduleName) {
|
|
405
|
-
if (!
|
|
410
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
406
411
|
this.logger.warn(`Plugin package "${moduleName}" is not installed and no interactive terminal is available.`);
|
|
407
412
|
return false;
|
|
408
413
|
}
|
|
409
414
|
const scope = this.isNpxInvocation() ? 'globally' : 'in the current project';
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
const answer = await readline.question(`Plugin package "${moduleName}" is not installed. Install it ${scope}? [y/N] `);
|
|
413
|
-
return /^(y|yes)$/i.test(answer.trim());
|
|
414
|
-
}
|
|
415
|
-
finally {
|
|
416
|
-
readline.close();
|
|
417
|
-
}
|
|
415
|
+
const answer = await this.logger.input(`Plugin package "${moduleName}" is not installed. Install it ${scope}? [y/N] `);
|
|
416
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
418
417
|
}
|
|
419
418
|
isPluginModuleResolvable(moduleName) {
|
|
420
419
|
try {
|
|
@@ -430,33 +429,53 @@ export class PluginLoader {
|
|
|
430
429
|
async installMissingPluginModules(entries) {
|
|
431
430
|
const missingModules = [...new Set(entries.map(entry => entry.moduleName))]
|
|
432
431
|
.filter(moduleName => !this.isPluginModuleResolvable(moduleName));
|
|
432
|
+
if (missingModules.length === 0)
|
|
433
|
+
return false;
|
|
434
|
+
if (this.autoInstallMissingPlugins) {
|
|
435
|
+
this.logger.info(`Automatically installing missing plugin packages: ${missingModules.join(', ')}`);
|
|
436
|
+
try {
|
|
437
|
+
await this.installMissingPlugin(missingModules);
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
this.logger.error('Failed to automatically install missing plugin packages:', error);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
let installedAny = false;
|
|
433
446
|
for (const moduleName of missingModules) {
|
|
434
447
|
const shouldInstall = await this.confirmPluginInstall(moduleName);
|
|
435
448
|
if (!shouldInstall)
|
|
436
449
|
continue;
|
|
437
450
|
try {
|
|
438
451
|
await this.installMissingPlugin(moduleName);
|
|
452
|
+
installedAny = true;
|
|
439
453
|
}
|
|
440
454
|
catch (error) {
|
|
441
455
|
this.logger.error(`Failed to install missing plugin "${moduleName}":`, error);
|
|
442
456
|
}
|
|
443
457
|
}
|
|
458
|
+
return installedAny;
|
|
444
459
|
}
|
|
445
|
-
async installMissingPlugin(
|
|
446
|
-
|
|
447
|
-
|
|
460
|
+
async installMissingPlugin(moduleNames) {
|
|
461
|
+
const packages = Array.isArray(moduleNames) ? moduleNames : [moduleNames];
|
|
462
|
+
for (const moduleName of packages) {
|
|
463
|
+
if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(moduleName)) {
|
|
464
|
+
throw new Error(`Cannot automatically install invalid package name "${moduleName}".`);
|
|
465
|
+
}
|
|
448
466
|
}
|
|
449
467
|
const packageManager = this.detectPackageManager();
|
|
450
468
|
const global = this.isNpxInvocation();
|
|
451
469
|
const argsByManager = {
|
|
452
|
-
npm: global ? ['install', '--global',
|
|
453
|
-
yarn: global ? ['global', 'add',
|
|
454
|
-
pnpm: global ? ['add', '--global',
|
|
455
|
-
bun: global ? ['add', '--global',
|
|
470
|
+
npm: global ? ['install', '--global', ...packages] : ['install', ...packages, '--save'],
|
|
471
|
+
yarn: global ? ['global', 'add', ...packages] : ['add', ...packages],
|
|
472
|
+
pnpm: global ? ['add', '--global', ...packages] : ['add', ...packages],
|
|
473
|
+
bun: global ? ['add', '--global', ...packages] : ['add', ...packages],
|
|
456
474
|
};
|
|
457
475
|
const args = argsByManager[packageManager] || argsByManager.npm;
|
|
458
476
|
const command = process.platform === 'win32' ? `${packageManager}.cmd` : packageManager;
|
|
459
|
-
|
|
477
|
+
const packageLabel = packages.map(moduleName => `"${moduleName}"`).join(', ');
|
|
478
|
+
this.logger.info(`Installing missing plugin package${packages.length === 1 ? '' : 's'} ${packageLabel} with ${command} ${args.join(' ')}` +
|
|
460
479
|
(global ? ' (global)' : ''));
|
|
461
480
|
// Windows command shims (for example npm.cmd) require cmd.exe; execFile
|
|
462
481
|
// otherwise fails before the package manager can start with spawn EINVAL.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yumerijs/loader",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"description": "Module loader for yumeri",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/js-yaml": "^4.0.9",
|
|
31
31
|
"@types/node": "^22.13.10",
|
|
32
|
-
"@yumerijs/core": "^3.0.
|
|
32
|
+
"@yumerijs/core": "^3.0.7",
|
|
33
33
|
"esbuild-register": "^3.6.0",
|
|
34
34
|
"typescript": "^6.0.3"
|
|
35
35
|
},
|