{"record":{"id":"ddfe97621322245a","repo":"bmad-code-org/BMAD-METHOD","slug":"source-is-required","errorCode":null,"errorMessage":"Source is required","messagePattern":"Source is required","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() returns isValid:false with error 'Source is required'. This occurs when the input is null, undefined, not a string, or an empty/whitespace-only string.","triggerScenarios":"Calling resolveSource('') , resolveSource(null), resolveSource(undefined), or resolveSource('   ') (whitespace only). Also triggered if a variable that was expected to hold a URL/path is uninitialized when passed.","commonSituations":"A CLI flag was not provided (e.g., the install source argument was omitted); an environment variable read for the source came back undefined; a previous step failed to produce a URL and passed the empty result forward.","solutions":["Provide a non-empty string: either a Git URL (https://..., git@...) or a local filesystem path.","Check that the variable holding the source string is initialized before calling resolveSource().","If the source comes from user input, validate it is a non-empty string before proceeding."],"exampleFix":"// before\nconst source = process.env.MODULE_URL; // undefined\nawait mgr.resolveSource(source);\n\n// after\nconst source = process.env.MODULE_URL;\nif (!source) throw new Error('MODULE_URL must be set');\nawait mgr.resolveSource(source);","handlingStrategy":"validation","validationCode":"if (!input || typeof input !== 'string' || input.trim().length === 0) {\n  throw new Error('A source URL or local path is required');\n}\nawait mgr.resolveSource(input);","typeGuard":"/**\n * @param {*} input\n * @returns {input is string}\n */\nfunction isNonEmptyString(input) {\n  return typeof input === 'string' && input.trim().length > 0;\n}","tryCatchPattern":"try {\n  const result = await mgr.resolveSource(input);\n} catch (e) {\n  if (e.message === 'Source is required') {\n    console.error('No source provided. Pass a Git URL or local path.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Always validate input is a non-empty string at the API boundary before calling resolveSource.","Use default parameter values or early returns for missing CLI arguments.","Log the variable name and value when debugging to identify which input is empty."],"tags":["input-validation","argument-error"],"backgroundTag":null,"analyzedSha":"b70486b9bdcb0a404d329e2a763b57964e7f1360","analyzedAt":"2026-08-13T01:21:12.247Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}