{"record":{"id":"76c04230837aaf7c","repo":"ruvnet/ruflo","slug":"module-modulepath-does-not-export-a-plugin","errorCode":null,"errorMessage":"Module ${modulePath} does not export a plugin","messagePattern":"Module (.+?) does not export a plugin","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/plugins/src/index.ts","lineNumber":342,"sourceCode":"}\n\n/**\n * Load and register a plugin module dynamically.\n *\n * @example\n * ```typescript\n * const plugin = await loadPlugin('./my-plugin.js');\n * ```\n */\nexport async function loadPlugin(\n  modulePath: string,\n  config?: Partial<PluginConfig>\n): Promise<IPlugin> {\n  const module = await import(modulePath);\n  const pluginOrFactory: IPlugin | PluginFactory = module.default ?? module.plugin;\n\n  if (!pluginOrFactory) {\n    throw new Error(`Module ${modulePath} does not export a plugin`);\n  }\n\n  const plugin = typeof pluginOrFactory === 'function'\n    ? await pluginOrFactory()\n    : pluginOrFactory;\n\n  await getDefaultRegistry().register(plugin, config);\n  return plugin;\n}\n\n/**\n * Initialize the plugin system.\n *\n * @example\n * ```typescript\n * await initializePlugins({\n *   coreVersion: '3.0.0',\n *   dataDir: './data',","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/plugins/src/index.ts#L324-L360","documentation":"loadPlugin(modulePath) dynamically imports the module and expects `export default` or a named `plugin` export containing an IPlugin instance or an async factory function. If neither export exists it throws before touching the registry. The lookup is exactly `module.default ?? module.plugin`, so any other export shape fails.","triggerScenarios":"The plugin module exports only a named class or const other than `plugin`; a CommonJS module.exports without ESM interop so module.default is undefined; the path resolving to the wrong file (typo, missing extension in native ESM, barrel index re-export that drops the plugin); a package whose main entry is not the plugin module.","commonSituations":"Third-party plugin packages using non-standard export names; CJS/ESM interop mismatches under tsconfig `esModuleInterop` changes; path mistakes under bundler-less Node ESM where extensions matter; refactors that moved the default export into a named one.","solutions":["Ensure the target module has `export default plugin` (or `export const plugin = ...`) — instance or async factory both work","Debug the resolution: `const m = await import(path); console.log(Object.keys(m), m.default)` and fix the path or export accordingly","For CJS plugins use `module.exports = plugin` so interop exposes it as default, or re-export via a small ESM wrapper"],"exampleFix":"// before (my-plugin/index.js)\nexport class MyPlugin { /* ... */ } // no default, no `plugin` export\n\n// after\nexport class MyPlugin { /* ... */ }\nexport default new MyPlugin();\n// or: export const plugin = async () => new MyPlugin();","handlingStrategy":"type-guard","validationCode":"// Inspect the module before handing it to loadPlugin:\nconst mod: any = await import(modulePath);\nif (!mod?.default && !mod?.plugin) {\n  throw new Error(\n    `${modulePath} exports [${Object.keys(mod).join(', ')}] — need default or 'plugin'`\n  );\n}","typeGuard":"type PluginExport = { default?: unknown; plugin?: unknown };\nfunction isPluginExport(mod: PluginExport): boolean {\n  const candidate = mod.default ?? mod.plugin;\n  return typeof candidate === 'object' || typeof candidate === 'function';\n}","tryCatchPattern":"try {\n  plugin = await loadPlugin(modulePath, config);\n} catch (err) {\n  if (err instanceof Error && /does not export a plugin/.test(err.message)) {\n    // log Object.keys(await import(modulePath)) to see the real export shape\n  }\n  throw err;\n}","preventionTips":["Standardize plugin packages on `export default` (instance or async factory)","Verify dynamic-import paths resolve (extension, casing) in native ESM","Write one contract test per plugin package asserting its default export exists"],"tags":["plugin-loading","dynamic-import","esm","module-exports"],"backgroundTag":"missing-module-export","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}