```ts
/**
 * Parses a semantic version string into its components.
 */
export function parseSemver(
  version: string,
): {
  major: number;
  minor: number;
  patch: number;
  prerelease: string | null;
  build: string | null;
} {
  const semverRegex =
    /^([1-9]\d*|0)\.([1-9]\d*|0)\.([1-9]\d*|0)(?:-([\w.-]+))?(?:\+([\w.-]+))?$/;

  const match = semverRegex.exec(version);
  if (!match) {
    throw new Error(`Invalid semver: ${version}`);
  }

  const [, majorStr, minorStr, patchStr, prerelease, build] = match;
  return {
    major: Number(majorStr),
    minor: Number(minorStr),
    patch: Number(patchStr),
    prerelease: prerelease ?? null,
    build: build ?? null,
  };
}
```