Dashboard › cli › Distillation
b22256a4-be83-4c64-8253-5ff95d9e966d["lore_tm_v1_PfLznQRTNqEYd89WEH9aKcZBCQbJo2P3gOWKZde_fF4","lore_tm_v1_9kQaZQvC88SDT1emOksvV9Y_igL-XnBitz5q9C4aWWg","lore_tm_v1_Q-6N1hHZGtboLFfnkun-ZsFySI3eKMImGRHbErnBFfU"]
packages/cli/src/lib/release-notes.ts implements stable and nightly release-note parsing, aggregation, filtering, attribution stripping, truncation, and best-effort fetching; only 3 user-facing categories are retained in exact display order: 1. "features" (### New Features / feat:), 2. "fixes" (### Bug Fixes / fix:), 3. "performance" (### Performance / perf:).ChangeSection contains category: ChangeCategory and raw markdown: string; ChangelogSummary contains fromVersion, toVersion, sections, totalItems, truncated, and originalCount.extractSections(body) uses marked.lexer() and splits only on depth-3 (###) headings. It normalizes headings by stripping EMOJI_RE = /[\u{2000}-\u{2BFF}\u{FE00}-\u{FE0F}\u{1F000}-\u{1FFFF}]/gu, trimming, and lowercasing; tokens beneath recognized headings are serialized through each tokenβs preserved raw property.countListItems(tokens) counts only top-level Tokens.List.items, excluding nested sublists; countMarkdownListItems(md) lexes markdown and delegates to it.stripAttributions(md) operates only on lines whose trimmed start is "- " and removes suffixes matching AUTHOR_SUFFIX_RE = /\s+by\s+@\S+\s+in\s+\[?#\d+\]?(?:\([^)]*\))?\s*$/, covering forms such as by @author in [#123](url) and by @author in #123.truncateSectionMarkdown(md, maxItems) preserves non-list token raw markdown, keeps whole lists while budget remains, slices an over-budget list with list.items.slice(0, remaining), preserves each retained itemβs raw markdown including nested content/formatting, removes subsequent lists, and returns { markdown, removed }.applySectionTruncation(sections, maxItems, originalCount) mutates sections in place, replacing each sectionβs markdown with its truncated version when needed. Non-final section budgets are proportional via Math.floor((sectionItems / originalCount) * maxItems), bounded to at least 1 and capped by remaining budget; the final section receives the remaining budget. It recomputes totalItems and sets truncated when totalItems < originalCount.buildSummaryFromSections() returns null only when sections.length === 0; otherwise it counts original list items, optionally invokes applySectionTruncation() when originalCount > maxItems, and returns both the post-truncation totalItems and pre-truncation originalCount.mergeSectionsByCategory(releases) extracts sections from each nonempty GitHub release body, strips author/PR attributions, concatenates same-category markdown with "\n", and emits merged sections in CATEGORY_ORDER.buildChangelogSummary(releases, fromVersion, toVersion, maxItems?) strips a leading v from tag_name, filters to the range exclusive of fromVersion and inclusive of toVersion using compareVersions, returns null when no releases are in range, then merges and summarizes their sections.NIGHTLY_VERSION_RE = /^[\d.]+(?:-\w+)?-dev\.(\d+)$/; extractNightlyTimestamp(version) returns the Unix-seconds suffix as an integer or null.parseCommitMessages(commits) uses only the first trimmed line, accepts conventional commits matching ^(\w+)(?:\([^)]*\))?:\s*(.+)$, maps only feat β "features", fix β "fixes", and perf β "performance", excludes any message containing #skip-changelog, and emits descriptions as - ${description} markdown grouped in category display order.CHANGELOG_MAX_RELEASES = 30 and ${GITHUB_RELEASES_URL}?per_page=${CHANGELOG_MAX_RELEASES} with getGitHubHeaders(). The value is intentionally higher than the delta-upgrade cap of 12; GitHub allows up to 100 per page, and 30 is intended to cover approximately 6+ months of weekly releases.fetchReleasesForChangelog() returns [] on fetch failure, non-OK response, non-JSON response, or a JSON value that is not an array; failures are optionally logged through logger.withTag("release-notes").fetchStableChangelog() accepts optional prefetchedReleases to avoid a duplicate GitHub Releases request; otherwise it calls fetchReleasesForChangelog(), returning null when no releases are available.fetchNightlyChangelog() queries https://api.github.com/repos/getsentry/cli/commits?sha=main&since=${sinceDate}&until=${untilDate}&per_page=100. Because GitHub since is inclusive and until is exclusive, both boundaries use the corresponding nightly timestamp plus 1 second to exclude the installed commit and include the target commit.fetchNightlyChangelog() returns null for invalid nightly versions, request failures, non-OK responses, invalid/non-array JSON, or an empty commit array; successful responses are passed through buildNightlyChangelogSummary().FetchChangelogOptions contains channel: "stable" | "nightly", fromVersion, toVersion, optional maxItems, and optional stable-only prefetchedReleases. fetchChangelog(opts) dispatches by channel, catches all errors, logs "Changelog fetch failed:", and returns null; it is designed to run in parallel with binary download so changelog collection adds zero latency.packages/cli/test/e2e/delta-upgrade.test.ts line 64, packages/cli/test/lib/upgrade.test.ts line 979, and packages/cli/test/lib/binary.test.ts line 43; packages/cli/src/lib/release-notes.ts line 564 uses GITHUB_RELEASES_URL, while packages/cli/src/lib/ghcr.ts contains GHCR package, token, manifest, and paginated tag-list endpoints.