(changelog: BumpyConfig['changelog'], rootDir: string)
| 162 | * Supports: "default", "./path/to/formatter.ts", or a module name. |
| 163 | */ |
| 164 | export async function loadFormatter(changelog: BumpyConfig['changelog'], rootDir: string): Promise<ChangelogFormatter> { |
| 165 | const [name, options] = Array.isArray(changelog) ? changelog : [changelog, {}]; |
| 166 | |
| 167 | // Built-in with options (e.g., ["github", { repo: "..." }]) |
| 168 | if (name === 'github') { |
| 169 | const { createGithubFormatter } = await import('./changelog-github.ts'); |
| 170 | return createGithubFormatter(options as import('./changelog-github.ts').GithubChangelogOptions); |
| 171 | } |
| 172 | |
| 173 | // Built-in formatter (no options) |
| 174 | if (typeof name === 'string' && BUILTIN_FORMATTERS[name]) { |
| 175 | const builtin = BUILTIN_FORMATTERS[name]; |
| 176 | if (typeof builtin === 'function' && builtin.length === 0) { |
| 177 | // Lazy-loaded formatter factory (like github) |
| 178 | return (builtin as () => Promise<ChangelogFormatter>)(); |
| 179 | } |
| 180 | return builtin as ChangelogFormatter; |
| 181 | } |
| 182 | |
| 183 | // Custom module |
| 184 | if (typeof name === 'string') { |
| 185 | try { |
| 186 | let modulePath: string; |
| 187 | if (name.startsWith('.')) { |
| 188 | // Relative path — resolve symlinks and verify it stays within the project root |
| 189 | modulePath = resolve(rootDir, name); |
| 190 | try { |
| 191 | modulePath = realpathSync(modulePath); |
| 192 | } catch { |
| 193 | // File doesn't exist yet — use the resolved path as-is |
| 194 | } |
| 195 | const rel = relative(realpathSync(rootDir), modulePath); |
| 196 | if (rel.startsWith('..') || resolve('/', rel) === resolve('/')) { |
| 197 | throw new Error(`Changelog formatter path "${name}" resolves outside the project root`); |
| 198 | } |
| 199 | } else { |
| 200 | // Bare module specifier (e.g. npm package name) |
| 201 | modulePath = name; |
| 202 | } |
| 203 | const mod = await import(modulePath); |
| 204 | // Support: export default fn, export const changelogFormatter = fn, or module is fn |
| 205 | const exported = mod.default || mod.changelogFormatter; |
| 206 | if (typeof exported === 'function') { |
| 207 | // If it takes options, call it as a factory; otherwise use it directly |
| 208 | // Heuristic: if the function returns a function, it's a factory |
| 209 | const result = exported(options); |
| 210 | if (typeof result === 'function') return result; |
| 211 | // If it returned a string/promise, it IS the formatter |
| 212 | return exported; |
| 213 | } |
| 214 | throw new Error(`Changelog module "${name}" does not export a function`); |
| 215 | } catch (err) { |
| 216 | log.warn(`Failed to load changelog formatter "${name}": ${err instanceof Error ? err.message : err}`); |
| 217 | log.warn('Falling back to default formatter'); |
| 218 | return defaultFormatter; |
| 219 | } |
| 220 | } |
| 221 |
no test coverage detected
searching dependent graphs…