@verdaccio/loaders 6.0.0-6-next.15 → 6.0.0-6-next.17
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/CHANGELOG.md +70 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +7 -12
- package/build/index.js.map +1 -1
- package/build/plugin-async-loader.d.ts +29 -0
- package/build/plugin-async-loader.js +156 -0
- package/build/plugin-async-loader.js.map +1 -0
- package/build/utils.d.ts +10 -0
- package/build/utils.js +57 -0
- package/build/utils.js.map +1 -0
- package/package.json +8 -5
- package/src/index.ts +1 -1
- package/src/plugin-async-loader.ts +132 -0
- package/src/utils.ts +40 -0
- package/test/partials/config/custom-prefix-auth.yaml +3 -0
- package/test/partials/config/npm-plugin-auth.yaml +3 -0
- package/test/partials/config/npm-plugin-not-found.yaml +3 -0
- package/test/partials/config/plugins-folder-fake.yaml +4 -0
- package/test/partials/config/relative-plugins.yaml +4 -0
- package/test/partials/config/scope-auth.yaml +3 -0
- package/test/partials/config/valid-plugin.yaml +3 -0
- package/test/partials/test-plugin-storage/verdaccio-scope/index.js +7 -0
- package/test/partials/test-plugin-storage/verdaccio-scope/package.json +11 -0
- package/test/plugin_loader_async.spec.ts +168 -0
- package/build/plugin-loader.d.ts +0 -16
- package/build/plugin-loader.js +0 -159
- package/build/plugin-loader.js.map +0 -1
- package/src/plugin-loader.ts +0 -143
- package/test/plugin_loader.spec.ts +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @verdaccio/loaders
|
|
2
2
|
|
|
3
|
+
## 6.0.0-6-next.17
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 9fc2e796: feat(plugins): improve plugin loader
|
|
8
|
+
|
|
9
|
+
### Changes
|
|
10
|
+
|
|
11
|
+
- Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227
|
|
12
|
+
- Avoid config collisions https://github.com/verdaccio/verdaccio/issues/928
|
|
13
|
+
- https://github.com/verdaccio/verdaccio/issues/1394
|
|
14
|
+
- `config.plugins` plugin path validations
|
|
15
|
+
- Updated algorithm for plugin loader.
|
|
16
|
+
- improved documentation (included dev)
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227
|
|
21
|
+
- Custom prefix:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
// config.yaml
|
|
25
|
+
server:
|
|
26
|
+
pluginPrefix: mycompany
|
|
27
|
+
middleware:
|
|
28
|
+
audit:
|
|
29
|
+
foo: 1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This configuration will look up for `mycompany-audit` instead `Verdaccio-audit`.
|
|
33
|
+
|
|
34
|
+
## Breaking Changes
|
|
35
|
+
|
|
36
|
+
### sinopia plugins
|
|
37
|
+
|
|
38
|
+
- `sinopia` fallback support is removed, but can be restored using `pluginPrefix`
|
|
39
|
+
|
|
40
|
+
### plugin filter
|
|
41
|
+
|
|
42
|
+
- method rename `filter_metadata`->`filterMetadata`
|
|
43
|
+
|
|
44
|
+
### Plugin constructor does not merge configs anymore https://github.com/verdaccio/verdaccio/issues/928
|
|
45
|
+
|
|
46
|
+
The plugin receives as first argument `config`, which represents the config of the plugin. Example:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
// config.yaml
|
|
50
|
+
auth:
|
|
51
|
+
plugin:
|
|
52
|
+
foo: 1
|
|
53
|
+
bar: 2
|
|
54
|
+
|
|
55
|
+
export class Plugin<T> {
|
|
56
|
+
public constructor(config: T, options: PluginOptions) {
|
|
57
|
+
console.log(config);
|
|
58
|
+
// {foo:1, bar: 2}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- @verdaccio/logger@6.0.0-6-next.16
|
|
66
|
+
|
|
67
|
+
## 6.0.0-6-next.16
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- @verdaccio/logger@6.0.0-6-next.15
|
|
72
|
+
|
|
3
73
|
## 6.0.0-6-next.15
|
|
4
74
|
|
|
5
75
|
### Patch Changes
|
package/build/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { asyncLoadPlugin } from './plugin-async-loader';
|
package/build/index.js
CHANGED
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (key in exports && exports[key] === _pluginLoader[key]) return;
|
|
12
|
-
Object.defineProperty(exports, key, {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _pluginLoader[key];
|
|
16
|
-
}
|
|
17
|
-
});
|
|
6
|
+
Object.defineProperty(exports, "asyncLoadPlugin", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _pluginAsyncLoader.asyncLoadPlugin;
|
|
10
|
+
}
|
|
18
11
|
});
|
|
12
|
+
|
|
13
|
+
var _pluginAsyncLoader = require("./plugin-async-loader");
|
|
19
14
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export { asyncLoadPlugin } from './plugin-async-loader';\n"],"mappings":";;;;;;;;;;;;AAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Config, IPlugin, Logger } from '@verdaccio/types';
|
|
2
|
+
export declare type Params = {
|
|
3
|
+
config: Config;
|
|
4
|
+
logger: Logger;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
|
|
8
|
+
*
|
|
9
|
+
* The loader follows the order:
|
|
10
|
+
* - If the at the `config.yaml` file the `plugins: ./plugins` is defined
|
|
11
|
+
* - If is absolute will use the provided path
|
|
12
|
+
* - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
|
|
13
|
+
* hosted at /root/plugins
|
|
14
|
+
* - The next step is find at the node_modules or global based on the `require` native algorithm.
|
|
15
|
+
* - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
|
|
16
|
+
* - If the package is not scoped, will use the default prefix: verdaccio-foo.
|
|
17
|
+
* - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
|
|
18
|
+
*
|
|
19
|
+
* The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
|
|
20
|
+
* The `params` is an object that contains the global configuration and the logger.
|
|
21
|
+
*
|
|
22
|
+
* @param {*} pluginConfigs the custom plugin section
|
|
23
|
+
* @param {*} params a set of params to initialize the plugin
|
|
24
|
+
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
25
|
+
* @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
|
|
26
|
+
* @return {Array} list of plugins
|
|
27
|
+
*/
|
|
28
|
+
export declare function asyncLoadPlugin<T extends IPlugin<T>>(pluginConfigs: any, params: Params, sanityCheck: any, prefix?: string): Promise<any>;
|
|
29
|
+
export declare function executePlugin(plugin: any, pluginConfig: any, params: Params): any;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.asyncLoadPlugin = asyncLoadPlugin;
|
|
7
|
+
exports.executePlugin = executePlugin;
|
|
8
|
+
|
|
9
|
+
var _debug = _interopRequireDefault(require("debug"));
|
|
10
|
+
|
|
11
|
+
var _promises = require("fs/promises");
|
|
12
|
+
|
|
13
|
+
var _path = require("path");
|
|
14
|
+
|
|
15
|
+
var _logger = require("@verdaccio/logger");
|
|
16
|
+
|
|
17
|
+
var _utils = require("./utils");
|
|
18
|
+
|
|
19
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
20
|
+
|
|
21
|
+
const debug = (0, _debug.default)('verdaccio:plugin:loader:async');
|
|
22
|
+
|
|
23
|
+
async function isDirectory(pathFolder) {
|
|
24
|
+
const stat = await (0, _promises.lstat)(pathFolder);
|
|
25
|
+
return stat.isDirectory();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
|
|
30
|
+
*
|
|
31
|
+
* The loader follows the order:
|
|
32
|
+
* - If the at the `config.yaml` file the `plugins: ./plugins` is defined
|
|
33
|
+
* - If is absolute will use the provided path
|
|
34
|
+
* - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
|
|
35
|
+
* hosted at /root/plugins
|
|
36
|
+
* - The next step is find at the node_modules or global based on the `require` native algorithm.
|
|
37
|
+
* - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
|
|
38
|
+
* - If the package is not scoped, will use the default prefix: verdaccio-foo.
|
|
39
|
+
* - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
|
|
40
|
+
*
|
|
41
|
+
* The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
|
|
42
|
+
* The `params` is an object that contains the global configuration and the logger.
|
|
43
|
+
*
|
|
44
|
+
* @param {*} pluginConfigs the custom plugin section
|
|
45
|
+
* @param {*} params a set of params to initialize the plugin
|
|
46
|
+
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
47
|
+
* @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
|
|
48
|
+
* @return {Array} list of plugins
|
|
49
|
+
*/
|
|
50
|
+
async function asyncLoadPlugin(pluginConfigs = {}, params, sanityCheck, prefix = 'verdaccio') {
|
|
51
|
+
const pluginsIds = Object.keys(pluginConfigs);
|
|
52
|
+
const {
|
|
53
|
+
config
|
|
54
|
+
} = params;
|
|
55
|
+
let plugins = [];
|
|
56
|
+
|
|
57
|
+
for (let pluginId of pluginsIds) {
|
|
58
|
+
debug('plugin %s', pluginId);
|
|
59
|
+
|
|
60
|
+
if (typeof config.plugins === 'string') {
|
|
61
|
+
let pluginsPath = config.plugins;
|
|
62
|
+
debug('plugin path %s', pluginsPath);
|
|
63
|
+
|
|
64
|
+
if (!(0, _path.isAbsolute)(pluginsPath)) {
|
|
65
|
+
if (typeof config.config_path === 'string' && !config.configPath) {
|
|
66
|
+
_logger.logger.error('configPath is missing and the legacy config.config_path is not available for loading plugins');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!config.configPath) {
|
|
70
|
+
_logger.logger.error('config path property is required for loading plugins');
|
|
71
|
+
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
pluginsPath = (0, _path.resolve)((0, _path.join)((0, _path.dirname)(config.configPath), pluginsPath));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_logger.logger.debug({
|
|
79
|
+
path: pluginsPath
|
|
80
|
+
}, 'plugins folder defined, loading plugins from @{path} '); // throws if is nto a directory
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await isDirectory(pluginsPath);
|
|
85
|
+
const pluginDir = pluginsPath;
|
|
86
|
+
const externalFilePlugin = (0, _path.resolve)(pluginDir, `${prefix}-${pluginId}`);
|
|
87
|
+
let plugin = (0, _utils.tryLoad)(externalFilePlugin);
|
|
88
|
+
|
|
89
|
+
if (plugin && (0, _utils.isValid)(plugin)) {
|
|
90
|
+
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
|
|
91
|
+
|
|
92
|
+
if (!sanityCheck(plugin)) {
|
|
93
|
+
_logger.logger.error({
|
|
94
|
+
content: externalFilePlugin
|
|
95
|
+
}, "@{content} doesn't look like a valid plugin");
|
|
96
|
+
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
plugins.push(plugin);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
_logger.logger.warn({
|
|
105
|
+
err: err.message,
|
|
106
|
+
pluginsPath,
|
|
107
|
+
pluginId
|
|
108
|
+
}, '@{err} on loading plugins at @{pluginsPath} for @{pluginId}');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof pluginId === 'string') {
|
|
113
|
+
const isScoped = pluginId.startsWith('@') && pluginId.includes('/');
|
|
114
|
+
debug('is scoped plugin %s', isScoped);
|
|
115
|
+
const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;
|
|
116
|
+
debug('plugin pkg name %s', pluginName);
|
|
117
|
+
let plugin = (0, _utils.tryLoad)(pluginName);
|
|
118
|
+
|
|
119
|
+
if (plugin && (0, _utils.isValid)(plugin)) {
|
|
120
|
+
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
|
|
121
|
+
|
|
122
|
+
if (!sanityCheck(plugin)) {
|
|
123
|
+
_logger.logger.error({
|
|
124
|
+
content: pluginName
|
|
125
|
+
}, "@{content} doesn't look like a valid plugin");
|
|
126
|
+
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
plugins.push(plugin);
|
|
131
|
+
continue;
|
|
132
|
+
} else {
|
|
133
|
+
_logger.logger.error({
|
|
134
|
+
pluginName
|
|
135
|
+
}, 'package not found, try to install @{pluginName} with a package manager');
|
|
136
|
+
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
debug('plugin found %s', plugins.length);
|
|
143
|
+
return plugins;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function executePlugin(plugin, pluginConfig, params) {
|
|
147
|
+
if ((0, _utils.isES6)(plugin)) {
|
|
148
|
+
debug('plugin is ES6'); // eslint-disable-next-line new-cap
|
|
149
|
+
|
|
150
|
+
return new plugin.default(pluginConfig, params);
|
|
151
|
+
} else {
|
|
152
|
+
debug('plugin is commonJS');
|
|
153
|
+
return plugin(pluginConfig, params);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=plugin-async-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-async-loader.js","names":["debug","buildDebug","isDirectory","pathFolder","stat","lstat","asyncLoadPlugin","pluginConfigs","params","sanityCheck","prefix","pluginsIds","Object","keys","config","plugins","pluginId","pluginsPath","isAbsolute","config_path","configPath","logger","error","resolve","join","dirname","path","pluginDir","externalFilePlugin","plugin","tryLoad","isValid","executePlugin","content","push","err","warn","message","isScoped","startsWith","includes","pluginName","length","pluginConfig","isES6","default"],"sources":["../src/plugin-async-loader.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { lstat } from 'fs/promises';\nimport { dirname, isAbsolute, join, resolve } from 'path';\n\nimport { logger } from '@verdaccio/logger';\nimport { Config, IPlugin, Logger } from '@verdaccio/types';\n\nimport { isES6, isValid, tryLoad } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:loader:async');\n\nasync function isDirectory(pathFolder) {\n const stat = await lstat(pathFolder);\n return stat.isDirectory();\n}\n\nexport type Params = { config: Config; logger: Logger };\n\n/**\n * The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.\n *\n * The loader follows the order:\n * - If the at the `config.yaml` file the `plugins: ./plugins` is defined\n * - If is absolute will use the provided path\n * - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be\n * hosted at /root/plugins\n * - The next step is find at the node_modules or global based on the `require` native algorithm.\n * - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`\n * - If the package is not scoped, will use the default prefix: verdaccio-foo.\n * - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.\n *\n * The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.\n * The `params` is an object that contains the global configuration and the logger.\n *\n * @param {*} pluginConfigs the custom plugin section\n * @param {*} params a set of params to initialize the plugin\n * @param {*} sanityCheck callback that check the shape that should fulfill the plugin\n * @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix\n * @return {Array} list of plugins\n */\nexport async function asyncLoadPlugin<T extends IPlugin<T>>(\n pluginConfigs: any = {},\n params: Params,\n sanityCheck: any,\n prefix: string = 'verdaccio'\n): Promise<any> {\n const pluginsIds = Object.keys(pluginConfigs);\n const { config } = params;\n let plugins: any[] = [];\n for (let pluginId of pluginsIds) {\n debug('plugin %s', pluginId);\n if (typeof config.plugins === 'string') {\n let pluginsPath = config.plugins;\n debug('plugin path %s', pluginsPath);\n if (!isAbsolute(pluginsPath)) {\n if (typeof config.config_path === 'string' && !config.configPath) {\n logger.error(\n 'configPath is missing and the legacy config.config_path is not available for loading plugins'\n );\n }\n\n if (!config.configPath) {\n logger.error('config path property is required for loading plugins');\n continue;\n }\n pluginsPath = resolve(join(dirname(config.configPath), pluginsPath));\n }\n\n logger.debug({ path: pluginsPath }, 'plugins folder defined, loading plugins from @{path} ');\n // throws if is nto a directory\n try {\n await isDirectory(pluginsPath);\n const pluginDir = pluginsPath;\n const externalFilePlugin = resolve(pluginDir, `${prefix}-${pluginId}`);\n let plugin = tryLoad(externalFilePlugin);\n if (plugin && isValid(plugin)) {\n plugin = executePlugin(plugin, pluginConfigs[pluginId], params);\n if (!sanityCheck(plugin)) {\n logger.error(\n { content: externalFilePlugin },\n \"@{content} doesn't look like a valid plugin\"\n );\n continue;\n }\n plugins.push(plugin);\n continue;\n }\n } catch (err: any) {\n logger.warn(\n { err: err.message, pluginsPath, pluginId },\n '@{err} on loading plugins at @{pluginsPath} for @{pluginId}'\n );\n }\n }\n\n if (typeof pluginId === 'string') {\n const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/');\n debug('is scoped plugin %s', isScoped);\n const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;\n debug('plugin pkg name %s', pluginName);\n let plugin = tryLoad(pluginName);\n if (plugin && isValid(plugin)) {\n plugin = executePlugin(plugin, pluginConfigs[pluginId], params);\n if (!sanityCheck(plugin)) {\n logger.error({ content: pluginName }, \"@{content} doesn't look like a valid plugin\");\n continue;\n }\n plugins.push(plugin);\n continue;\n } else {\n logger.error(\n { pluginName },\n 'package not found, try to install @{pluginName} with a package manager'\n );\n continue;\n }\n }\n }\n debug('plugin found %s', plugins.length);\n return plugins;\n}\n\nexport function executePlugin(plugin, pluginConfig, params: Params) {\n if (isES6(plugin)) {\n debug('plugin is ES6');\n // eslint-disable-next-line new-cap\n return new plugin.default(pluginConfig, params);\n } else {\n debug('plugin is commonJS');\n return plugin(pluginConfig, params);\n }\n}\n"],"mappings":"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"}
|
package/build/utils.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Config } from '@verdaccio/types';
|
|
2
|
+
export declare function mergeConfig(appConfig: any, pluginConfig: any): Config;
|
|
3
|
+
export declare function isValid(plugin: any): boolean;
|
|
4
|
+
export declare function isES6(plugin: any): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Requires a module.
|
|
7
|
+
* @param {*} path the module's path
|
|
8
|
+
* @return {Object}
|
|
9
|
+
*/
|
|
10
|
+
export declare function tryLoad(path: string): any;
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isES6 = isES6;
|
|
7
|
+
exports.isValid = isValid;
|
|
8
|
+
exports.mergeConfig = mergeConfig;
|
|
9
|
+
exports.tryLoad = tryLoad;
|
|
10
|
+
|
|
11
|
+
var _debug = _interopRequireDefault(require("debug"));
|
|
12
|
+
|
|
13
|
+
var _lodash = _interopRequireDefault(require("lodash"));
|
|
14
|
+
|
|
15
|
+
var _logger = require("@verdaccio/logger");
|
|
16
|
+
|
|
17
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
18
|
+
|
|
19
|
+
const debug = (0, _debug.default)('verdaccio:plugin:loader:utils');
|
|
20
|
+
const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
|
|
21
|
+
|
|
22
|
+
function mergeConfig(appConfig, pluginConfig) {
|
|
23
|
+
return _lodash.default.merge(appConfig, pluginConfig);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isValid(plugin) {
|
|
27
|
+
return _lodash.default.isFunction(plugin) || _lodash.default.isFunction(plugin.default);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isES6(plugin) {
|
|
31
|
+
return Object.keys(plugin).includes('default');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Requires a module.
|
|
35
|
+
* @param {*} path the module's path
|
|
36
|
+
* @return {Object}
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function tryLoad(path) {
|
|
41
|
+
try {
|
|
42
|
+
debug('loading plugin %s', path);
|
|
43
|
+
return require(path);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err.code === MODULE_NOT_FOUND) {
|
|
46
|
+
debug('plugin %s not found', path);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_logger.logger.error({
|
|
51
|
+
err: err.msg
|
|
52
|
+
}, 'error loading plugin @{err}');
|
|
53
|
+
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","names":["debug","buildDebug","MODULE_NOT_FOUND","mergeConfig","appConfig","pluginConfig","_","merge","isValid","plugin","isFunction","default","isES6","Object","keys","includes","tryLoad","path","require","err","code","logger","error","msg"],"sources":["../src/utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport _ from 'lodash';\n\nimport { logger } from '@verdaccio/logger';\nimport { Config } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:plugin:loader:utils');\n\nconst MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';\n\nexport function mergeConfig(appConfig, pluginConfig): Config {\n return _.merge(appConfig, pluginConfig);\n}\n\nexport function isValid(plugin): boolean {\n return _.isFunction(plugin) || _.isFunction(plugin.default);\n}\n\nexport function isES6(plugin): boolean {\n return Object.keys(plugin).includes('default');\n}\n\n/**\n * Requires a module.\n * @param {*} path the module's path\n * @return {Object}\n */\nexport function tryLoad(path: string): any {\n try {\n debug('loading plugin %s', path);\n return require(path);\n } catch (err: any) {\n if (err.code === MODULE_NOT_FOUND) {\n debug('plugin %s not found', path);\n return null;\n }\n logger.error({ err: err.msg }, 'error loading plugin @{err}');\n throw err;\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;;AACA;;AAEA;;;;AAGA,MAAMA,KAAK,GAAG,IAAAC,cAAA,EAAW,+BAAX,CAAd;AAEA,MAAMC,gBAAgB,GAAG,kBAAzB;;AAEO,SAASC,WAAT,CAAqBC,SAArB,EAAgCC,YAAhC,EAAsD;EAC3D,OAAOC,eAAA,CAAEC,KAAF,CAAQH,SAAR,EAAmBC,YAAnB,CAAP;AACD;;AAEM,SAASG,OAAT,CAAiBC,MAAjB,EAAkC;EACvC,OAAOH,eAAA,CAAEI,UAAF,CAAaD,MAAb,KAAwBH,eAAA,CAAEI,UAAF,CAAaD,MAAM,CAACE,OAApB,CAA/B;AACD;;AAEM,SAASC,KAAT,CAAeH,MAAf,EAAgC;EACrC,OAAOI,MAAM,CAACC,IAAP,CAAYL,MAAZ,EAAoBM,QAApB,CAA6B,SAA7B,CAAP;AACD;AAED;AACA;AACA;AACA;AACA;;;AACO,SAASC,OAAT,CAAiBC,IAAjB,EAAoC;EACzC,IAAI;IACFjB,KAAK,CAAC,mBAAD,EAAsBiB,IAAtB,CAAL;IACA,OAAOC,OAAO,CAACD,IAAD,CAAd;EACD,CAHD,CAGE,OAAOE,GAAP,EAAiB;IACjB,IAAIA,GAAG,CAACC,IAAJ,KAAalB,gBAAjB,EAAmC;MACjCF,KAAK,CAAC,qBAAD,EAAwBiB,IAAxB,CAAL;MACA,OAAO,IAAP;IACD;;IACDI,cAAA,CAAOC,KAAP,CAAa;MAAEH,GAAG,EAAEA,GAAG,CAACI;IAAX,CAAb,EAA+B,6BAA/B;;IACA,MAAMJ,GAAN;EACD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/loaders",
|
|
3
|
-
"version": "6.0.0-6-next.
|
|
3
|
+
"version": "6.0.0-6-next.17",
|
|
4
4
|
"description": "loaders logic",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
"url": "https://github.com/verdaccio/verdaccio"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@verdaccio/logger": "6.0.0-6-next.
|
|
16
|
+
"@verdaccio/logger": "6.0.0-6-next.16",
|
|
17
17
|
"debug": "4.3.4",
|
|
18
18
|
"lodash": "4.17.21"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@verdaccio/core": "6.0.0-6-next.
|
|
22
|
-
"@verdaccio/config": "6.0.0-6-next.
|
|
23
|
-
"@verdaccio/types": "11.0.0-6-next.
|
|
21
|
+
"@verdaccio/core": "6.0.0-6-next.48",
|
|
22
|
+
"@verdaccio/config": "6.0.0-6-next.48",
|
|
23
|
+
"@verdaccio/types": "11.0.0-6-next.17",
|
|
24
|
+
"@verdaccio-scope/verdaccio-auth-foo": "0.0.2",
|
|
25
|
+
"verdaccio-auth-memory": "11.0.0-6-next.13",
|
|
26
|
+
"customprefix-auth": "1.0.0-6-next.0"
|
|
24
27
|
},
|
|
25
28
|
"homepage": "https://verdaccio.org",
|
|
26
29
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { asyncLoadPlugin } from './plugin-async-loader';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import buildDebug from 'debug';
|
|
2
|
+
import { lstat } from 'fs/promises';
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
import { logger } from '@verdaccio/logger';
|
|
6
|
+
import { Config, IPlugin, Logger } from '@verdaccio/types';
|
|
7
|
+
|
|
8
|
+
import { isES6, isValid, tryLoad } from './utils';
|
|
9
|
+
|
|
10
|
+
const debug = buildDebug('verdaccio:plugin:loader:async');
|
|
11
|
+
|
|
12
|
+
async function isDirectory(pathFolder) {
|
|
13
|
+
const stat = await lstat(pathFolder);
|
|
14
|
+
return stat.isDirectory();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Params = { config: Config; logger: Logger };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
|
|
21
|
+
*
|
|
22
|
+
* The loader follows the order:
|
|
23
|
+
* - If the at the `config.yaml` file the `plugins: ./plugins` is defined
|
|
24
|
+
* - If is absolute will use the provided path
|
|
25
|
+
* - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
|
|
26
|
+
* hosted at /root/plugins
|
|
27
|
+
* - The next step is find at the node_modules or global based on the `require` native algorithm.
|
|
28
|
+
* - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
|
|
29
|
+
* - If the package is not scoped, will use the default prefix: verdaccio-foo.
|
|
30
|
+
* - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
|
|
31
|
+
*
|
|
32
|
+
* The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
|
|
33
|
+
* The `params` is an object that contains the global configuration and the logger.
|
|
34
|
+
*
|
|
35
|
+
* @param {*} pluginConfigs the custom plugin section
|
|
36
|
+
* @param {*} params a set of params to initialize the plugin
|
|
37
|
+
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
38
|
+
* @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
|
|
39
|
+
* @return {Array} list of plugins
|
|
40
|
+
*/
|
|
41
|
+
export async function asyncLoadPlugin<T extends IPlugin<T>>(
|
|
42
|
+
pluginConfigs: any = {},
|
|
43
|
+
params: Params,
|
|
44
|
+
sanityCheck: any,
|
|
45
|
+
prefix: string = 'verdaccio'
|
|
46
|
+
): Promise<any> {
|
|
47
|
+
const pluginsIds = Object.keys(pluginConfigs);
|
|
48
|
+
const { config } = params;
|
|
49
|
+
let plugins: any[] = [];
|
|
50
|
+
for (let pluginId of pluginsIds) {
|
|
51
|
+
debug('plugin %s', pluginId);
|
|
52
|
+
if (typeof config.plugins === 'string') {
|
|
53
|
+
let pluginsPath = config.plugins;
|
|
54
|
+
debug('plugin path %s', pluginsPath);
|
|
55
|
+
if (!isAbsolute(pluginsPath)) {
|
|
56
|
+
if (typeof config.config_path === 'string' && !config.configPath) {
|
|
57
|
+
logger.error(
|
|
58
|
+
'configPath is missing and the legacy config.config_path is not available for loading plugins'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!config.configPath) {
|
|
63
|
+
logger.error('config path property is required for loading plugins');
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
pluginsPath = resolve(join(dirname(config.configPath), pluginsPath));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
logger.debug({ path: pluginsPath }, 'plugins folder defined, loading plugins from @{path} ');
|
|
70
|
+
// throws if is nto a directory
|
|
71
|
+
try {
|
|
72
|
+
await isDirectory(pluginsPath);
|
|
73
|
+
const pluginDir = pluginsPath;
|
|
74
|
+
const externalFilePlugin = resolve(pluginDir, `${prefix}-${pluginId}`);
|
|
75
|
+
let plugin = tryLoad(externalFilePlugin);
|
|
76
|
+
if (plugin && isValid(plugin)) {
|
|
77
|
+
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
|
|
78
|
+
if (!sanityCheck(plugin)) {
|
|
79
|
+
logger.error(
|
|
80
|
+
{ content: externalFilePlugin },
|
|
81
|
+
"@{content} doesn't look like a valid plugin"
|
|
82
|
+
);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
plugins.push(plugin);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
logger.warn(
|
|
90
|
+
{ err: err.message, pluginsPath, pluginId },
|
|
91
|
+
'@{err} on loading plugins at @{pluginsPath} for @{pluginId}'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof pluginId === 'string') {
|
|
97
|
+
const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/');
|
|
98
|
+
debug('is scoped plugin %s', isScoped);
|
|
99
|
+
const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;
|
|
100
|
+
debug('plugin pkg name %s', pluginName);
|
|
101
|
+
let plugin = tryLoad(pluginName);
|
|
102
|
+
if (plugin && isValid(plugin)) {
|
|
103
|
+
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
|
|
104
|
+
if (!sanityCheck(plugin)) {
|
|
105
|
+
logger.error({ content: pluginName }, "@{content} doesn't look like a valid plugin");
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
plugins.push(plugin);
|
|
109
|
+
continue;
|
|
110
|
+
} else {
|
|
111
|
+
logger.error(
|
|
112
|
+
{ pluginName },
|
|
113
|
+
'package not found, try to install @{pluginName} with a package manager'
|
|
114
|
+
);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
debug('plugin found %s', plugins.length);
|
|
120
|
+
return plugins;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function executePlugin(plugin, pluginConfig, params: Params) {
|
|
124
|
+
if (isES6(plugin)) {
|
|
125
|
+
debug('plugin is ES6');
|
|
126
|
+
// eslint-disable-next-line new-cap
|
|
127
|
+
return new plugin.default(pluginConfig, params);
|
|
128
|
+
} else {
|
|
129
|
+
debug('plugin is commonJS');
|
|
130
|
+
return plugin(pluginConfig, params);
|
|
131
|
+
}
|
|
132
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import buildDebug from 'debug';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { logger } from '@verdaccio/logger';
|
|
5
|
+
import { Config } from '@verdaccio/types';
|
|
6
|
+
|
|
7
|
+
const debug = buildDebug('verdaccio:plugin:loader:utils');
|
|
8
|
+
|
|
9
|
+
const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
|
|
10
|
+
|
|
11
|
+
export function mergeConfig(appConfig, pluginConfig): Config {
|
|
12
|
+
return _.merge(appConfig, pluginConfig);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isValid(plugin): boolean {
|
|
16
|
+
return _.isFunction(plugin) || _.isFunction(plugin.default);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isES6(plugin): boolean {
|
|
20
|
+
return Object.keys(plugin).includes('default');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Requires a module.
|
|
25
|
+
* @param {*} path the module's path
|
|
26
|
+
* @return {Object}
|
|
27
|
+
*/
|
|
28
|
+
export function tryLoad(path: string): any {
|
|
29
|
+
try {
|
|
30
|
+
debug('loading plugin %s', path);
|
|
31
|
+
return require(path);
|
|
32
|
+
} catch (err: any) {
|
|
33
|
+
if (err.code === MODULE_NOT_FOUND) {
|
|
34
|
+
debug('plugin %s not found', path);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
logger.error({ err: err.msg }, 'error loading plugin @{err}');
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
import { Config, parseConfigFile } from '@verdaccio/config';
|
|
4
|
+
import { logger, setup } from '@verdaccio/logger';
|
|
5
|
+
|
|
6
|
+
import { asyncLoadPlugin } from '../src/plugin-async-loader';
|
|
7
|
+
|
|
8
|
+
function getConfig(file: string) {
|
|
9
|
+
const conPath = path.join(__dirname, './partials/config', file);
|
|
10
|
+
return new Config(parseConfigFile(conPath));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const authSanitize = function (plugin) {
|
|
14
|
+
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const pluginsPartialsFolder = path.join(__dirname, './partials/test-plugin-storage');
|
|
18
|
+
|
|
19
|
+
setup();
|
|
20
|
+
|
|
21
|
+
describe('plugin loader', () => {
|
|
22
|
+
describe('file plugins', () => {
|
|
23
|
+
describe('absolute path', () => {
|
|
24
|
+
test('testing auth valid plugin loader', async () => {
|
|
25
|
+
const config = getConfig('valid-plugin.yaml');
|
|
26
|
+
config.plugins = pluginsPartialsFolder;
|
|
27
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize);
|
|
28
|
+
|
|
29
|
+
expect(plugins).toHaveLength(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should handle does not exist plugin folder', async () => {
|
|
33
|
+
const config = getConfig('plugins-folder-fake.yaml');
|
|
34
|
+
const plugins = await asyncLoadPlugin(
|
|
35
|
+
config.auth,
|
|
36
|
+
{ logger: logger, config: config },
|
|
37
|
+
authSanitize
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(plugins).toHaveLength(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('testing load auth npm package invalid method check', async () => {
|
|
44
|
+
const config = getConfig('valid-plugin.yaml');
|
|
45
|
+
config.plugins = pluginsPartialsFolder;
|
|
46
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod);
|
|
47
|
+
|
|
48
|
+
expect(plugins).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('should fails if plugins folder is not a directory', async () => {
|
|
52
|
+
const config = getConfig('plugins-folder-fake.yaml');
|
|
53
|
+
// force file instead a folder.
|
|
54
|
+
config.plugins = path.join(__dirname, 'just-a-file.js');
|
|
55
|
+
const plugins = await asyncLoadPlugin(
|
|
56
|
+
config.auth,
|
|
57
|
+
{ logger: logger, config: config },
|
|
58
|
+
authSanitize
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(plugins).toHaveLength(0);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('relative path', () => {
|
|
65
|
+
test('should resolve plugin based on relative path', async () => {
|
|
66
|
+
const config = getConfig('relative-plugins.yaml');
|
|
67
|
+
// force file instead a folder.
|
|
68
|
+
const plugins = await asyncLoadPlugin(
|
|
69
|
+
config.auth,
|
|
70
|
+
{ logger: logger, config: config },
|
|
71
|
+
authSanitize
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
expect(plugins).toHaveLength(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('should fails if config path is missing', async () => {
|
|
78
|
+
const config = getConfig('relative-plugins.yaml');
|
|
79
|
+
// @ts-expect-error
|
|
80
|
+
config.configPath = undefined;
|
|
81
|
+
// @ts-expect-error
|
|
82
|
+
config.config_path = undefined;
|
|
83
|
+
// force file instead a folder.
|
|
84
|
+
const plugins = await asyncLoadPlugin(
|
|
85
|
+
config.auth,
|
|
86
|
+
{ logger: logger, config: config },
|
|
87
|
+
authSanitize
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(plugins).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// config.config_path is not considered for loading plugins due legacy support
|
|
94
|
+
test('should fails if config path is missing (only config_path)', async () => {
|
|
95
|
+
const config = getConfig('relative-plugins.yaml');
|
|
96
|
+
// @ts-expect-error
|
|
97
|
+
config.configPath = undefined;
|
|
98
|
+
// force file instead a folder.
|
|
99
|
+
const plugins = await asyncLoadPlugin(
|
|
100
|
+
config.auth,
|
|
101
|
+
{ logger: logger, config: config },
|
|
102
|
+
authSanitize
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(plugins).toHaveLength(0);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('npm plugins', () => {
|
|
111
|
+
test('testing load auth npm package', async () => {
|
|
112
|
+
const config = getConfig('npm-plugin-auth.yaml');
|
|
113
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize);
|
|
114
|
+
|
|
115
|
+
expect(plugins).toHaveLength(1);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should handle not found installed package', async () => {
|
|
119
|
+
const config = getConfig('npm-plugin-not-found.yaml');
|
|
120
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod);
|
|
121
|
+
|
|
122
|
+
expect(plugins).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('testing load auth npm package invalid method check', async () => {
|
|
126
|
+
const config = getConfig('npm-plugin-auth.yaml');
|
|
127
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, (p) => p.anyMethod);
|
|
128
|
+
|
|
129
|
+
expect(plugins).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('testing load auth npm package custom prefix', async () => {
|
|
133
|
+
const config = getConfig('custom-prefix-auth.yaml');
|
|
134
|
+
const plugins = await asyncLoadPlugin(
|
|
135
|
+
config.auth,
|
|
136
|
+
{ config, logger },
|
|
137
|
+
authSanitize,
|
|
138
|
+
'customprefix'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(plugins).toHaveLength(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('testing load auth scope npm package', async () => {
|
|
145
|
+
const config = getConfig('scope-auth.yaml');
|
|
146
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize);
|
|
147
|
+
expect(plugins).toHaveLength(1);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('fallback plugins', () => {
|
|
152
|
+
test('should fallback to npm package if does not find on plugins folder', async () => {
|
|
153
|
+
const config = getConfig('npm-plugin-auth.yaml');
|
|
154
|
+
config.plugins = pluginsPartialsFolder;
|
|
155
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize);
|
|
156
|
+
|
|
157
|
+
expect(plugins).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('should fallback to npm package if plugins folder does not exist', async () => {
|
|
161
|
+
const config = getConfig('npm-plugin-auth.yaml');
|
|
162
|
+
config.plugins = '/does-not-exist';
|
|
163
|
+
const plugins = await asyncLoadPlugin(config.auth, { config, logger }, authSanitize);
|
|
164
|
+
|
|
165
|
+
expect(plugins).toHaveLength(1);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
package/build/plugin-loader.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Config, IPlugin } from '@verdaccio/types';
|
|
2
|
-
export declare const MODULE_NOT_FOUND = "MODULE_NOT_FOUND";
|
|
3
|
-
/**
|
|
4
|
-
* Load a plugin following the rules
|
|
5
|
-
* - First try to load from the internal directory plugins (which will disappear soon or later).
|
|
6
|
-
* - A second attempt from the external plugin directory
|
|
7
|
-
* - A third attempt from node_modules, in case to have multiple match as for instance
|
|
8
|
-
* verdaccio-ldap
|
|
9
|
-
* and sinopia-ldap. All verdaccio prefix will have preferences.
|
|
10
|
-
* @param {*} config a reference of the configuration settings
|
|
11
|
-
* @param {*} pluginConfigs
|
|
12
|
-
* @param {*} params a set of params to initialize the plugin
|
|
13
|
-
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
14
|
-
* @return {Array} list of plugins
|
|
15
|
-
*/
|
|
16
|
-
export declare function loadPlugin<T extends IPlugin<T>>(config: Config, pluginConfigs: any, params: any, sanityCheck: any, prefix?: string): any[];
|
package/build/plugin-loader.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.MODULE_NOT_FOUND = void 0;
|
|
7
|
-
exports.loadPlugin = loadPlugin;
|
|
8
|
-
|
|
9
|
-
var _debug = _interopRequireDefault(require("debug"));
|
|
10
|
-
|
|
11
|
-
var _lodash = _interopRequireDefault(require("lodash"));
|
|
12
|
-
|
|
13
|
-
var _path = _interopRequireDefault(require("path"));
|
|
14
|
-
|
|
15
|
-
var _logger = require("@verdaccio/logger");
|
|
16
|
-
|
|
17
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
18
|
-
|
|
19
|
-
const debug = (0, _debug.default)('verdaccio:plugin:loader');
|
|
20
|
-
const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
|
|
21
|
-
/**
|
|
22
|
-
* Requires a module.
|
|
23
|
-
* @param {*} path the module's path
|
|
24
|
-
* @return {Object}
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
exports.MODULE_NOT_FOUND = MODULE_NOT_FOUND;
|
|
28
|
-
|
|
29
|
-
function tryLoad(path) {
|
|
30
|
-
try {
|
|
31
|
-
return require(path);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
if (err.code === MODULE_NOT_FOUND) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
throw err;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function mergeConfig(appConfig, pluginConfig) {
|
|
42
|
-
return _lodash.default.merge(appConfig, pluginConfig);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function isValid(plugin) {
|
|
46
|
-
return _lodash.default.isFunction(plugin) || _lodash.default.isFunction(plugin.default);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function isES6(plugin) {
|
|
50
|
-
return Object.keys(plugin).includes('default');
|
|
51
|
-
} // export type PluginGeneric<R, T extends IPlugin<R> = ;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Load a plugin following the rules
|
|
55
|
-
* - First try to load from the internal directory plugins (which will disappear soon or later).
|
|
56
|
-
* - A second attempt from the external plugin directory
|
|
57
|
-
* - A third attempt from node_modules, in case to have multiple match as for instance
|
|
58
|
-
* verdaccio-ldap
|
|
59
|
-
* and sinopia-ldap. All verdaccio prefix will have preferences.
|
|
60
|
-
* @param {*} config a reference of the configuration settings
|
|
61
|
-
* @param {*} pluginConfigs
|
|
62
|
-
* @param {*} params a set of params to initialize the plugin
|
|
63
|
-
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
64
|
-
* @return {Array} list of plugins
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
function loadPlugin(config, pluginConfigs = {}, params, sanityCheck, prefix = 'verdaccio') {
|
|
69
|
-
return Object.keys(pluginConfigs).map(pluginId => {
|
|
70
|
-
let plugin;
|
|
71
|
-
|
|
72
|
-
const localPlugin = _path.default.resolve(__dirname + '/../plugins', pluginId); // try local plugins first
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
plugin = tryLoad(localPlugin); // try the external plugin directory
|
|
76
|
-
|
|
77
|
-
if (plugin === null && config.plugins) {
|
|
78
|
-
const pluginDir = config.plugins;
|
|
79
|
-
|
|
80
|
-
const externalFilePlugin = _path.default.resolve(pluginDir, pluginId);
|
|
81
|
-
|
|
82
|
-
plugin = tryLoad(externalFilePlugin); // npm package
|
|
83
|
-
|
|
84
|
-
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
|
85
|
-
plugin = tryLoad(_path.default.resolve(pluginDir, `${prefix}-${pluginId}`)); // compatibility for old sinopia plugins
|
|
86
|
-
|
|
87
|
-
if (!plugin) {
|
|
88
|
-
plugin = tryLoad(_path.default.resolve(pluginDir, `sinopia-${pluginId}`));
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
} // npm package
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
|
95
|
-
plugin = tryLoad(`${prefix}-${pluginId}`); // compatibility for old sinopia plugins
|
|
96
|
-
|
|
97
|
-
if (!plugin) {
|
|
98
|
-
plugin = tryLoad(`sinopia-${pluginId}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (plugin === null) {
|
|
103
|
-
plugin = tryLoad(pluginId);
|
|
104
|
-
} // relative to config path
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
debug('config path: %s', config.configPath);
|
|
108
|
-
|
|
109
|
-
if (plugin === null && pluginId.match(/^\.\.?($|\/)/) && config.configPath) {
|
|
110
|
-
plugin = tryLoad(_path.default.resolve(_path.default.dirname(config.configPath), pluginId));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (plugin === null) {
|
|
114
|
-
_logger.logger.error({
|
|
115
|
-
content: pluginId,
|
|
116
|
-
prefix
|
|
117
|
-
}, 'plugin not found. try npm install @{prefix}-@{content}');
|
|
118
|
-
|
|
119
|
-
throw Error(`
|
|
120
|
-
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (!isValid(plugin)) {
|
|
124
|
-
_logger.logger.error({
|
|
125
|
-
content: pluginId
|
|
126
|
-
}, '@{prefix}-@{content} plugin does not have the right code structure');
|
|
127
|
-
|
|
128
|
-
throw Error(`"${pluginId}" plugin does not have the right code structure`);
|
|
129
|
-
}
|
|
130
|
-
/* eslint new-cap:off */
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
plugin = isES6(plugin) ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params) : new plugin(pluginConfigs[pluginId], params);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
plugin = null;
|
|
137
|
-
|
|
138
|
-
_logger.logger.error({
|
|
139
|
-
error,
|
|
140
|
-
pluginId
|
|
141
|
-
}, 'error loading a plugin @{pluginId}: @{error}');
|
|
142
|
-
}
|
|
143
|
-
/* eslint new-cap:off */
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (plugin === null || !sanityCheck(plugin)) {
|
|
147
|
-
_logger.logger.error({
|
|
148
|
-
content: pluginId,
|
|
149
|
-
prefix
|
|
150
|
-
}, "@{prefix}-@{content} doesn't look like a valid plugin");
|
|
151
|
-
|
|
152
|
-
throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
debug('Plugin successfully loaded: %o-%o', pluginId, prefix);
|
|
156
|
-
return plugin;
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
//# sourceMappingURL=plugin-loader.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-loader.js","names":["debug","buildDebug","MODULE_NOT_FOUND","tryLoad","path","require","err","code","mergeConfig","appConfig","pluginConfig","_","merge","isValid","plugin","isFunction","default","isES6","Object","keys","includes","loadPlugin","config","pluginConfigs","params","sanityCheck","prefix","map","pluginId","localPlugin","Path","resolve","__dirname","plugins","pluginDir","externalFilePlugin","match","configPath","dirname","logger","error","content","Error"],"sources":["../src/plugin-loader.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport _ from 'lodash';\nimport Path from 'path';\n\nimport { logger } from '@verdaccio/logger';\nimport { Config, IPlugin } from '@verdaccio/types';\n\nconst debug = buildDebug('verdaccio:plugin:loader');\n\nexport const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';\n\n/**\n * Requires a module.\n * @param {*} path the module's path\n * @return {Object}\n */\nfunction tryLoad(path: string): any {\n try {\n return require(path);\n } catch (err: any) {\n if (err.code === MODULE_NOT_FOUND) {\n return null;\n }\n throw err;\n }\n}\n\nfunction mergeConfig(appConfig, pluginConfig): Config {\n return _.merge(appConfig, pluginConfig);\n}\n\nfunction isValid(plugin): boolean {\n return _.isFunction(plugin) || _.isFunction(plugin.default);\n}\n\nfunction isES6(plugin): boolean {\n return Object.keys(plugin).includes('default');\n}\n\n// export type PluginGeneric<R, T extends IPlugin<R> = ;\n\n/**\n * Load a plugin following the rules\n * - First try to load from the internal directory plugins (which will disappear soon or later).\n * - A second attempt from the external plugin directory\n * - A third attempt from node_modules, in case to have multiple match as for instance\n * verdaccio-ldap\n * and sinopia-ldap. All verdaccio prefix will have preferences.\n * @param {*} config a reference of the configuration settings\n * @param {*} pluginConfigs\n * @param {*} params a set of params to initialize the plugin\n * @param {*} sanityCheck callback that check the shape that should fulfill the plugin\n * @return {Array} list of plugins\n */\nexport function loadPlugin<T extends IPlugin<T>>(\n config: Config,\n pluginConfigs: any = {},\n params: any,\n sanityCheck: any,\n prefix: string = 'verdaccio'\n): any[] {\n return Object.keys(pluginConfigs).map((pluginId: string): IPlugin<T> => {\n let plugin;\n\n const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId);\n // try local plugins first\n plugin = tryLoad(localPlugin);\n\n // try the external plugin directory\n if (plugin === null && config.plugins) {\n const pluginDir = config.plugins;\n const externalFilePlugin = Path.resolve(pluginDir, pluginId);\n plugin = tryLoad(externalFilePlugin);\n\n // npm package\n if (plugin === null && pluginId.match(/^[^\\.\\/]/)) {\n plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`));\n // compatibility for old sinopia plugins\n if (!plugin) {\n plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`));\n }\n }\n }\n\n // npm package\n if (plugin === null && pluginId.match(/^[^\\.\\/]/)) {\n plugin = tryLoad(`${prefix}-${pluginId}`);\n // compatibility for old sinopia plugins\n if (!plugin) {\n plugin = tryLoad(`sinopia-${pluginId}`);\n }\n }\n\n if (plugin === null) {\n plugin = tryLoad(pluginId);\n }\n\n // relative to config path\n debug('config path: %s', config.configPath);\n if (plugin === null && pluginId.match(/^\\.\\.?($|\\/)/) && config.configPath) {\n plugin = tryLoad(Path.resolve(Path.dirname(config.configPath), pluginId));\n }\n\n if (plugin === null) {\n logger.error(\n { content: pluginId, prefix },\n 'plugin not found. try npm install @{prefix}-@{content}'\n );\n throw Error(`\n ${prefix}-${pluginId} plugin not found. try \"npm install ${prefix}-${pluginId}\"`);\n }\n\n if (!isValid(plugin)) {\n logger.error(\n { content: pluginId },\n '@{prefix}-@{content} plugin does not have the right code structure'\n );\n throw Error(`\"${pluginId}\" plugin does not have the right code structure`);\n }\n\n /* eslint new-cap:off */\n try {\n plugin = isES6(plugin)\n ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params)\n : new plugin(pluginConfigs[pluginId], params);\n } catch (error: any) {\n plugin = null;\n logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');\n }\n /* eslint new-cap:off */\n\n if (plugin === null || !sanityCheck(plugin)) {\n logger.error(\n { content: pluginId, prefix },\n \"@{prefix}-@{content} doesn't look like a valid plugin\"\n );\n throw Error(`sanity check has failed, \"${pluginId}\" is not a valid plugin`);\n }\n\n debug('Plugin successfully loaded: %o-%o', pluginId, prefix);\n return plugin;\n });\n}\n"],"mappings":"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"}
|
package/src/plugin-loader.ts
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import buildDebug from 'debug';
|
|
2
|
-
import _ from 'lodash';
|
|
3
|
-
import Path from 'path';
|
|
4
|
-
|
|
5
|
-
import { logger } from '@verdaccio/logger';
|
|
6
|
-
import { Config, IPlugin } from '@verdaccio/types';
|
|
7
|
-
|
|
8
|
-
const debug = buildDebug('verdaccio:plugin:loader');
|
|
9
|
-
|
|
10
|
-
export const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Requires a module.
|
|
14
|
-
* @param {*} path the module's path
|
|
15
|
-
* @return {Object}
|
|
16
|
-
*/
|
|
17
|
-
function tryLoad(path: string): any {
|
|
18
|
-
try {
|
|
19
|
-
return require(path);
|
|
20
|
-
} catch (err: any) {
|
|
21
|
-
if (err.code === MODULE_NOT_FOUND) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
throw err;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function mergeConfig(appConfig, pluginConfig): Config {
|
|
29
|
-
return _.merge(appConfig, pluginConfig);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function isValid(plugin): boolean {
|
|
33
|
-
return _.isFunction(plugin) || _.isFunction(plugin.default);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function isES6(plugin): boolean {
|
|
37
|
-
return Object.keys(plugin).includes('default');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// export type PluginGeneric<R, T extends IPlugin<R> = ;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Load a plugin following the rules
|
|
44
|
-
* - First try to load from the internal directory plugins (which will disappear soon or later).
|
|
45
|
-
* - A second attempt from the external plugin directory
|
|
46
|
-
* - A third attempt from node_modules, in case to have multiple match as for instance
|
|
47
|
-
* verdaccio-ldap
|
|
48
|
-
* and sinopia-ldap. All verdaccio prefix will have preferences.
|
|
49
|
-
* @param {*} config a reference of the configuration settings
|
|
50
|
-
* @param {*} pluginConfigs
|
|
51
|
-
* @param {*} params a set of params to initialize the plugin
|
|
52
|
-
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
|
|
53
|
-
* @return {Array} list of plugins
|
|
54
|
-
*/
|
|
55
|
-
export function loadPlugin<T extends IPlugin<T>>(
|
|
56
|
-
config: Config,
|
|
57
|
-
pluginConfigs: any = {},
|
|
58
|
-
params: any,
|
|
59
|
-
sanityCheck: any,
|
|
60
|
-
prefix: string = 'verdaccio'
|
|
61
|
-
): any[] {
|
|
62
|
-
return Object.keys(pluginConfigs).map((pluginId: string): IPlugin<T> => {
|
|
63
|
-
let plugin;
|
|
64
|
-
|
|
65
|
-
const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId);
|
|
66
|
-
// try local plugins first
|
|
67
|
-
plugin = tryLoad(localPlugin);
|
|
68
|
-
|
|
69
|
-
// try the external plugin directory
|
|
70
|
-
if (plugin === null && config.plugins) {
|
|
71
|
-
const pluginDir = config.plugins;
|
|
72
|
-
const externalFilePlugin = Path.resolve(pluginDir, pluginId);
|
|
73
|
-
plugin = tryLoad(externalFilePlugin);
|
|
74
|
-
|
|
75
|
-
// npm package
|
|
76
|
-
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
|
77
|
-
plugin = tryLoad(Path.resolve(pluginDir, `${prefix}-${pluginId}`));
|
|
78
|
-
// compatibility for old sinopia plugins
|
|
79
|
-
if (!plugin) {
|
|
80
|
-
plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// npm package
|
|
86
|
-
if (plugin === null && pluginId.match(/^[^\.\/]/)) {
|
|
87
|
-
plugin = tryLoad(`${prefix}-${pluginId}`);
|
|
88
|
-
// compatibility for old sinopia plugins
|
|
89
|
-
if (!plugin) {
|
|
90
|
-
plugin = tryLoad(`sinopia-${pluginId}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (plugin === null) {
|
|
95
|
-
plugin = tryLoad(pluginId);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// relative to config path
|
|
99
|
-
debug('config path: %s', config.configPath);
|
|
100
|
-
if (plugin === null && pluginId.match(/^\.\.?($|\/)/) && config.configPath) {
|
|
101
|
-
plugin = tryLoad(Path.resolve(Path.dirname(config.configPath), pluginId));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (plugin === null) {
|
|
105
|
-
logger.error(
|
|
106
|
-
{ content: pluginId, prefix },
|
|
107
|
-
'plugin not found. try npm install @{prefix}-@{content}'
|
|
108
|
-
);
|
|
109
|
-
throw Error(`
|
|
110
|
-
${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!isValid(plugin)) {
|
|
114
|
-
logger.error(
|
|
115
|
-
{ content: pluginId },
|
|
116
|
-
'@{prefix}-@{content} plugin does not have the right code structure'
|
|
117
|
-
);
|
|
118
|
-
throw Error(`"${pluginId}" plugin does not have the right code structure`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* eslint new-cap:off */
|
|
122
|
-
try {
|
|
123
|
-
plugin = isES6(plugin)
|
|
124
|
-
? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params)
|
|
125
|
-
: new plugin(pluginConfigs[pluginId], params);
|
|
126
|
-
} catch (error: any) {
|
|
127
|
-
plugin = null;
|
|
128
|
-
logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}');
|
|
129
|
-
}
|
|
130
|
-
/* eslint new-cap:off */
|
|
131
|
-
|
|
132
|
-
if (plugin === null || !sanityCheck(plugin)) {
|
|
133
|
-
logger.error(
|
|
134
|
-
{ content: pluginId, prefix },
|
|
135
|
-
"@{prefix}-@{content} doesn't look like a valid plugin"
|
|
136
|
-
);
|
|
137
|
-
throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
debug('Plugin successfully loaded: %o-%o', pluginId, prefix);
|
|
141
|
-
return plugin;
|
|
142
|
-
});
|
|
143
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
|
|
3
|
-
import { setup } from '@verdaccio/logger';
|
|
4
|
-
|
|
5
|
-
import { loadPlugin } from '../src/plugin-loader';
|
|
6
|
-
|
|
7
|
-
setup([]);
|
|
8
|
-
|
|
9
|
-
describe('plugin loader', () => {
|
|
10
|
-
const relativePath = path.join(__dirname, './partials/test-plugin-storage');
|
|
11
|
-
const buildConf = (name) => {
|
|
12
|
-
return {
|
|
13
|
-
config_path: path.join(__dirname, './'),
|
|
14
|
-
max_users: 0,
|
|
15
|
-
auth: {
|
|
16
|
-
[`${relativePath}/${name}`]: {},
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
describe('auth plugins', () => {
|
|
22
|
-
test('testing auth valid plugin loader', () => {
|
|
23
|
-
const _config = buildConf('verdaccio-plugin');
|
|
24
|
-
// @ts-ignore
|
|
25
|
-
const plugins = loadPlugin(_config, _config.auth, {}, function (plugin) {
|
|
26
|
-
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
expect(plugins).toHaveLength(1);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('testing storage valid plugin loader', () => {
|
|
33
|
-
const _config = buildConf('verdaccio-es6-plugin');
|
|
34
|
-
// @ts-ignore
|
|
35
|
-
const plugins = loadPlugin(_config, _config.auth, {}, function (p) {
|
|
36
|
-
return p.getPackageStorage;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
expect(plugins).toHaveLength(1);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('testing auth plugin invalid plugin', () => {
|
|
43
|
-
const _config = buildConf('invalid-plugin');
|
|
44
|
-
try {
|
|
45
|
-
// @ts-ignore
|
|
46
|
-
loadPlugin(_config, _config.auth, {}, function (p) {
|
|
47
|
-
return p.authenticate || p.allow_access || p.allow_publish;
|
|
48
|
-
});
|
|
49
|
-
} catch (e: any) {
|
|
50
|
-
expect(e.message).toEqual(
|
|
51
|
-
`"${relativePath}/invalid-plugin" plugin does not have the right code structure`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('testing auth plugin invalid plugin sanityCheck', () => {
|
|
57
|
-
const _config = buildConf('invalid-plugin-sanity');
|
|
58
|
-
try {
|
|
59
|
-
// @ts-ignore
|
|
60
|
-
loadPlugin(_config, _config.auth, {}, function (plugin) {
|
|
61
|
-
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
|
|
62
|
-
});
|
|
63
|
-
} catch (err: any) {
|
|
64
|
-
expect(err.message).toEqual(
|
|
65
|
-
`sanity check has failed, "${relativePath}/invalid-plugin-sanity" is not a valid plugin`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('testing auth plugin no plugins', () => {
|
|
71
|
-
const _config = buildConf('invalid-package');
|
|
72
|
-
try {
|
|
73
|
-
// @ts-ignore
|
|
74
|
-
loadPlugin(_config, _config.auth, {}, function (plugin) {
|
|
75
|
-
return plugin.authenticate || plugin.allow_access || plugin.allow_publish;
|
|
76
|
-
});
|
|
77
|
-
} catch (e: any) {
|
|
78
|
-
expect(e.message).toMatch('plugin not found');
|
|
79
|
-
expect(e.message.replace(/\\/g, '/')).toMatch(
|
|
80
|
-
'/partials/test-plugin-storage/invalid-package'
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test.todo('test middleware plugins');
|
|
86
|
-
test.todo('test storage plugins');
|
|
87
|
-
});
|
|
88
|
-
});
|