fix: single source of truth for version across crate and tests

Replace all hardcoded "1.1.0" version strings with env!("CARGO_PKG_VERSION")
in src/, so Cargo.toml is the sole source for the built binary. Tests
intentionally use a separate hardcoded constant in tests/common.rs to act
as a canary — they will fail on a version bump until manually updated.

- src/project/mod.rs: add WEEVIL_VERSION const, wire into Tera context,
  generated README, and .weevil-version marker
- tests/common.rs: new file, holds EXPECTED_VERSION for all test crates
- tests/{integration,project_lifecycle,unit/config_tests}.rs: pull from
  common instead of env! or inline literals
This commit is contained in:
Eric Ratliff
2026-01-31 14:17:51 -06:00
parent d2cc62e32f
commit 5596f5bade
10 changed files with 71 additions and 43 deletions

View File

@@ -1,8 +1,8 @@
[package] [package]
name = "weevil" name = "weevil"
version = "1.0.0" version = "1.1.0-beta.1"
edition = "2021" edition = "2021"
authors = ["Eric Ratliff <eric@intrepidfusion.com>"] authors = ["Eric Ratliff <eric@nxlearn.net>"]
description = "FTC robotics project generator - bores into complexity, emerges with clean code" description = "FTC robotics project generator - bores into complexity, emerges with clean code"
license = "MIT" license = "MIT"

View File

@@ -1,6 +1,7 @@
// File: src/lib.rs // File: src/lib.rs
// Library interface for testing // Library interface for testing
pub mod version;
pub mod sdk; pub mod sdk;
pub mod project; pub mod project;
pub mod commands; pub mod commands;

View File

