{"record":{"id":"912d8338476edf26","repo":"flipped-aurora/gin-vue-admin","slug":"plugin-archive-contains-multiple-web-plugins","errorCode":null,"errorMessage":"plugin archive contains multiple web plugins","messagePattern":"plugin archive contains multiple web plugins","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/auto_code_plugin.go","lineNumber":298,"sourceCode":"\t\t\tif parts[index+1] != \"plugin\" || (parts[index] != \"server\" && parts[index] != \"web\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tname := parts[index+2]\n\t\t\tif err := utils.ValidatePluginName(name); err != nil {\n\t\t\t\treturn pluginArchive{}, err\n\t\t\t}\n\t\t\tpart := &pluginArchivePart{\n\t\t\t\troot: filepath.Join(tempDir, filepath.FromSlash(strings.Join(parts[:index+2], \"/\"))),\n\t\t\t\tname: name,\n\t\t\t}\n\t\t\tif parts[index] == \"server\" {\n\t\t\t\tif archive.server != nil && (archive.server.name != part.name || archive.server.root != part.root) {\n\t\t\t\t\treturn pluginArchive{}, errors.New(\"plugin archive contains multiple server plugins\")\n\t\t\t\t}\n\t\t\t\tarchive.server = part\n\t\t\t} else {\n\t\t\t\tif archive.web != nil && (archive.web.name != part.name || archive.web.root != part.root) {\n\t\t\t\t\treturn pluginArchive{}, errors.New(\"plugin archive contains multiple web plugins\")\n\t\t\t\t}\n\t\t\t\tarchive.web = part\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif archive.server == nil && archive.web == nil {\n\t\treturn pluginArchive{}, errors.New(\"invalid plugin archive\")\n\t}\n\treturn archive, nil\n}\n\nfunc pluginInstallRoot(component, parentPlugin string) (string, error) {\n\troot, err := utils.JoinWithinRoot(global.GVA_CONFIG.AutoCode.Root, component, \"plugin\")\n\tif err != nil || parentPlugin == \"\" {\n\t\treturn root, err\n\t}\n\tparentPath, err := utils.JoinWithinRoot(root, parentPlugin)","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/auto_code_plugin.go#L280-L316","documentation":"Symmetric to the server-side check: the installer allows at most one web plugin part per archive. If it encounters a second distinct web plugin root (different name or path under the web side), it rejects the archive with this error.","triggerScenarios":"Installing an archive containing multiple unique directories under the web root, e.g. `web/src/plugin/foo` and `web/src/plugin/bar`. findPluginArchive throws on the second distinct web part.","commonSituations":"Zipping the whole `web/src/plugin` directory; stale files from a previously extracted plugin left in the staging folder; packaging tools that bundle multiple frontend plugins.","solutions":["Rebuild the archive with exactly one web plugin directory.","Delete unrelated or stale web plugin directories from the zip.","Install each web plugin using its own separate archive."],"exampleFix":"// before (archive layout)\nweb/src/plugin/foo/\nweb/src/plugin/bar/\n// after\nweb/src/plugin/foo/  // single web plugin per archive","handlingStrategy":"validation","validationCode":"const webDirs = new Set(entries.filter(e => /(^|\\/)web\\/src\\/plugin\\/[^/]+\\//.test(e)).map(e => e.split('/')[3]))\nif (webDirs.size > 1) throw new Error(`archive has ${webDirs.size} web plugins: ${[...webDirs]}`)","typeGuard":null,"tryCatchPattern":"try {\n  await installPlugin(archive)\n} catch (e) {\n  if (e.message.includes('multiple web plugins')) {\n    showHint('Split the archive: one web plugin per zip.')\n  } else { throw e }\n}","preventionTips":["Archive the specific plugin folder, not the parent plugin directory.","Exclude unrelated web plugins from the packaging glob.","Validate archive entry list in CI before publishing the artifact."],"tags":["plugin-install","archive-validation"],"backgroundTag":"plugin-archive-structure-invalid","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}