diff --git a/Cargo.lock b/Cargo.lock index 2e6dc2da28..e42a889e24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,14 +60,14 @@ dependencies = [ [[package]] name = "alacritty_terminal" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bitflags 2.9.0", "camino", "serde", "serde_json", "serde_yaml", - "shell-color 1.10.0", + "shell-color 1.10.1", "tracing", "unicode-width 0.2.0", "vte 0.15.0", @@ -259,7 +259,7 @@ checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "appkit-nsworkspace-bindings" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bindgen 0.71.1", "objc", @@ -1495,7 +1495,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chat_cli" -version = "1.10.0" +version = "1.10.1" dependencies = [ "amzn-codewhisperer-client", "amzn-codewhisperer-streaming-client", @@ -2246,7 +2246,7 @@ checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "dbus" -version = "1.10.0" +version = "1.10.1" dependencies = [ "async_zip", "fig_os_shim", @@ -2793,7 +2793,7 @@ dependencies = [ [[package]] name = "fig-api-mock" -version = "1.10.0" +version = "1.10.1" dependencies = [ "async-trait", "clap", @@ -2806,7 +2806,7 @@ dependencies = [ [[package]] name = "fig_api_client" -version = "1.10.0" +version = "1.10.1" dependencies = [ "amzn-codewhisperer-client", "amzn-codewhisperer-streaming-client", @@ -2841,7 +2841,7 @@ dependencies = [ [[package]] name = "fig_auth" -version = "1.10.0" +version = "1.10.1" dependencies = [ "async-trait", "aws-credential-types", @@ -2875,7 +2875,7 @@ dependencies = [ [[package]] name = "fig_aws_common" -version = "1.10.0" +version = "1.10.1" dependencies = [ "aws-runtime", "aws-smithy-runtime", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "fig_desktop" -version = "1.10.0" +version = "1.10.1" dependencies = [ "accessibility-sys", "anyhow", @@ -2981,7 +2981,7 @@ dependencies = [ [[package]] name = "fig_desktop_api" -version = "1.10.0" +version = "1.10.1" dependencies = [ "anstream", "async-trait", @@ -3018,7 +3018,7 @@ dependencies = [ [[package]] name = "fig_diagnostic" -version = "1.10.0" +version = "1.10.1" dependencies = [ "cfg-if", "clap", @@ -3038,7 +3038,7 @@ dependencies = [ [[package]] name = "fig_input_method" -version = "1.10.0" +version = "1.10.1" dependencies = [ "apple-bundle", "fig_ipc", @@ -3059,7 +3059,7 @@ dependencies = [ [[package]] name = "fig_install" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bitflags 2.9.0", "bytes", @@ -3098,7 +3098,7 @@ dependencies = [ [[package]] name = "fig_integrations" -version = "1.10.0" +version = "1.10.1" dependencies = [ "accessibility-sys", "async-trait", @@ -3131,7 +3131,7 @@ dependencies = [ [[package]] name = "fig_ipc" -version = "1.10.0" +version = "1.10.1" dependencies = [ "async-trait", "base64 0.22.1", @@ -3154,7 +3154,7 @@ dependencies = [ [[package]] name = "fig_log" -version = "1.10.0" +version = "1.10.1" dependencies = [ "fig_util", "parking_lot", @@ -3167,7 +3167,7 @@ dependencies = [ [[package]] name = "fig_os_shim" -version = "1.10.0" +version = "1.10.1" dependencies = [ "cfg-if", "dirs 5.0.1", @@ -3180,7 +3180,7 @@ dependencies = [ [[package]] name = "fig_proto" -version = "1.10.0" +version = "1.10.1" dependencies = [ "arbitrary", "bytes", @@ -3203,7 +3203,7 @@ dependencies = [ [[package]] name = "fig_remote_ipc" -version = "1.10.0" +version = "1.10.1" dependencies = [ "anyhow", "async-trait", @@ -3221,7 +3221,7 @@ dependencies = [ [[package]] name = "fig_request" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bytes", "cfg-if", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "fig_settings" -version = "1.10.0" +version = "1.10.1" dependencies = [ "fd-lock", "fig_test", @@ -3267,7 +3267,7 @@ dependencies = [ [[package]] name = "fig_telemetry" -version = "1.10.0" +version = "1.10.1" dependencies = [ "amzn-codewhisperer-client", "amzn-toolkit-telemetry-client", @@ -3301,7 +3301,7 @@ dependencies = [ [[package]] name = "fig_telemetry_core" -version = "1.10.0" +version = "1.10.1" dependencies = [ "amzn-codewhisperer-client", "amzn-toolkit-telemetry-client", @@ -3315,7 +3315,7 @@ dependencies = [ [[package]] name = "fig_test" -version = "1.10.0" +version = "1.10.1" dependencies = [ "fig_test_macro", "tokio", @@ -3323,7 +3323,7 @@ dependencies = [ [[package]] name = "fig_test_macro" -version = "1.10.0" +version = "1.10.1" dependencies = [ "quote", "syn 2.0.101", @@ -3331,7 +3331,7 @@ dependencies = [ [[package]] name = "fig_test_utils" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bytes", "fig_os_shim", @@ -3349,7 +3349,7 @@ dependencies = [ [[package]] name = "fig_util" -version = "1.10.0" +version = "1.10.1" dependencies = [ "appkit-nsworkspace-bindings", "bstr", @@ -3389,7 +3389,7 @@ dependencies = [ [[package]] name = "figterm" -version = "1.10.0" +version = "1.10.1" dependencies = [ "alacritty_terminal", "anyhow", @@ -3434,7 +3434,7 @@ dependencies = [ "serde", "serde_json", "shared_library", - "shell-color 1.10.0", + "shell-color 1.10.1", "shell-words", "shellexpand", "shlex", @@ -3453,7 +3453,7 @@ dependencies = [ [[package]] name = "figterm2" -version = "1.10.0" +version = "1.10.1" dependencies = [ "anyhow", "async-trait", @@ -5170,7 +5170,7 @@ dependencies = [ [[package]] name = "macos-utils" -version = "1.10.0" +version = "1.10.1" dependencies = [ "accessibility", "accessibility-sys", @@ -6997,7 +6997,7 @@ dependencies = [ [[package]] name = "q_cli" -version = "1.10.0" +version = "1.10.1" dependencies = [ "amzn-codewhisperer-client", "amzn-codewhisperer-streaming-client", @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "shell-color" -version = "1.10.0" +version = "1.10.1" dependencies = [ "bitflags 2.9.0", "fig_test", diff --git a/Cargo.toml b/Cargo.toml index 011326081c..2bb7a35922 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ authors = [ edition = "2021" homepage = "https://aws.amazon.com/q/" publish = false -version = "1.10.0" +version = "1.10.1" license = "MIT OR Apache-2.0" [workspace.dependencies] diff --git a/crates/chat-cli/src/cli/chat/mod.rs b/crates/chat-cli/src/cli/chat/mod.rs index c4378c4ff3..9645732e02 100644 --- a/crates/chat-cli/src/cli/chat/mod.rs +++ b/crates/chat-cli/src/cli/chat/mod.rs @@ -160,7 +160,7 @@ use crate::mcp_client::{ use crate::platform::Context; use crate::telemetry::TelemetryThread; use crate::telemetry::core::ToolUseEventBuilder; -use crate::util::CHAT_BINARY_NAME; +use crate::util::CLI_BINARY_NAME; /// Help text for the compact command fn compact_help_text() -> String { @@ -324,7 +324,7 @@ pub async fn chat( if !crate::util::system_info::in_cloudshell() && !crate::auth::is_logged_in(database).await { bail!( "You are not logged in, please log in with {}", - format!("{CHAT_BINARY_NAME} login").bold() + format!("{CLI_BINARY_NAME} login").bold() ); } diff --git a/crates/chat-cli/src/cli/user.rs b/crates/chat-cli/src/cli/user.rs index 07cdacf10b..0f34a2213a 100644 --- a/crates/chat-cli/src/cli/user.rs +++ b/crates/chat-cli/src/cli/user.rs @@ -49,7 +49,7 @@ use crate::util::spinner::{ }; use crate::util::system_info::is_remote; use crate::util::{ - CHAT_BINARY_NAME, + CLI_BINARY_NAME, PRODUCT_NAME, choose, input, @@ -123,7 +123,7 @@ impl UserSubcommand { if crate::auth::is_logged_in(database).await { eyre::bail!( "Already logged in, please logout with {} first", - format!("{CHAT_BINARY_NAME} logout").magenta() + format!("{CLI_BINARY_NAME} logout").magenta() ); } @@ -137,7 +137,7 @@ impl UserSubcommand { println!("You are now logged out"); println!( "Run {} to log back in to {PRODUCT_NAME}", - format!("{CHAT_BINARY_NAME} login").magenta() + format!("{CLI_BINARY_NAME} login").magenta() ); Ok(ExitCode::SUCCESS) }, @@ -189,7 +189,7 @@ impl UserSubcommand { if !crate::util::system_info::in_cloudshell() && !crate::auth::is_logged_in(database).await { bail!( "You are not logged in, please log in with {}", - format!("{CHAT_BINARY_NAME} login").bold() + format!("{CLI_BINARY_NAME} login").bold() ); } diff --git a/crates/chat-cli/src/database/mod.rs b/crates/chat-cli/src/database/mod.rs index dfa62d85dc..426913cbf4 100644 --- a/crates/chat-cli/src/database/mod.rs +++ b/crates/chat-cli/src/database/mod.rs @@ -352,15 +352,12 @@ impl Database { let mut conn = self.pool.get()?; let transaction = conn.transaction()?; - // select the max migration id - let max_id = max_migration(&transaction); + let max_version = max_migration_version(&transaction); for (version, migration) in MIGRATIONS.iter().enumerate() { - // skip migrations that already exist - match max_id { - Some(max_id) if max_id >= version as i64 => continue, - _ => (), - }; + if has_migration(&transaction, version, max_version)? { + continue; + } // execute the migration transaction.execute_batch(migration.sql)?; @@ -443,11 +440,51 @@ impl Database { } } -fn max_migration>(conn: &C) -> Option { - let mut stmt = conn.prepare("SELECT MAX(id) FROM migrations").ok()?; +fn max_migration_version>(conn: &C) -> Option { + let mut stmt = conn.prepare("SELECT MAX(version) FROM migrations").ok()?; stmt.query_row([], |row| row.get(0)).ok() } +fn has_migration>( + conn: &C, + version: usize, + max_version: Option, +) -> Result { + // IMPORTANT: Due to a bug with the first 7 migrations, we have to check manually + // + // Background: the migrations table stores two identifying keys: the sqlite auto-generated + // auto-incrementing key `id`, and the `version` which is the index of the `MIGRATIONS` + // constant. + // + // Checking whether a migration exists would compare id with version, but since id is 1-indexed + // and version is 0-indexed, we would actually skip the last migration! Therefore, it's + // possible users are missing a critical migration (namely, auth_kv table creation) when + // upgrading to the qchat build (which includes two new migrations). Hence, we have to check + // all migrations until version 7 to make sure that nothing is missed. + if version <= 7 { + let mut stmt = match conn.prepare("SELECT COUNT(*) FROM migrations WHERE version = ?1") { + Ok(stmt) => stmt, + // If the migrations table does not exist, then we can reasonably say no migrations + // will exist. + Err(Error::SqliteFailure(_, Some(msg))) if msg.contains("no such table") => { + return Ok(false); + }, + Err(err) => return Err(err.into()), + }; + let count: i32 = stmt.query_row([version], |row| row.get(0))?; + return Ok(count >= 1); + } + + // Continuing from the previously implemented logic - any migrations after the 7th can have a simple + // maximum version check, since we can reasonably assume if any version >=7 will have all + // migrations prior to it. + #[allow(clippy::match_like_matches_macro)] + Ok(match max_version { + Some(max_version) if max_version >= version as i64 => true, + _ => false, + }) +} + #[cfg(test)] mod tests { use super::*; @@ -477,7 +514,7 @@ mod tests { let db = Database::new().await.unwrap(); // assert migration count is correct - let max_migration = max_migration(&&*db.pool.get().unwrap()); + let max_migration = max_migration_version(&&*db.pool.get().unwrap()); assert_eq!(max_migration, Some(MIGRATIONS.len() as i64)); } diff --git a/crates/chat-cli/src/util/consts.rs b/crates/chat-cli/src/util/consts.rs index d58d7bb527..ea7a3d4058 100644 --- a/crates/chat-cli/src/util/consts.rs +++ b/crates/chat-cli/src/util/consts.rs @@ -1,6 +1,8 @@ #[cfg(windows)] pub const APP_PROCESS_NAME: &str = "q_desktop.exe"; +/// TODO(brandonskiser): revert back to "qchat" for prompting login after standalone releases. +pub const CLI_BINARY_NAME: &str = "q"; pub const CHAT_BINARY_NAME: &str = "qchat"; pub const PRODUCT_NAME: &str = "Amazon Q"; diff --git a/crates/fig_settings/src/sqlite/mod.rs b/crates/fig_settings/src/sqlite/mod.rs index 0e89697f19..7b08d531e7 100644 --- a/crates/fig_settings/src/sqlite/mod.rs +++ b/crates/fig_settings/src/sqlite/mod.rs @@ -119,15 +119,12 @@ impl Db { let mut conn = self.pool.get()?; let transaction = conn.transaction()?; - // select the max migration id - let max_id = max_migration(&transaction); + let max_version = max_migration_version(&transaction); for (version, migration) in MIGRATIONS.iter().enumerate() { - // skip migrations that already exist - match max_id { - Some(max_id) if max_id >= version as i64 => continue, - _ => (), - }; + if has_migration(&transaction, version, max_version)? { + continue; + } // execute the migration transaction.execute_batch(migration.sql)?; @@ -289,11 +286,47 @@ impl Db { } } -fn max_migration>(conn: &C) -> Option { - let mut stmt = conn.prepare("SELECT MAX(id) FROM migrations").ok()?; +fn max_migration_version>(conn: &C) -> Option { + let mut stmt = conn.prepare("SELECT MAX(version) FROM migrations").ok()?; stmt.query_row([], |row| row.get(0)).ok() } +fn has_migration>(conn: &C, version: usize, max_version: Option) -> Result { + // IMPORTANT: Due to a bug with the first 7 migrations, we have to check manually + // + // Background: the migrations table stores two identifying keys: the sqlite auto-generated + // auto-incrementing key `id`, and the `version` which is the index of the `MIGRATIONS` + // constant. + // + // Checking whether a migration exists would compare id with version, but since id is 1-indexed + // and version is 0-indexed, we would actually skip the last migration! Therefore, it's + // possible users are missing a critical migration (namely, auth_kv table creation) when + // upgrading to the qchat build (which includes two new migrations). Hence, we have to check + // all migrations until version 7 to make sure that nothing is missed. + if version <= 7 { + let mut stmt = match conn.prepare("SELECT COUNT(*) FROM migrations WHERE version = ?1") { + Ok(stmt) => stmt, + // If the migrations table does not exist, then we can reasonably say no migrations + // will exist. + Err(Error::SqliteFailure(_, Some(msg))) if msg.contains("no such table") => { + return Ok(false); + }, + Err(err) => return Err(err.into()), + }; + let count: i32 = stmt.query_row([version], |row| row.get(0))?; + return Ok(count >= 1); + } + + // Continuing from the previously implemented logic - any migrations after the 7th can have a simple + // maximum version check, since we can reasonably assume if any version >=7 will have all + // migrations prior to it. + #[allow(clippy::match_like_matches_macro)] + Ok(match max_version { + Some(max_version) if max_version >= version as i64 => true, + _ => false, + }) +} + #[cfg(test)] mod tests { use super::*; @@ -309,7 +342,7 @@ mod tests { let db = mock(); // assert migration count is correct - let max_migration = max_migration(&&*db.pool.get().unwrap()); + let max_migration = max_migration_version(&&*db.pool.get().unwrap()); assert_eq!(max_migration, Some(MIGRATIONS.len() as i64)); } diff --git a/crates/q_cli/src/cli/mod.rs b/crates/q_cli/src/cli/mod.rs index f31d765347..f26a07baa2 100644 --- a/crates/q_cli/src/cli/mod.rs +++ b/crates/q_cli/src/cli/mod.rs @@ -70,6 +70,7 @@ use tokio::signal::ctrl_c; use tracing::{ Level, debug, + error, }; use self::integrations::IntegrationsSubcommands; @@ -381,10 +382,13 @@ impl Cli { let secret_store = SecretStore::new().await.ok(); if let Some(secret_store) = secret_store { - if let Ok(database) = database() { + if let Ok(database) = database().map_err(|err| error!(?err, "failed to open database")) { if let Ok(token) = BuilderIdToken::load(&secret_store, false).await { if let Ok(token) = serde_json::to_string(&token) { - database.set_auth_value("codewhisperer:odic:token", token).ok(); + database + .set_auth_value("codewhisperer:odic:token", token) + .map_err(|err| error!(?err, "failed to write credentials to auth db")) + .ok(); } } } diff --git a/feed.json b/feed.json index e1028d1b1e..5023b37a3f 100644 --- a/feed.json +++ b/feed.json @@ -10,6 +10,18 @@ "hidden": true, "changes": [] }, + { + "type": "release", + "date": "2025-05-16", + "version": "1.10.1", + "title": "Version 1.10.1", + "changes": [ + { + "type": "fixed", + "description": "An issue where `q chat` would prompt for login while already logged in - [#1862](https://github.com/aws/amazon-q-developer-cli/pull/1862)" + } + ] + }, { "type": "release", "date": "2025-05-15",