@yumerijs/loader 3.1.0 → 3.1.1
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 +6 -0
- package/dist/index.js +86 -16
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,12 @@ export declare class PluginLoader {
|
|
|
57
57
|
private _unloadSinglePlugin;
|
|
58
58
|
reloadPlugin(pluginName: string): Promise<void>;
|
|
59
59
|
private watchPlugin;
|
|
60
|
+
private isMissingModuleError;
|
|
61
|
+
private isNpxInvocation;
|
|
62
|
+
private detectPackageManager;
|
|
63
|
+
private confirmPluginInstall;
|
|
64
|
+
private installMissingPlugin;
|
|
65
|
+
private importPluginModule;
|
|
60
66
|
loadModule(pluginName: string): Promise<Plugin>;
|
|
61
67
|
checkPluginDependencies(pluginPath: string): Promise<boolean>;
|
|
62
68
|
installPluginDependencies(pluginName: string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,13 @@ import * as path from 'path';
|
|
|
2
2
|
import { Core, Logger, Context, fallback, Schema, I18n, resolvePluginModule } from '@yumerijs/core';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
|
-
import {
|
|
5
|
+
import { execFile } from 'child_process';
|
|
6
|
+
import { createInterface } from 'readline/promises';
|
|
7
|
+
import { stdin as input, stdout as output } from 'process';
|
|
6
8
|
import { fileURLToPath } from 'url';
|
|
7
9
|
import * as yaml from 'js-yaml';
|
|
8
10
|
import * as chokidar from 'chokidar';
|
|
9
|
-
const
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
10
12
|
export class PluginLoader {
|
|
11
13
|
pluginsDir;
|
|
12
14
|
core;
|
|
@@ -360,11 +362,91 @@ export class PluginLoader {
|
|
|
360
362
|
});
|
|
361
363
|
this.pluginWatchers[pluginName] = watcher;
|
|
362
364
|
}
|
|
365
|
+
isMissingModuleError(error, moduleName) {
|
|
366
|
+
const candidate = error;
|
|
367
|
+
if (candidate?.code !== 'ERR_MODULE_NOT_FOUND')
|
|
368
|
+
return false;
|
|
369
|
+
return typeof candidate.message === 'string' && candidate.message.includes(moduleName);
|
|
370
|
+
}
|
|
371
|
+
isNpxInvocation() {
|
|
372
|
+
const argv = process.argv.map(value => value.toLowerCase());
|
|
373
|
+
const env = process.env;
|
|
374
|
+
return Boolean(env.npm_config_npx_command ||
|
|
375
|
+
env.npm_command === 'exec' ||
|
|
376
|
+
argv.some(value => /(?:^|[\\/])npx(?:\.cmd)?$/.test(value)) ||
|
|
377
|
+
(env._ && /(?:^|[\\/])npx(?:\.cmd)?$/.test(env._.toLowerCase())));
|
|
378
|
+
}
|
|
379
|
+
detectPackageManager() {
|
|
380
|
+
const cwd = process.cwd();
|
|
381
|
+
try {
|
|
382
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
383
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
384
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
385
|
+
if (typeof packageJson.packageManager === 'string') {
|
|
386
|
+
return packageJson.packageManager.split('@')[0];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Fall back to lockfile detection when package.json is unavailable or invalid.
|
|
392
|
+
}
|
|
393
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
|
|
394
|
+
return 'pnpm';
|
|
395
|
+
if (fs.existsSync(path.join(cwd, 'yarn.lock')))
|
|
396
|
+
return 'yarn';
|
|
397
|
+
if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock')))
|
|
398
|
+
return 'bun';
|
|
399
|
+
return 'npm';
|
|
400
|
+
}
|
|
401
|
+
async confirmPluginInstall(moduleName) {
|
|
402
|
+
if (!input.isTTY || !output.isTTY) {
|
|
403
|
+
this.logger.warn(`Plugin package "${moduleName}" is not installed and no interactive terminal is available.`);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
const scope = this.isNpxInvocation() ? 'globally' : 'in the current project';
|
|
407
|
+
const readline = createInterface({ input, output });
|
|
408
|
+
try {
|
|
409
|
+
const answer = await readline.question(`Plugin package "${moduleName}" is not installed. Install it ${scope}? [y/N] `);
|
|
410
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
411
|
+
}
|
|
412
|
+
finally {
|
|
413
|
+
readline.close();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
async installMissingPlugin(moduleName) {
|
|
417
|
+
const packageManager = this.detectPackageManager();
|
|
418
|
+
const global = this.isNpxInvocation();
|
|
419
|
+
const argsByManager = {
|
|
420
|
+
npm: global ? ['install', '--global', moduleName] : ['install', moduleName, '--save'],
|
|
421
|
+
yarn: global ? ['global', 'add', moduleName] : ['add', moduleName],
|
|
422
|
+
pnpm: global ? ['add', '--global', moduleName] : ['add', moduleName],
|
|
423
|
+
bun: global ? ['add', '--global', moduleName] : ['add', moduleName],
|
|
424
|
+
};
|
|
425
|
+
const args = argsByManager[packageManager] || argsByManager.npm;
|
|
426
|
+
const command = process.platform === 'win32' ? `${packageManager}.cmd` : packageManager;
|
|
427
|
+
this.logger.info(`Installing missing plugin "${moduleName}" with ${command} ${args.join(' ')}` +
|
|
428
|
+
(global ? ' (global)' : ''));
|
|
429
|
+
await execFileAsync(command, args, { cwd: process.cwd() });
|
|
430
|
+
}
|
|
431
|
+
async importPluginModule(moduleName) {
|
|
432
|
+
try {
|
|
433
|
+
return await import(moduleName);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
if (!this.isMissingModuleError(error, moduleName))
|
|
437
|
+
throw error;
|
|
438
|
+
const shouldInstall = await this.confirmPluginInstall(moduleName);
|
|
439
|
+
if (!shouldInstall)
|
|
440
|
+
throw error;
|
|
441
|
+
await this.installMissingPlugin(moduleName);
|
|
442
|
+
return await import(moduleName);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
363
445
|
async loadModule(pluginName) {
|
|
364
446
|
const entry = this.getPluginEntry(pluginName);
|
|
365
447
|
if (!entry)
|
|
366
448
|
throw new Error(`Plugin configuration not found for instance "${pluginName}".`);
|
|
367
|
-
const pluginModule = await
|
|
449
|
+
const pluginModule = await this.importPluginModule(entry.moduleName);
|
|
368
450
|
const context = this.getContext(pluginName, {});
|
|
369
451
|
return resolvePluginModule(pluginModule, context, entry.config);
|
|
370
452
|
}
|
|
@@ -372,19 +454,7 @@ export class PluginLoader {
|
|
|
372
454
|
return true;
|
|
373
455
|
}
|
|
374
456
|
async installPluginDependencies(pluginName) {
|
|
375
|
-
|
|
376
|
-
this.logger.info(`Installing dependencies for plugin: ${pluginName}`);
|
|
377
|
-
const { stdout, stderr } = await execAsync(`npm install ${pluginName} --save`);
|
|
378
|
-
this.logger.info(`stdout: ${stdout}`);
|
|
379
|
-
if (stderr) {
|
|
380
|
-
this.logger.error(`stderr: ${stderr}`);
|
|
381
|
-
}
|
|
382
|
-
this.logger.info(`Dependencies installed for plugin: ${pluginName}`);
|
|
383
|
-
}
|
|
384
|
-
catch (error) {
|
|
385
|
-
this.logger.error(`Error installing dependencies for plugin ${pluginName}:`, error);
|
|
386
|
-
throw error;
|
|
387
|
-
}
|
|
457
|
+
await this.installMissingPlugin(pluginName);
|
|
388
458
|
}
|
|
389
459
|
}
|
|
390
460
|
export default PluginLoader;
|