@yumerijs/loader 3.1.2 → 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 +5 -1
- package/dist/index.js +78 -28
- 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>;
|
|
@@ -63,6 +65,8 @@ export declare class PluginLoader {
|
|
|
63
65
|
private isNpxInvocation;
|
|
64
66
|
private detectPackageManager;
|
|
65
67
|
private confirmPluginInstall;
|
|
68
|
+
private isPluginModuleResolvable;
|
|
69
|
+
private installMissingPluginModules;
|
|
66
70
|
private installMissingPlugin;
|
|
67
71
|
private importPluginModule;
|
|
68
72
|
loadModule(pluginName: string): Promise<Plugin>;
|
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,7 +151,13 @@ 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
|
+
}
|
|
156
|
+
// Resolve and install every configured plugin before any plugin module is loaded.
|
|
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;
|
|
155
161
|
}
|
|
156
162
|
// 第一阶段:将 optional 与 depend 一样处理,尽量让可选服务先完成加载并注入。
|
|
157
163
|
while (await this._loadPendingPlugins(true)) {
|
|
@@ -165,6 +171,7 @@ export class PluginLoader {
|
|
|
165
171
|
if (pendingPlugins.length > 0) {
|
|
166
172
|
this.logger.warn('Some plugins could not be loaded due to unresolved required dependencies:', pendingPlugins);
|
|
167
173
|
}
|
|
174
|
+
return false;
|
|
168
175
|
}
|
|
169
176
|
async loadSinglePlugin(pluginName, triggerPendingCheck = true, onlypending = false, requireOptionalDependencies = true) {
|
|
170
177
|
if (!this.pluginStatus[pluginName]) {
|
|
@@ -400,48 +407,91 @@ export class PluginLoader {
|
|
|
400
407
|
return 'npm';
|
|
401
408
|
}
|
|
402
409
|
async confirmPluginInstall(moduleName) {
|
|
403
|
-
if (!
|
|
410
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
404
411
|
this.logger.warn(`Plugin package "${moduleName}" is not installed and no interactive terminal is available.`);
|
|
405
412
|
return false;
|
|
406
413
|
}
|
|
407
414
|
const scope = this.isNpxInvocation() ? 'globally' : 'in the current project';
|
|
408
|
-
const
|
|
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());
|
|
417
|
+
}
|
|
418
|
+
isPluginModuleResolvable(moduleName) {
|
|
409
419
|
try {
|
|
410
|
-
|
|
411
|
-
return
|
|
420
|
+
import.meta.resolve(moduleName);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
if (this.isMissingModuleError(error, moduleName))
|
|
425
|
+
return false;
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async installMissingPluginModules(entries) {
|
|
430
|
+
const missingModules = [...new Set(entries.map(entry => entry.moduleName))]
|
|
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
|
+
}
|
|
412
444
|
}
|
|
413
|
-
|
|
414
|
-
|
|
445
|
+
let installedAny = false;
|
|
446
|
+
for (const moduleName of missingModules) {
|
|
447
|
+
const shouldInstall = await this.confirmPluginInstall(moduleName);
|
|
448
|
+
if (!shouldInstall)
|
|
449
|
+
continue;
|
|
450
|
+
try {
|
|
451
|
+
await this.installMissingPlugin(moduleName);
|
|
452
|
+
installedAny = true;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
this.logger.error(`Failed to install missing plugin "${moduleName}":`, error);
|
|
456
|
+
}
|
|
415
457
|
}
|
|
458
|
+
return installedAny;
|
|
416
459
|
}
|
|
417
|
-
async installMissingPlugin(
|
|
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
|
+
}
|
|
466
|
+
}
|
|
418
467
|
const packageManager = this.detectPackageManager();
|
|
419
468
|
const global = this.isNpxInvocation();
|
|
420
469
|
const argsByManager = {
|
|
421
|
-
npm: global ? ['install', '--global',
|
|
422
|
-
yarn: global ? ['global', 'add',
|
|
423
|
-
pnpm: global ? ['add', '--global',
|
|
424
|
-
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],
|
|
425
474
|
};
|
|
426
475
|
const args = argsByManager[packageManager] || argsByManager.npm;
|
|
427
476
|
const command = process.platform === 'win32' ? `${packageManager}.cmd` : packageManager;
|
|
428
|
-
|
|
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(' ')}` +
|
|
429
479
|
(global ? ' (global)' : ''));
|
|
480
|
+
// Windows command shims (for example npm.cmd) require cmd.exe; execFile
|
|
481
|
+
// otherwise fails before the package manager can start with spawn EINVAL.
|
|
482
|
+
if (process.platform === 'win32') {
|
|
483
|
+
await execFileAsync(process.env.ComSpec || 'cmd.exe', [
|
|
484
|
+
'/d',
|
|
485
|
+
'/s',
|
|
486
|
+
'/c',
|
|
487
|
+
`${command} ${args.join(' ')}`,
|
|
488
|
+
], { cwd: process.cwd() });
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
430
491
|
await execFileAsync(command, args, { cwd: process.cwd() });
|
|
431
492
|
}
|
|
432
493
|
async importPluginModule(moduleName) {
|
|
433
|
-
|
|
434
|
-
return await import(moduleName);
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
if (!this.isMissingModuleError(error, moduleName))
|
|
438
|
-
throw error;
|
|
439
|
-
const shouldInstall = await this.confirmPluginInstall(moduleName);
|
|
440
|
-
if (!shouldInstall)
|
|
441
|
-
throw error;
|
|
442
|
-
await this.installMissingPlugin(moduleName);
|
|
443
|
-
return await import(moduleName);
|
|
444
|
-
}
|
|
494
|
+
return await import(moduleName);
|
|
445
495
|
}
|
|
446
496
|
async loadModule(pluginName) {
|
|
447
497
|
const entry = this.getPluginEntry(pluginName);
|
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
|
},
|