Dashboard › cli › Distillation
d3782c63-f0bc-445a-9ae2-c566224d181c["lore_tm_v1_zDurEDGLf3ZDFWiVk44ZsS7j43DcX4QuoL9LAd_Qh9Q","lore_tm_v1_yLReJdusaaZlSJrzb22i9vGkEDUQV6rLxpOyjJprkbg","lore_tm_v1_dL98UTsKByUknj6VFRpeexjTOT_Vj1aqOMFbZXfnTXw","lore_tm_v1__SOJatKvugwjP9_29WjvORxhtajZdh3gtp0xPU2Db_c"]
packages/cli/src/lib/release-notes.ts implements release-note parsing and aggregation with marked.lexer() AST processing, retaining only 3 user-facing categories in this display order: 1. features from ### New Features or feat: commits, 2. fixes from ### Bug Fixes or fix: commits, 3. performance from ### Performance or perf: commits.packages/cli/src/lib/release-notes.ts defines ChangeCategory = "features" | "fixes" | "performance", ChangeSection = { category: ChangeCategory; markdown: string }, and ChangelogSummary with fromVersion, toVersion, sections, totalItems, truncated, and originalCount.EMOJI_RE to strip emoji/symbol ranges from headings, AUTHOR_SUFFIX_RE to remove by @author in [#123](url) or by @author in #123 suffixes, NIGHTLY_VERSION_RE for X.Y.Z-dev.<unix-seconds>, CONVENTIONAL_COMMIT_RE for type(scope): description, and VERSION_PREFIX_RE = /^v/.extractSections(body) splits only on depth-3 headings, normalizes headings by removing emoji, trimming, and lowercasing, and collects all tokens under matched sections—including depth-4 scope headings, lists, and other block content. tokensToMarkdown() losslessly reconstructs source by joining each token’s raw property.countListItems(tokens) counts only direct/top-level items in list tokens, not nested sublists; countMarkdownListItems(md) lexes markdown before applying that count.stripAttributions(md) works line-by-line and removes author/PR suffixes only from lines whose trimmed start is - .truncateSectionMarkdown(md, maxItems) walks parsed list tokens, preserves non-list token raw markdown, removes lists after the budget is exhausted, and truncates a partially retained list by joining retained item raw values so nested content and inline formatting survive.applySectionTruncation() mutates the sections array in place, replacing markdown with truncated versions when originalCount > maxItems. Non-final sections receive Math.floor((sectionItems / originalCount) * maxItems), constrained to at least 1 and no more than the remaining budget; the final section receives the remaining budget. It recomputes totalItems and sets truncated when totalItems < originalCount.buildSummaryFromSections() returns null only when there are no sections; otherwise it records the pre-truncation originalCount, optionally applies truncation, and returns the resulting ChangelogSummary.mergeSectionsByCategory() extracts sections from each non-empty GitHub release body, strips list-item attributions, concatenates same-category markdown with "\n", and emits merged sections in features, fixes, performance order.buildChangelogSummary() strips a leading v from release tags and includes releases strictly newer than fromVersion and less than or equal to toVersion; it returns null when no release is in range.parseCommitMessages() excludes commits containing #skip-changelog, uses only the trimmed first line, accepts optional conventional-commit scopes, retains only feat, fix, and perf, and serializes accepted descriptions as - ${description} markdown grouped in category display order.extractNightlyTimestamp(version) parses the Unix-seconds component from X.Y.Z-dev.<unix-seconds> and returns null for an invalid format or nonnumeric timestamp.CHANGELOG_MAX_RELEASES = 30 and requests ${GITHUB_RELEASES_URL}?per_page=${CHANGELOG_MAX_RELEASES} with getGitHubHeaders(). This is deliberately higher than delta upgrade’s 12-release cap; GitHub allows up to 100, and 30 is intended to cover approximately 6+ months of weekly releases.fetchReleasesForChangelog() returns [] for fetch failures, non-OK responses, non-JSON responses, or non-array JSON, with debug logging for fetch/shape/parse failures. fetchStableChangelog() accepts optional prefetchedReleases to avoid a duplicate API request and otherwise fetches the 30-release changelog window.fetchNightlyChangelog() derives since and until from nightly timestamps, adding 1 second to both because GitHub’s since is inclusive and until is exclusive. It requests https://api.github.com/repos/getsentry/cli/commits?sha=main&since=${sinceDate}&until=${untilDate}&per_page=100 with getGitHubHeaders().fetchNightlyChangelog() returns null for invalid nightly versions, fetch errors, non-OK responses, invalid JSON, non-array data, or zero commits; valid commit data is passed to buildNightlyChangelogSummary().FetchChangelogOptions contains channel: "stable" | "nightly", fromVersion, toVersion, optional maxItems, and optional stable-only prefetchedReleases. fetchChangelog(opts) dispatches by channel and is best-effort: it returns null on any failure and is designed to run in parallel with binary download for zero added latency.packages/cli/src/lib/version-check.ts uses CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000, NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000, and JITTER_FACTOR = 0.2.shouldCheckForUpdate() immediately checks when lastChecked === null; otherwise it applies ±20% interval jitter and uses probability 1 - Math.exp(-elapsed / effectiveInterval), yielding approximately 0% at 0 intervals, 63% at 1 interval, and 86% at 2 intervals.upgrade, --version, -V, --json, token, or init. They are also suppressed for cli setup and cli fix, matched only when cli is the first argument to avoid false positives such as --project setup.abortPendingVersionCheck() aborts pendingAbortController and resets it to null, allowing the process to exit while a version fetch is pending.maybePrefetchPatches(channel, latestVersion, signal) runs only when semverCompare(latestVersion, CLI_VERSION) === 1; it calls prefetchNightlyPatches() or prefetchStablePatches() by channel, silently catches prefetch errors, then opportunistically calls cleanupPatchCache() and ignores cleanup failures.checkForUpdateInBackgroundImpl() does not block and never throws—errors are caught and reported to Sentry. A failure while reading update-check DB state is sent through Sentry.captureException() and stops the check.Sentry.startSpanManual() with name: "version-check", op: "version.check", and forceTransaction: true; nightly checks call fetchLatestNightlyVersion(signal), stable checks call fetchLatestFromGitHub(signal), successful results are persisted with setVersionCheckInfo(latestVersion), and delta patches are prefetched afterward.version_check.error and version_check.error_type rather than captured as Sentry Issues; success uses span status code 1, failure uses code 2, and finally clears pendingAbortController and ends the span.process.stderr.isTTY; non-TTY contexts such as scripts, CI, pipes, and editor captures receive no notification.getUpdateNotificationWithCopy() never throws—errors are caught and reported to Sentry. It returns null for non-TTY stderr, a prior notification in the same process, missing cached version data, a cached version that is not newer than CLI_VERSION, or a notification within the 24-hour DB-backed rate-limit window.notifiedThisProcess plus persisted last_notified to never double-emit: the in-process latch prevents repeated output in one process, while canNotifyAgain() enforces the 24-hour cross-invocation interval and permits a first-ever notification when lastNotified === null.getUpdateNotificationWithCopy() calls markUpdateNotified(). If persistence fails, it captures the exception but still returns the banner, then sets notifiedThisProcess = true; this favors showing the update notice over swallowing it.New nightly available: and stable updates Update available:, shows ${CLI_VERSION} -> ${latestVersion}, and tells the user to run "sentry cli upgrade".A new version of sentry-cli is available (${latestVersion}). Upgrading may resolve this — we fix a lot of bugs in every release. Run "sentry cli upgrade" to update.resetUpdateNotificationState() resets notifiedThisProcess = false for tests and is marked internal/non-production.SENTRY_CLI_NO_UPDATE_CHECK === "1" disables both maybeCheckForUpdateInBackground() and normal update-notification output; the environment is checked at runtime for test isolation.getErrorUpdateNotification(error, args) first honors command suppression; user errors identified by isUserError(error) receive the standard neutral banner, while non-user errors receive the contextual upgrade nudge. It shares TTY, rate-limit, and once-per-process gates with getUpdateNotification(), so the two paths never double-emit.wait 2>/dev/null || true to let the background curl finish while ignoring its exit status.