Skip to content

Commit 7dfd88d

Browse files
committed
chore(core): prepare 0.6.0 release
Add git-cliff release notes generation so GitHub releases credit PR authors and contributors, then wire the generated body into the release workflow. Bump the workspace to 0.6.0 and fix connection-manager release blockers around URL paste options, Mongo timeout persistence, and imported manual order values.
1 parent 410dd18 commit 7dfd88d

7 files changed

Lines changed: 168 additions & 15 deletions

File tree

.github/workflows/release.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ jobs:
8383
uses: actions/checkout@v4
8484
with:
8585
submodules: recursive
86+
fetch-depth: 0
8687

8788
- name: Download all artifacts
8889
uses: actions/download-artifact@v4
@@ -97,11 +98,22 @@ jobs:
9798
sha256sum * > SHA256SUMS.txt
9899
cat SHA256SUMS.txt
99100
101+
- name: Generate release notes
102+
uses: orhun/git-cliff-action@v4
103+
with:
104+
config: cliff.toml
105+
args: --current --github-repo ${{ github.repository }}
106+
github_token: ${{ secrets.GITHUB_TOKEN }}
107+
env:
108+
OUTPUT: RELEASE_NOTES.md
109+
GITHUB_REPO: ${{ github.repository }}
110+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
100112
- name: Create GitHub Release
101113
uses: softprops/action-gh-release@v2
102114
with:
103115
files: release/*
104-
generate_release_notes: true
116+
body_path: RELEASE_NOTES.md
105117
draft: false
106118
prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-beta') || contains(github.ref, '-alpha') }}
107119
env:
@@ -125,8 +137,17 @@ jobs:
125137
run: cargo publish -p tui-syntax --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
126138
continue-on-error: true # May already be published
127139

128-
- name: Wait for crates.io
129-
run: sleep 30
140+
- name: Wait for crates.io index
141+
run: |
142+
VERSION=$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json, sys; print(next(p["version"] for p in json.load(sys.stdin)["packages"] if p["name"] == "tui-syntax"))')
143+
for _ in {1..20}; do
144+
if cargo search tui-syntax --limit 1 | grep -q "tui-syntax = \"$VERSION\""; then
145+
exit 0
146+
fi
147+
sleep 15
148+
done
149+
echo "tui-syntax $VERSION did not appear in the crates.io index in time" >&2
150+
exit 1
130151
131152
- name: Publish tsql
132153
run: cargo publish -p tsql --token ${{ secrets.CARGO_REGISTRY_TOKEN }}

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = ["crates/*"]
44
exclude = ["vendor/tui-confirm-dialog"]
55

66
[workspace.package]
7-
version = "0.5.0"
7+
version = "0.6.0"
88
edition = "2021"
99
license = "MIT"
1010
repository = "https://github.com/fcoury/tsql"

cliff.toml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[changelog]
2+
header = """
3+
## What's Changed
4+
5+
"""
6+
body = """
7+
{% for group, commits in commits | group_by(attribute="group") %}
8+
### {{ group | upper_first }}
9+
{% for commit in commits %}
10+
{% set message = commit.message | split(pat="\n") | first | trim -%}
11+
{% if commit.remote.pr_number -%}
12+
{% set pr_suffix = " (#" ~ commit.remote.pr_number ~ ")" -%}
13+
{% set message = message | replace(from=pr_suffix, to="") -%}
14+
{% endif -%}
15+
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}{{ message | upper_first }}{% if commit.remote.username %} by @{{ commit.remote.username }}{% endif %}{% if commit.remote.pr_number %} in #{{ commit.remote.pr_number }}{% endif %}
16+
{%- endfor %}
17+
18+
{% endfor %}
19+
{% if github.contributors %}
20+
## Contributors
21+
{% for contributor in github.contributors %}
22+
- @{{ contributor.username }}{% if contributor.pr_number %} in #{{ contributor.pr_number }}{% endif %}{% if contributor.is_first_time %} (first-time contributor){% endif %}
23+
{%- endfor %}
24+
{% endif %}
25+
"""
26+
trim = true
27+
postprocessors = [
28+
{ pattern = ' by @[^[:space:]]+\[bot\]', replace = "" },
29+
{ pattern = '\n- @[^[:space:]]+\[bot\][^\n]*', replace = "" },
30+
]
31+
32+
[git]
33+
conventional_commits = true
34+
filter_unconventional = false
35+
split_commits = false
36+
tag_pattern = "v[0-9].*"
37+
sort_commits = "oldest"
38+
39+
commit_parsers = [
40+
{ message = "^feat", group = "Features" },
41+
{ message = "^fix", group = "Bug Fixes" },
42+
{ message = "^perf", group = "Performance" },
43+
{ message = "^refactor", group = "Refactoring" },
44+
{ message = "^docs", group = "Documentation" },
45+
{ message = "^test", group = "Tests" },
46+
{ message = "^chore", group = "Maintenance" },
47+
]

crates/tsql/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ tui_confirm_dialog_with_mouse.workspace = true
6767
tempfile.workspace = true
6868

6969
# Internal crate
70-
tui-syntax = { path = "../tui-syntax", version = "0.5.0" }
70+
tui-syntax = { path = "../tui-syntax", version = "0.6.0" }
7171

7272
[dev-dependencies]
7373
dotenvy.workspace = true

crates/tsql/src/config/connections.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,9 +1501,10 @@ pub fn import_from_path(
15011501
let mut summary = ImportSummary::default();
15021502

15031503
for mut entry in incoming.connections.into_iter() {
1504-
// Drop favorite slot to avoid collisions on import; the user can
1505-
// reassign after review.
1504+
// Drop source-file ordering metadata to avoid collisions on import;
1505+
// the user can reassign after review.
15061506
entry.favorite = None;
1507+
entry.order = 0;
15071508

15081509
if entry.validate().is_err() {
15091510
summary
@@ -1981,6 +1982,43 @@ user = "me"
19811982
assert_eq!(summary3.imported, 0);
19821983
}
19831984

1985+
#[test]
1986+
fn test_import_resets_source_order_before_appending() {
1987+
let tmp = tempfile::tempdir().unwrap();
1988+
let path = tmp.path().join("connections.toml");
1989+
1990+
let mut incoming = ConnectionsFile::new();
1991+
incoming.connections.push(ConnectionEntry {
1992+
name: "imported".to_string(),
1993+
host: "h".to_string(),
1994+
database: "d".to_string(),
1995+
user: "u".to_string(),
1996+
favorite: Some(1),
1997+
order: 1,
1998+
..Default::default()
1999+
});
2000+
write_connections_atomic(&path, &incoming).unwrap();
2001+
2002+
let mut target = ConnectionsFile::new();
2003+
target
2004+
.add(ConnectionEntry {
2005+
name: "existing".to_string(),
2006+
host: "h".to_string(),
2007+
database: "d".to_string(),
2008+
user: "u".to_string(),
2009+
order: 3,
2010+
..Default::default()
2011+
})
2012+
.unwrap();
2013+
2014+
let summary = import_from_path(&mut target, &path, ImportConflict::Rename).unwrap();
2015+
assert_eq!(summary.imported, 1);
2016+
2017+
let imported = target.find_by_name("imported").unwrap();
2018+
assert_eq!(imported.favorite, None);
2019+
assert_eq!(imported.order, 4);
2020+
}
2021+
19842022
#[test]
19852023
fn test_connection_to_url_without_password() {
19862024
let entry = ConnectionEntry {

crates/tsql/src/ui/connection_form.rs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,12 +1000,7 @@ impl ConnectionFormModal {
10001000
Some(self.folder.trim().to_string())
10011001
},
10021002
application_name: None,
1003-
connect_timeout_secs: self
1004-
.connect_timeout_secs
1005-
.trim()
1006-
.parse::<u64>()
1007-
.ok()
1008-
.filter(|v| *v > 0),
1003+
connect_timeout_secs: None,
10091004
..Default::default()
10101005
},
10111006
}
@@ -1028,6 +1023,11 @@ impl ConnectionFormModal {
10281023
self.user = entry.user;
10291024
self.ssl_mode = entry.ssl_mode.unwrap_or(SslMode::Disable);
10301025
self.ssl_mode_index = self.ssl_mode.to_index();
1026+
self.application_name = entry.application_name.unwrap_or_default();
1027+
self.connect_timeout_secs = entry
1028+
.connect_timeout_secs
1029+
.map(|secs| secs.to_string())
1030+
.unwrap_or_default();
10311031

10321032
if let Some(pwd) = password {
10331033
self.password = pwd;
@@ -1039,6 +1039,8 @@ impl ConnectionFormModal {
10391039
self.port_cursor = Self::char_count(&self.port);
10401040
self.database_cursor = Self::char_count(&self.database);
10411041
self.user_cursor = Self::char_count(&self.user);
1042+
self.application_name_cursor = Self::char_count(&self.application_name);
1043+
self.connect_timeout_cursor = Self::char_count(&self.connect_timeout_secs);
10421044

10431045
// Clear URL paste field
10441046
self.url_paste.clear();
@@ -2133,6 +2135,30 @@ mod tests {
21332135
assert!(form.url_paste.is_empty());
21342136
}
21352137

2138+
#[test]
2139+
fn test_url_paste_preserves_postgres_query_options_on_save() {
2140+
let mut form = ConnectionFormModal::new();
2141+
form.name = "prod".to_string();
2142+
form.focused = FormField::UrlPaste;
2143+
form.url_paste =
2144+
"postgres://admin@db.example.com/production?connect_timeout=5&application_name=tsql"
2145+
.to_string();
2146+
2147+
let action = form.handle_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
2148+
assert!(matches!(action, ConnectionFormAction::StatusMessage(_)));
2149+
assert_eq!(form.application_name, "tsql");
2150+
assert_eq!(form.connect_timeout_secs, "5");
2151+
2152+
let action = form.handle_key(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
2153+
match action {
2154+
ConnectionFormAction::Save { entry, .. } => {
2155+
assert_eq!(entry.application_name.as_deref(), Some("tsql"));
2156+
assert_eq!(entry.connect_timeout_secs, Some(5));
2157+
}
2158+
other => panic!("Expected Save action, got {:?}", other),
2159+
}
2160+
}
2161+
21362162
#[test]
21372163
fn test_url_paste_decodes_postgres_username() {
21382164
let mut form = ConnectionFormModal::new();
@@ -2193,6 +2219,27 @@ mod tests {
21932219
}
21942220
}
21952221

2222+
#[test]
2223+
fn test_save_mongodb_drops_postgres_only_timeout() {
2224+
let mut form = ConnectionFormModal::new();
2225+
form.name = "mongo1".to_string();
2226+
form.kind = DbKind::Mongo;
2227+
form.host = "mongo.example.com".to_string();
2228+
form.port = "27018".to_string();
2229+
form.database = "sample".to_string();
2230+
form.user = "admin".to_string();
2231+
form.connect_timeout_secs = "7".to_string();
2232+
2233+
let action = form.handle_key(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL));
2234+
match action {
2235+
ConnectionFormAction::Save { entry, .. } => {
2236+
assert_eq!(entry.kind, DbKind::Mongo);
2237+
assert_eq!(entry.connect_timeout_secs, None);
2238+
}
2239+
other => panic!("Expected Save action, got {:?}", other),
2240+
}
2241+
}
2242+
21962243
#[test]
21972244
fn test_test_connection_mongodb_includes_password_in_runtime_url() {
21982245
let mut form = ConnectionFormModal::new();

0 commit comments

Comments
 (0)