@@ -1,6 +1,7 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use colored::*; use colored::*;
use anyhow::Result; use anyhow::Result;
use weevil::version::WEEVIL_VERSION;
mod commands; mod commands;
mod sdk; mod sdk;
@@ -9,9 +10,12 @@ mod templates;
#[derive(Parser)] #[derive(Parser)]
#[command(name = "weevil")] #[command(name = "weevil")]
#[command(author = "Eric Barch <eric@intrepidfusion.com>")] #[command(author = "Eric Ratliff <eric@nxlearn.net>")]
#[command(version = "1.0.0")] #[command(version = WEEVIL_VERSION)]
#[command(about = "FTC robotics project generator - bores into complexity, emerges with clean code", long_about = None)] #[command(
about = "FTC robotics project generator - bores into complexity, emerges with clean code",
long_about = None
)]
struct Cli { struct Cli {
#[command(subcommand)] #[command(subcommand)]
command: Commands, command: Commands,
@@ -134,13 +138,11 @@ fn main() -> Result<()> {
Commands::Deploy { path, usb, wifi, ip } => { Commands::Deploy { path, usb, wifi, ip } => {
commands::deploy::deploy_project(&path, usb, wifi, ip.as_deref()) commands::deploy::deploy_project(&path, usb, wifi, ip.as_deref())
} }
Commands::Sdk { command } => { Commands::Sdk { command } => match command {
match command { SdkCommands::Install => commands::sdk::install_sdks(),
SdkCommands::Install => commands::sdk::install_sdks(), SdkCommands::Status => commands::sdk::show_status(),
SdkCommands::Status => commands::sdk::show_status(), SdkCommands::Update => commands::sdk::update_sdks(),
SdkCommands::Update => commands::sdk::update_sdks(), },
}
}
Commands::Config { path, set_sdk } => { Commands::Config { path, set_sdk } => {
if let Some(sdk_path) = set_sdk { if let Some(sdk_path) = set_sdk {
commands::config::set_sdk(&path, &sdk_path) commands::config::set_sdk(&path, &sdk_path)
@@ -153,7 +155,12 @@ fn main() -> Result<()> {
fn print_banner() { fn print_banner() {
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
println!("{}", " 🪲 Weevil - FTC Project Generator v1.0.0".bright_cyan().bold()); println!(
"{}",
format!(" 🪲 Weevil - FTC Project Generator v{}", WEEVIL_VERSION)
.bright_cyan()
.bold()
);
println!("{}", " Nexus Workshops LLC".bright_cyan()); println!("{}", " Nexus Workshops LLC".bright_cyan());
println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan()); println!("{}", "═══════════════════════════════════════════════════════════".bright_cyan());
println!(); println!();

View File

@@ -3,6 +3,8 @@ use std::path::{Path, PathBuf};
use std::fs; use std::fs;
use anyhow::{Result, Context, bail}; use anyhow::{Result, Context, bail};
const WEEVIL_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ProjectConfig { pub struct ProjectConfig {
pub project_name: String, pub project_name: String,
@@ -24,7 +26,7 @@ impl ProjectConfig {
Ok(Self { Ok(Self {
project_name: project_name.to_string(), project_name: project_name.to_string(),
weevil_version: "1.0.0".to_string(), weevil_version: WEEVIL_VERSION.to_string(),
ftc_sdk_path, ftc_sdk_path,
ftc_sdk_version, ftc_sdk_version,
android_sdk_path, android_sdk_path,

View File

@@ -7,6 +7,8 @@ use git2::Repository;
use crate::sdk::SdkConfig; use crate::sdk::SdkConfig;
const WEEVIL_VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod deployer; pub mod deployer;
pub mod config; pub mod config;
@@ -68,7 +70,7 @@ impl ProjectBuilder {
let mut _context = TeraContext::new(); let mut _context = TeraContext::new();
_context.insert("project_name", &self.name); _context.insert("project_name", &self.name);
_context.insert("sdk_dir", &sdk_config.ftc_sdk_path.to_string_lossy()); _context.insert("sdk_dir", &sdk_config.ftc_sdk_path.to_string_lossy());
_context.insert("generator_version", "1.0.0"); _context.insert("generator_version", WEEVIL_VERSION);
self.create_project_files(project_path, sdk_config)?; self.create_project_files(project_path, sdk_config)?;
@@ -84,7 +86,7 @@ impl ProjectBuilder {
let readme = format!( let readme = format!(
r#"# {} r#"# {}
FTC Robot Project generated by Weevil v1.0.0 FTC Robot Project generated by Weevil v{}
## Quick Start ## Quick Start
```bash ```bash
@@ -111,7 +113,7 @@ deploy.bat
2. Test locally: `./gradlew test` 2. Test locally: `./gradlew test`
3. Deploy: `./deploy.sh` (or `deploy.bat` on Windows) 3. Deploy: `./deploy.sh` (or `deploy.bat` on Windows)
"#, "#,
self.name self.name, WEEVIL_VERSION
); );
fs::write(project_path.join("README.md"), readme)?; fs::write(project_path.join("README.md"), readme)?;
@@ -120,7 +122,7 @@ deploy.bat
fs::write(project_path.join(".gitignore"), gitignore)?; fs::write(project_path.join(".gitignore"), gitignore)?;
// Version marker // Version marker
fs::write(project_path.join(".weevil-version"), "1.0.0")?; fs::write(project_path.join(".weevil-version"), WEEVIL_VERSION)?;
// build.gradle.kts - Pure Java with deployToSDK task // build.gradle.kts - Pure Java with deployToSDK task
// Escape backslashes for Windows paths in Kotlin strings // Escape backslashes for Windows paths in Kotlin strings

1
src/version.rs Normal file
View File

@@ -0,0 +1 @@
pub const WEEVIL_VERSION: &str = env!("CARGO_PKG_VERSION");

3
tests/common.rs Normal file
View File

@@ -0,0 +1,3 @@
// Intentionally hardcoded. When you bump the version in Cargo.toml,
// tests will fail here until you update this to match.
pub const EXPECTED_VERSION: &str = "1.1.0-beta.1";

View File

@@ -2,6 +2,10 @@ use assert_cmd::prelude::*;
use predicates::prelude::*; use predicates::prelude::*;
use std::process::Command; use std::process::Command;
#[path = "common.rs"]
mod common;
use common::EXPECTED_VERSION;
#[path = "integration/environment_tests.rs"] #[path = "integration/environment_tests.rs"]
mod environment_tests; mod environment_tests;
@@ -25,7 +29,7 @@ fn test_version_command() {
cmd.assert() cmd.assert()
.success() .success()
.stdout(predicate::str::contains("1.0.0")); .stdout(predicate::str::contains(EXPECTED_VERSION));
} }
#[test] #[test]

View File

@@ -6,6 +6,10 @@ use std::fs;
use weevil::project::{ProjectBuilder, ProjectConfig}; use weevil::project::{ProjectBuilder, ProjectConfig};
use weevil::sdk::SdkConfig; use weevil::sdk::SdkConfig;
#[path = "common.rs"]
mod common;
use common::EXPECTED_VERSION;
// Note: These tests use the actual FTC SDK if available, or skip if not // Note: These tests use the actual FTC SDK if available, or skip if not
// For true unit testing with mocks, we'd need to refactor to use dependency injection // For true unit testing with mocks, we'd need to refactor to use dependency injection
@@ -26,7 +30,7 @@ fn test_config_create_and_save() {
assert_eq!(config.project_name, "test-robot"); assert_eq!(config.project_name, "test-robot");
assert_eq!(config.ftc_sdk_path, sdk_path); assert_eq!(config.ftc_sdk_path, sdk_path);
assert_eq!(config.android_sdk_path, android_sdk_path); assert_eq!(config.android_sdk_path, android_sdk_path);
assert_eq!(config.weevil_version, "1.0.0"); assert_eq!(config.weevil_version, EXPECTED_VERSION);
// Save and reload // Save and reload
let project_path = temp_dir.path().join("project"); let project_path = temp_dir.path().join("project");
@@ -60,7 +64,7 @@ fn test_config_toml_format() {
let content = fs::read_to_string(project_path.join(".weevil.toml")).unwrap(); let content = fs::read_to_string(project_path.join(".weevil.toml")).unwrap();
assert!(content.contains("project_name = \"my-robot\"")); assert!(content.contains("project_name = \"my-robot\""));
assert!(content.contains("weevil_version = \"1.0.0\"")); assert!(content.contains(&format!("weevil_version = \"{}\"", EXPECTED_VERSION)));
assert!(content.contains("ftc_sdk_path")); assert!(content.contains("ftc_sdk_path"));
assert!(content.contains("ftc_sdk_version")); assert!(content.contains("ftc_sdk_version"));
assert!(content.contains("android_sdk_path")); assert!(content.contains("android_sdk_path"));

View File

@@ -6,6 +6,10 @@ use std::path::PathBuf;
use tempfile::TempDir; use tempfile::TempDir;
use std::fs; use std::fs;
#[path = "../common.rs"]
mod common;
use common::EXPECTED_VERSION;
#[test] #[test]
fn test_config_create_and_save() { fn test_config_create_and_save() {
let temp_dir = TempDir::new().unwrap(); let temp_dir = TempDir::new().unwrap();
@@ -15,7 +19,7 @@ fn test_config_create_and_save() {
assert_eq!(config.project_name, "test-robot"); assert_eq!(config.project_name, "test-robot");
assert_eq!(config.ftc_sdk_path, sdk_path); assert_eq!(config.ftc_sdk_path, sdk_path);
assert_eq!(config.weevil_version, "1.0.0"); assert_eq!(config.weevil_version, EXPECTED_VERSION);
// Save and reload // Save and reload
config.save(temp_dir.path()).unwrap(); config.save(temp_dir.path()).unwrap();
@@ -45,7 +49,7 @@ fn test_config_toml_format() {
let content = fs::read_to_string(temp_dir.path().join(".weevil.toml")).unwrap(); let content = fs::read_to_string(temp_dir.path().join(".weevil.toml")).unwrap();
assert!(content.contains("project_name = \"my-robot\"")); assert!(content.contains("project_name = \"my-robot\""));
assert!(content.contains("weevil_version = \"1.0.0\"")); assert!(content.contains(&format!("weevil_version = \"{}\"", EXPECTED_VERSION)));
assert!(content.contains("ftc_sdk_path")); assert!(content.contains("ftc_sdk_path"));
} }