(rootDir: string, opts: GenerateOptions)
| 39 | }; |
| 40 | |
| 41 | export async function generateCommand(rootDir: string, opts: GenerateOptions): Promise<void> { |
| 42 | const config = await loadConfig(rootDir); |
| 43 | const packages = await discoverPackages(rootDir, config); |
| 44 | |
| 45 | // Get commits — either from explicit ref or from branch divergence point |
| 46 | let commits: { hash: string; subject: string; body: string }[]; |
| 47 | |
| 48 | if (opts.from) { |
| 49 | log.step(`Scanning commits from ${colorize(opts.from, 'cyan')}...`); |
| 50 | const rawLog = tryRunArgs(['git', 'log', `${opts.from}..HEAD`, '--format=%H%n%s%n%b%n---END---'], { cwd: rootDir }); |
| 51 | if (!rawLog) { |
| 52 | log.info('No commits found since ' + opts.from); |
| 53 | return; |
| 54 | } |
| 55 | commits = parseGitLog(rawLog); |
| 56 | } else { |
| 57 | log.step(`Scanning commits on this branch (vs ${colorize(config.baseBranch, 'cyan')})...`); |
| 58 | commits = getBranchCommits(rootDir, config.baseBranch); |
| 59 | } |
| 60 | |
| 61 | if (commits.length === 0) { |
| 62 | log.info('No commits found on this branch.'); |
| 63 | return; |
| 64 | } |
| 65 | |
| 66 | log.dim(` Found ${commits.length} commit(s)`); |
| 67 | |
| 68 | // Build scope → package name mapping for CC resolution |
| 69 | const scopeMap = buildScopeMap(packages, config); |
| 70 | |
| 71 | // Collect releases from all commits |
| 72 | const releaseMap = new Map<string, { type: BumpType; messages: string[] }>(); |
| 73 | |
| 74 | let ccCount = 0; |
| 75 | let fileBasedCount = 0; |
| 76 | |
| 77 | for (const commit of commits) { |
| 78 | const cc = parseConventionalCommit(commit); |
| 79 | |
| 80 | if (cc) { |
| 81 | // Conventional commit — use type/scope for bump level |
| 82 | ccCount++; |
| 83 | const bump: BumpType = cc.breaking ? 'major' : BUMP_MAP[cc.type] || 'patch'; |
| 84 | |
| 85 | let pkgNames: string[] = []; |
| 86 | if (cc.scope) { |
| 87 | const resolved = resolveScope(cc.scope, scopeMap, packages); |
| 88 | if (resolved.length > 0) { |
| 89 | pkgNames = resolved; |
| 90 | } |
| 91 | // If scope didn't resolve, fall through to file-based detection below |
| 92 | } |
| 93 | |
| 94 | if (pkgNames.length > 0) { |
| 95 | for (const name of pkgNames) { |
| 96 | mergeRelease(releaseMap, name, bump, cc.description); |
| 97 | } |
| 98 | continue; |
no test coverage detected
searching dependent graphs…