Skip to content

Commit 798e737

Browse files
committed
fix: add config migration for [stats] section and bump to 0.2.1
v0.2.0 shipped the required [stats] config field without migration logic, so any pre-existing clawshell.toml fails to parse on upgrade. - New to_v0_2_1 migration step injects [stats] with persist_path = "/etc/clawshell/stats.json" when the section is absent. - Version gate (from < 0.2.1) catches both 0.1.x and 0.2.0 configs. - Bump version to 0.2.1 across Cargo.toml, example config, README, and all npm packages.
1 parent 87955a4 commit 798e737

10 files changed

Lines changed: 166 additions & 11 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
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
@@ -1,6 +1,6 @@
11
[package]
22
name = "clawshell"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
edition = "2024"
55
license-file = "LICENSE"
66
homepage = "https://clawshell.org/"

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ sudo clawshell config --edit # open in $EDITOR
210210
A minimal config looks like this:
211211

212212
```toml
213-
version = "0.2.0"
213+
version = "0.2.1"
214214
log_level = "info"
215215

216216
[server]

‎clawshell.example.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# ClawShell Configuration
2-
version = "0.2.0"
2+
version = "0.2.1"
33

44
# Log level: trace, debug, info, warn, error
55
log_level = "info"

‎npm/clawshell-darwin-arm64/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clawshell/clawshell-darwin-arm64",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "ClawShell binary for macOS ARM64 (Apple Silicon)",
55
"license": "Apache-2.0",
66
"repository": {

‎npm/clawshell-linux-arm64/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clawshell/clawshell-linux-arm64",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "ClawShell binary for Linux ARM64",
55
"license": "Apache-2.0",
66
"repository": {

‎npm/clawshell-linux-x64/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clawshell/clawshell-linux-x64",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "ClawShell binary for Linux x64",
55
"license": "Apache-2.0",
66
"repository": {

‎npm/clawshell/package.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clawshell/clawshell",
3-
"version": "0.2.0",
3+
"version": "0.2.1",
44
"description": "The essential safety harness for OpenClaw's PII & API data",
55
"license": "Apache-2.0",
66
"repository": {
@@ -12,9 +12,9 @@
1212
"clawshell": "bin/clawshell.js"
1313
},
1414
"optionalDependencies": {
15-
"@clawshell/clawshell-darwin-arm64": "0.2.0",
16-
"@clawshell/clawshell-linux-arm64": "0.2.0",
17-
"@clawshell/clawshell-linux-x64": "0.2.0"
15+
"@clawshell/clawshell-darwin-arm64": "0.2.1",
16+
"@clawshell/clawshell-linux-arm64": "0.2.1",
17+
"@clawshell/clawshell-linux-x64": "0.2.1"
1818
},
1919
"engines": {
2020
"node": ">=16"

‎src/migration/targets/clawshell_toml/versions/mod.rs‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod to_v0_1_0_alpha_0;
2+
mod to_v0_2_1;
23

34
use crate::migration::core::{AmbiguityResolver, ConfigVersion};
45
use crate::migration::target::TargetError;
@@ -33,6 +34,13 @@ pub fn apply_versioned_steps(
3334
output.merge(step_output);
3435
}
3536

37+
let v0_2_1: ConfigVersion = "0.2.1".parse().expect("literal config version must parse");
38+
39+
if from < &v0_2_1 && to >= &v0_2_1 {
40+
let step_output = to_v0_2_1::apply(target_name, table, resolver)?;
41+
output.merge(step_output);
42+
}
43+
3644
Ok(output)
3745
}
3846

@@ -105,4 +113,58 @@ openai_base_url = "https://api.openai.com"
105113
assert!(output.applied_steps.is_empty());
106114
assert!(output.warnings.is_empty());
107115
}
116+
117+
#[test]
118+
fn test_apply_versioned_steps_adds_stats_from_0_1_1() {
119+
let mut table: toml::value::Table = toml::from_str(
120+
r#"
121+
version = "0.1.1"
122+
123+
[server]
124+
host = "127.0.0.1"
125+
[upstream]
126+
openai_base_url = "https://api.openai.com"
127+
"#,
128+
)
129+
.unwrap();
130+
131+
let from: ConfigVersion = "0.1.1".parse().unwrap();
132+
let to: ConfigVersion = "0.2.1".parse().unwrap();
133+
134+
let mut resolver = NoopResolver;
135+
let output = apply_versioned_steps("clawshell", &mut table, &from, &to, &mut resolver)
136+
.expect("migration should succeed");
137+
138+
assert!(output.applied_steps.iter().any(|s| s.contains("[stats]")));
139+
let stats = table.get("stats").unwrap().as_table().unwrap();
140+
assert_eq!(
141+
stats.get("persist_path").unwrap().as_str().unwrap(),
142+
"/etc/clawshell/stats.json"
143+
);
144+
}
145+
146+
#[test]
147+
fn test_apply_versioned_steps_adds_stats_from_0_2_0() {
148+
let mut table: toml::value::Table = toml::from_str(
149+
r#"
150+
version = "0.2.0"
151+
152+
[server]
153+
host = "127.0.0.1"
154+
[upstream]
155+
openai_base_url = "https://api.openai.com"
156+
"#,
157+
)
158+
.unwrap();
159+
160+
let from: ConfigVersion = "0.2.0".parse().unwrap();
161+
let to: ConfigVersion = "0.2.1".parse().unwrap();
162+
163+
let mut resolver = NoopResolver;
164+
let output = apply_versioned_steps("clawshell", &mut table, &from, &to, &mut resolver)
165+
.expect("migration should succeed");
166+
167+
assert!(output.applied_steps.iter().any(|s| s.contains("[stats]")));
168+
assert!(table.contains_key("stats"));
169+
}
108170
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use crate::migration::core::AmbiguityResolver;
2+
use crate::migration::target::TargetError;
3+
4+
use super::VersionStepOutput;
5+
6+
const STEP_ID: &str = "add `[stats]` section with default persist_path";
7+
8+
pub fn apply(
9+
_target_name: &str,
10+
table: &mut toml::value::Table,
11+
_resolver: &mut dyn AmbiguityResolver,
12+
) -> Result<VersionStepOutput, TargetError> {
13+
let mut output = VersionStepOutput::default();
14+
15+
if !table.contains_key("stats") {
16+
let mut stats = toml::value::Table::new();
17+
stats.insert(
18+
"persist_path".to_string(),
19+
toml::Value::String("/etc/clawshell/stats.json".to_string()),
20+
);
21+
table.insert("stats".to_string(), toml::Value::Table(stats));
22+
output.applied_steps.push(STEP_ID.to_string());
23+
}
24+
25+
Ok(output)
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use super::*;
31+
use crate::migration::core::{AmbiguityResolutionError, AmbiguousChoice, MigrationIssue};
32+
33+
#[derive(Debug)]
34+
struct NoopResolver;
35+
36+
impl AmbiguityResolver for NoopResolver {
37+
fn resolve(
38+
&mut self,
39+
_issue: &MigrationIssue,
40+
) -> Result<AmbiguousChoice, AmbiguityResolutionError> {
41+
Ok(AmbiguousChoice::ApplyRecommended)
42+
}
43+
}
44+
45+
#[test]
46+
fn test_injects_stats_when_missing() {
47+
let mut table: toml::value::Table = toml::from_str(
48+
r#"
49+
[server]
50+
host = "127.0.0.1"
51+
[upstream]
52+
openai_base_url = "https://api.openai.com"
53+
"#,
54+
)
55+
.unwrap();
56+
57+
let mut resolver = NoopResolver;
58+
let output = apply("clawshell", &mut table, &mut resolver).unwrap();
59+
assert_eq!(output.applied_steps.len(), 1);
60+
assert!(output.applied_steps[0].contains("[stats]"));
61+
62+
let stats = table.get("stats").unwrap().as_table().unwrap();
63+
assert_eq!(
64+
stats.get("persist_path").unwrap().as_str().unwrap(),
65+
"/etc/clawshell/stats.json"
66+
);
67+
}
68+
69+
#[test]
70+
fn test_skips_when_stats_already_present() {
71+
let mut table: toml::value::Table = toml::from_str(
72+
r#"
73+
[server]
74+
host = "127.0.0.1"
75+
[upstream]
76+
openai_base_url = "https://api.openai.com"
77+
[stats]
78+
persist_path = "/custom/path/stats.json"
79+
"#,
80+
)
81+
.unwrap();
82+
83+
let mut resolver = NoopResolver;
84+
let output = apply("clawshell", &mut table, &mut resolver).unwrap();
85+
assert!(output.applied_steps.is_empty());
86+
87+
let stats = table.get("stats").unwrap().as_table().unwrap();
88+
assert_eq!(
89+
stats.get("persist_path").unwrap().as_str().unwrap(),
90+
"/custom/path/stats.json"
91+
);
92+
}
93+
}

0 commit comments

Comments
 (0)