@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 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<void>;
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 (!input.isTTY || !output.isTTY) {
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 readline = createInterface({ input, output });
411
- try {
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(moduleName) {
446
- if (!/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(moduleName)) {
447
- throw new Error(`Cannot automatically install invalid package name "${moduleName}".`);
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', moduleName] : ['install', moduleName, '--save'],
453
- yarn: global ? ['global', 'add', moduleName] : ['add', moduleName],
454
- pnpm: global ? ['add', '--global', moduleName] : ['add', moduleName],
455
- bun: global ? ['add', '--global', moduleName] : ['add', moduleName],
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
- this.logger.info(`Installing missing plugin "${moduleName}" with ${command} ${args.join(' ')}` +
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",
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.5",
32
+ "@yumerijs/core": "^3.0.7",
33
33
  "esbuild-register": "^3.6.0",
34
34
  "typescript": "^6.0.3"
35
35
  },