chore: new changelog generator (#855)
* chore: new changelog generator * ci: require a change file on PRs
This commit is contained in:
parent
a2a3e5b7b1
commit
cfcf7a7669
13 changed files with 193 additions and 183 deletions
5
.changeset/github_release_changelog.md
Normal file
5
.changeset/github_release_changelog.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
default: minor
|
||||
---
|
||||
|
||||
# The changelog page now shows each GitHub release's notes
|
||||
35
.github/workflows/changelog_publish.yml
vendored
Normal file
35
.github/workflows/changelog_publish.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
name: Publish Changelog
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: changelog-publish
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
steps:
|
||||
# The launcher reads releases/latest/download/changelog.json, so the
|
||||
# snapshot of the last 50 release bodies always lives on the latest release.
|
||||
- name: Build changelog.json from release bodies
|
||||
run: |
|
||||
gh api "repos/$GH_REPO/releases?per_page=50" --jq '[.[]
|
||||
| select(.draft | not)
|
||||
| select(.tag_name | startswith("oneclient-"))
|
||||
| {
|
||||
version: (.tag_name | ltrimstr("oneclient-")),
|
||||
body: ((.body // "") | sub("^Automated release for OneClient [^\n]*\\s*"; ""))
|
||||
}]' > changelog.json
|
||||
jq -r '.[].version' changelog.json
|
||||
|
||||
- name: Upload to latest release
|
||||
run: gh release upload "$(gh release view --json tagName -q .tagName)" changelog.json --clobber
|
||||
23
.github/workflows/changeset_check.yml
vendored
Normal file
23
.github/workflows/changeset_check.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
name: Changeset Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
changeset:
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no changeset') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Require a change file
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
if ! git diff --name-only --diff-filter=A "origin/$BASE_REF...HEAD" -- '.changeset/*.md' | grep -q .; then
|
||||
echo "::error::Add a change file with \`knope document-change\`, or label the PR 'no changeset' if it isn't user-facing."
|
||||
exit 1
|
||||
fi
|
||||
103
.github/workflows/oneclient_release.yml
vendored
103
.github/workflows/oneclient_release.yml
vendored
|
|
@ -3,17 +3,6 @@ name: OneClient Release Build
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump:
|
||||
description: 'Bump the workspace version in Cargo.toml before releasing'
|
||||
type: choice
|
||||
required: false
|
||||
default: none
|
||||
options:
|
||||
- none
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
# NOTE: CARGO_PACKAGER_SIGN_PRIVATE_KEY* are deliberately NOT declared at
|
||||
# workflow level. Swatinem/rust-cache folds every `CARGO*`-prefixed environment
|
||||
|
|
@ -37,18 +26,25 @@ jobs:
|
|||
sha: ${{ steps.version.outputs.sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
# Knope finds the commit (and so the PR author) that added each change file
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Rust
|
||||
if: inputs.bump != 'none'
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
restore-cache: false
|
||||
|
||||
- name: Resolve version, bump if requested, refuse to clobber a release
|
||||
- name: Install Knope
|
||||
uses: ./.github/actions/install-cargo-tool
|
||||
with:
|
||||
tool: knope
|
||||
version: 0.23.0
|
||||
|
||||
- name: Consume changesets
|
||||
id: version
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUMP: ${{ inputs.bump }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
|
@ -57,63 +53,29 @@ jobs:
|
|||
sed -n -E '/^\[workspace\.package\]/,/^\[/ s/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' Cargo.toml | head -n1
|
||||
}
|
||||
|
||||
# No change files means a re-run of a release whose version commit
|
||||
# already landed, so carry on with the current version.
|
||||
RELEASED=0
|
||||
if compgen -G ".changeset/*.md" > /dev/null; then
|
||||
knope prepare-release
|
||||
cargo update --workspace --offline || cargo update --workspace
|
||||
RELEASED=1
|
||||
fi
|
||||
|
||||
VERSION="$(read_version)"
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "::error::Could not read version from [workspace.package] in Cargo.toml"
|
||||
exit 1
|
||||
fi
|
||||
echo "Current version: $VERSION"
|
||||
|
||||
if [ "$BUMP" != "none" ]; then
|
||||
if ! [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
|
||||
echo "::error::Version '$VERSION' is not plain semver major.minor.patch; cannot bump automatically."
|
||||
exit 1
|
||||
fi
|
||||
MAJOR="${BASH_REMATCH[1]}"; MINOR="${BASH_REMATCH[2]}"; PATCH="${BASH_REMATCH[3]}"
|
||||
case "$BUMP" in
|
||||
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
|
||||
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
|
||||
patch) PATCH=$((PATCH + 1)) ;;
|
||||
esac
|
||||
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
|
||||
echo "Bumping ($BUMP): $VERSION -> $NEW_VERSION"
|
||||
|
||||
# Only rewrite the `version` key inside [workspace.package]; the
|
||||
# dependency pins further down the file must not be touched.
|
||||
NEW_VERSION="$NEW_VERSION" python3 - <<'PY'
|
||||
import os, re
|
||||
new = os.environ["NEW_VERSION"]
|
||||
src = open("Cargo.toml").read()
|
||||
start = src.index("[workspace.package]")
|
||||
end = src.find("\n[", start + 1)
|
||||
end = len(src) if end == -1 else end
|
||||
section, count = re.subn(
|
||||
r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new}"', src[start:end], count=1
|
||||
)
|
||||
assert count == 1, "no version key found in [workspace.package]"
|
||||
open("Cargo.toml", "w").write(src[:start] + section + src[end:])
|
||||
PY
|
||||
|
||||
# Workspace members inherit `version.workspace = true`, so only the
|
||||
# lockfile's own entries for those members need refreshing.
|
||||
cargo update --workspace --offline || cargo update --workspace
|
||||
|
||||
ACTUAL="$(read_version)"
|
||||
if [ "$ACTUAL" != "$NEW_VERSION" ]; then
|
||||
echo "::error::Version rewrite failed (Cargo.toml still reports '$ACTUAL')"
|
||||
exit 1
|
||||
fi
|
||||
VERSION="$NEW_VERSION"
|
||||
fi
|
||||
|
||||
TAG="oneclient-$VERSION"
|
||||
echo "Releasing OneClient $VERSION"
|
||||
|
||||
# Abort rather than clobber: a published release (or an existing tag,
|
||||
# which only appears once a release is published) means this version
|
||||
# already shipped. A *draft* is a previous failed/partial run of this
|
||||
# same workflow, so reuse it.
|
||||
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
|
||||
echo "::error::Tag $TAG already exists on origin - OneClient $VERSION has already been released. Re-run with a bump, or bump the version in Cargo.toml."
|
||||
echo "::error::Tag $TAG already exists on origin - OneClient $VERSION has already been released. Add a change file with \`knope document-change\` first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -123,17 +85,17 @@ jobs:
|
|||
RELEASE_EXISTS=1
|
||||
fi
|
||||
if [ "$RELEASE_EXISTS" = "1" ] && [ "$IS_DRAFT" != "true" ]; then
|
||||
echo "::error::Release $TAG already exists and is published - refusing to overwrite OneClient $VERSION. Re-run with a bump, or bump the version in Cargo.toml."
|
||||
echo "::error::Release $TAG already exists and is published - refusing to overwrite OneClient $VERSION. Add a change file with \`knope document-change\` first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Commit the bump only after the guards pass, so a rejected release
|
||||
# does not leave a stray version bump on the branch.
|
||||
if [ "$BUMP" != "none" ]; then
|
||||
# Commit only after the guards pass, so a rejected release does not
|
||||
# leave a stray version bump on the branch.
|
||||
if [ "$RELEASED" = "1" ]; then
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add Cargo.toml Cargo.lock
|
||||
git commit -m "chore: bump to $VERSION"
|
||||
git add -A Cargo.toml Cargo.lock .changeset CHANGELOG.md
|
||||
git commit -m "chore: release $VERSION"
|
||||
git push origin "HEAD:${GITHUB_REF_NAME}"
|
||||
fi
|
||||
|
||||
|
|
@ -143,11 +105,18 @@ jobs:
|
|||
echo "Reusing existing draft release $TAG"
|
||||
gh release edit "$TAG" --target "$SHA"
|
||||
else
|
||||
# The body is what the launcher's changelog page shows
|
||||
awk -v heading="## $VERSION " '/^## /{p = index($0, heading) == 1; next} p' \
|
||||
CHANGELOG.md > notes.md 2>/dev/null || true
|
||||
if ! grep -q '[^[:space:]]' notes.md; then
|
||||
echo "::error::No changelog entry for $VERSION. Add a change file with \`knope document-change\` first."
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$TAG" \
|
||||
--draft \
|
||||
--target "$SHA" \
|
||||
--title "OneClient $VERSION" \
|
||||
--notes "Automated release for OneClient $VERSION."
|
||||
--notes-file notes.md
|
||||
fi
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
|
@ -189,7 +158,7 @@ jobs:
|
|||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
# Pin to the commit `prepare` resolved - with `bump` that is the version
|
||||
# Pin to the commit `prepare` resolved - after a release that is the version
|
||||
# commit it just pushed, which github.sha would not point at.
|
||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -73,6 +73,15 @@ and per-OS prerequisites.
|
|||
Once you have finished making your changes, create a pull request (PR) to submit them.
|
||||
|
||||
- Fill out the template to help reviewers understand your changes and the purpose of your PR.
|
||||
- If your change is user-facing, add a change file with `knope document-change`, or create `.changeset/<anything>.md` by hand:
|
||||
```md
|
||||
---
|
||||
default: patch
|
||||
---
|
||||
|
||||
# Fixed the thing players noticed
|
||||
```
|
||||
Use `patch` for fixes, `minor` for features and `major` for breaking changes. The title becomes your line in the release notes. PRs without one fail the `Changeset Check` unless a maintainer labels them `no changeset`.
|
||||
- If you are addressing an existing issue, don't forget to [link your PR to the issue].
|
||||
- Enable the checkbox to [allow maintainer edits] so that the branch can be updated for merging.
|
||||
- Once you submit your PR, a team member will review your proposal. They may ask questions or request additional information.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,11 @@ cargo packager --release -p oneclient_app --formats <targets>
|
|||
|
||||
The workspace shares a single version, defined in the root [`Cargo.toml`](./Cargo.toml) under `[workspace.package]`.
|
||||
|
||||
Versions and release notes come from [Knope](https://knope.tech) change files in [`.changeset/`](./.changeset).
|
||||
Add one per user-facing change with `knope document-change` (installed via `cargo install knope`). The
|
||||
`OneClient Release Build` workflow consumes them, bumps the version, writes [`CHANGELOG.md`](./CHANGELOG.md),
|
||||
and uses the new entry as the GitHub release body, which is what the launcher's changelog page shows.
|
||||
|
||||
|
||||
## Code signing
|
||||
|
||||
|
|
|
|||
28
knope.toml
Normal file
28
knope.toml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
versioned_files = ["Cargo.toml"]
|
||||
changelog = "CHANGELOG.md"
|
||||
|
||||
[changes]
|
||||
ignore_conventional_commits = true
|
||||
|
||||
[release_notes]
|
||||
change_templates = [
|
||||
"- $summary by [$pr_author_login](https://github.com/$pr_author_login)",
|
||||
"- $summary",
|
||||
]
|
||||
|
||||
[github]
|
||||
owner = "Polyfrost"
|
||||
repo = "OneLauncher"
|
||||
|
||||
[[workflows]]
|
||||
name = "prepare-release"
|
||||
|
||||
[[workflows.steps]]
|
||||
type = "PrepareRelease"
|
||||
|
||||
[[workflows]]
|
||||
name = "document-change"
|
||||
|
||||
[[workflows.steps]]
|
||||
type = "CreateChangeFile"
|
||||
|
|
@ -41,7 +41,7 @@ pub use queries::{
|
|||
UseDiscardLeftovers, UseLogAction, UseRefreshAccount, UseRemoveAccount, UseScreenshotAction,
|
||||
UseSetDefaultAccount, UseStorageAction, UseUploadLog, VERSIONS_PAGE_SIZE,
|
||||
accounts_have_microsoft, available_bundles, bundle_overrides_map, bundles_with_status_items,
|
||||
category_list, changelog_error, changelog_groups, changelog_is_loading, cluster_art_url,
|
||||
category_list, changelog_entries, changelog_error, changelog_is_loading, cluster_art_url,
|
||||
cluster_content_items, content_type_for_slug, disable_warnings, game_versions,
|
||||
has_migration_data, invalidate_cluster_content_queries, invalidate_cluster_queries,
|
||||
invalidate_java_queries, invalidate_leftovers_queries, invalidate_logs_queries,
|
||||
|
|
|
|||
|
|
@ -1,38 +1,30 @@
|
|||
use freya::query::{Query, QueryCapability, UseQuery, use_query};
|
||||
use oneclient_core::{ChangelogGroup, LauncherError, fetch_changelog, parse_changelog};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ChangelogKeys {
|
||||
pub meta_url_base: String,
|
||||
}
|
||||
use oneclient_core::{ChangelogEntry, LauncherError, fetch_changelog};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ChangelogQuery;
|
||||
|
||||
impl QueryCapability for ChangelogQuery {
|
||||
type Ok = Vec<ChangelogGroup>;
|
||||
type Ok = Vec<ChangelogEntry>;
|
||||
type Err = LauncherError;
|
||||
type Keys = ChangelogKeys;
|
||||
type Keys = ();
|
||||
|
||||
async fn run(&self, _keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
|
||||
let state = crate::launcher::state()?;
|
||||
let markdown = fetch_changelog(&state.services.requester).await?;
|
||||
Ok(parse_changelog(&markdown))
|
||||
fetch_changelog(&state.services.requester).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_changelog() -> UseQuery<ChangelogQuery> {
|
||||
let meta_url_base = super::use_meta_url_key();
|
||||
|
||||
use_query(Query::new(ChangelogKeys { meta_url_base }, ChangelogQuery))
|
||||
use_query(Query::new((), ChangelogQuery))
|
||||
}
|
||||
|
||||
pub fn changelog_groups(query: &UseQuery<ChangelogQuery>) -> Option<Vec<ChangelogGroup>> {
|
||||
pub fn changelog_entries(query: &UseQuery<ChangelogQuery>) -> Option<Vec<ChangelogEntry>> {
|
||||
super::state::settled_or_loading(query)
|
||||
}
|
||||
|
||||
pub fn latest_changelog_version(query: &UseQuery<ChangelogQuery>) -> Option<String> {
|
||||
changelog_groups(query).and_then(|groups| groups.first().map(|group| group.version.clone()))
|
||||
changelog_entries(query).and_then(|entries| entries.first().map(|entry| entry.version.clone()))
|
||||
}
|
||||
|
||||
pub fn changelog_error(query: &UseQuery<ChangelogQuery>) -> Option<String> {
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ pub use bundles::{
|
|||
use_onboarding_bundles,
|
||||
};
|
||||
pub use changelog::{
|
||||
changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version,
|
||||
changelog_entries, changelog_error, changelog_is_loading, latest_changelog_version,
|
||||
use_changelog,
|
||||
};
|
||||
pub use cluster_content::{cluster_content_items, use_cluster_content, use_migratable_routes};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use freya::animation::{
|
|||
use freya::prelude::*;
|
||||
|
||||
use super::settings_page;
|
||||
use crate::components::{Icon, IconType};
|
||||
use crate::components::{Icon, IconType, Markdown, MarkdownStyle};
|
||||
use crate::hooks::{
|
||||
changelog_error, changelog_groups, changelog_is_loading, latest_changelog_version,
|
||||
changelog_entries, changelog_error, changelog_is_loading, latest_changelog_version,
|
||||
use_changelog, use_dispatch, use_settings_snapshot,
|
||||
};
|
||||
use crate::theme::colors;
|
||||
|
|
@ -55,15 +55,15 @@ impl Component for SettingsChangelog {
|
|||
.into_element();
|
||||
}
|
||||
|
||||
let groups = changelog_groups(&query).unwrap_or_default();
|
||||
let entries = changelog_entries(&query).unwrap_or_default();
|
||||
|
||||
settings_page()
|
||||
.children(groups.into_iter().enumerate().map(|(i, group)| {
|
||||
let current = group.version == installed_version;
|
||||
.children(entries.into_iter().enumerate().map(|(i, entry)| {
|
||||
let current = entry.version == installed_version;
|
||||
ReleaseCard {
|
||||
version: group.version,
|
||||
version: entry.version,
|
||||
current,
|
||||
changes: group.changes,
|
||||
body: entry.body,
|
||||
initially_open: i == 0,
|
||||
}
|
||||
.into_element()
|
||||
|
|
@ -79,7 +79,7 @@ const CHEVRON_CLOSED_DEG: f32 = -90.;
|
|||
struct ReleaseCard {
|
||||
version: String,
|
||||
current: bool,
|
||||
changes: Vec<String>,
|
||||
body: String,
|
||||
initially_open: bool,
|
||||
}
|
||||
|
||||
|
|
@ -110,8 +110,6 @@ impl Component for ReleaseCard {
|
|||
self.version.clone()
|
||||
};
|
||||
|
||||
let changes = self.changes.clone();
|
||||
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::fill())
|
||||
|
|
@ -144,48 +142,30 @@ impl Component for ReleaseCard {
|
|||
),
|
||||
)
|
||||
.maybe_child(is_open.then(|| {
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::fill())
|
||||
.spacing(4.)
|
||||
.padding(Gaps::new(0., 0., 0., 6.))
|
||||
.children(if changes.is_empty() {
|
||||
vec![
|
||||
rect()
|
||||
.child(
|
||||
label()
|
||||
.text("No changes recorded for this version.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.into_element(),
|
||||
]
|
||||
} else {
|
||||
changes
|
||||
.into_iter()
|
||||
.map(|change| {
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.spacing(8.)
|
||||
.child(
|
||||
label()
|
||||
.text("•")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(change)
|
||||
.font_size(12.)
|
||||
.width(Size::flex(1.0))
|
||||
.color(colors::fg_primary()),
|
||||
)
|
||||
.into_element()
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.into_element()
|
||||
if self.body.trim().is_empty() {
|
||||
label()
|
||||
.text("No changes recorded for this version.")
|
||||
.font_size(12.)
|
||||
.color(colors::fg_secondary())
|
||||
.into_element()
|
||||
} else {
|
||||
Markdown::new(self.body.clone())
|
||||
.width(Size::fill())
|
||||
.style(MarkdownStyle {
|
||||
color: colors::fg_primary(),
|
||||
color_link: colors::code_info(),
|
||||
color_code: colors::fg_primary(),
|
||||
background_code: colors::component_bg(),
|
||||
background_blockquote: colors::component_bg(),
|
||||
border_blockquote: colors::brand(),
|
||||
background_divider: colors::component_border(),
|
||||
headings: [18., 16., 14., 13., 12., 12.],
|
||||
paragraph_size: 12.,
|
||||
code_font_size: 11.,
|
||||
..MarkdownStyle::default()
|
||||
})
|
||||
.into_element()
|
||||
}
|
||||
}))
|
||||
.into_element()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,60 +3,24 @@ use oneclient_common::paths;
|
|||
use oneclient_net::RequestClient;
|
||||
use oneclient_net::{EtagPolicy, fetch_cached};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ChangelogGroup {
|
||||
const CHANGELOG_URL: &str =
|
||||
"https://github.com/Polyfrost/OneLauncher/releases/latest/download/changelog.json";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||
pub struct ChangelogEntry {
|
||||
pub version: String,
|
||||
pub changes: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn parse_changelog(data: &str) -> Vec<ChangelogGroup> {
|
||||
let mut groups = Vec::new();
|
||||
|
||||
for line in data.lines() {
|
||||
if let Some(version) = line.strip_prefix("# ") {
|
||||
groups.push(ChangelogGroup {
|
||||
version: version.trim().to_string(),
|
||||
changes: Vec::new(),
|
||||
});
|
||||
} else if let Some(change) = line.strip_prefix("- ") {
|
||||
if let Some(group) = groups.last_mut() {
|
||||
group.changes.push(change.trim().to_string());
|
||||
}
|
||||
} else if line.trim() == "###" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
groups
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(net))]
|
||||
pub async fn fetch_changelog(net: &RequestClient) -> LauncherResult<String> {
|
||||
let url = format!("{}/oneclient/CHANGE_LOG.md", net.config().meta_url_base);
|
||||
let cache_path = paths::caches_dir()?.join("CHANGE_LOG.md");
|
||||
pub async fn fetch_changelog(net: &RequestClient) -> LauncherResult<Vec<ChangelogEntry>> {
|
||||
let cache_path = paths::caches_dir()?.join("changelog.json");
|
||||
|
||||
let fetched = fetch_cached(net, &url, &cache_path, EtagPolicy::CommitNow)
|
||||
let fetched = fetch_cached(net, CHANGELOG_URL, &cache_path, EtagPolicy::CommitNow)
|
||||
.await?
|
||||
.ok_or_else(|| LauncherError::InvalidSettingsProfile {
|
||||
reason: "changelog is unavailable and not cached".to_string(),
|
||||
})?;
|
||||
|
||||
Ok(fetched.text())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_changelog_groups_versions_and_bullets() {
|
||||
let data = "# 2.0.0\n\n- Faster UI\n- Bug fixes\n# 1.9.0\n- Older change\n###\nignored\n";
|
||||
let groups = parse_changelog(data);
|
||||
|
||||
assert_eq!(groups.len(), 2);
|
||||
assert_eq!(groups[0].version, "2.0.0");
|
||||
assert_eq!(groups[0].changes, vec!["Faster UI", "Bug fixes"]);
|
||||
assert_eq!(groups[1].version, "1.9.0");
|
||||
assert_eq!(groups[1].changes, vec!["Older change"]);
|
||||
}
|
||||
Ok(fetched.json()?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ pub mod tos;
|
|||
pub mod verify;
|
||||
pub mod versions;
|
||||
|
||||
pub use changelog::{ChangelogGroup, fetch_changelog, parse_changelog};
|
||||
pub use changelog::{ChangelogEntry, fetch_changelog};
|
||||
pub use clusters::{
|
||||
Cluster, ClusterError, ClusterManager, ClusterStage, ClusterUpdate, CreateClusterOptions,
|
||||
apply_bundle_java_override, ensure_from_bundles, ensure_from_versions,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue