From 5c50ec9a2ced21c06ce690bb45af2019a514149d Mon Sep 17 00:00:00 2001 From: Juan Picado Date: Tue, 14 Jun 2022 07:47:17 +0200 Subject: [PATCH] feat: add scope support loading plugins (#3227) * feat: add scope support loading plugins * format * Update src/lib/plugin-loader.ts Co-authored-by: Michael Prentice * Update src/lib/plugin-loader.ts Co-authored-by: Michael Prentice * chore: add tests * chore: add comment * format * chore: update dep * chore: add better name Co-authored-by: Michael Prentice --- .pnp.js | 18 +++++- ...th-foo-npm-0.0.2-e8d6fdf0d9-99f4727a67.zip | Bin 0 -> 5986 bytes package.json | 1 + src/lib/plugin-loader.ts | 52 ++++++++++++++++-- .../unit/modules/plugin/plugin_loader.spec.ts | 21 +++++++ yarn.lock | Bin 543991 -> 544401 bytes 6 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 .yarn/cache/@verdaccio-scope-verdaccio-auth-foo-npm-0.0.2-e8d6fdf0d9-99f4727a67.zip diff --git a/.pnp.js b/.pnp.js index e108acb50..358444e45 100755 --- a/.pnp.js +++ b/.pnp.js @@ -79,6 +79,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/semver", "npm:7.3.9"], ["@typescript-eslint/eslint-plugin", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:4.33.0"], ["@typescript-eslint/parser", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:4.33.0"], + ["@verdaccio-scope/verdaccio-auth-foo", "npm:0.0.2"], ["@verdaccio/commons-api", "npm:10.2.0"], ["@verdaccio/eslint-config", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:10.0.0"], ["@verdaccio/local-storage", "npm:10.3.0"], @@ -5723,6 +5724,16 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@verdaccio-scope/verdaccio-auth-foo", [ + ["npm:0.0.2", { + "packageLocation": "./.yarn/cache/@verdaccio-scope-verdaccio-auth-foo-npm-0.0.2-e8d6fdf0d9-99f4727a67.zip/node_modules/@verdaccio-scope/verdaccio-auth-foo/", + "packageDependencies": [ + ["@verdaccio-scope/verdaccio-auth-foo", "npm:0.0.2"], + ["@verdaccio/commons-api", "npm:10.2.0"] + ], + "linkType": "HARD", + }] + ]], ["@verdaccio/commons-api", [ ["npm:10.2.0", { "packageLocation": "./.yarn/cache/@verdaccio-commons-api-npm-10.2.0-d36a19383f-d93c93220a.zip/node_modules/@verdaccio/commons-api/", @@ -8036,14 +8047,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ]], ["core-js", [ ["npm:2.6.9", { - "packageLocation": "./.yarn/cache/core-js-npm-2.6.9-f821bf686c-00c30207eb.zip/node_modules/core-js/", + "packageLocation": "./.yarn/unplugged/core-js-npm-2.6.9-f821bf686c/node_modules/core-js/", "packageDependencies": [ ["core-js", "npm:2.6.9"] ], "linkType": "HARD", }], ["npm:3.22.4", { - "packageLocation": "./.yarn/cache/core-js-npm-3.22.4-4469b89edf-1305b2b9c1.zip/node_modules/core-js/", + "packageLocation": "./.yarn/unplugged/core-js-npm-3.22.4-4469b89edf/node_modules/core-js/", "packageDependencies": [ ["core-js", "npm:3.22.4"] ], @@ -15293,7 +15304,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ]], ["puppeteer", [ ["npm:5.5.0", { - "packageLocation": "./.yarn/cache/puppeteer-npm-5.5.0-bba75ba998-08ba8a7da5.zip/node_modules/puppeteer/", + "packageLocation": "./.yarn/unplugged/puppeteer-npm-5.5.0-bba75ba998/node_modules/puppeteer/", "packageDependencies": [ ["puppeteer", "npm:5.5.0"], ["debug", "virtual:fada3bd8ad326a7c196d0c24aae1d5410b84126805d4b297cac3abf549e077c61a437968e49905247d38e2ca430b4cee29c78b779ec928550ea7a1cdf2adc3c1#npm:4.1.1"], @@ -18111,6 +18122,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@types/semver", "npm:7.3.9"], ["@typescript-eslint/eslint-plugin", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:4.33.0"], ["@typescript-eslint/parser", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:4.33.0"], + ["@verdaccio-scope/verdaccio-auth-foo", "npm:0.0.2"], ["@verdaccio/commons-api", "npm:10.2.0"], ["@verdaccio/eslint-config", "virtual:7f7b3df50ee4b7b1719ad19fad11505dc2788f3227a7e5cc9ca19f71d8cb309c9d33b532ea2b2b60ab65abf6cc12153df4643c5e6e17d01ea0ae0492723bb4b4#npm:10.0.0"], ["@verdaccio/local-storage", "npm:10.3.0"], diff --git a/.yarn/cache/@verdaccio-scope-verdaccio-auth-foo-npm-0.0.2-e8d6fdf0d9-99f4727a67.zip b/.yarn/cache/@verdaccio-scope-verdaccio-auth-foo-npm-0.0.2-e8d6fdf0d9-99f4727a67.zip new file mode 100644 index 0000000000000000000000000000000000000000..86c0a5529edc6115015738ed116e0f488529b528 GIT binary patch literal 5986 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 270790269..e96b72c80 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@types/semver": "7.3.9", "@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/parser": "4.33.0", + "@verdaccio-scope/verdaccio-auth-foo": "0.0.2", "@verdaccio/eslint-config": "^10.0.0", "@verdaccio/types": "10.5.1", "all-contributors-cli": "6.20.0", diff --git a/src/lib/plugin-loader.ts b/src/lib/plugin-loader.ts index 4065a96d5..fdc4ae15a 100644 --- a/src/lib/plugin-loader.ts +++ b/src/lib/plugin-loader.ts @@ -1,3 +1,4 @@ +import buildDebug from 'debug'; import _ from 'lodash'; import Path from 'path'; @@ -6,6 +7,8 @@ import { Config, IPlugin } from '@verdaccio/types'; import { MODULE_NOT_FOUND } from './constants'; import { logger } from './logger'; +const debug = buildDebug('verdaccio:plugin:loader'); + /** * Requires a module. * @param {*} path the module's path @@ -13,11 +16,14 @@ import { logger } from './logger'; */ function tryLoad(path: string): any { try { + debug('loading plugin %s', path); return require(path); } catch (err) { if (err.code === MODULE_NOT_FOUND) { + debug('plugin %s not found', path); return null; } + logger.error({ err: err.msg }, 'error loading plugin @{err}'); throw err; } } @@ -39,6 +45,7 @@ function isES6(plugin): boolean { /** * Load a plugin following the rules * - First try to load from the internal directory plugins (which will disappear soon or later). + * - If the package is scoped eg: @scope/foo, try to load as a package * - A second attempt from the external plugin directory * - A third attempt from node_modules, in case to have multiple match as for instance verdaccio-ldap * and sinopia-ldap. All verdaccio prefix will have preferences. @@ -51,6 +58,11 @@ function isES6(plugin): boolean { export default function loadPlugin>(config: Config, pluginConfigs: any = {}, params: any, sanityCheck: any, prefix: string = 'verdaccio'): any[] { return Object.keys(pluginConfigs).map((pluginId: string): IPlugin => { let plugin; + const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/'); + debug('isScoped %s', isScoped); + if (isScoped) { + plugin = tryLoad(pluginId); + } const localPlugin = Path.resolve(__dirname + '/../plugins', pluginId); // try local plugins first @@ -68,6 +80,9 @@ export default function loadPlugin>(config: Config, pluginC // compatibility for old sinopia plugins if (!plugin) { plugin = tryLoad(Path.resolve(pluginDir, `sinopia-${pluginId}`)); + if (plugin) { + logger.warn({ name: pluginId }, `plugin names that start with sinopia-* will be removed in the future, please rename package to verdaccio-*`); + } } } } @@ -79,6 +94,9 @@ export default function loadPlugin>(config: Config, pluginC if (!plugin) { plugin = tryLoad(`sinopia-${pluginId}`); } + if (plugin) { + debug('plugin %s is an npm package', pluginId); + } } if (plugin === null) { @@ -91,9 +109,17 @@ export default function loadPlugin>(config: Config, pluginC } if (plugin === null) { - logger.error({ content: pluginId, prefix }, 'plugin not found. try npm install @{prefix}-@{content}'); - throw Error(` - ${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`); + if (isScoped) { + logger.error({ content: pluginId }, 'plugin not found. try npm install @{content}'); + } else { + logger.error({ content: pluginId, prefix }, 'plugin not found. try npm install @{prefix}-@{content}'); + } + const msg = isScoped + ? ` + ${pluginId} plugin not found. try "npm install ${pluginId}"` + : ` + ${prefix}-${pluginId} plugin not found. try "npm install ${prefix}-${pluginId}"`; + throw Error(msg); } if (!isValid(plugin)) { @@ -103,7 +129,13 @@ export default function loadPlugin>(config: Config, pluginC /* eslint new-cap:off */ try { - plugin = isES6(plugin) ? new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params) : plugin(pluginConfigs[pluginId], params); + if (isES6(plugin)) { + debug('plugin is ES6'); + plugin = new plugin.default(mergeConfig(config, pluginConfigs[pluginId]), params); + } else { + debug('plugin is commonJS'); + plugin = plugin(pluginConfigs[pluginId], params); + } } catch (error) { plugin = null; logger.error({ error, pluginId }, 'error loading a plugin @{pluginId}: @{error}'); @@ -111,11 +143,19 @@ export default function loadPlugin>(config: Config, pluginC /* eslint new-cap:off */ if (plugin === null || !sanityCheck(plugin)) { - logger.error({ content: pluginId, prefix }, "@{prefix}-@{content} doesn't look like a valid plugin"); + if (isScoped) { + logger.error({ content: pluginId }, "@{content} doesn't look like a valid plugin"); + } else { + logger.error({ content: pluginId, prefix }, "@{prefix}-@{content} doesn't look like a valid plugin"); + } throw Error(`sanity check has failed, "${pluginId}" is not a valid plugin`); } - logger.warn({ content: pluginId, prefix }, 'Plugin successfully loaded: @{prefix}-@{content}'); + if (isScoped) { + logger.info({ content: pluginId }, 'plugin successfully loaded: @{content}'); + } else { + logger.info({ content: pluginId, prefix }, 'plugin successfully loaded: @{prefix}-@{content}'); + } return plugin; }); } diff --git a/test/unit/modules/plugin/plugin_loader.spec.ts b/test/unit/modules/plugin/plugin_loader.spec.ts index 3b189166a..39efb6d2a 100644 --- a/test/unit/modules/plugin/plugin_loader.spec.ts +++ b/test/unit/modules/plugin/plugin_loader.spec.ts @@ -28,6 +28,27 @@ describe('plugin loader', () => { expect(plugins).toHaveLength(1); }); + test('fails on load scoped auth missing package', () => { + const _config = buildConf('@scope/package'); + try { + // @ts-ignore + loadPlugin(_config, { '@scope/package': {} }, {}, undefined); + } catch (e) { + expect(e.message).toMatch(`@scope/package plugin not found. try \"npm install @scope/package\"`); + } + }); + + // This package is locally installed, just a dummy scoped auth plugin + // TODO: move this package to the public registry + test('should load @verdaccio-scope/verdaccio-auth-foo scoped package', () => { + const _config = buildConf('@verdaccio-scope/verdaccio-auth-foo'); + // @ts-ignore + const plugins = loadPlugin(_config, { '@verdaccio-scope/verdaccio-auth-foo': {} }, {}, function (plugin) { + return plugin.authenticate || plugin.allow_access || plugin.allow_publish; + }); + expect(plugins).toHaveLength(1); + }); + test('testing storage valid plugin loader', () => { const _config = buildConf('verdaccio-es6-plugin'); // @ts-ignore diff --git a/yarn.lock b/yarn.lock index 24da545763b54fbab559caf6714259abd016ca96..a339a746d87f8a071e327637bdb5f6292b0aeabb 100644 GIT binary patch delta 330 zcmaiwu}Z{17=*bPjT~oZWn+>`ker)8o84?6U?cbrUh`-3FJ9s8;i;_=1hKTa#lpgO z5U%q9EPWZ96I>raEWT!#ftmSwPM%(p{(kTloHdXww4Kw@4PADq_0(o~eRZB0YmaC1 zi<*tu7}9ze$KJ4*+F2bl_qr4Ay8bj6?*9{ZccXC6y?;j|*RP}GRw=_JNEA|)Lgk$x zGRz1iHCG^HTL3T0oP#2XU;vmj2q=uv1mNWg^Gae_K~8{}cVN806k<^UA?O0Cj$_MO zEs1n4xMYAtoD(;uYu)GL;Lv5h_`{?LJR6?Sv+3DdO`0hENdBCZ`su%KIJ=GxR?BD` GPksP+DQD0C delta 60 zcmbO@SMmEy#fBEfElkn2(>d*#B&PSeGwHQ+*)st#GZ3=?F)I+WZRfIQzk71}ds`-n K?FO72iLL-gLlQIq