@verdaccio/loaders 6.0.0-6-next.16 → 6.0.0-6-next.18

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 CHANGED
@@ -1,5 +1,75 @@
1
1
  # @verdaccio/loaders
2
2
 
3
+ ## 6.0.0-6-next.18
4
+
5
+ ### Patch Changes
6
+
7
+ - @verdaccio/logger@6.0.0-6-next.17
8
+
9
+ ## 6.0.0-6-next.17
10
+
11
+ ### Major Changes
12
+
13
+ - 9fc2e796: feat(plugins): improve plugin loader
14
+
15
+ ### Changes
16
+
17
+ - Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227
18
+ - Avoid config collisions https://github.com/verdaccio/verdaccio/issues/928
19
+ - https://github.com/verdaccio/verdaccio/issues/1394
20
+ - `config.plugins` plugin path validations
21
+ - Updated algorithm for plugin loader.
22
+ - improved documentation (included dev)
23
+
24
+ ## Features
25
+
26
+ - Add scope plugin support to 6.x https://github.com/verdaccio/verdaccio/pull/3227
27
+ - Custom prefix:
28
+
29
+ ```
30
+ // config.yaml
31
+ server:
32
+ pluginPrefix: mycompany
33
+ middleware:
34
+ audit:
35
+ foo: 1
36
+ ```
37
+
38
+ This configuration will look up for `mycompany-audit` instead `Verdaccio-audit`.
39
+
40
+ ## Breaking Changes
41
+
42
+ ### sinopia plugins
43
+
44
+ - `sinopia` fallback support is removed, but can be restored using `pluginPrefix`
45
+
46
+ ### plugin filter
47
+
48
+ - method rename `filter_metadata`->`filterMetadata`
49
+
50
+ ### Plugin constructor does not merge configs anymore https://github.com/verdaccio/verdaccio/issues/928
51
+
52
+ The plugin receives as first argument `config`, which represents the config of the plugin. Example:
53
+
54
+ ```
55
+ // config.yaml
56
+ auth:
57
+ plugin:
58
+ foo: 1
59
+ bar: 2
60
+
61
+ export class Plugin<T> {
62
+ public constructor(config: T, options: PluginOptions) {
63
+ console.log(config);
64
+ // {foo:1, bar: 2}
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Patch Changes
70
+
71
+ - @verdaccio/logger@6.0.0-6-next.16
72
+
3
73
  ## 6.0.0-6-next.16
4
74
 
5
75
  ### Patch Changes
package/build/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from './plugin-loader';
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
- var _pluginLoader = require("./plugin-loader");
8
-
9
- Object.keys(_pluginLoader).forEach(function (key) {
10
- if (key === "default" || key === "__esModule") return;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export * from './plugin-loader';\n"],"mappings":";;;;;;AAAA;;AAAA;EAAA;EAAA;EAAA;IAAA;IAAA;MAAA;IAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["export { asyncLoadPlugin } from './plugin-async-loader';\n"],"mappings":";;;;;;;;;;;;AAAA"}
@@ -0,0 +1,31 @@
1
+ import { pluginUtils } from '@verdaccio/core';
2
+ import { Config, Logger } from '@verdaccio/types';
3
+ import { PluginType } from './utils';
4
+ export declare type Params = {
5
+ config: Config;
6
+ logger: Logger;
7
+ };
8
+ /**
9
+ * The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
10
+ *
11
+ * The loader follows the order:
12
+ * - If the at the `config.yaml` file the `plugins: ./plugins` is defined
13
+ * - If is absolute will use the provided path
14
+ * - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
15
+ * hosted at /root/plugins
16
+ * - The next step is find at the node_modules or global based on the `require` native algorithm.
17
+ * - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
18
+ * - If the package is not scoped, will use the default prefix: verdaccio-foo.
19
+ * - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
20
+ *
21
+ * The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
22
+ * The `params` is an object that contains the global configuration and the logger.
23
+ *
24
+ * @param {*} pluginConfigs the custom plugin section
25
+ * @param {*} params a set of params to initialize the plugin
26
+ * @param {*} sanityCheck callback that check the shape that should fulfill the plugin
27
+ * @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
28
+ * @return {Array} list of plugins
29
+ */
30
+ export declare function asyncLoadPlugin<T extends pluginUtils.Plugin<T>>(pluginConfigs: any, params: Params, sanityCheck: (plugin: PluginType<T>) => boolean, prefix?: string): Promise<PluginType<T>[]>;
31
+ export declare function executePlugin<T>(plugin: PluginType<T>, pluginConfig: unknown, params: Params): PluginType<T>;
@@ -0,0 +1,163 @@
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
+ // type Plugins<T> =
29
+ // | pluginUtils.Auth<T>
30
+ // | pluginUtils.Storage<T>
31
+ // | pluginUtils.ExpressMiddleware<T, unknown, unknown>;
32
+
33
+ /**
34
+ * The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
35
+ *
36
+ * The loader follows the order:
37
+ * - If the at the `config.yaml` file the `plugins: ./plugins` is defined
38
+ * - If is absolute will use the provided path
39
+ * - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
40
+ * hosted at /root/plugins
41
+ * - The next step is find at the node_modules or global based on the `require` native algorithm.
42
+ * - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
43
+ * - If the package is not scoped, will use the default prefix: verdaccio-foo.
44
+ * - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
45
+ *
46
+ * The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
47
+ * The `params` is an object that contains the global configuration and the logger.
48
+ *
49
+ * @param {*} pluginConfigs the custom plugin section
50
+ * @param {*} params a set of params to initialize the plugin
51
+ * @param {*} sanityCheck callback that check the shape that should fulfill the plugin
52
+ * @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
53
+ * @return {Array} list of plugins
54
+ */
55
+ async function asyncLoadPlugin(pluginConfigs = {}, params, sanityCheck, prefix = 'verdaccio') {
56
+ const pluginsIds = Object.keys(pluginConfigs);
57
+ const {
58
+ config
59
+ } = params;
60
+ let plugins = [];
61
+
62
+ for (let pluginId of pluginsIds) {
63
+ debug('plugin %s', pluginId);
64
+
65
+ if (typeof config.plugins === 'string') {
66
+ let pluginsPath = config.plugins;
67
+ debug('plugin path %s', pluginsPath);
68
+
69
+ if (!(0, _path.isAbsolute)(pluginsPath)) {
70
+ if (typeof config.config_path === 'string' && !config.configPath) {
71
+ _logger.logger.error('configPath is missing and the legacy config.config_path is not available for loading plugins');
72
+ }
73
+
74
+ if (!config.configPath) {
75
+ _logger.logger.error('config path property is required for loading plugins');
76
+
77
+ continue;
78
+ }
79
+
80
+ pluginsPath = (0, _path.resolve)((0, _path.join)((0, _path.dirname)(config.configPath), pluginsPath));
81
+ }
82
+
83
+ _logger.logger.debug({
84
+ path: pluginsPath
85
+ }, 'plugins folder defined, loading plugins from @{path} '); // throws if is nto a directory
86
+
87
+
88
+ try {
89
+ await isDirectory(pluginsPath);
90
+ const pluginDir = pluginsPath;
91
+ const externalFilePlugin = (0, _path.resolve)(pluginDir, `${prefix}-${pluginId}`);
92
+ let plugin = (0, _utils.tryLoad)(externalFilePlugin);
93
+
94
+ if (plugin && (0, _utils.isValid)(plugin)) {
95
+ plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
96
+
97
+ if (!sanityCheck(plugin)) {
98
+ _logger.logger.error({
99
+ content: externalFilePlugin
100
+ }, "@{content} doesn't look like a valid plugin");
101
+
102
+ continue;
103
+ }
104
+
105
+ plugins.push(plugin);
106
+ continue;
107
+ }
108
+ } catch (err) {
109
+ _logger.logger.warn({
110
+ err: err.message,
111
+ pluginsPath,
112
+ pluginId
113
+ }, '@{err} on loading plugins at @{pluginsPath} for @{pluginId}');
114
+ }
115
+ }
116
+
117
+ if (typeof pluginId === 'string') {
118
+ const isScoped = pluginId.startsWith('@') && pluginId.includes('/');
119
+ debug('is scoped plugin %s', isScoped);
120
+ const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;
121
+ debug('plugin pkg name %s', pluginName);
122
+ let plugin = (0, _utils.tryLoad)(pluginName);
123
+
124
+ if (plugin && (0, _utils.isValid)(plugin)) {
125
+ plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
126
+
127
+ if (!sanityCheck(plugin)) {
128
+ _logger.logger.error({
129
+ content: pluginName
130
+ }, "@{content} doesn't look like a valid plugin");
131
+
132
+ continue;
133
+ }
134
+
135
+ plugins.push(plugin);
136
+ continue;
137
+ } else {
138
+ _logger.logger.error({
139
+ pluginName
140
+ }, 'package not found, try to install @{pluginName} with a package manager');
141
+
142
+ continue;
143
+ }
144
+ }
145
+ }
146
+
147
+ debug('plugin found %s', plugins.length);
148
+ return plugins;
149
+ }
150
+
151
+ function executePlugin(plugin, pluginConfig, params) {
152
+ if ((0, _utils.isES6)(plugin)) {
153
+ debug('plugin is ES6'); // @ts-expect-error no relevant for the code
154
+ // eslint-disable-next-line new-cap
155
+
156
+ return new plugin.default(pluginConfig, params);
157
+ } else {
158
+ debug('plugin is commonJS'); // @ts-expect-error improve this type
159
+
160
+ return plugin(pluginConfig, params);
161
+ }
162
+ }
163
+ //# 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 { pluginUtils } from '@verdaccio/core';\nimport { logger } from '@verdaccio/logger';\nimport { Config, Logger } from '@verdaccio/types';\n\nimport { PluginType, isES6, isValid, tryLoad } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:loader:async');\n\nasync function isDirectory(pathFolder: string) {\n const stat = await lstat(pathFolder);\n return stat.isDirectory();\n}\n\nexport type Params = { config: Config; logger: Logger };\n\n// type Plugins<T> =\n// | pluginUtils.Auth<T>\n// | pluginUtils.Storage<T>\n// | pluginUtils.ExpressMiddleware<T, unknown, unknown>;\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 pluginUtils.Plugin<T>>(\n pluginConfigs: any = {},\n params: Params,\n sanityCheck: (plugin: PluginType<T>) => boolean,\n prefix: string = 'verdaccio'\n): Promise<PluginType<T>[]> {\n const pluginsIds = Object.keys(pluginConfigs);\n const { config } = params;\n let plugins: PluginType<T>[] = [];\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<T>(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<T>(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<T>(\n plugin: PluginType<T>,\n pluginConfig: unknown,\n params: Params\n): PluginType<T> {\n if (isES6(plugin)) {\n debug('plugin is ES6');\n // @ts-expect-error no relevant for the code\n // eslint-disable-next-line new-cap\n return new plugin.default(pluginConfig, params) as Plugin;\n } else {\n debug('plugin is commonJS');\n // @ts-expect-error improve this type\n return plugin(pluginConfig, params) as PluginType<T>;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { pluginUtils } from '@verdaccio/core';
2
+ export declare type PluginType<T> = T extends pluginUtils.Plugin<T> ? T : never;
3
+ export declare function isValid<T>(plugin: PluginType<T>): boolean;
4
+ export declare function isES6<T>(plugin: PluginType<T>): boolean;
5
+ /**
6
+ * Requires a module.
7
+ * @param {*} path the module's path
8
+ * @return {Object}
9
+ */
10
+ export declare function tryLoad<T>(path: string): PluginType<T> | null;
package/build/utils.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isES6 = isES6;
7
+ exports.isValid = isValid;
8
+ exports.tryLoad = tryLoad;
9
+
10
+ var _debug = _interopRequireDefault(require("debug"));
11
+
12
+ var _lodash = _interopRequireDefault(require("lodash"));
13
+
14
+ var _logger = require("@verdaccio/logger");
15
+
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+
18
+ const debug = (0, _debug.default)('verdaccio:plugin:loader:utils');
19
+ const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
20
+
21
+ function isValid(plugin) {
22
+ // @ts-expect-error default not relevant
23
+ return _lodash.default.isFunction(plugin) || _lodash.default.isFunction(plugin.default);
24
+ }
25
+
26
+ function isES6(plugin) {
27
+ return Object.keys(plugin).includes('default');
28
+ }
29
+ /**
30
+ * Requires a module.
31
+ * @param {*} path the module's path
32
+ * @return {Object}
33
+ */
34
+
35
+
36
+ function tryLoad(path) {
37
+ try {
38
+ debug('loading plugin %s', path);
39
+ return require(path);
40
+ } catch (err) {
41
+ if (err.code === MODULE_NOT_FOUND) {
42
+ debug('plugin %s not found', path);
43
+ return null;
44
+ }
45
+
46
+ _logger.logger.error({
47
+ err: err.msg
48
+ }, 'error loading plugin @{err}');
49
+
50
+ throw err;
51
+ }
52
+ }
53
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","names":["debug","buildDebug","MODULE_NOT_FOUND","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 { pluginUtils } from '@verdaccio/core';\nimport { logger } from '@verdaccio/logger';\n\nconst debug = buildDebug('verdaccio:plugin:loader:utils');\nconst MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';\n\nexport type PluginType<T> = T extends pluginUtils.Plugin<T> ? T : never;\n\nexport function isValid<T>(plugin: PluginType<T>): boolean {\n // @ts-expect-error default not relevant\n return _.isFunction(plugin) || _.isFunction(plugin.default);\n}\n\nexport function isES6<T>(plugin: PluginType<T>): 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<T>(path: string): PluginType<T> | null {\n try {\n debug('loading plugin %s', path);\n return require(path) as PluginType<T>;\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;;AAGA;;;;AAEA,MAAMA,KAAK,GAAG,IAAAC,cAAA,EAAW,+BAAX,CAAd;AACA,MAAMC,gBAAgB,GAAG,kBAAzB;;AAIO,SAASC,OAAT,CAAoBC,MAApB,EAAoD;EACzD;EACA,OAAOC,eAAA,CAAEC,UAAF,CAAaF,MAAb,KAAwBC,eAAA,CAAEC,UAAF,CAAaF,MAAM,CAACG,OAApB,CAA/B;AACD;;AAEM,SAASC,KAAT,CAAkBJ,MAAlB,EAAkD;EACvD,OAAOK,MAAM,CAACC,IAAP,CAAYN,MAAZ,EAAoBO,QAApB,CAA6B,SAA7B,CAAP;AACD;AAED;AACA;AACA;AACA;AACA;;;AACO,SAASC,OAAT,CAAoBC,IAApB,EAAwD;EAC7D,IAAI;IACFb,KAAK,CAAC,mBAAD,EAAsBa,IAAtB,CAAL;IACA,OAAOC,OAAO,CAACD,IAAD,CAAd;EACD,CAHD,CAGE,OAAOE,GAAP,EAAiB;IACjB,IAAIA,GAAG,CAACC,IAAJ,KAAad,gBAAjB,EAAmC;MACjCF,KAAK,CAAC,qBAAD,EAAwBa,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.16",
3
+ "version": "6.0.0-6-next.18",
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.15",
16
+ "@verdaccio/logger": "6.0.0-6-next.17",
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.47",
22
- "@verdaccio/config": "6.0.0-6-next.47",
23
- "@verdaccio/types": "11.0.0-6-next.16"
21
+ "@verdaccio/core": "6.0.0-6-next.49",
22
+ "@verdaccio/config": "6.0.0-6-next.49",
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.14",
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 * from './plugin-loader';
1
+ export { asyncLoadPlugin } from './plugin-async-loader';
@@ -0,0 +1,144 @@
1
+ import buildDebug from 'debug';
2
+ import { lstat } from 'fs/promises';
3
+ import { dirname, isAbsolute, join, resolve } from 'path';
4
+
5
+ import { pluginUtils } from '@verdaccio/core';
6
+ import { logger } from '@verdaccio/logger';
7
+ import { Config, Logger } from '@verdaccio/types';
8
+
9
+ import { PluginType, isES6, isValid, tryLoad } from './utils';
10
+
11
+ const debug = buildDebug('verdaccio:plugin:loader:async');
12
+
13
+ async function isDirectory(pathFolder: string) {
14
+ const stat = await lstat(pathFolder);
15
+ return stat.isDirectory();
16
+ }
17
+
18
+ export type Params = { config: Config; logger: Logger };
19
+
20
+ // type Plugins<T> =
21
+ // | pluginUtils.Auth<T>
22
+ // | pluginUtils.Storage<T>
23
+ // | pluginUtils.ExpressMiddleware<T, unknown, unknown>;
24
+
25
+ /**
26
+ * The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
27
+ *
28
+ * The loader follows the order:
29
+ * - If the at the `config.yaml` file the `plugins: ./plugins` is defined
30
+ * - If is absolute will use the provided path
31
+ * - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
32
+ * hosted at /root/plugins
33
+ * - The next step is find at the node_modules or global based on the `require` native algorithm.
34
+ * - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
35
+ * - If the package is not scoped, will use the default prefix: verdaccio-foo.
36
+ * - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
37
+ *
38
+ * The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
39
+ * The `params` is an object that contains the global configuration and the logger.
40
+ *
41
+ * @param {*} pluginConfigs the custom plugin section
42
+ * @param {*} params a set of params to initialize the plugin
43
+ * @param {*} sanityCheck callback that check the shape that should fulfill the plugin
44
+ * @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
45
+ * @return {Array} list of plugins
46
+ */
47
+ export async function asyncLoadPlugin<T extends pluginUtils.Plugin<T>>(
48
+ pluginConfigs: any = {},
49
+ params: Params,
50
+ sanityCheck: (plugin: PluginType<T>) => boolean,
51
+ prefix: string = 'verdaccio'
52
+ ): Promise<PluginType<T>[]> {
53
+ const pluginsIds = Object.keys(pluginConfigs);
54
+ const { config } = params;
55
+ let plugins: PluginType<T>[] = [];
56
+ for (let pluginId of pluginsIds) {
57
+ debug('plugin %s', pluginId);
58
+ if (typeof config.plugins === 'string') {
59
+ let pluginsPath = config.plugins;
60
+ debug('plugin path %s', pluginsPath);
61
+ if (!isAbsolute(pluginsPath)) {
62
+ if (typeof config.config_path === 'string' && !config.configPath) {
63
+ logger.error(
64
+ 'configPath is missing and the legacy config.config_path is not available for loading plugins'
65
+ );
66
+ }
67
+
68
+ if (!config.configPath) {
69
+ logger.error('config path property is required for loading plugins');
70
+ continue;
71
+ }
72
+ pluginsPath = resolve(join(dirname(config.configPath), pluginsPath));
73
+ }
74
+
75
+ logger.debug({ path: pluginsPath }, 'plugins folder defined, loading plugins from @{path} ');
76
+ // throws if is nto a directory
77
+ try {
78
+ await isDirectory(pluginsPath);
79
+ const pluginDir = pluginsPath;
80
+ const externalFilePlugin = resolve(pluginDir, `${prefix}-${pluginId}`);
81
+ let plugin = tryLoad<T>(externalFilePlugin);
82
+ if (plugin && isValid(plugin)) {
83
+ plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
84
+ if (!sanityCheck(plugin)) {
85
+ logger.error(
86
+ { content: externalFilePlugin },
87
+ "@{content} doesn't look like a valid plugin"
88
+ );
89
+ continue;
90
+ }
91
+ plugins.push(plugin);
92
+ continue;
93
+ }
94
+ } catch (err: any) {
95
+ logger.warn(
96
+ { err: err.message, pluginsPath, pluginId },
97
+ '@{err} on loading plugins at @{pluginsPath} for @{pluginId}'
98
+ );
99
+ }
100
+ }
101
+
102
+ if (typeof pluginId === 'string') {
103
+ const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/');
104
+ debug('is scoped plugin %s', isScoped);
105
+ const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;
106
+ debug('plugin pkg name %s', pluginName);
107
+ let plugin = tryLoad<T>(pluginName);
108
+ if (plugin && isValid(plugin)) {
109
+ plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
110
+ if (!sanityCheck(plugin)) {
111
+ logger.error({ content: pluginName }, "@{content} doesn't look like a valid plugin");
112
+ continue;
113
+ }
114
+ plugins.push(plugin);
115
+ continue;
116
+ } else {
117
+ logger.error(
118
+ { pluginName },
119
+ 'package not found, try to install @{pluginName} with a package manager'
120
+ );
121
+ continue;
122
+ }
123
+ }
124
+ }
125
+ debug('plugin found %s', plugins.length);
126
+ return plugins;
127
+ }
128
+
129
+ export function executePlugin<T>(
130
+ plugin: PluginType<T>,
131
+ pluginConfig: unknown,
132
+ params: Params
133
+ ): PluginType<T> {
134
+ if (isES6(plugin)) {
135
+ debug('plugin is ES6');
136
+ // @ts-expect-error no relevant for the code
137
+ // eslint-disable-next-line new-cap
138
+ return new plugin.default(pluginConfig, params) as Plugin;
139
+ } else {
140
+ debug('plugin is commonJS');
141
+ // @ts-expect-error improve this type
142
+ return plugin(pluginConfig, params) as PluginType<T>;
143
+ }
144
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,38 @@
1
+ import buildDebug from 'debug';
2
+ import _ from 'lodash';
3
+
4
+ import { pluginUtils } from '@verdaccio/core';
5
+ import { logger } from '@verdaccio/logger';
6
+
7
+ const debug = buildDebug('verdaccio:plugin:loader:utils');
8
+ const MODULE_NOT_FOUND = 'MODULE_NOT_FOUND';
9
+
10
+ export type PluginType<T> = T extends pluginUtils.Plugin<T> ? T : never;
11
+
12
+ export function isValid<T>(plugin: PluginType<T>): boolean {
13
+ // @ts-expect-error default not relevant
14
+ return _.isFunction(plugin) || _.isFunction(plugin.default);
15
+ }
16
+
17
+ export function isES6<T>(plugin: PluginType<T>): boolean {
18
+ return Object.keys(plugin).includes('default');
19
+ }
20
+
21
+ /**
22
+ * Requires a module.
23
+ * @param {*} path the module's path
24
+ * @return {Object}
25
+ */
26
+ export function tryLoad<T>(path: string): PluginType<T> | null {
27
+ try {
28
+ debug('loading plugin %s', path);
29
+ return require(path) as PluginType<T>;
30
+ } catch (err: any) {
31
+ if (err.code === MODULE_NOT_FOUND) {
32
+ debug('plugin %s not found', path);
33
+ return null;
34
+ }
35
+ logger.error({ err: err.msg }, 'error loading plugin @{err}');
36
+ throw err;
37
+ }
38
+ }
@@ -0,0 +1,3 @@
1
+ auth:
2
+ '@verdaccio-scope/verdaccio-auth-foo':
3
+ enabled: true
@@ -0,0 +1,3 @@
1
+ auth:
2
+ auth-memory:
3
+ enabled: true
@@ -0,0 +1,3 @@
1
+ auth:
2
+ not-found:
3
+ enabled: true
@@ -0,0 +1,4 @@
1
+ auth:
2
+ something:
3
+ enabled: true
4
+ plugins: /roo/does-not-exist
@@ -0,0 +1,4 @@
1
+ plugins: '../test-plugin-storage'
2
+ auth:
3
+ plugin:
4
+ enabled: true
@@ -0,0 +1,3 @@
1
+ auth:
2
+ '@verdaccio-scope/verdaccio-auth-foo':
3
+ enabled: true
@@ -0,0 +1,3 @@
1
+ auth:
2
+ plugin:
3
+ enabled: true
@@ -0,0 +1,7 @@
1
+ function ValidScopedVerdaccioPlugin() {
2
+ return {
3
+ authenticate: function () {},
4
+ };
5
+ }
6
+
7
+ module.exports = ValidVerdaccioPlugin;