fix: require resolve (#4356)

* fix: require resolve

* fix: error

* fix: skip realpath

* fix: fs.realpath
This commit is contained in:
chenos 2024-05-16 18:46:56 +08:00 committed by GitHub
parent cb8aa0d931
commit 6e3595c0be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 105 additions and 100 deletions

View File

@ -27,7 +27,11 @@ export default (app: Application) => {
const upgrading = await fsExists(file); const upgrading = await fsExists(file);
if (upgrading) { if (upgrading) {
await app.upgrade(); await app.upgrade();
await fs.promises.rm(file); try {
await fs.promises.rm(file);
} catch (error) {
// skip
}
} else if (options.quickstart) { } else if (options.quickstart) {
if (await app.isInstalled()) { if (await app.isInstalled()) {
await app.upgrade(); await app.upgrade();

View File

@ -16,7 +16,7 @@ import fg from 'fast-glob';
import fs from 'fs'; import fs from 'fs';
import _ from 'lodash'; import _ from 'lodash';
import net from 'net'; import net from 'net';
import { basename, dirname, join, resolve, sep } from 'path'; import { basename, join, resolve, sep } from 'path';
import Application from '../application'; import Application from '../application';
import { createAppProxy, tsxRerunning } from '../helper'; import { createAppProxy, tsxRerunning } from '../helper';
import { Plugin } from '../plugin'; import { Plugin } from '../plugin';
@ -29,6 +29,7 @@ import {
copyTempPackageToStorageAndLinkToNodeModules, copyTempPackageToStorageAndLinkToNodeModules,
downloadAndUnzipToTempDir, downloadAndUnzipToTempDir,
getNpmInfo, getNpmInfo,
getPluginBasePath,
getPluginInfoByNpm, getPluginInfoByNpm,
removeTmpDir, removeTmpDir,
updatePluginByCompressedFileUrl, updatePluginByCompressedFileUrl,
@ -395,7 +396,7 @@ export class PluginManager {
* @internal * @internal
*/ */
async loadCommands() { async loadCommands() {
this.app.log.debug('load commands'); this.app.log.info('load commands');
const items = await this.repository.find({ const items = await this.repository.find({
filter: { filter: {
enabled: true, enabled: true,
@ -404,21 +405,16 @@ export class PluginManager {
const packageNames: string[] = items.map((item) => item.packageName); const packageNames: string[] = items.map((item) => item.packageName);
const source = []; const source = [];
for (const packageName of packageNames) { for (const packageName of packageNames) {
const file = require.resolve(packageName); const dirname = await getPluginBasePath(packageName);
const sourceDir = basename(dirname(file)) === 'src' ? 'src' : 'dist'; const directory = join(dirname, 'server/commands/*.' + (basename(dirname) === 'src' ? 'ts' : 'js'));
const directory = join(
packageName,
sourceDir,
'server/commands/*.' + (basename(dirname(file)) === 'src' ? 'ts' : 'js'),
);
source.push(directory.replaceAll(sep, '/')); source.push(directory.replaceAll(sep, '/'));
} }
for (const plugin of this.options.plugins || []) { for (const plugin of this.options.plugins || []) {
if (typeof plugin === 'string') { if (typeof plugin === 'string') {
const packageName = await PluginManager.getPackageName(plugin); const { packageName } = await PluginManager.parseName(plugin);
const file = require.resolve(packageName); const dirname = await getPluginBasePath(packageName);
const sourceDir = basename(dirname(file)) === 'src' ? 'src' : 'lib'; const directory = join(dirname, 'server/commands/*.' + (basename(dirname) === 'src' ? 'ts' : 'js'));
const directory = join(packageName, sourceDir, 'server/commands/*.' + (sourceDir === 'src' ? 'ts' : 'js'));
source.push(directory.replaceAll(sep, '/')); source.push(directory.replaceAll(sep, '/'));
} }
} }

View File

@ -9,7 +9,7 @@
/* istanbul ignore next -- @preserve */ /* istanbul ignore next -- @preserve */
import { importModule, isURL } from '@nocobase/utils'; import { importModule, isURL, requireResolve } from '@nocobase/utils';
import { createStoragePluginSymLink } from '@nocobase/utils/plugin-symlink'; import { createStoragePluginSymLink } from '@nocobase/utils/plugin-symlink';
import axios, { AxiosRequestConfig } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
import decompress from 'decompress'; import decompress from 'decompress';
@ -585,3 +585,19 @@ export async function checkAndGetCompatible(packageName: string) {
depsCompatible: compatible, depsCompatible: compatible,
}; };
} }
export async function getPluginBasePath(packageName: string) {
if (!packageName) {
return;
}
const file = await fs.realpath(await requireResolve(packageName));
try {
const basePath = await fs.realpath(path.resolve(process.env.NODE_MODULES_PATH, packageName, 'src'));
if (file.startsWith(basePath)) {
return basePath;
}
} catch (error) {
// skip
}
return path.dirname(path.dirname(file));
}

View File

@ -11,14 +11,13 @@
import { Model } from '@nocobase/database'; import { Model } from '@nocobase/database';
import { LoggerOptions } from '@nocobase/logger'; import { LoggerOptions } from '@nocobase/logger';
import { fsExists, importModule } from '@nocobase/utils'; import { fsExists } from '@nocobase/utils';
import fs from 'fs'; import fs from 'fs';
import glob from 'glob';
import type { TFuncKey, TOptions } from 'i18next'; import type { TFuncKey, TOptions } from 'i18next';
import { basename, resolve } from 'path'; import { resolve } from 'path';
import { Application } from './application'; import { Application } from './application';
import { getExposeChangelogUrl, getExposeReadmeUrl, InstallOptions } from './plugin-manager'; import { InstallOptions, getExposeChangelogUrl, getExposeReadmeUrl } from './plugin-manager';
import { checkAndGetCompatible } from './plugin-manager/utils'; import { checkAndGetCompatible, getPluginBasePath } from './plugin-manager/utils';
export interface PluginInterface { export interface PluginInterface {
beforeLoad?: () => void; beforeLoad?: () => void;
@ -146,46 +145,16 @@ export abstract class Plugin<O = any> implements PluginInterface {
this.options = options || {}; this.options = options || {};
} }
/**
* @internal
*/
async loadCommands() {
const extensions = ['js', 'ts'];
const directory = resolve(
process.env.NODE_MODULES_PATH,
this.options.packageName,
await this.getSourceDir(),
'server/commands',
);
const patten = `${directory}/*.{${extensions.join(',')}}`;
const files = glob.sync(patten, {
ignore: ['**/*.d.ts'],
});
for (const file of files) {
let filename = basename(file);
filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
const callback = await importModule(file);
callback(this.app);
}
if (files.length) {
this.app.log.debug(`load commands [${this.name}]`);
}
}
/** /**
* @internal * @internal
*/ */
async loadMigrations() { async loadMigrations() {
this.app.log.debug(`load plugin migrations [${this.name}]`); this.app.log.debug(`load plugin migrations [${this.name}]`);
if (!this.options.packageName) { const basePath = await this.getPluginBasePath();
if (!basePath) {
return { beforeLoad: [], afterSync: [], afterLoad: [] }; return { beforeLoad: [], afterSync: [], afterLoad: [] };
} }
const directory = resolve( const directory = resolve(basePath, 'server/migrations');
process.env.NODE_MODULES_PATH,
this.options.packageName,
await this.getSourceDir(),
'server/migrations',
);
return await this.app.loadMigrations({ return await this.app.loadMigrations({
directory, directory,
namespace: this.options.packageName, namespace: this.options.packageName,
@ -195,19 +164,22 @@ export abstract class Plugin<O = any> implements PluginInterface {
}); });
} }
private async getPluginBasePath() {
if (!this.options.packageName) {
return;
}
return getPluginBasePath(this.options.packageName);
}
/** /**
* @internal * @internal
*/ */
async loadCollections() { async loadCollections() {
if (!this.options.packageName) { const basePath = await this.getPluginBasePath();
if (!basePath) {
return; return;
} }
const directory = resolve( const directory = resolve(basePath, 'server/collections');
process.env.NODE_MODULES_PATH,
this.options.packageName,
await this.getSourceDir(),
'server/collections',
);
if (await fsExists(directory)) { if (await fsExists(directory)) {
await this.db.import({ await this.db.import({
directory, directory,
@ -265,38 +237,6 @@ export abstract class Plugin<O = any> implements PluginInterface {
return results; return results;
} }
/**
* @internal
*/
protected async getSourceDir() {
if (this._sourceDir) {
return this._sourceDir;
}
if (await this.isDev()) {
return (this._sourceDir = 'src');
}
if (basename(__dirname) === 'src') {
return (this._sourceDir = 'src');
}
return (this._sourceDir = this.isPreset ? 'lib' : 'dist');
}
/**
* @internal
*/
protected async isDev() {
if (!this.options.packageName) {
return false;
}
const file = await fs.promises.realpath(
resolve(process.env.NODE_MODULES_PATH || resolve(process.cwd(), 'node_modules'), this.options.packageName),
);
if (file.startsWith(resolve(process.cwd(), 'packages'))) {
return !!process.env.IS_DEV_CMD;
}
return false;
}
} }
export default Plugin; export default Plugin;

View File

@ -1,5 +1,5 @@
const { dirname, resolve } = require('path'); const { dirname, resolve } = require('path');
const { readFile, writeFile, readdir, symlink, unlink, mkdir, stat } = require('fs').promises; const { realpath, readFile, writeFile, readdir, symlink, unlink, mkdir, stat } = require('fs').promises;
async function getStoragePluginNames(target) { async function getStoragePluginNames(target) {
const plugins = []; const plugins = [];
@ -76,6 +76,10 @@ async function createDevPluginSymLink(pluginName) {
} }
const link = resolve(nodeModulesPath, pluginName); const link = resolve(nodeModulesPath, pluginName);
if (await fsExists(link)) { if (await fsExists(link)) {
const real = await realpath(link);
if (real === resolve(packagePluginsPath, pluginName)) {
return;
}
await unlink(link); await unlink(link);
} }
await symlink(resolve(packagePluginsPath, pluginName), link, 'dir'); await symlink(resolve(packagePluginsPath, pluginName), link, 'dir');

View File

@ -7,9 +7,25 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import fs from 'fs';
import path from 'path'; import path from 'path';
import { pathToFileURL } from 'url'; import { pathToFileURL } from 'url';
export async function requireResolve(m: any) {
if (!process.env.VITEST) {
return require.resolve(m);
}
// 以下逻辑仅用于测试环境,因为 vitest 不支持对 require 调用进行别名
const json = JSON.parse(
await fs.promises.readFile(path.resolve(process.cwd(), './tsconfig.paths.json'), { encoding: 'utf8' }),
);
const paths = json.compilerOptions.paths;
if (paths[m]) {
return require.resolve(path.resolve(process.cwd(), paths[m][0], 'index.ts'));
}
return require.resolve(m);
}
export function requireModule(m: any) { export function requireModule(m: any) {
if (typeof m === 'string') { if (typeof m === 'string') {
m = require(m); m = require(m);

View File

@ -0,0 +1,34 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { MockServer, createMockServer } from '@nocobase/test';
describe('cli', () => {
let app: MockServer;
beforeEach(async () => {
app = await createMockServer({
plugins: ['nocobase'],
});
});
afterEach(async () => {
await app.destroy();
});
test('find', async () => {
try {
await app.runCommand('restore');
} catch (error) {
// skip
}
const command = app.findCommand('restore');
expect(command).toBeDefined();
});
});

View File

@ -87,14 +87,9 @@ export class PresetNocoBase extends Plugin {
} }
async getPackageJson(name) { async getPackageJson(name) {
let packageName = name; const { packageName } = await PluginManager.parseName(name);
try {
packageName = await PluginManager.getPackageName(name);
} catch (error) {
packageName = name;
}
const packageJson = await PluginManager.getPackageJson(packageName); const packageJson = await PluginManager.getPackageJson(packageName);
return packageJson; return { ...packageJson, name: packageName };
} }
async allPlugins() { async allPlugins() {