{"record":{"id":"bd6756f8f522782e","repo":"agalwood/Motrix","slug":"plugin-manifest-invalid-bd6756","errorCode":"PLUGIN_MANIFEST_INVALID","errorMessage":"unparseable host version: ${s}","messagePattern":"unparseable host version: (.+?)","errorType":"validation","errorClass":"PluginManifestInvalid","httpStatus":null,"severity":"error","filePath":"src/core/plugin/manifest/parse.ts","lineNumber":63,"sourceCode":"export interface ParseResult {\n  manifest: ManifestZodOutput\n  warnings: ManifestWarning[]\n}\n\nexport function semverSatisfies(version: string, range: string): boolean {\n  if (range.trim() === '*') return true\n  const v = parseVer(version)\n  const clauses = range.trim().split(/\\s+/)\n  for (const c of clauses) {\n    if (!testClause(v, c)) return false\n  }\n  return true\n}\n\nfunction parseVer(s: string): [number, number, number] {\n  const m = /^(\\d+)\\.(\\d+)\\.(\\d+)/.exec(s)\n  if (!m)\n    throw new PluginManifestInvalid(\n      'plugin.manifest.host_version_unparseable',\n      `unparseable host version: ${s}`\n    )\n  return [Number(m[1]), Number(m[2]), Number(m[3])]\n}\n\nfunction testClause(v: [number, number, number], clause: string): boolean {\n  const m = /^(>=|<=|>|<|=|\\^|~)?(\\d+)\\.(\\d+)\\.(\\d+)/.exec(clause)\n  if (!m)\n    throw new PluginManifestInvalid(\n      'plugin.manifest.engines_unparseable',\n      `unparseable range clause: ${clause}`\n    )\n  const op = m[1] ?? '='\n  const r: [number, number, number] = [Number(m[2]), Number(m[3]), Number(m[4])]\n  const cmp = cmpVer(v, r)\n  switch (op) {\n    case '>=':","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/manifest/parse.ts#L45-L81","documentation":"parseVer() is the internal helper behind semverSatisfies(); it expects a major.minor.patch prefix matching /^(\\d+)\\.(\\d+)\\.(\\d+)/. It is called on the HOST version (opts.hostVersion), not the plugin's. If the host version string lacks that numeric triple, parsing aborts — this is a host/app misconfiguration, not a plugin author error.","triggerScenarios":"Calling parseManifest(raw, {hostVersion}) with a hostVersion like 'v2', '2.x', '2.0.0-beta', empty string, or a git SHA. semverSatisfies invokes parseVer on the host version when engines.motrix is declared.","commonSituations":"Host app built with a non-semver version stamp (e.g. injected '0.0.0-dev' or a commit hash); test fixture passed hostVersion='latest'; build pipeline set VERSION to a git describe string with no numeric triple.","solutions":["Pass a hostVersion that starts with numeric major.minor.patch, e.g. '2.5.0' (a pre-release suffix like '-beta' is fine since only the prefix is parsed).","In tests, use a real semver triple for hostVersion rather than a placeholder.","Ensure the app's build pipeline emits a semver-shaped version into the value passed as hostVersion."],"exampleFix":"// before\nparseManifest(raw, {hostVersion: 'v2-latest'})\n// after\nparseManifest(raw, {hostVersion: '2.5.0'})","handlingStrategy":"validation","validationCode":"function assertSemverPrefix(s:string){ if(!/^(\\d+)\\.(\\d+)\\.(\\d+)/.test(s)) throw new Error(`hostVersion \"${s}\" unparseable`) }\nassertSemverPrefix(hostVersion)\nparseManifest(raw, {hostVersion})","typeGuard":"const hasSemverPrefix = (s:string): boolean => /^(\\d+)\\.(\\d+)\\.(\\d+)/.test(s)","tryCatchPattern":"try { parseManifest(raw, {hostVersion}) }\ncatch(e){ if(e instanceof PluginManifestInvalid && e.validationCode==='plugin.manifest.host_version_unparseable'){ /* fix the host version string */ } else throw e }","preventionTips":["Always pass a numeric major.minor.patch hostVersion (a pre-release suffix is fine).","Do not feed git SHAs, 'latest', or '0.0.0' into hostVersion.","Centralize the app version stamp in the build pipeline and assert its shape."],"tags":["manifest","semver","host-version","internal-parse","misconfiguration"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}