{"record":{"id":"35ff8562c1c6c28d","repo":"bmad-code-org/BMAD-METHOD","slug":"not-a-valid-git-url-or-local-path","errorCode":null,"errorMessage":"Not a valid Git URL or local path","messagePattern":"Not a valid Git URL or local path","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/installer/modules/custom-module-manager.js","lineNumber":331,"sourceCode":"\n    if (!Array.isArray(plugins) || plugins.length === 0) {\n      throw new Error('marketplace.json contains no plugins');\n    }\n\n    return plugins.map((plugin) => this._normalizeCustomModule(plugin, sourceUrl, marketplaceData));\n  }\n\n  // ─── Source Resolution ────────────────────────────────────────────────────\n\n  /**\n   * High-level coordinator: parse input, clone if URL, determine discovery vs direct mode.\n   * @param {string} input - URL or local path\n   * @param {Object} [options] - Options passed to cloneRepo\n   * @returns {Object} { parsed, rootDir, repoPath, sourceUrl, marketplace, mode: 'discovery'|'direct' }\n   */\n  async resolveSource(input, options = {}) {\n    const parsed = this.parseSource(input);\n    if (!parsed.isValid) throw new Error(parsed.error);\n\n    let rootDir;\n    let repoPath;\n    let sourceUrl;\n\n    if (parsed.type === 'local') {\n      rootDir = parsed.localPath;\n      repoPath = null;\n      sourceUrl = null;\n    } else {\n      repoPath = await this.cloneRepo(input, options);\n      sourceUrl = parsed.cloneUrl;\n      rootDir = parsed.subdir ? path.join(repoPath, parsed.subdir) : repoPath;\n\n      if (parsed.subdir && !(await fs.pathExists(rootDir))) {\n        throw new Error(`Subdirectory '${parsed.subdir}' not found in cloned repository`);\n      }\n    }","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/bmad-code-org/BMAD-METHOD/blob/b70486b9bdcb0a404d329e2a763b57964e7f1360/tools/installer/modules/custom-module-manager.js#L313-L349","documentation":"Thrown by resolveSource() when parseSource() cannot match the input as a local path, SSH Git URL (git@host:owner/repo), or HTTP(S) URL. The input falls through all detection patterns and is rejected.","triggerScenarios":"Calling resolveSource() with inputs like 'org/repo' (no protocol, no path prefix), 'github.com/org/repo' (no https://), 'ftp://server/repo', or any string that doesn't start with a path indicator or recognized URL scheme.","commonSituations":"User types a shorthand repo like 'org/repo' instead of a full URL; a copy-paste loses the https:// prefix; an SCP-like syntax without the git@ user prefix; a URL with a typo in the protocol.","solutions":["Prefix the input with 'https://' for HTTP(S) Git URLs: 'https://github.com/org/repo'.","Use SSH format with the git@ prefix: 'git@github.com:org/repo.git'.","For local paths, ensure the path starts with '/', './', '../', '~', or is a Windows absolute path."],"exampleFix":"// before\nawait mgr.resolveSource('github.com/org/repo');\n\n// after\nawait mgr.resolveSource('https://github.com/org/repo');","handlingStrategy":"validation","validationCode":"function looksLikeValidSource(input) {\n  if (!input || typeof input !== 'string') return false;\n  const trimmed = input.trim();\n  if (trimmed.startsWith('/') || trimmed.startsWith('./') || trimmed.startsWith('../') || trimmed.startsWith('~')) return true;\n  if (/^https?:\\/\\//i.test(trimmed)) return true;\n  if (/^git@[^:]+:.+/.test(trimmed)) return true;\n  return false;\n}\n\nif (!looksLikeValidSource(input)) {\n  throw new Error('Input must be a Git URL (https://...) or local path');\n}","typeGuard":"function isRecognizedSource(input) {\n  return typeof input === 'string' && (\n    /^[./~]/.test(input) ||\n    /^https?:\\/\\//i.test(input) ||\n    /^git@[^:]+:/.test(input) ||\n    require('path').win32.isAbsolute(input)\n  );\n}","tryCatchPattern":"try {\n  await mgr.resolveSource(input);\n} catch (e) {\n  if (e.message === 'Not a valid Git URL or local path') {\n    console.error('Provide a full URL (https://github.com/org/repo) or SSH (git@github.com:org/repo) or local path.');\n  }\n  throw e;\n}","preventionTips":["Always prefix URLs with https:// or use the git@ SSH format.","Avoid shorthand like 'org/repo' — the parser requires a protocol or path indicator.","Validate the input format before passing to resolveSource."],"tags":["input-validation","url-parsing","git"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}