@verdaccio/plugin-verifier 1.0.0-next-9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +255 -0
  4. package/bin/verdaccio-plugin-verifier +6 -0
  5. package/build/_virtual/_rolldown/runtime.cjs +23 -0
  6. package/build/cli.cjs +15 -0
  7. package/build/cli.cjs.map +1 -0
  8. package/build/cli.d.ts +2 -0
  9. package/build/cli.d.ts.map +1 -0
  10. package/build/cli.js +14 -0
  11. package/build/cli.js.map +1 -0
  12. package/build/commands/verify.cjs +76 -0
  13. package/build/commands/verify.cjs.map +1 -0
  14. package/build/commands/verify.d.ts +11 -0
  15. package/build/commands/verify.d.ts.map +1 -0
  16. package/build/commands/verify.js +74 -0
  17. package/build/commands/verify.js.map +1 -0
  18. package/build/diagnostics.cjs +209 -0
  19. package/build/diagnostics.cjs.map +1 -0
  20. package/build/diagnostics.d.ts +13 -0
  21. package/build/diagnostics.d.ts.map +1 -0
  22. package/build/diagnostics.js +207 -0
  23. package/build/diagnostics.js.map +1 -0
  24. package/build/index.cjs +11 -0
  25. package/build/index.d.ts +5 -0
  26. package/build/index.d.ts.map +1 -0
  27. package/build/index.js +4 -0
  28. package/build/sanity-checks.cjs +27 -0
  29. package/build/sanity-checks.cjs.map +1 -0
  30. package/build/sanity-checks.d.ts +11 -0
  31. package/build/sanity-checks.d.ts.map +1 -0
  32. package/build/sanity-checks.js +22 -0
  33. package/build/sanity-checks.js.map +1 -0
  34. package/build/types.d.ts +80 -0
  35. package/build/types.d.ts.map +1 -0
  36. package/build/verify-plugin.cjs +87 -0
  37. package/build/verify-plugin.cjs.map +1 -0
  38. package/build/verify-plugin.d.ts +19 -0
  39. package/build/verify-plugin.d.ts.map +1 -0
  40. package/build/verify-plugin.js +85 -0
  41. package/build/verify-plugin.js.map +1 -0
  42. package/package.json +63 -0
  43. package/src/cli.ts +14 -0
  44. package/src/commands/verify.ts +100 -0
  45. package/src/diagnostics.ts +274 -0
  46. package/src/index.ts +10 -0
  47. package/src/sanity-checks.ts +27 -0
  48. package/src/types.ts +83 -0
  49. package/src/verify-plugin.ts +115 -0
  50. package/tests/diagnostics.spec.ts +323 -0
  51. package/tests/fixtures/entry-point-bad-json/package.json +1 -0
  52. package/tests/fixtures/entry-point-exports-default/package.json +7 -0
  53. package/tests/fixtures/entry-point-exports-import-default/package.json +9 -0
  54. package/tests/fixtures/entry-point-exports-import-string/package.json +7 -0
  55. package/tests/fixtures/entry-point-exports-string/package.json +5 -0
  56. package/tests/fixtures/entry-point-main/package.json +3 -0
  57. package/tests/fixtures/entry-point-module/package.json +3 -0
  58. package/tests/fixtures/package.json +3 -0
  59. package/tests/fixtures/verdaccio-invalid-plugin/index.js +2 -0
  60. package/tests/fixtures/verdaccio-null-plugin/index.js +4 -0
  61. package/tests/fixtures/verdaccio-throwing-plugin/index.js +4 -0
  62. package/tests/fixtures/verdaccio-valid-auth-es6-plugin/index.js +14 -0
  63. package/tests/fixtures/verdaccio-valid-auth-plugin/index.js +20 -0
  64. package/tests/fixtures/verdaccio-valid-filter-plugin/index.js +12 -0
  65. package/tests/fixtures/verdaccio-valid-middleware-plugin/index.js +10 -0
  66. package/tests/fixtures/verdaccio-valid-storage-plugin/index.js +30 -0
  67. package/tests/fixtures/verdaccio-wrong-category-plugin/index.js +11 -0
  68. package/tests/verify-plugin.spec.ts +218 -0
  69. package/tsconfig.build.json +4 -0
  70. package/tsconfig.json +24 -0
  71. package/vite.config.mjs +69 -0
  72. package/vitest.config.mjs +11 -0
@@ -0,0 +1,80 @@
1
+ import { PLUGIN_CATEGORY } from '@verdaccio/core';
2
+ export type PluginCategory = (typeof PLUGIN_CATEGORY)[keyof typeof PLUGIN_CATEGORY];
3
+ export interface VerifyPluginOptions {
4
+ /**
5
+ * The plugin identifier as it would appear in config.yaml.
6
+ * For file-based plugins, this is the folder name inside `pluginsFolder`.
7
+ * For npm plugins, this is the package name (without the prefix for unscoped packages).
8
+ */
9
+ pluginPath: string;
10
+ /**
11
+ * The plugin category to verify against.
12
+ */
13
+ category: PluginCategory;
14
+ /**
15
+ * Optional plugin configuration passed as the first argument when instantiating the plugin.
16
+ */
17
+ pluginConfig?: Record<string, unknown>;
18
+ /**
19
+ * Optional custom sanity check function. If not provided, the default
20
+ * sanity check for the given category is used.
21
+ */
22
+ sanityCheck?: (plugin: any) => boolean;
23
+ /**
24
+ * Plugin name prefix. Defaults to 'verdaccio'.
25
+ */
26
+ prefix?: string;
27
+ /**
28
+ * Absolute path to a plugins folder for file-based plugin loading.
29
+ * Maps to `config.plugins` in Verdaccio configuration.
30
+ */
31
+ pluginsFolder?: string;
32
+ /**
33
+ * Absolute path to the Verdaccio configuration file.
34
+ * Maps to `config.configPath` — required by plugins that resolve
35
+ * relative paths (e.g. htpasswd resolves its `file` relative to this).
36
+ */
37
+ configPath?: string;
38
+ }
39
+ export interface DiagnosticStep {
40
+ /**
41
+ * The verification phase this diagnostic refers to.
42
+ */
43
+ phase: 'resolve' | 'export' | 'instantiate' | 'sanity-check';
44
+ /**
45
+ * Whether this phase passed.
46
+ */
47
+ pass: boolean;
48
+ /**
49
+ * Human-readable message describing the result.
50
+ */
51
+ message: string;
52
+ }
53
+ export interface VerifyResult {
54
+ /**
55
+ * Whether the plugin was successfully loaded and passed all checks.
56
+ */
57
+ success: boolean;
58
+ /**
59
+ * The plugin identifier used.
60
+ */
61
+ pluginName: string;
62
+ /**
63
+ * The plugin category verified against.
64
+ */
65
+ category: PluginCategory;
66
+ /**
67
+ * Number of plugins successfully loaded.
68
+ */
69
+ pluginsLoaded: number;
70
+ /**
71
+ * Error message if loading failed.
72
+ */
73
+ error?: string;
74
+ /**
75
+ * Step-by-step diagnostics showing which phase passed or failed.
76
+ * Only populated when the plugin fails to load.
77
+ */
78
+ diagnostics?: DiagnosticStep[];
79
+ }
80
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAEpF,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC;IACvC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,cAAc,CAAC;IAC7D;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IACd;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;CAChC"}
@@ -0,0 +1,87 @@
1
+ const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
+ const require_sanity_checks = require("./sanity-checks.cjs");
3
+ const require_diagnostics = require("./diagnostics.cjs");
4
+ let debug = require("debug");
5
+ debug = require_runtime.__toESM(debug);
6
+ let node_path = require("node:path");
7
+ let _verdaccio_core = require("@verdaccio/core");
8
+ let _verdaccio_loaders = require("@verdaccio/loaders");
9
+ let _verdaccio_logger = require("@verdaccio/logger");
10
+ //#region src/verify-plugin.ts
11
+ var debug$1 = (0, debug.default)("verdaccio:plugin:verifier");
12
+ /**
13
+ * Verifies that a plugin can be loaded by Verdaccio.
14
+ *
15
+ * Uses `asyncLoadPlugin` from `@verdaccio/loaders` — the same loader
16
+ * Verdaccio uses at startup — so the verification is identical to what
17
+ * happens in production.
18
+ *
19
+ * Steps verified:
20
+ * 1. Module resolution — can the plugin be found/required?
21
+ * 2. Export validation — does it export a function or a class (default export)?
22
+ * 3. Instantiation — can the plugin be instantiated with a config and options?
23
+ * 4. Sanity check — does the instance implement the required methods for its category?
24
+ *
25
+ * When loading fails, detailed diagnostics are included in the result
26
+ * to pinpoint exactly which step failed and why.
27
+ */
28
+ async function verifyPlugin(options) {
29
+ const { pluginPath, category, pluginConfig = {}, sanityCheck: customSanityCheck, prefix = _verdaccio_core.PLUGIN_PREFIX, pluginsFolder, configPath } = options;
30
+ debug$1("verifying plugin %o for category %o", pluginPath, category);
31
+ debug$1("prefix: %o, pluginsFolder: %o", prefix, pluginsFolder);
32
+ await (0, _verdaccio_logger.setup)({});
33
+ const sanityCheck = customSanityCheck ?? require_sanity_checks.getSanityCheck(category);
34
+ debug$1("using %s sanity check", customSanityCheck ? "custom" : "default");
35
+ const config = { ...pluginConfig };
36
+ if (pluginsFolder) {
37
+ config.plugins = (0, node_path.resolve)(pluginsFolder);
38
+ debug$1("resolved plugins folder: %o", config.plugins);
39
+ }
40
+ if (configPath) {
41
+ config.configPath = configPath;
42
+ debug$1("config path: %o", config.configPath);
43
+ }
44
+ const pluginConfigs = { [pluginPath]: pluginConfig };
45
+ debug$1("plugin config: %o", pluginConfigs);
46
+ try {
47
+ const plugins = await (0, _verdaccio_loaders.asyncLoadPlugin)(pluginConfigs, {
48
+ config,
49
+ logger: _verdaccio_logger.logger
50
+ }, sanityCheck, false, prefix, category);
51
+ debug$1("plugins loaded: %o", plugins.length);
52
+ if (plugins.length > 0) {
53
+ debug$1("verification succeeded for %o", pluginPath);
54
+ return {
55
+ success: true,
56
+ pluginName: pluginPath,
57
+ category,
58
+ pluginsLoaded: plugins.length
59
+ };
60
+ }
61
+ debug$1("verification failed, running diagnostics for %o", pluginPath);
62
+ const diagnostics = await require_diagnostics.runDiagnostics(options);
63
+ return {
64
+ success: false,
65
+ pluginName: pluginPath,
66
+ category,
67
+ pluginsLoaded: 0,
68
+ error: diagnostics.find((d) => !d.pass)?.message ?? `Plugin "${pluginPath}" could not be loaded for category "${category}"`,
69
+ diagnostics
70
+ };
71
+ } catch (err) {
72
+ debug$1("verification error for %o: %o", pluginPath, err.message);
73
+ const diagnostics = await require_diagnostics.runDiagnostics(options);
74
+ return {
75
+ success: false,
76
+ pluginName: pluginPath,
77
+ category,
78
+ pluginsLoaded: 0,
79
+ error: err.message,
80
+ diagnostics
81
+ };
82
+ }
83
+ }
84
+ //#endregion
85
+ exports.verifyPlugin = verifyPlugin;
86
+
87
+ //# sourceMappingURL=verify-plugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-plugin.cjs","names":[],"sources":["../src/verify-plugin.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { resolve } from 'node:path';\n\nimport { PLUGIN_PREFIX } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport { logger, setup } from '@verdaccio/logger';\n\nimport { runDiagnostics } from './diagnostics';\nimport { getSanityCheck } from './sanity-checks';\nimport type { VerifyPluginOptions, VerifyResult } from './types';\n\nconst debug = buildDebug('verdaccio:plugin:verifier');\n\n/**\n * Verifies that a plugin can be loaded by Verdaccio.\n *\n * Uses `asyncLoadPlugin` from `@verdaccio/loaders` — the same loader\n * Verdaccio uses at startup — so the verification is identical to what\n * happens in production.\n *\n * Steps verified:\n * 1. Module resolution — can the plugin be found/required?\n * 2. Export validation — does it export a function or a class (default export)?\n * 3. Instantiation — can the plugin be instantiated with a config and options?\n * 4. Sanity check — does the instance implement the required methods for its category?\n *\n * When loading fails, detailed diagnostics are included in the result\n * to pinpoint exactly which step failed and why.\n */\nexport async function verifyPlugin(options: VerifyPluginOptions): Promise<VerifyResult> {\n const {\n pluginPath,\n category,\n pluginConfig = {},\n sanityCheck: customSanityCheck,\n prefix = PLUGIN_PREFIX,\n pluginsFolder,\n configPath,\n } = options;\n\n debug('verifying plugin %o for category %o', pluginPath, category);\n debug('prefix: %o, pluginsFolder: %o', prefix, pluginsFolder);\n\n await setup({});\n\n const sanityCheck = customSanityCheck ?? getSanityCheck(category);\n debug('using %s sanity check', customSanityCheck ? 'custom' : 'default');\n\n const config: any = {\n ...pluginConfig,\n };\n\n if (pluginsFolder) {\n config.plugins = resolve(pluginsFolder);\n debug('resolved plugins folder: %o', config.plugins);\n }\n\n if (configPath) {\n config.configPath = configPath;\n debug('config path: %o', config.configPath);\n }\n\n const pluginConfigs = { [pluginPath]: pluginConfig };\n debug('plugin config: %o', pluginConfigs);\n\n try {\n const plugins = await asyncLoadPlugin(\n pluginConfigs,\n { config, logger },\n sanityCheck,\n false,\n prefix,\n category\n );\n\n debug('plugins loaded: %o', plugins.length);\n\n if (plugins.length > 0) {\n debug('verification succeeded for %o', pluginPath);\n return {\n success: true,\n pluginName: pluginPath,\n category,\n pluginsLoaded: plugins.length,\n };\n }\n\n debug('verification failed, running diagnostics for %o', pluginPath);\n const diagnostics = await runDiagnostics(options);\n const failedStep = diagnostics.find((d) => !d.pass);\n\n return {\n success: false,\n pluginName: pluginPath,\n category,\n pluginsLoaded: 0,\n error:\n failedStep?.message ??\n `Plugin \"${pluginPath}\" could not be loaded for category \"${category}\"`,\n diagnostics,\n };\n } catch (err: any) {\n debug('verification error for %o: %o', pluginPath, err.message);\n const diagnostics = await runDiagnostics(options);\n\n return {\n success: false,\n pluginName: pluginPath,\n category,\n pluginsLoaded: 0,\n error: err.message,\n diagnostics,\n };\n }\n}\n"],"mappings":";;;;;;;;;;AAWA,IAAM,WAAA,GAAA,MAAA,SAAmB,4BAA4B;;;;;;;;;;;;;;;;;AAkBrD,eAAsB,aAAa,SAAqD;CACtF,MAAM,EACJ,YACA,UACA,eAAe,EAAE,EACjB,aAAa,mBACb,SAAS,gBAAA,eACT,eACA,eACE;AAEJ,SAAM,uCAAuC,YAAY,SAAS;AAClE,SAAM,iCAAiC,QAAQ,cAAc;AAE7D,QAAA,GAAA,kBAAA,OAAY,EAAE,CAAC;CAEf,MAAM,cAAc,qBAAqB,sBAAA,eAAe,SAAS;AACjE,SAAM,yBAAyB,oBAAoB,WAAW,UAAU;CAExE,MAAM,SAAc,EAClB,GAAG,cACJ;AAED,KAAI,eAAe;AACjB,SAAO,WAAA,GAAA,UAAA,SAAkB,cAAc;AACvC,UAAM,+BAA+B,OAAO,QAAQ;;AAGtD,KAAI,YAAY;AACd,SAAO,aAAa;AACpB,UAAM,mBAAmB,OAAO,WAAW;;CAG7C,MAAM,gBAAgB,GAAG,aAAa,cAAc;AACpD,SAAM,qBAAqB,cAAc;AAEzC,KAAI;EACF,MAAM,UAAU,OAAA,GAAA,mBAAA,iBACd,eACA;GAAE;GAAQ,QAAA,kBAAA;GAAQ,EAClB,aACA,OACA,QACA,SACD;AAED,UAAM,sBAAsB,QAAQ,OAAO;AAE3C,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAM,iCAAiC,WAAW;AAClD,UAAO;IACL,SAAS;IACT,YAAY;IACZ;IACA,eAAe,QAAQ;IACxB;;AAGH,UAAM,mDAAmD,WAAW;EACpE,MAAM,cAAc,MAAM,oBAAA,eAAe,QAAQ;AAGjD,SAAO;GACL,SAAS;GACT,YAAY;GACZ;GACA,eAAe;GACf,OAPiB,YAAY,MAAM,MAAM,CAAC,EAAE,KAAK,EAQnC,WACZ,WAAW,WAAW,sCAAsC,SAAS;GACvE;GACD;UACM,KAAU;AACjB,UAAM,iCAAiC,YAAY,IAAI,QAAQ;EAC/D,MAAM,cAAc,MAAM,oBAAA,eAAe,QAAQ;AAEjD,SAAO;GACL,SAAS;GACT,YAAY;GACZ;GACA,eAAe;GACf,OAAO,IAAI;GACX;GACD"}
@@ -0,0 +1,19 @@
1
+ import { VerifyPluginOptions, VerifyResult } from './types';
2
+ /**
3
+ * Verifies that a plugin can be loaded by Verdaccio.
4
+ *
5
+ * Uses `asyncLoadPlugin` from `@verdaccio/loaders` — the same loader
6
+ * Verdaccio uses at startup — so the verification is identical to what
7
+ * happens in production.
8
+ *
9
+ * Steps verified:
10
+ * 1. Module resolution — can the plugin be found/required?
11
+ * 2. Export validation — does it export a function or a class (default export)?
12
+ * 3. Instantiation — can the plugin be instantiated with a config and options?
13
+ * 4. Sanity check — does the instance implement the required methods for its category?
14
+ *
15
+ * When loading fails, detailed diagnostics are included in the result
16
+ * to pinpoint exactly which step failed and why.
17
+ */
18
+ export declare function verifyPlugin(options: VerifyPluginOptions): Promise<VerifyResult>;
19
+ //# sourceMappingURL=verify-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-plugin.d.ts","sourceRoot":"","sources":["../src/verify-plugin.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIjE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAqFtF"}
@@ -0,0 +1,85 @@
1
+ import { getSanityCheck } from "./sanity-checks.js";
2
+ import { runDiagnostics } from "./diagnostics.js";
3
+ import buildDebug from "debug";
4
+ import { resolve } from "node:path";
5
+ import { PLUGIN_PREFIX } from "@verdaccio/core";
6
+ import { asyncLoadPlugin } from "@verdaccio/loaders";
7
+ import { logger, setup } from "@verdaccio/logger";
8
+ //#region src/verify-plugin.ts
9
+ var debug = buildDebug("verdaccio:plugin:verifier");
10
+ /**
11
+ * Verifies that a plugin can be loaded by Verdaccio.
12
+ *
13
+ * Uses `asyncLoadPlugin` from `@verdaccio/loaders` — the same loader
14
+ * Verdaccio uses at startup — so the verification is identical to what
15
+ * happens in production.
16
+ *
17
+ * Steps verified:
18
+ * 1. Module resolution — can the plugin be found/required?
19
+ * 2. Export validation — does it export a function or a class (default export)?
20
+ * 3. Instantiation — can the plugin be instantiated with a config and options?
21
+ * 4. Sanity check — does the instance implement the required methods for its category?
22
+ *
23
+ * When loading fails, detailed diagnostics are included in the result
24
+ * to pinpoint exactly which step failed and why.
25
+ */
26
+ async function verifyPlugin(options) {
27
+ const { pluginPath, category, pluginConfig = {}, sanityCheck: customSanityCheck, prefix = PLUGIN_PREFIX, pluginsFolder, configPath } = options;
28
+ debug("verifying plugin %o for category %o", pluginPath, category);
29
+ debug("prefix: %o, pluginsFolder: %o", prefix, pluginsFolder);
30
+ await setup({});
31
+ const sanityCheck = customSanityCheck ?? getSanityCheck(category);
32
+ debug("using %s sanity check", customSanityCheck ? "custom" : "default");
33
+ const config = { ...pluginConfig };
34
+ if (pluginsFolder) {
35
+ config.plugins = resolve(pluginsFolder);
36
+ debug("resolved plugins folder: %o", config.plugins);
37
+ }
38
+ if (configPath) {
39
+ config.configPath = configPath;
40
+ debug("config path: %o", config.configPath);
41
+ }
42
+ const pluginConfigs = { [pluginPath]: pluginConfig };
43
+ debug("plugin config: %o", pluginConfigs);
44
+ try {
45
+ const plugins = await asyncLoadPlugin(pluginConfigs, {
46
+ config,
47
+ logger
48
+ }, sanityCheck, false, prefix, category);
49
+ debug("plugins loaded: %o", plugins.length);
50
+ if (plugins.length > 0) {
51
+ debug("verification succeeded for %o", pluginPath);
52
+ return {
53
+ success: true,
54
+ pluginName: pluginPath,
55
+ category,
56
+ pluginsLoaded: plugins.length
57
+ };
58
+ }
59
+ debug("verification failed, running diagnostics for %o", pluginPath);
60
+ const diagnostics = await runDiagnostics(options);
61
+ return {
62
+ success: false,
63
+ pluginName: pluginPath,
64
+ category,
65
+ pluginsLoaded: 0,
66
+ error: diagnostics.find((d) => !d.pass)?.message ?? `Plugin "${pluginPath}" could not be loaded for category "${category}"`,
67
+ diagnostics
68
+ };
69
+ } catch (err) {
70
+ debug("verification error for %o: %o", pluginPath, err.message);
71
+ const diagnostics = await runDiagnostics(options);
72
+ return {
73
+ success: false,
74
+ pluginName: pluginPath,
75
+ category,
76
+ pluginsLoaded: 0,
77
+ error: err.message,
78
+ diagnostics
79
+ };
80
+ }
81
+ }
82
+ //#endregion
83
+ export { verifyPlugin };
84
+
85
+ //# sourceMappingURL=verify-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify-plugin.js","names":[],"sources":["../src/verify-plugin.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { resolve } from 'node:path';\n\nimport { PLUGIN_PREFIX } from '@verdaccio/core';\nimport { asyncLoadPlugin } from '@verdaccio/loaders';\nimport { logger, setup } from '@verdaccio/logger';\n\nimport { runDiagnostics } from './diagnostics';\nimport { getSanityCheck } from './sanity-checks';\nimport type { VerifyPluginOptions, VerifyResult } from './types';\n\nconst debug = buildDebug('verdaccio:plugin:verifier');\n\n/**\n * Verifies that a plugin can be loaded by Verdaccio.\n *\n * Uses `asyncLoadPlugin` from `@verdaccio/loaders` — the same loader\n * Verdaccio uses at startup — so the verification is identical to what\n * happens in production.\n *\n * Steps verified:\n * 1. Module resolution — can the plugin be found/required?\n * 2. Export validation — does it export a function or a class (default export)?\n * 3. Instantiation — can the plugin be instantiated with a config and options?\n * 4. Sanity check — does the instance implement the required methods for its category?\n *\n * When loading fails, detailed diagnostics are included in the result\n * to pinpoint exactly which step failed and why.\n */\nexport async function verifyPlugin(options: VerifyPluginOptions): Promise<VerifyResult> {\n const {\n pluginPath,\n category,\n pluginConfig = {},\n sanityCheck: customSanityCheck,\n prefix = PLUGIN_PREFIX,\n pluginsFolder,\n configPath,\n } = options;\n\n debug('verifying plugin %o for category %o', pluginPath, category);\n debug('prefix: %o, pluginsFolder: %o', prefix, pluginsFolder);\n\n await setup({});\n\n const sanityCheck = customSanityCheck ?? getSanityCheck(category);\n debug('using %s sanity check', customSanityCheck ? 'custom' : 'default');\n\n const config: any = {\n ...pluginConfig,\n };\n\n if (pluginsFolder) {\n config.plugins = resolve(pluginsFolder);\n debug('resolved plugins folder: %o', config.plugins);\n }\n\n if (configPath) {\n config.configPath = configPath;\n debug('config path: %o', config.configPath);\n }\n\n const pluginConfigs = { [pluginPath]: pluginConfig };\n debug('plugin config: %o', pluginConfigs);\n\n try {\n const plugins = await asyncLoadPlugin(\n pluginConfigs,\n { config, logger },\n sanityCheck,\n false,\n prefix,\n category\n );\n\n debug('plugins loaded: %o', plugins.length);\n\n if (plugins.length > 0) {\n debug('verification succeeded for %o', pluginPath);\n return {\n success: true,\n pluginName: pluginPath,\n category,\n pluginsLoaded: plugins.length,\n };\n }\n\n debug('verification failed, running diagnostics for %o', pluginPath);\n const diagnostics = await runDiagnostics(options);\n const failedStep = diagnostics.find((d) => !d.pass);\n\n return {\n success: false,\n pluginName: pluginPath,\n category,\n pluginsLoaded: 0,\n error:\n failedStep?.message ??\n `Plugin \"${pluginPath}\" could not be loaded for category \"${category}\"`,\n diagnostics,\n };\n } catch (err: any) {\n debug('verification error for %o: %o', pluginPath, err.message);\n const diagnostics = await runDiagnostics(options);\n\n return {\n success: false,\n pluginName: pluginPath,\n category,\n pluginsLoaded: 0,\n error: err.message,\n diagnostics,\n };\n }\n}\n"],"mappings":";;;;;;;;AAWA,IAAM,QAAQ,WAAW,4BAA4B;;;;;;;;;;;;;;;;;AAkBrD,eAAsB,aAAa,SAAqD;CACtF,MAAM,EACJ,YACA,UACA,eAAe,EAAE,EACjB,aAAa,mBACb,SAAS,eACT,eACA,eACE;AAEJ,OAAM,uCAAuC,YAAY,SAAS;AAClE,OAAM,iCAAiC,QAAQ,cAAc;AAE7D,OAAM,MAAM,EAAE,CAAC;CAEf,MAAM,cAAc,qBAAqB,eAAe,SAAS;AACjE,OAAM,yBAAyB,oBAAoB,WAAW,UAAU;CAExE,MAAM,SAAc,EAClB,GAAG,cACJ;AAED,KAAI,eAAe;AACjB,SAAO,UAAU,QAAQ,cAAc;AACvC,QAAM,+BAA+B,OAAO,QAAQ;;AAGtD,KAAI,YAAY;AACd,SAAO,aAAa;AACpB,QAAM,mBAAmB,OAAO,WAAW;;CAG7C,MAAM,gBAAgB,GAAG,aAAa,cAAc;AACpD,OAAM,qBAAqB,cAAc;AAEzC,KAAI;EACF,MAAM,UAAU,MAAM,gBACpB,eACA;GAAE;GAAQ;GAAQ,EAClB,aACA,OACA,QACA,SACD;AAED,QAAM,sBAAsB,QAAQ,OAAO;AAE3C,MAAI,QAAQ,SAAS,GAAG;AACtB,SAAM,iCAAiC,WAAW;AAClD,UAAO;IACL,SAAS;IACT,YAAY;IACZ;IACA,eAAe,QAAQ;IACxB;;AAGH,QAAM,mDAAmD,WAAW;EACpE,MAAM,cAAc,MAAM,eAAe,QAAQ;AAGjD,SAAO;GACL,SAAS;GACT,YAAY;GACZ;GACA,eAAe;GACf,OAPiB,YAAY,MAAM,MAAM,CAAC,EAAE,KAAK,EAQnC,WACZ,WAAW,WAAW,sCAAsC,SAAS;GACvE;GACD;UACM,KAAU;AACjB,QAAM,iCAAiC,YAAY,IAAI,QAAQ;EAC/D,MAAM,cAAc,MAAM,eAAe,QAAQ;AAEjD,SAAO;GACL,SAAS;GACT,YAAY;GACZ;GACA,eAAe;GACf,OAAO,IAAI;GACX;GACD"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@verdaccio/plugin-verifier",
3
+ "version": "1.0.0-next-9.1",
4
+ "description": "Tool to verify that a Verdaccio plugin can be loaded and passes sanity checks",
5
+ "type": "module",
6
+ "main": "./build/index.js",
7
+ "types": "build/index.d.ts",
8
+ "bin": {
9
+ "verdaccio-plugin-verifier": "./bin/verdaccio-plugin-verifier"
10
+ },
11
+ "author": {
12
+ "name": "Juan Picado",
13
+ "email": "juanpicado19@gmail.com"
14
+ },
15
+ "repository": {
16
+ "type": "https",
17
+ "url": "https://github.com/verdaccio/verdaccio",
18
+ "directory": "packages/tools/plugin-verifier"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/verdaccio/verdaccio/issues"
22
+ },
23
+ "license": "MIT",
24
+ "homepage": "https://verdaccio.org",
25
+ "keywords": [
26
+ "verdaccio",
27
+ "plugin",
28
+ "testing",
29
+ "verification",
30
+ "cli"
31
+ ],
32
+ "engines": {
33
+ "node": ">=24"
34
+ },
35
+ "dependencies": {
36
+ "@verdaccio/core": "9.0.0-next-9.5",
37
+ "@verdaccio/loaders": "9.0.0-next-9.5",
38
+ "@verdaccio/logger": "9.0.0-next-9.5",
39
+ "clipanion": "4.0.0-rc.4",
40
+ "debug": "4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@verdaccio/config": "9.0.0-next-9.5",
44
+ "@verdaccio/types": "14.0.0-next-9.2",
45
+ "vitest": "4.1.0"
46
+ },
47
+ "funding": {
48
+ "type": "opencollective",
49
+ "url": "https://opencollective.com/verdaccio"
50
+ },
51
+ "exports": {
52
+ ".": {
53
+ "types": "./build/index.d.ts",
54
+ "import": "./build/index.js"
55
+ }
56
+ },
57
+ "scripts": {
58
+ "clean": "rimraf ./build",
59
+ "test": "vitest run",
60
+ "watch": "vite build --watch",
61
+ "build": "vite build"
62
+ }
63
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { Cli } from 'clipanion';
2
+
3
+ import { VerifyCommand } from './commands/verify';
4
+
5
+ const [node, app, ...args] = process.argv;
6
+
7
+ const cli = new Cli({
8
+ binaryLabel: 'verdaccio-plugin-verifier',
9
+ binaryName: `${node} ${app}`,
10
+ binaryVersion: '1.0.0-next-9.0',
11
+ });
12
+
13
+ cli.register(VerifyCommand);
14
+ cli.runExit(args, Cli.defaultContext);
@@ -0,0 +1,100 @@
1
+ import { Command, Option } from 'clipanion';
2
+ import buildDebug from 'debug';
3
+
4
+ import { PLUGIN_CATEGORY, PLUGIN_PREFIX } from '@verdaccio/core';
5
+
6
+ import { verifyPlugin } from '../verify-plugin';
7
+
8
+ const debug = buildDebug('verdaccio:plugin:verifier:cli');
9
+ const VALID_CATEGORIES = Object.values(PLUGIN_CATEGORY);
10
+
11
+ export class VerifyCommand extends Command {
12
+ public static paths = [Command.Default];
13
+
14
+ static usage = Command.Usage({
15
+ description: 'Verify that a Verdaccio plugin can be loaded and passes sanity checks',
16
+ details: `
17
+ This command uses the same plugin loader that Verdaccio runs at startup
18
+ (\`asyncLoadPlugin\` from \`@verdaccio/loaders\`) to verify that a plugin
19
+ can be resolved, instantiated, and passes the required sanity checks
20
+ for its category.
21
+
22
+ The plugin is identified by its short name (as it appears in \`config.yaml\`),
23
+ and the loader applies the prefix automatically. For example, \`my-auth\`
24
+ resolves to \`verdaccio-my-auth\` in the plugins folder or \`node_modules\`.
25
+
26
+ Scoped packages (e.g. \`@myorg/my-plugin\`) are used as-is without a prefix.
27
+
28
+ Enable debug output with the DEBUG environment variable:
29
+ DEBUG=verdaccio:plugin:verifier* verdaccio-plugin-verifier my-auth --category authentication
30
+ `,
31
+ examples: [
32
+ [
33
+ 'Verify an auth plugin from a plugins folder',
34
+ 'verdaccio-plugin-verifier my-auth --category authentication --plugins-folder ./plugins',
35
+ ],
36
+ [
37
+ 'Verify a storage plugin installed via npm',
38
+ 'verdaccio-plugin-verifier my-storage --category storage',
39
+ ],
40
+ [
41
+ 'Verify a scoped plugin with a custom prefix',
42
+ 'verdaccio-plugin-verifier @myorg/my-plugin --category middleware --prefix mycompany',
43
+ ],
44
+ ],
45
+ });
46
+
47
+ private pluginPath = Option.String({
48
+ required: true,
49
+ name: 'plugin',
50
+ });
51
+
52
+ private category = Option.String('--category,-c', {
53
+ required: true,
54
+ description: `Plugin category: ${VALID_CATEGORIES.join(', ')}`,
55
+ });
56
+
57
+ private pluginsFolder = Option.String('--plugins-folder,-d', {
58
+ description: 'Absolute path to the plugins directory (maps to config.plugins)',
59
+ });
60
+
61
+ private prefix = Option.String('--prefix,-p', {
62
+ description: `Plugin name prefix (default: "${PLUGIN_PREFIX}")`,
63
+ });
64
+
65
+ public async execute(): Promise<number> {
66
+ debug('command invoked with plugin=%o category=%o', this.pluginPath, this.category);
67
+ debug('pluginsFolder=%o prefix=%o', this.pluginsFolder, this.prefix);
68
+
69
+ if (!VALID_CATEGORIES.includes(this.category)) {
70
+ this.context.stderr.write(
71
+ `Error: Invalid category "${this.category}". Must be one of: ${VALID_CATEGORIES.join(', ')}\n`
72
+ );
73
+ return 1;
74
+ }
75
+
76
+ const result = await verifyPlugin({
77
+ pluginPath: this.pluginPath,
78
+ category: this.category,
79
+ pluginsFolder: this.pluginsFolder,
80
+ prefix: this.prefix,
81
+ });
82
+
83
+ if (result.success) {
84
+ this.context.stdout.write(
85
+ `Plugin "${result.pluginName}" verified successfully for category "${result.category}" (${result.pluginsLoaded} instance(s) loaded)\n`
86
+ );
87
+ return 0;
88
+ }
89
+
90
+ this.context.stderr.write(`Plugin verification failed: ${result.error}\n`);
91
+ if (result.diagnostics && result.diagnostics.length > 0) {
92
+ this.context.stderr.write('\nDiagnostics:\n');
93
+ for (const step of result.diagnostics) {
94
+ const icon = step.pass ? 'PASS' : 'FAIL';
95
+ this.context.stderr.write(` [${icon}] ${step.phase}: ${step.message}\n`);
96
+ }
97
+ }
98
+ return 1;
99
+ }
100
+ }