making linters happy
Some checks failed
CI / Rust Blocking Checks (push) Failing after 22s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 9s
CI / Web Advisory Checks (push) Successful in 32s
CI / Security Advisory Checks (push) Has been cancelled
Some checks failed
CI / Rust Blocking Checks (push) Failing after 22s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 9s
CI / Web Advisory Checks (push) Successful in 32s
CI / Security Advisory Checks (push) Has been cancelled
This commit is contained in:
@@ -107,7 +107,7 @@ impl HistoryQueryParams {
|
|||||||
pub fn to_repo_params(
|
pub fn to_repo_params(
|
||||||
&self,
|
&self,
|
||||||
) -> attune_common::repositories::entity_history::HistoryQueryParams {
|
) -> attune_common::repositories::entity_history::HistoryQueryParams {
|
||||||
let limit = (self.page_size.min(1000).max(1)) as i64;
|
let limit = (self.page_size.clamp(1, 1000)) as i64;
|
||||||
let offset = ((self.page.saturating_sub(1)) as i64) * limit;
|
let offset = ((self.page.saturating_sub(1)) as i64) * limit;
|
||||||
|
|
||||||
attune_common::repositories::entity_history::HistoryQueryParams {
|
attune_common::repositories::entity_history::HistoryQueryParams {
|
||||||
|
|||||||
@@ -160,7 +160,10 @@ pub async fn create_action(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if action with same ref already exists
|
// Check if action with same ref already exists
|
||||||
if let Some(_) = ActionRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if ActionRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Action with ref '{}' already exists",
|
"Action with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
|
|||||||
@@ -1877,7 +1877,7 @@ pub async fn stream_artifact(
|
|||||||
Some((
|
Some((
|
||||||
Ok(Event::default()
|
Ok(Event::default()
|
||||||
.event("content")
|
.event("content")
|
||||||
.data(String::from_utf8_lossy(&buf).into_owned())),
|
.data(String::from_utf8_lossy(&buf))),
|
||||||
TailState::Tailing {
|
TailState::Tailing {
|
||||||
full_path,
|
full_path,
|
||||||
file_path,
|
file_path,
|
||||||
@@ -1967,7 +1967,7 @@ pub async fn stream_artifact(
|
|||||||
Some((
|
Some((
|
||||||
Ok(Event::default()
|
Ok(Event::default()
|
||||||
.event("append")
|
.event("append")
|
||||||
.data(String::from_utf8_lossy(&new_buf).into_owned())),
|
.data(String::from_utf8_lossy(&new_buf))),
|
||||||
TailState::Tailing {
|
TailState::Tailing {
|
||||||
full_path,
|
full_path,
|
||||||
file_path,
|
file_path,
|
||||||
|
|||||||
@@ -158,7 +158,10 @@ pub async fn register(
|
|||||||
.map_err(|e| ApiError::ValidationError(format!("Invalid registration request: {}", e)))?;
|
.map_err(|e| ApiError::ValidationError(format!("Invalid registration request: {}", e)))?;
|
||||||
|
|
||||||
// Check if login already exists
|
// Check if login already exists
|
||||||
if let Some(_) = IdentityRepository::find_by_login(&state.db, &payload.login).await? {
|
if IdentityRepository::find_by_login(&state.db, &payload.login)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Identity with login '{}' already exists",
|
"Identity with login '{}' already exists",
|
||||||
payload.login
|
payload.login
|
||||||
|
|||||||
@@ -138,7 +138,10 @@ pub async fn create_key(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if key with same ref already exists
|
// Check if key with same ref already exists
|
||||||
if let Some(_) = KeyRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if KeyRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Key with ref '{}' already exists",
|
"Key with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
|
|||||||
@@ -585,10 +585,9 @@ pub async fn upload_pack(
|
|||||||
skip_tests,
|
skip_tests,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.inspect_err(|_e| {
|
||||||
// Clean up permanent storage on failure
|
// Clean up permanent storage on failure
|
||||||
let _ = std::fs::remove_dir_all(&final_path);
|
let _ = std::fs::remove_dir_all(&final_path);
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Fetch the registered pack
|
// Fetch the registered pack
|
||||||
@@ -947,8 +946,8 @@ async fn register_pack_internal(
|
|||||||
// a best-effort optimisation for non-Docker (bare-metal) setups
|
// a best-effort optimisation for non-Docker (bare-metal) setups
|
||||||
// where the API host has the interpreter available.
|
// where the API host has the interpreter available.
|
||||||
if let Some(ref env_cfg) = exec_config.environment {
|
if let Some(ref env_cfg) = exec_config.environment {
|
||||||
if env_cfg.env_type != "none" {
|
if env_cfg.env_type != "none"
|
||||||
if !env_dir.exists() && !env_cfg.create_command.is_empty() {
|
&& !env_dir.exists() && !env_cfg.create_command.is_empty() {
|
||||||
// Ensure parent directories exist
|
// Ensure parent directories exist
|
||||||
if let Some(parent) = env_dir.parent() {
|
if let Some(parent) = env_dir.parent() {
|
||||||
let _ = std::fs::create_dir_all(parent);
|
let _ = std::fs::create_dir_all(parent);
|
||||||
@@ -1003,7 +1002,6 @@ async fn register_pack_internal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to install dependencies if manifest file exists.
|
// Attempt to install dependencies if manifest file exists.
|
||||||
// Same caveat as above — this is best-effort in the API service.
|
// Same caveat as above — this is best-effort in the API service.
|
||||||
@@ -1107,9 +1105,7 @@ async fn register_pack_internal(
|
|||||||
if is_new_pack {
|
if is_new_pack {
|
||||||
let _ = PackRepository::delete(&state.db, pack.id).await;
|
let _ = PackRepository::delete(&state.db, pack.id).await;
|
||||||
}
|
}
|
||||||
return Err(ApiError::BadRequest(format!(
|
return Err(ApiError::BadRequest("Pack registration failed: tests did not pass. Use force=true to register anyway.".to_string()));
|
||||||
"Pack registration failed: tests did not pass. Use force=true to register anyway."
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !test_passed && force {
|
if !test_passed && force {
|
||||||
@@ -1359,10 +1355,9 @@ pub async fn install_pack(
|
|||||||
request.skip_tests,
|
request.skip_tests,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.inspect_err(|_e| {
|
||||||
// Clean up the permanent storage if registration fails
|
// Clean up the permanent storage if registration fails
|
||||||
let _ = std::fs::remove_dir_all(&final_path);
|
let _ = std::fs::remove_dir_all(&final_path);
|
||||||
e
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Fetch the registered pack
|
// Fetch the registered pack
|
||||||
|
|||||||
@@ -290,7 +290,10 @@ pub async fn create_rule(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if rule with same ref already exists
|
// Check if rule with same ref already exists
|
||||||
if let Some(_) = RuleRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if RuleRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Rule with ref '{}' already exists",
|
"Rule with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
|
|||||||
@@ -198,7 +198,10 @@ pub async fn create_trigger(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if trigger with same ref already exists
|
// Check if trigger with same ref already exists
|
||||||
if let Some(_) = TriggerRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if TriggerRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Trigger with ref '{}' already exists",
|
"Trigger with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
@@ -623,7 +626,10 @@ pub async fn create_sensor(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if sensor with same ref already exists
|
// Check if sensor with same ref already exists
|
||||||
if let Some(_) = SensorRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if SensorRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Sensor with ref '{}' already exists",
|
"Sensor with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
|
|||||||
@@ -714,6 +714,7 @@ pub async fn receive_webhook(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to log webhook events
|
// Helper function to log webhook events
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn log_webhook_event(
|
async fn log_webhook_event(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
trigger: &attune_common::models::trigger::Trigger,
|
trigger: &attune_common::models::trigger::Trigger,
|
||||||
@@ -753,6 +754,7 @@ async fn log_webhook_event(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to log failures when trigger is not found
|
// Helper function to log failures when trigger is not found
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn log_webhook_failure(
|
async fn log_webhook_failure(
|
||||||
_state: &AppState,
|
_state: &AppState,
|
||||||
webhook_key: String,
|
webhook_key: String,
|
||||||
|
|||||||
@@ -181,7 +181,10 @@ pub async fn create_workflow(
|
|||||||
request.validate()?;
|
request.validate()?;
|
||||||
|
|
||||||
// Check if workflow with same ref already exists
|
// Check if workflow with same ref already exists
|
||||||
if let Some(_) = WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref).await? {
|
if WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref)
|
||||||
|
.await?
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::Conflict(format!(
|
return Err(ApiError::Conflict(format!(
|
||||||
"Workflow with ref '{}' already exists",
|
"Workflow with ref '{}' already exists",
|
||||||
request.r#ref
|
request.r#ref
|
||||||
@@ -519,7 +522,7 @@ pub async fn update_workflow_file(
|
|||||||
|
|
||||||
/// Write a workflow definition to disk as YAML
|
/// Write a workflow definition to disk as YAML
|
||||||
async fn write_workflow_yaml(
|
async fn write_workflow_yaml(
|
||||||
packs_base_dir: &PathBuf,
|
packs_base_dir: &std::path::Path,
|
||||||
pack_ref: &str,
|
pack_ref: &str,
|
||||||
request: &SaveWorkflowFileRequest,
|
request: &SaveWorkflowFileRequest,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
@@ -630,9 +633,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
"# Action definition for workflow {}.{}",
|
"# Action definition for workflow {}.{}",
|
||||||
pack_ref, request.name
|
pack_ref, request.name
|
||||||
));
|
));
|
||||||
lines.push(format!(
|
lines.push("# The workflow graph (tasks, transitions, variables) is in:".to_string());
|
||||||
"# The workflow graph (tasks, transitions, variables) is in:"
|
|
||||||
));
|
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
"# actions/workflows/{}.workflow.yaml",
|
"# actions/workflows/{}.workflow.yaml",
|
||||||
request.name
|
request.name
|
||||||
@@ -646,7 +647,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
lines.push(format!("description: \"{}\"", desc.replace('"', "\\\"")));
|
lines.push(format!("description: \"{}\"", desc.replace('"', "\\\"")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lines.push(format!("enabled: true"));
|
lines.push("enabled: true".to_string());
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
"workflow_file: workflows/{}.workflow.yaml",
|
"workflow_file: workflows/{}.workflow.yaml",
|
||||||
request.name
|
request.name
|
||||||
@@ -658,7 +659,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
if !obj.is_empty() {
|
if !obj.is_empty() {
|
||||||
lines.push(String::new());
|
lines.push(String::new());
|
||||||
let params_yaml = serde_yaml_ng::to_string(params).unwrap_or_default();
|
let params_yaml = serde_yaml_ng::to_string(params).unwrap_or_default();
|
||||||
lines.push(format!("parameters:"));
|
lines.push("parameters:".to_string());
|
||||||
// Indent the YAML output under `parameters:`
|
// Indent the YAML output under `parameters:`
|
||||||
for line in params_yaml.lines() {
|
for line in params_yaml.lines() {
|
||||||
lines.push(format!(" {}", line));
|
lines.push(format!(" {}", line));
|
||||||
@@ -673,7 +674,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
if !obj.is_empty() {
|
if !obj.is_empty() {
|
||||||
lines.push(String::new());
|
lines.push(String::new());
|
||||||
let output_yaml = serde_yaml_ng::to_string(output).unwrap_or_default();
|
let output_yaml = serde_yaml_ng::to_string(output).unwrap_or_default();
|
||||||
lines.push(format!("output:"));
|
lines.push("output:".to_string());
|
||||||
for line in output_yaml.lines() {
|
for line in output_yaml.lines() {
|
||||||
lines.push(format!(" {}", line));
|
lines.push(format!(" {}", line));
|
||||||
}
|
}
|
||||||
@@ -685,7 +686,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
if let Some(ref tags) = request.tags {
|
if let Some(ref tags) = request.tags {
|
||||||
if !tags.is_empty() {
|
if !tags.is_empty() {
|
||||||
lines.push(String::new());
|
lines.push(String::new());
|
||||||
lines.push(format!("tags:"));
|
lines.push("tags:".to_string());
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
lines.push(format!(" - {}", tag));
|
lines.push(format!(" - {}", tag));
|
||||||
}
|
}
|
||||||
@@ -701,6 +702,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
|
|||||||
/// This ensures the workflow appears in action lists and the action palette in the
|
/// This ensures the workflow appears in action lists and the action palette in the
|
||||||
/// workflow builder. The action is linked to the workflow definition via the
|
/// workflow builder. The action is linked to the workflow definition via the
|
||||||
/// `workflow_def` FK.
|
/// `workflow_def` FK.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn create_companion_action(
|
async fn create_companion_action(
|
||||||
db: &sqlx::PgPool,
|
db: &sqlx::PgPool,
|
||||||
workflow_ref: &str,
|
workflow_ref: &str,
|
||||||
@@ -835,6 +837,7 @@ async fn update_companion_action(
|
|||||||
///
|
///
|
||||||
/// If the action already exists, update it. If it doesn't exist (e.g., for workflows
|
/// If the action already exists, update it. If it doesn't exist (e.g., for workflows
|
||||||
/// created before the companion-action fix), create it.
|
/// created before the companion-action fix), create it.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn ensure_companion_action(
|
async fn ensure_companion_action(
|
||||||
db: &sqlx::PgPool,
|
db: &sqlx::PgPool,
|
||||||
workflow_def_id: i64,
|
workflow_def_id: i64,
|
||||||
|
|||||||
@@ -362,11 +362,11 @@ impl Drop for TestContext {
|
|||||||
let test_packs_dir = self.test_packs_dir.clone();
|
let test_packs_dir = self.test_packs_dir.clone();
|
||||||
|
|
||||||
// Spawn cleanup task in background
|
// Spawn cleanup task in background
|
||||||
let _ = tokio::spawn(async move {
|
drop(tokio::spawn(async move {
|
||||||
if let Err(e) = cleanup_test_schema(&schema).await {
|
if let Err(e) = cleanup_test_schema(&schema).await {
|
||||||
eprintln!("Failed to cleanup test schema {}: {}", schema, e);
|
eprintln!("Failed to cleanup test schema {}: {}", schema, e);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
// Cleanup the test packs directory synchronously
|
// Cleanup the test packs directory synchronously
|
||||||
let _ = std::fs::remove_dir_all(&test_packs_dir);
|
let _ = std::fs::remove_dir_all(&test_packs_dir);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ async fn test_sync_pack_workflows_endpoint() {
|
|||||||
// Use unique pack name to avoid conflicts in parallel tests
|
// Use unique pack name to avoid conflicts in parallel tests
|
||||||
let pack_name = format!(
|
let pack_name = format!(
|
||||||
"test_pack_{}",
|
"test_pack_{}",
|
||||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create temporary directory for pack workflows
|
// Create temporary directory for pack workflows
|
||||||
@@ -100,7 +100,7 @@ async fn test_validate_pack_workflows_endpoint() {
|
|||||||
// Use unique pack name to avoid conflicts in parallel tests
|
// Use unique pack name to avoid conflicts in parallel tests
|
||||||
let pack_name = format!(
|
let pack_name = format!(
|
||||||
"test_pack_{}",
|
"test_pack_{}",
|
||||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create pack in database
|
// Create pack in database
|
||||||
@@ -158,7 +158,7 @@ async fn test_sync_workflows_requires_authentication() {
|
|||||||
// Use unique pack name to avoid conflicts in parallel tests
|
// Use unique pack name to avoid conflicts in parallel tests
|
||||||
let pack_name = format!(
|
let pack_name = format!(
|
||||||
"test_pack_{}",
|
"test_pack_{}",
|
||||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create pack in database
|
// Create pack in database
|
||||||
@@ -185,7 +185,7 @@ async fn test_validate_workflows_requires_authentication() {
|
|||||||
// Use unique pack name to avoid conflicts in parallel tests
|
// Use unique pack name to avoid conflicts in parallel tests
|
||||||
let pack_name = format!(
|
let pack_name = format!(
|
||||||
"test_pack_{}",
|
"test_pack_{}",
|
||||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create pack in database
|
// Create pack in database
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use helpers::*;
|
|||||||
fn unique_pack_name() -> String {
|
fn unique_pack_name() -> String {
|
||||||
format!(
|
format!(
|
||||||
"test_pack_{}",
|
"test_pack_{}",
|
||||||
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
|
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,8 +180,8 @@ impl ApiClient {
|
|||||||
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
||||||
let response = req.send().await.context("Failed to send request to API")?;
|
let response = req.send().await.context("Failed to send request to API")?;
|
||||||
|
|
||||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||||
if self.refresh_auth_token().await? {
|
&& self.refresh_auth_token().await? {
|
||||||
// Retry with new token
|
// Retry with new token
|
||||||
let req = self.attach_body(self.build_request(method, path), body);
|
let req = self.attach_body(self.build_request(method, path), body);
|
||||||
let response = req
|
let response = req
|
||||||
@@ -190,7 +190,6 @@ impl ApiClient {
|
|||||||
.context("Failed to send request to API (retry)")?;
|
.context("Failed to send request to API (retry)")?;
|
||||||
return self.handle_response(response).await;
|
return self.handle_response(response).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.handle_response(response).await
|
self.handle_response(response).await
|
||||||
}
|
}
|
||||||
@@ -205,8 +204,8 @@ impl ApiClient {
|
|||||||
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
let req = self.attach_body(self.build_request(method.clone(), path), body);
|
||||||
let response = req.send().await.context("Failed to send request to API")?;
|
let response = req.send().await.context("Failed to send request to API")?;
|
||||||
|
|
||||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||||
if self.refresh_auth_token().await? {
|
&& self.refresh_auth_token().await? {
|
||||||
let req = self.attach_body(self.build_request(method, path), body);
|
let req = self.attach_body(self.build_request(method, path), body);
|
||||||
let response = req
|
let response = req
|
||||||
.send()
|
.send()
|
||||||
@@ -214,7 +213,6 @@ impl ApiClient {
|
|||||||
.context("Failed to send request to API (retry)")?;
|
.context("Failed to send request to API (retry)")?;
|
||||||
return self.handle_empty_response(response).await;
|
return self.handle_empty_response(response).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.handle_empty_response(response).await
|
self.handle_empty_response(response).await
|
||||||
}
|
}
|
||||||
@@ -393,8 +391,8 @@ impl ApiClient {
|
|||||||
.await
|
.await
|
||||||
.context("Failed to send multipart request to API")?;
|
.context("Failed to send multipart request to API")?;
|
||||||
|
|
||||||
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
|
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
|
||||||
if self.refresh_auth_token().await? {
|
&& self.refresh_auth_token().await? {
|
||||||
// Retry with new token
|
// Retry with new token
|
||||||
let req = build_multipart_request(self, &file_bytes)?;
|
let req = build_multipart_request(self, &file_bytes)?;
|
||||||
let response = req
|
let response = req
|
||||||
@@ -403,7 +401,6 @@ impl ApiClient {
|
|||||||
.context("Failed to send multipart request to API (retry)")?;
|
.context("Failed to send multipart request to API (retry)")?;
|
||||||
return self.handle_response(response).await;
|
return self.handle_response(response).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.handle_response(response).await
|
self.handle_response(response).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,6 +314,7 @@ async fn handle_show(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_update(
|
async fn handle_update(
|
||||||
action_ref: String,
|
action_ref: String,
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
@@ -415,6 +416,7 @@ async fn handle_delete(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_execute(
|
async fn handle_execute(
|
||||||
action_ref: String,
|
action_ref: String,
|
||||||
params: Vec<String>,
|
params: Vec<String>,
|
||||||
@@ -454,12 +456,9 @@ async fn handle_execute(
|
|||||||
parameters,
|
parameters,
|
||||||
};
|
};
|
||||||
|
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!("Executing action: {}", action_ref));
|
output::print_info(&format!("Executing action: {}", action_ref));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = "/executions/execute".to_string();
|
let path = "/executions/execute".to_string();
|
||||||
let execution: Execution = client.post(&path, &request).await?;
|
let execution: Execution = client.post(&path, &request).await?;
|
||||||
@@ -481,15 +480,12 @@ async fn handle_execute(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!(
|
output::print_info(&format!(
|
||||||
"Waiting for execution {} to complete...",
|
"Waiting for execution {} to complete...",
|
||||||
execution.id
|
execution.id
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let verbose = matches!(output_format, OutputFormat::Table);
|
let verbose = matches!(output_format, OutputFormat::Table);
|
||||||
let summary = wait_for_execution(WaitOptions {
|
let summary = wait_for_execution(WaitOptions {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ pub async fn handle_execution_command(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_list(
|
async fn handle_list(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
pack: Option<String>,
|
pack: Option<String>,
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ pub async fn handle_pack_command(
|
|||||||
///
|
///
|
||||||
/// Splits on `_`, `-`, or `.` and title-cases each word.
|
/// Splits on `_`, `-`, or `.` and title-cases each word.
|
||||||
fn label_from_ref(r: &str) -> String {
|
fn label_from_ref(r: &str) -> String {
|
||||||
r.split(|c| c == '_' || c == '-' || c == '.')
|
r.split(['_', '-', '.'])
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(|word| {
|
.map(|word| {
|
||||||
let mut chars = word.chars();
|
let mut chars = word.chars();
|
||||||
@@ -484,6 +484,7 @@ fn label_from_ref(r: &str) -> String {
|
|||||||
.join(" ")
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_create(
|
async fn handle_create(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
ref_flag: Option<String>,
|
ref_flag: Option<String>,
|
||||||
@@ -725,6 +726,7 @@ async fn handle_show(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_install(
|
async fn handle_install(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
source: String,
|
source: String,
|
||||||
@@ -742,8 +744,7 @@ async fn handle_install(
|
|||||||
// Detect source type
|
// Detect source type
|
||||||
let source_type = detect_source_type(&source, ref_spec.as_deref(), no_registry);
|
let source_type = detect_source_type(&source, ref_spec.as_deref(), no_registry);
|
||||||
|
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!(
|
output::print_info(&format!(
|
||||||
"Installing pack from: {} ({})",
|
"Installing pack from: {} ({})",
|
||||||
source, source_type
|
source, source_type
|
||||||
@@ -753,8 +754,6 @@ async fn handle_install(
|
|||||||
output::print_info("⚠ Dependency validation will be skipped");
|
output::print_info("⚠ Dependency validation will be skipped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = InstallPackRequest {
|
let request = InstallPackRequest {
|
||||||
source: source.clone(),
|
source: source.clone(),
|
||||||
@@ -880,13 +879,10 @@ async fn handle_upload(
|
|||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("unknown");
|
.unwrap_or("unknown");
|
||||||
|
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
|
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
|
||||||
output::print_info("Creating archive...");
|
output::print_info("Creating archive...");
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build an in-memory tar.gz of the pack directory
|
// Build an in-memory tar.gz of the pack directory
|
||||||
let tar_gz_bytes = {
|
let tar_gz_bytes = {
|
||||||
@@ -908,15 +904,12 @@ async fn handle_upload(
|
|||||||
|
|
||||||
let archive_size_kb = tar_gz_bytes.len() / 1024;
|
let archive_size_kb = tar_gz_bytes.len() / 1024;
|
||||||
|
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!(
|
output::print_info(&format!(
|
||||||
"Archive ready ({} KB), uploading...",
|
"Archive ready ({} KB), uploading...",
|
||||||
archive_size_kb
|
archive_size_kb
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
let config = CliConfig::load_with_profile(profile.as_deref())?;
|
||||||
let mut client = ApiClient::from_config(&config, api_url);
|
let mut client = ApiClient::from_config(&config, api_url);
|
||||||
@@ -1014,8 +1007,7 @@ async fn handle_register(
|
|||||||
&& !path.starts_with("/app/")
|
&& !path.starts_with("/app/")
|
||||||
&& !path.starts_with("/packs");
|
&& !path.starts_with("/packs");
|
||||||
if looks_local {
|
if looks_local {
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!("Registering pack from: {}", path));
|
output::print_info(&format!("Registering pack from: {}", path));
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"⚠ Warning: '{}' looks like a local path. If the API is running in \
|
"⚠ Warning: '{}' looks like a local path. If the API is running in \
|
||||||
@@ -1024,16 +1016,9 @@ async fn handle_register(
|
|||||||
path, path
|
path, path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
} else if output_format == OutputFormat::Table {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match output_format {
|
|
||||||
OutputFormat::Table => {
|
|
||||||
output::print_info(&format!("Registering pack from: {}", path));
|
output::print_info(&format!("Registering pack from: {}", path));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let request = RegisterPackRequest {
|
let request = RegisterPackRequest {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
@@ -1173,14 +1158,11 @@ async fn handle_test(
|
|||||||
let executor = TestExecutor::new(pack_base_dir);
|
let executor = TestExecutor::new(pack_base_dir);
|
||||||
|
|
||||||
// Print test start message
|
// Print test start message
|
||||||
match output_format {
|
if output_format == OutputFormat::Table {
|
||||||
OutputFormat::Table => {
|
|
||||||
println!();
|
println!();
|
||||||
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
|
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute tests
|
// Execute tests
|
||||||
let result = executor
|
let result = executor
|
||||||
@@ -1688,7 +1670,7 @@ async fn handle_index_entry(
|
|||||||
|
|
||||||
if let Some(ref git) = git_url {
|
if let Some(ref git) = git_url {
|
||||||
let default_ref = format!("v{}", version);
|
let default_ref = format!("v{}", version);
|
||||||
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
|
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
|
||||||
let git_source = serde_json::json!({
|
let git_source = serde_json::json!({
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": git,
|
"url": git,
|
||||||
@@ -1790,6 +1772,7 @@ async fn handle_index_entry(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_update(
|
async fn handle_update(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
pack_ref: String,
|
pack_ref: String,
|
||||||
|
|||||||
@@ -76,11 +76,9 @@ pub async fn handle_index_update(
|
|||||||
if output_format == OutputFormat::Table {
|
if output_format == OutputFormat::Table {
|
||||||
output::print_info(&format!("Updating existing entry for '{}'", pack_ref));
|
output::print_info(&format!("Updating existing entry for '{}'", pack_ref));
|
||||||
}
|
}
|
||||||
} else {
|
} else if output_format == OutputFormat::Table {
|
||||||
if output_format == OutputFormat::Table {
|
|
||||||
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
|
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate checksum
|
// Calculate checksum
|
||||||
if output_format == OutputFormat::Table {
|
if output_format == OutputFormat::Table {
|
||||||
@@ -93,7 +91,7 @@ pub async fn handle_index_update(
|
|||||||
|
|
||||||
if let Some(ref git) = git_url {
|
if let Some(ref git) = git_url {
|
||||||
let default_ref = format!("v{}", version);
|
let default_ref = format!("v{}", version);
|
||||||
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
|
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
|
||||||
install_sources.push(serde_json::json!({
|
install_sources.push(serde_json::json!({
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": git,
|
"url": git,
|
||||||
@@ -318,14 +316,12 @@ pub async fn handle_index_merge(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
packs_map.insert(pack_ref.to_string(), pack.clone());
|
packs_map.insert(pack_ref.to_string(), pack.clone());
|
||||||
} else {
|
} else if output_format == OutputFormat::Table {
|
||||||
if output_format == OutputFormat::Table {
|
|
||||||
output::print_info(&format!(
|
output::print_info(&format!(
|
||||||
" Keeping '{}' at {} (newer than {})",
|
" Keeping '{}' at {} (newer than {})",
|
||||||
pack_ref, existing_version, new_version
|
pack_ref, existing_version, new_version
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
duplicates_resolved += 1;
|
duplicates_resolved += 1;
|
||||||
} else {
|
} else {
|
||||||
packs_map.insert(pack_ref.to_string(), pack.clone());
|
packs_map.insert(pack_ref.to_string(), pack.clone());
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ async fn handle_show(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_update(
|
async fn handle_update(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
rule_ref: String,
|
rule_ref: String,
|
||||||
@@ -477,6 +478,7 @@ async fn handle_toggle(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn handle_create(
|
async fn handle_create(
|
||||||
profile: &Option<String>,
|
profile: &Option<String>,
|
||||||
name: String,
|
name: String,
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ fn resolve_ws_url(opts: &WaitOptions<'_>) -> Option<String> {
|
|||||||
let api_url = opts.api_client.base_url();
|
let api_url = opts.api_client.base_url();
|
||||||
|
|
||||||
// Transform http(s)://host:PORT/... → ws(s)://host:8081
|
// Transform http(s)://host:PORT/... → ws(s)://host:8081
|
||||||
let ws_url = derive_notifier_url(&api_url)?;
|
let ws_url = derive_notifier_url(api_url)?;
|
||||||
Some(ws_url)
|
Some(ws_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ fn test_pack_index_entry_generates_valid_json() {
|
|||||||
// Verify metadata
|
// Verify metadata
|
||||||
assert_eq!(json["author"], "Test Author");
|
assert_eq!(json["author"], "Test Author");
|
||||||
assert_eq!(json["license"], "Apache-2.0");
|
assert_eq!(json["license"], "Apache-2.0");
|
||||||
assert!(json["keywords"].as_array().unwrap().len() > 0);
|
assert!(!json["keywords"].as_array().unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -212,7 +212,7 @@ fn test_pack_index_entry_with_archive_url() {
|
|||||||
let stdout = String::from_utf8(output.get_output().stdout.clone()).unwrap();
|
let stdout = String::from_utf8(output.get_output().stdout.clone()).unwrap();
|
||||||
|
|
||||||
let json: Value = serde_json::from_str(&stdout).unwrap();
|
let json: Value = serde_json::from_str(&stdout).unwrap();
|
||||||
assert!(json["install_sources"].as_array().unwrap().len() > 0);
|
assert!(!json["install_sources"].as_array().unwrap().is_empty());
|
||||||
|
|
||||||
let archive_source = &json["install_sources"][0];
|
let archive_source = &json["install_sources"][0];
|
||||||
assert_eq!(archive_source["type"], "archive");
|
assert_eq!(archive_source["type"], "archive");
|
||||||
|
|||||||
@@ -45,21 +45,16 @@ pub struct Claims {
|
|||||||
pub metadata: Option<serde_json::Value>,
|
pub metadata: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum TokenType {
|
pub enum TokenType {
|
||||||
|
#[default]
|
||||||
Access,
|
Access,
|
||||||
Refresh,
|
Refresh,
|
||||||
Sensor,
|
Sensor,
|
||||||
Execution,
|
Execution,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TokenType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Access
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration for JWT tokens
|
/// Configuration for JWT tokens
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct JwtConfig {
|
pub struct JwtConfig {
|
||||||
@@ -247,11 +242,7 @@ pub fn validate_token(token: &str, config: &JwtConfig) -> Result<Claims, JwtErro
|
|||||||
|
|
||||||
/// Extract token from Authorization header
|
/// Extract token from Authorization header
|
||||||
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
|
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
|
||||||
if auth_header.starts_with("Bearer ") {
|
auth_header.strip_prefix("Bearer ")
|
||||||
Some(&auth_header[7..])
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -45,21 +45,16 @@ pub mod enums {
|
|||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
/// How parameters should be delivered to an action
|
/// How parameters should be delivered to an action
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ParameterDelivery {
|
pub enum ParameterDelivery {
|
||||||
/// Pass parameters via stdin (secure, recommended for most cases)
|
/// Pass parameters via stdin (secure, recommended for most cases)
|
||||||
|
#[default]
|
||||||
Stdin,
|
Stdin,
|
||||||
/// Pass parameters via temporary file (secure, best for large payloads)
|
/// Pass parameters via temporary file (secure, best for large payloads)
|
||||||
File,
|
File,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ParameterDelivery {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Stdin
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ParameterDelivery {
|
impl fmt::Display for ParameterDelivery {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -99,31 +94,23 @@ pub mod enums {
|
|||||||
&self,
|
&self,
|
||||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||||
self.to_string(),
|
|
||||||
buf,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format for parameter serialization
|
/// Format for parameter serialization
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ParameterFormat {
|
pub enum ParameterFormat {
|
||||||
/// KEY='VALUE' format (one per line)
|
/// KEY='VALUE' format (one per line)
|
||||||
Dotenv,
|
Dotenv,
|
||||||
/// JSON object
|
/// JSON object
|
||||||
|
#[default]
|
||||||
Json,
|
Json,
|
||||||
/// YAML format
|
/// YAML format
|
||||||
Yaml,
|
Yaml,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ParameterFormat {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ParameterFormat {
|
impl fmt::Display for ParameterFormat {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -165,18 +152,16 @@ pub mod enums {
|
|||||||
&self,
|
&self,
|
||||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||||
self.to_string(),
|
|
||||||
buf,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format for action output parsing
|
/// Format for action output parsing
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum OutputFormat {
|
pub enum OutputFormat {
|
||||||
/// Plain text (no parsing)
|
/// Plain text (no parsing)
|
||||||
|
#[default]
|
||||||
Text,
|
Text,
|
||||||
/// Parse as JSON
|
/// Parse as JSON
|
||||||
Json,
|
Json,
|
||||||
@@ -186,12 +171,6 @@ pub mod enums {
|
|||||||
Jsonl,
|
Jsonl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OutputFormat {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OutputFormat {
|
impl fmt::Display for OutputFormat {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -235,10 +214,7 @@ pub mod enums {
|
|||||||
&self,
|
&self,
|
||||||
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
buf: &mut sqlx::postgres::PgArgumentBuffer,
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||||
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
|
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
|
||||||
self.to_string(),
|
|
||||||
buf,
|
|
||||||
)?)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,20 +347,17 @@ pub mod enums {
|
|||||||
/// - `Public`: viewable by all authenticated users on the platform.
|
/// - `Public`: viewable by all authenticated users on the platform.
|
||||||
/// - `Private`: restricted based on the artifact's `scope` and `owner` fields.
|
/// - `Private`: restricted based on the artifact's `scope` and `owner` fields.
|
||||||
/// Full RBAC enforcement is deferred; for now the field enables filtering.
|
/// Full RBAC enforcement is deferred; for now the field enables filtering.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
|
#[derive(
|
||||||
|
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema,
|
||||||
|
)]
|
||||||
#[sqlx(type_name = "artifact_visibility_enum", rename_all = "lowercase")]
|
#[sqlx(type_name = "artifact_visibility_enum", rename_all = "lowercase")]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ArtifactVisibility {
|
pub enum ArtifactVisibility {
|
||||||
Public,
|
Public,
|
||||||
|
#[default]
|
||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ArtifactVisibility {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Private
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
|
||||||
#[sqlx(type_name = "workflow_task_status_enum", rename_all = "lowercase")]
|
#[sqlx(type_name = "workflow_task_status_enum", rename_all = "lowercase")]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ mod tests {
|
|||||||
fn test_message_queue_creation() {
|
fn test_message_queue_creation() {
|
||||||
// This test just verifies the struct can be instantiated
|
// This test just verifies the struct can be instantiated
|
||||||
// Actual connection tests require a running RabbitMQ instance
|
// Actual connection tests require a running RabbitMQ instance
|
||||||
assert!(true);
|
// (nothing to assert without a live broker)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -69,20 +69,15 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// Message delivery mode
|
/// Message delivery mode
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DeliveryMode {
|
pub enum DeliveryMode {
|
||||||
/// Non-persistent messages (faster, but may be lost on broker restart)
|
/// Non-persistent messages (faster, but may be lost on broker restart)
|
||||||
NonPersistent = 1,
|
NonPersistent = 1,
|
||||||
/// Persistent messages (slower, but survive broker restart)
|
/// Persistent messages (slower, but survive broker restart)
|
||||||
|
#[default]
|
||||||
Persistent = 2,
|
Persistent = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeliveryMode {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Persistent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message priority (0-9, higher is more urgent)
|
/// Message priority (0-9, higher is more urgent)
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct Priority(u8);
|
pub struct Priority(u8);
|
||||||
@@ -125,25 +120,21 @@ impl fmt::Display for Priority {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Message acknowledgment mode
|
/// Message acknowledgment mode
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum AckMode {
|
pub enum AckMode {
|
||||||
/// Automatically acknowledge messages after delivery
|
/// Automatically acknowledge messages after delivery
|
||||||
Auto,
|
Auto,
|
||||||
/// Manually acknowledge messages after processing
|
/// Manually acknowledge messages after processing
|
||||||
|
#[default]
|
||||||
Manual,
|
Manual,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AckMode {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Manual
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Exchange type
|
/// Exchange type
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum ExchangeType {
|
pub enum ExchangeType {
|
||||||
/// Direct exchange - routes messages with exact routing key match
|
/// Direct exchange - routes messages with exact routing key match
|
||||||
|
#[default]
|
||||||
Direct,
|
Direct,
|
||||||
/// Topic exchange - routes messages using pattern matching
|
/// Topic exchange - routes messages using pattern matching
|
||||||
Topic,
|
Topic,
|
||||||
@@ -165,12 +156,6 @@ impl ExchangeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExchangeType {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ExchangeType {
|
impl fmt::Display for ExchangeType {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}", self.as_str())
|
write!(f, "{}", self.as_str())
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ impl PackEnvironmentStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_str(s: &str) -> Option<Self> {
|
pub fn parse_status(s: &str) -> Option<Self> {
|
||||||
match s {
|
match s {
|
||||||
"pending" => Some(Self::Pending),
|
"pending" => Some(Self::Pending),
|
||||||
"installing" => Some(Self::Installing),
|
"installing" => Some(Self::Installing),
|
||||||
@@ -194,7 +194,7 @@ impl PackEnvironmentManager {
|
|||||||
|
|
||||||
if let Some(row) = row {
|
if let Some(row) = row {
|
||||||
let status_str: String = row.try_get("status")?;
|
let status_str: String = row.try_get("status")?;
|
||||||
let status = PackEnvironmentStatus::from_str(&status_str)
|
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||||
.unwrap_or(PackEnvironmentStatus::Failed);
|
.unwrap_or(PackEnvironmentStatus::Failed);
|
||||||
|
|
||||||
Ok(Some(PackEnvironment {
|
Ok(Some(PackEnvironment {
|
||||||
@@ -343,7 +343,7 @@ impl PackEnvironmentManager {
|
|||||||
let mut environments = Vec::new();
|
let mut environments = Vec::new();
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let status_str: String = row.try_get("status")?;
|
let status_str: String = row.try_get("status")?;
|
||||||
let status = PackEnvironmentStatus::from_str(&status_str)
|
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||||
.unwrap_or(PackEnvironmentStatus::Failed);
|
.unwrap_or(PackEnvironmentStatus::Failed);
|
||||||
|
|
||||||
environments.push(PackEnvironment {
|
environments.push(PackEnvironment {
|
||||||
@@ -452,8 +452,8 @@ impl PackEnvironmentManager {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let status_str: String = row.try_get("status")?;
|
let status_str: String = row.try_get("status")?;
|
||||||
let status =
|
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||||
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Pending);
|
.unwrap_or(PackEnvironmentStatus::Pending);
|
||||||
|
|
||||||
Ok(PackEnvironment {
|
Ok(PackEnvironment {
|
||||||
id: row.try_get("id")?,
|
id: row.try_get("id")?,
|
||||||
@@ -496,8 +496,8 @@ impl PackEnvironmentManager {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let status_str: String = row.try_get("status")?;
|
let status_str: String = row.try_get("status")?;
|
||||||
let status =
|
let status = PackEnvironmentStatus::parse_status(&status_str)
|
||||||
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Ready);
|
.unwrap_or(PackEnvironmentStatus::Ready);
|
||||||
|
|
||||||
Ok(PackEnvironment {
|
Ok(PackEnvironment {
|
||||||
id: row.try_get("id")?,
|
id: row.try_get("id")?,
|
||||||
@@ -580,7 +580,7 @@ impl PackEnvironmentManager {
|
|||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
install_log.push_str(&format!("\n=== {} ===\n", action.name));
|
install_log.push_str(&format!("\n=== {} ===\n", action.name));
|
||||||
install_log.push_str(&output);
|
install_log.push_str(&output);
|
||||||
install_log.push_str("\n");
|
install_log.push('\n');
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error_msg = format!("Installer '{}' failed: {}", action.name, e);
|
let error_msg = format!("Installer '{}' failed: {}", action.name, e);
|
||||||
@@ -701,8 +701,7 @@ impl PackEnvironmentManager {
|
|||||||
.map(|obj| {
|
.map(|obj| {
|
||||||
obj.iter()
|
obj.iter()
|
||||||
.filter_map(|(k, v)| {
|
.filter_map(|(k, v)| {
|
||||||
v.as_str()
|
v.as_str().and_then(|s| {
|
||||||
.map(|s| {
|
|
||||||
let resolved = self
|
let resolved = self
|
||||||
.resolve_template(
|
.resolve_template(
|
||||||
s,
|
s,
|
||||||
@@ -714,7 +713,6 @@ impl PackEnvironmentManager {
|
|||||||
.ok()?;
|
.ok()?;
|
||||||
Some((k.clone(), resolved))
|
Some((k.clone(), resolved))
|
||||||
})
|
})
|
||||||
.flatten()
|
|
||||||
})
|
})
|
||||||
.collect::<HashMap<String, String>>()
|
.collect::<HashMap<String, String>>()
|
||||||
})
|
})
|
||||||
@@ -877,9 +875,9 @@ mod tests {
|
|||||||
fn test_environment_status_conversion() {
|
fn test_environment_status_conversion() {
|
||||||
assert_eq!(PackEnvironmentStatus::Ready.as_str(), "ready");
|
assert_eq!(PackEnvironmentStatus::Ready.as_str(), "ready");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PackEnvironmentStatus::from_str("ready"),
|
PackEnvironmentStatus::parse_status("ready"),
|
||||||
Some(PackEnvironmentStatus::Ready)
|
Some(PackEnvironmentStatus::Ready)
|
||||||
);
|
);
|
||||||
assert_eq!(PackEnvironmentStatus::from_str("invalid"), None);
|
assert_eq!(PackEnvironmentStatus::parse_status("invalid"), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,17 +151,15 @@ impl DependencyValidator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let error = if !satisfied {
|
let error = if !satisfied {
|
||||||
if detected_version.is_none() {
|
if let Some(ref detected) = detected_version {
|
||||||
Some(format!("Runtime '{}' not found on system", runtime))
|
version_constraint.as_ref().map(|constraint| {
|
||||||
} else if let Some(ref constraint) = version_constraint {
|
format!(
|
||||||
Some(format!(
|
|
||||||
"Runtime '{}' version {} does not satisfy constraint '{}'",
|
"Runtime '{}' version {} does not satisfy constraint '{}'",
|
||||||
runtime,
|
runtime, detected, constraint
|
||||||
detected_version.as_ref().unwrap(),
|
)
|
||||||
constraint
|
})
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
Some(format!("Runtime '{}' not found on system", runtime))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -192,15 +190,13 @@ impl DependencyValidator {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let error = if !satisfied {
|
let error = if !satisfied {
|
||||||
if installed_version.is_none() {
|
if let Some(ref installed) = installed_version {
|
||||||
Some(format!("Required pack '{}' is not installed", pack_ref))
|
|
||||||
} else {
|
|
||||||
Some(format!(
|
Some(format!(
|
||||||
"Pack '{}' version {} does not satisfy constraint '{}'",
|
"Pack '{}' version {} does not satisfy constraint '{}'",
|
||||||
pack_ref,
|
pack_ref, installed, version_constraint
|
||||||
installed_version.as_ref().unwrap(),
|
|
||||||
version_constraint
|
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
Some(format!("Required pack '{}' is not installed", pack_ref))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -335,30 +331,30 @@ fn match_version_constraint(version: &str, constraint: &str) -> Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse constraint
|
// Parse constraint
|
||||||
if constraint.starts_with(">=") {
|
if let Some(stripped) = constraint.strip_prefix(">=") {
|
||||||
let required = constraint[2..].trim();
|
let required = stripped.trim();
|
||||||
Ok(compare_versions(version, required)? >= 0)
|
Ok(compare_versions(version, required)? >= 0)
|
||||||
} else if constraint.starts_with("<=") {
|
} else if let Some(stripped) = constraint.strip_prefix("<=") {
|
||||||
let required = constraint[2..].trim();
|
let required = stripped.trim();
|
||||||
Ok(compare_versions(version, required)? <= 0)
|
Ok(compare_versions(version, required)? <= 0)
|
||||||
} else if constraint.starts_with('>') {
|
} else if let Some(stripped) = constraint.strip_prefix('>') {
|
||||||
let required = constraint[1..].trim();
|
let required = stripped.trim();
|
||||||
Ok(compare_versions(version, required)? > 0)
|
Ok(compare_versions(version, required)? > 0)
|
||||||
} else if constraint.starts_with('<') {
|
} else if let Some(stripped) = constraint.strip_prefix('<') {
|
||||||
let required = constraint[1..].trim();
|
let required = stripped.trim();
|
||||||
Ok(compare_versions(version, required)? < 0)
|
Ok(compare_versions(version, required)? < 0)
|
||||||
} else if constraint.starts_with('=') {
|
} else if let Some(stripped) = constraint.strip_prefix('=') {
|
||||||
let required = constraint[1..].trim();
|
let required = stripped.trim();
|
||||||
Ok(compare_versions(version, required)? == 0)
|
Ok(compare_versions(version, required)? == 0)
|
||||||
} else if constraint.starts_with('^') {
|
} else if let Some(stripped) = constraint.strip_prefix('^') {
|
||||||
// Caret: Compatible with version (major.minor.patch)
|
// Caret: Compatible with version (major.minor.patch)
|
||||||
// ^1.2.3 := >=1.2.3 <2.0.0
|
// ^1.2.3 := >=1.2.3 <2.0.0
|
||||||
let required = constraint[1..].trim();
|
let required = stripped.trim();
|
||||||
match_caret_constraint(version, required)
|
match_caret_constraint(version, required)
|
||||||
} else if constraint.starts_with('~') {
|
} else if let Some(stripped) = constraint.strip_prefix('~') {
|
||||||
// Tilde: Approximately equivalent to version
|
// Tilde: Approximately equivalent to version
|
||||||
// ~1.2.3 := >=1.2.3 <1.3.0
|
// ~1.2.3 := >=1.2.3 <1.3.0
|
||||||
let required = constraint[1..].trim();
|
let required = stripped.trim();
|
||||||
match_tilde_constraint(version, required)
|
match_tilde_constraint(version, required)
|
||||||
} else {
|
} else {
|
||||||
// Exact match
|
// Exact match
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ impl PackInstaller {
|
|||||||
clone_cmd.arg("--depth").arg("1");
|
clone_cmd.arg("--depth").arg("1");
|
||||||
}
|
}
|
||||||
|
|
||||||
clone_cmd.arg(&url).arg(&install_dir);
|
clone_cmd.arg(url).arg(&install_dir);
|
||||||
|
|
||||||
let output = clone_cmd
|
let output = clone_cmd
|
||||||
.output()
|
.output()
|
||||||
@@ -421,7 +421,11 @@ impl PackInstaller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine filename from URL
|
// Determine filename from URL
|
||||||
let filename = url.split('/').last().unwrap_or("archive.zip").to_string();
|
let filename = url
|
||||||
|
.split('/')
|
||||||
|
.next_back()
|
||||||
|
.unwrap_or("archive.zip")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
let archive_path = self.temp_dir.join(&filename);
|
let archive_path = self.temp_dir.join(&filename);
|
||||||
|
|
||||||
|
|||||||
@@ -288,8 +288,7 @@ impl<'a> PackComponentLoader<'a> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Check for unique constraint violation (race condition)
|
// Check for unique constraint violation (race condition)
|
||||||
if let Error::Database(ref db_err) = e {
|
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
|
||||||
if let sqlx::Error::Database(ref inner) = db_err {
|
|
||||||
if inner.is_unique_violation() {
|
if inner.is_unique_violation() {
|
||||||
info!(
|
info!(
|
||||||
"Runtime '{}' already exists (concurrent creation), treating as update",
|
"Runtime '{}' already exists (concurrent creation), treating as update",
|
||||||
@@ -300,7 +299,6 @@ impl<'a> PackComponentLoader<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let msg = format!("Failed to create runtime '{}': {}", runtime_ref, e);
|
let msg = format!("Failed to create runtime '{}': {}", runtime_ref, e);
|
||||||
warn!("{}", msg);
|
warn!("{}", msg);
|
||||||
result.warnings.push(msg);
|
result.warnings.push(msg);
|
||||||
@@ -438,8 +436,7 @@ impl<'a> PackComponentLoader<'a> {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Check for unique constraint violation (race condition)
|
// Check for unique constraint violation (race condition)
|
||||||
if let Error::Database(ref db_err) = e {
|
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
|
||||||
if let sqlx::Error::Database(ref inner) = db_err {
|
|
||||||
if inner.is_unique_violation() {
|
if inner.is_unique_violation() {
|
||||||
info!(
|
info!(
|
||||||
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
|
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
|
||||||
@@ -449,7 +446,6 @@ impl<'a> PackComponentLoader<'a> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"Failed to create version '{}' for runtime '{}': {}",
|
"Failed to create version '{}' for runtime '{}': {}",
|
||||||
version_str, runtime_ref, e
|
version_str, runtime_ref, e
|
||||||
|
|||||||
@@ -272,11 +272,6 @@ impl Checksum {
|
|||||||
|
|
||||||
Ok(Self { algorithm, hash })
|
Ok(Self { algorithm, hash })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format as "algorithm:hash"
|
|
||||||
pub fn to_string(&self) -> String {
|
|
||||||
format!("{}:{}", self.algorithm, self.hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Checksum {
|
impl std::fmt::Display for Checksum {
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ impl Update for ActionRepository {
|
|||||||
|
|
||||||
query.push(", updated = NOW() WHERE id = ");
|
query.push(", updated = NOW() WHERE id = ");
|
||||||
query.push_bind(id);
|
query.push_bind(id);
|
||||||
query.push(&format!(" RETURNING {}", ACTION_COLUMNS));
|
query.push(format!(" RETURNING {}", ACTION_COLUMNS));
|
||||||
|
|
||||||
let action = query
|
let action = query
|
||||||
.build_query_as::<Action>()
|
.build_query_as::<Action>()
|
||||||
@@ -554,7 +554,6 @@ impl ActionRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repository for Policy operations
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Policy Repository
|
// Policy Repository
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub struct HistoryQueryParams {
|
|||||||
impl HistoryQueryParams {
|
impl HistoryQueryParams {
|
||||||
/// Returns the effective limit, capped at 1000.
|
/// Returns the effective limit, capped at 1000.
|
||||||
pub fn effective_limit(&self) -> i64 {
|
pub fn effective_limit(&self) -> i64 {
|
||||||
self.limit.unwrap_or(100).min(1000).max(1)
|
self.limit.unwrap_or(100).clamp(1, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the effective offset.
|
/// Returns the effective offset.
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ impl EnforcementRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = &filters.status {
|
if let Some(status) = &filters.status {
|
||||||
push_condition!("status = ", status.clone());
|
push_condition!("status = ", *status);
|
||||||
}
|
}
|
||||||
if let Some(rule_id) = filters.rule {
|
if let Some(rule_id) = filters.rule {
|
||||||
push_condition!("rule = ", rule_id);
|
push_condition!("rule = ", rule_id);
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ impl ExecutionRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = &filters.status {
|
if let Some(status) = &filters.status {
|
||||||
push_condition!("e.status = ", status.clone());
|
push_condition!("e.status = ", *status);
|
||||||
}
|
}
|
||||||
if let Some(action_ref) = &filters.action_ref {
|
if let Some(action_ref) = &filters.action_ref {
|
||||||
push_condition!("e.action_ref = ", action_ref.clone());
|
push_condition!("e.action_ref = ", action_ref.clone());
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ impl Update for IdentityRepository {
|
|||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
// Convert RowNotFound to NotFound error
|
// Convert RowNotFound to NotFound error
|
||||||
if matches!(e, sqlx::Error::RowNotFound) {
|
if matches!(e, sqlx::Error::RowNotFound) {
|
||||||
return crate::Error::not_found("identity", "id", &id.to_string());
|
return crate::Error::not_found("identity", "id", id.to_string());
|
||||||
}
|
}
|
||||||
e.into()
|
e.into()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ impl InquiryRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(status) = &filters.status {
|
if let Some(status) = &filters.status {
|
||||||
push_condition!("status = ", status.clone());
|
push_condition!("status = ", *status);
|
||||||
}
|
}
|
||||||
if let Some(execution_id) = filters.execution {
|
if let Some(execution_id) = filters.execution {
|
||||||
push_condition!("execution = ", execution_id);
|
push_condition!("execution = ", execution_id);
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ impl KeyRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref owner_type) = filters.owner_type {
|
if let Some(ref owner_type) = filters.owner_type {
|
||||||
push_condition!("owner_type = ", owner_type.clone());
|
push_condition!("owner_type = ", *owner_type);
|
||||||
}
|
}
|
||||||
if let Some(ref owner) = filters.owner {
|
if let Some(ref owner) = filters.owner {
|
||||||
push_condition!("owner = ", owner.clone());
|
push_condition!("owner = ", owner.clone());
|
||||||
|
|||||||
@@ -408,6 +408,7 @@ impl PackRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update installation metadata for a pack
|
/// Update installation metadata for a pack
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn update_installation_metadata<'e, E>(
|
pub async fn update_installation_metadata<'e, E>(
|
||||||
executor: E,
|
executor: E,
|
||||||
id: i64,
|
id: i64,
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ impl Update for RuntimeVersionRepository {
|
|||||||
|
|
||||||
query.push(" WHERE id = ");
|
query.push(" WHERE id = ");
|
||||||
query.push_bind(id);
|
query.push_bind(id);
|
||||||
query.push(&format!(" RETURNING {}", SELECT_COLUMNS));
|
query.push(format!(" RETURNING {}", SELECT_COLUMNS));
|
||||||
|
|
||||||
let row = query
|
let row = query
|
||||||
.build_query_as::<RuntimeVersion>()
|
.build_query_as::<RuntimeVersion>()
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ impl Update for TriggerRepository {
|
|||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
// Convert RowNotFound to NotFound error
|
// Convert RowNotFound to NotFound error
|
||||||
if matches!(e, sqlx::Error::RowNotFound) {
|
if matches!(e, sqlx::Error::RowNotFound) {
|
||||||
return crate::Error::not_found("trigger", "id", &id.to_string());
|
return crate::Error::not_found("trigger", "id", id.to_string());
|
||||||
}
|
}
|
||||||
e.into()
|
e.into()
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -423,14 +423,11 @@ mod tests {
|
|||||||
"always_available": true
|
"always_available": true
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(
|
assert!(verification
|
||||||
verification
|
|
||||||
.get("always_available")
|
.get("always_available")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_bool()
|
.as_bool()
|
||||||
.unwrap(),
|
.unwrap());
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ impl TestExecutor {
|
|||||||
);
|
);
|
||||||
|
|
||||||
match self
|
match self
|
||||||
.execute_test_suite(&pack_dir, runner_name, runner_config)
|
.execute_test_suite(pack_dir, runner_name, runner_config)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(suite_result) => {
|
Ok(suite_result) => {
|
||||||
@@ -369,7 +369,7 @@ impl TestExecutor {
|
|||||||
let total = self.extract_number(&text, "Total Tests:");
|
let total = self.extract_number(&text, "Total Tests:");
|
||||||
let passed = self.extract_number(&text, "Passed:");
|
let passed = self.extract_number(&text, "Passed:");
|
||||||
let failed = self.extract_number(&text, "Failed:");
|
let failed = self.extract_number(&text, "Failed:");
|
||||||
let skipped = self.extract_number(&text, "Skipped:").or_else(|| Some(0));
|
let skipped = self.extract_number(&text, "Skipped:").or(Some(0));
|
||||||
|
|
||||||
// If we couldn't parse counts, use exit code
|
// If we couldn't parse counts, use exit code
|
||||||
let (total, passed, failed, skipped) = if total.is_none() || passed.is_none() {
|
let (total, passed, failed, skipped) = if total.is_none() || passed.is_none() {
|
||||||
@@ -441,7 +441,6 @@ impl TestExecutor {
|
|||||||
.and_then(|line| {
|
.and_then(|line| {
|
||||||
line.split(label)
|
line.split(label)
|
||||||
.nth(1)?
|
.nth(1)?
|
||||||
.trim()
|
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.next()?
|
.next()?
|
||||||
.parse::<i32>()
|
.parse::<i32>()
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
a.iter()
|
a.iter()
|
||||||
.all(|(k, v)| b.get(k).map_or(false, |bv| json_eq(v, bv)))
|
.all(|(k, v)| b.get(k).is_some_and(|bv| json_eq(v, bv)))
|
||||||
}
|
}
|
||||||
// Different types (other than number cross-compare) are never equal
|
// Different types (other than number cross-compare) are never equal
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -680,10 +680,8 @@ impl NumericValue {
|
|||||||
fn as_numeric(v: &JsonValue) -> Option<NumericValue> {
|
fn as_numeric(v: &JsonValue) -> Option<NumericValue> {
|
||||||
if let Some(i) = v.as_i64() {
|
if let Some(i) = v.as_i64() {
|
||||||
Some(NumericValue::Int(i))
|
Some(NumericValue::Int(i))
|
||||||
} else if let Some(f) = v.as_f64() {
|
|
||||||
Some(NumericValue::Float(f))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
v.as_f64().map(NumericValue::Float)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,8 +363,8 @@ mod tests {
|
|||||||
let ctx = TestContext::new();
|
let ctx = TestContext::new();
|
||||||
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
|
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
eval_expression("number(\"3.14\")", &ctx).unwrap(),
|
eval_expression("number(\"3.15\")", &ctx).unwrap(),
|
||||||
json!(3.14)
|
json!(3.15)
|
||||||
);
|
);
|
||||||
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
|
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
|
||||||
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));
|
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));
|
||||||
|
|||||||
@@ -431,8 +431,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_float() {
|
fn test_float() {
|
||||||
let kinds = tokenize("3.14");
|
let kinds = tokenize("3.15");
|
||||||
assert_eq!(kinds, vec![TokenKind::Float(3.14), TokenKind::Eof]);
|
assert_eq!(kinds, vec![TokenKind::Float(3.15), TokenKind::Eof]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -187,12 +187,14 @@ impl WorkflowLoader {
|
|||||||
.map(|e| e.to_string())
|
.map(|e| e.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
if validation_error.is_some() && !self.config.skip_validation {
|
if let Some(ref err) = validation_error {
|
||||||
|
if !self.config.skip_validation {
|
||||||
return Err(Error::validation(format!(
|
return Err(Error::validation(format!(
|
||||||
"Workflow validation failed: {}",
|
"Workflow validation failed: {}",
|
||||||
validation_error.as_ref().unwrap()
|
err
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(LoadedWorkflow {
|
Ok(LoadedWorkflow {
|
||||||
file: file.clone(),
|
file: file.clone(),
|
||||||
|
|||||||
@@ -1449,7 +1449,7 @@ tasks:
|
|||||||
publish:
|
publish:
|
||||||
- validation_passed: true
|
- validation_passed: true
|
||||||
- count: 42
|
- count: 42
|
||||||
- ratio: 3.14
|
- ratio: 3.15
|
||||||
- label: "hello"
|
- label: "hello"
|
||||||
- template_val: "{{ result().data }}"
|
- template_val: "{{ result().data }}"
|
||||||
- nothing: null
|
- nothing: null
|
||||||
@@ -1486,7 +1486,7 @@ tasks:
|
|||||||
} else if let Some(val) = map.get("count") {
|
} else if let Some(val) = map.get("count") {
|
||||||
assert_eq!(val, &serde_json::json!(42), "integer");
|
assert_eq!(val, &serde_json::json!(42), "integer");
|
||||||
} else if let Some(val) = map.get("ratio") {
|
} else if let Some(val) = map.get("ratio") {
|
||||||
assert_eq!(val, &serde_json::json!(3.14), "float");
|
assert_eq!(val, &serde_json::json!(3.15), "float");
|
||||||
} else if let Some(val) = map.get("label") {
|
} else if let Some(val) = map.get("label") {
|
||||||
assert_eq!(val, &serde_json::json!("hello"), "string");
|
assert_eq!(val, &serde_json::json!("hello"), "string");
|
||||||
} else if let Some(val) = map.get("template_val") {
|
} else if let Some(val) = map.get("template_val") {
|
||||||
|
|||||||
@@ -97,11 +97,11 @@ impl WorkflowRegistrar {
|
|||||||
debug!("Registering workflow: {}", loaded.file.ref_name);
|
debug!("Registering workflow: {}", loaded.file.ref_name);
|
||||||
|
|
||||||
// Check for validation errors
|
// Check for validation errors
|
||||||
if loaded.validation_error.is_some() {
|
if let Some(ref err) = loaded.validation_error {
|
||||||
if self.options.skip_invalid {
|
if self.options.skip_invalid {
|
||||||
return Err(Error::validation(format!(
|
return Err(Error::validation(format!(
|
||||||
"Workflow has validation errors: {}",
|
"Workflow has validation errors: {}",
|
||||||
loaded.validation_error.as_ref().unwrap()
|
err
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -252,6 +252,7 @@ impl WorkflowRegistrar {
|
|||||||
///
|
///
|
||||||
/// `effective_ref` and `effective_label` are the resolved values (which may
|
/// `effective_ref` and `effective_label` are the resolved values (which may
|
||||||
/// have been derived from the filename when the workflow YAML omits them).
|
/// have been derived from the filename when the workflow YAML omits them).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn create_companion_action(
|
async fn create_companion_action(
|
||||||
&self,
|
&self,
|
||||||
workflow_def_id: i64,
|
workflow_def_id: i64,
|
||||||
@@ -298,6 +299,7 @@ impl WorkflowRegistrar {
|
|||||||
///
|
///
|
||||||
/// `effective_ref` and `effective_label` are the resolved values (which may
|
/// `effective_ref` and `effective_label` are the resolved values (which may
|
||||||
/// have been derived from the filename when the workflow YAML omits them).
|
/// have been derived from the filename when the workflow YAML omits them).
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn ensure_companion_action(
|
async fn ensure_companion_action(
|
||||||
&self,
|
&self,
|
||||||
workflow_def_id: i64,
|
workflow_def_id: i64,
|
||||||
@@ -425,8 +427,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_registration_options_default() {
|
fn test_registration_options_default() {
|
||||||
let options = RegistrationOptions::default();
|
let options = RegistrationOptions::default();
|
||||||
assert_eq!(options.update_existing, true);
|
assert!(options.update_existing);
|
||||||
assert_eq!(options.skip_invalid, true);
|
assert!(options.skip_invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -439,7 +441,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(result.ref_name, "test.workflow");
|
assert_eq!(result.ref_name, "test.workflow");
|
||||||
assert_eq!(result.created, true);
|
assert!(result.created);
|
||||||
assert_eq!(result.workflow_def_id, 123);
|
assert_eq!(result.workflow_def_id, 123);
|
||||||
assert_eq!(result.warnings.len(), 0);
|
assert_eq!(result.warnings.len(), 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ impl WorkflowValidator {
|
|||||||
reachable
|
reachable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect cycles using DFS
|
// Detect cycles using DFS
|
||||||
// Cycle detection removed - cycles are now valid in workflow graphs
|
// Cycle detection removed - cycles are now valid in workflow graphs
|
||||||
// Workflows are directed graphs (not DAGs) and cycles are supported
|
// Workflows are directed graphs (not DAGs) and cycles are supported
|
||||||
// for use cases like monitoring loops, retry patterns, etc.
|
// for use cases like monitoring loops, retry patterns, etc.
|
||||||
@@ -328,7 +328,7 @@ impl WorkflowValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate variable names in vars
|
// Validate variable names in vars
|
||||||
for (key, _) in &workflow.vars {
|
for key in workflow.vars.keys() {
|
||||||
if !Self::is_valid_variable_name(key) {
|
if !Self::is_valid_variable_name(key) {
|
||||||
return Err(ValidationError::SemanticError(format!(
|
return Err(ValidationError::SemanticError(format!(
|
||||||
"Invalid variable name: {}",
|
"Invalid variable name: {}",
|
||||||
|
|||||||
@@ -781,7 +781,7 @@ async fn test_find_executions_by_enforcement() {
|
|||||||
config: None,
|
config: None,
|
||||||
env_vars: None,
|
env_vars: None,
|
||||||
parent: None,
|
parent: None,
|
||||||
enforcement: if i == 2 { None } else { None }, // Can't reference non-existent enforcement
|
enforcement: None, // Can't reference non-existent enforcement
|
||||||
executor: None,
|
executor: None,
|
||||||
status: ExecutionStatus::Requested,
|
status: ExecutionStatus::Requested,
|
||||||
result: None,
|
result: None,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async fn test_create_key_system_owner() {
|
|||||||
assert_eq!(key.owner_pack, None);
|
assert_eq!(key.owner_pack, None);
|
||||||
assert_eq!(key.owner_action, None);
|
assert_eq!(key.owner_action, None);
|
||||||
assert_eq!(key.owner_sensor, None);
|
assert_eq!(key.owner_sensor, None);
|
||||||
assert_eq!(key.encrypted, false);
|
assert!(!key.encrypted);
|
||||||
assert_eq!(key.value, "test_value");
|
assert_eq!(key.value, "test_value");
|
||||||
assert!(key.created.timestamp() > 0);
|
assert!(key.created.timestamp() > 0);
|
||||||
assert!(key.updated.timestamp() > 0);
|
assert!(key.updated.timestamp() > 0);
|
||||||
@@ -52,7 +52,7 @@ async fn test_create_key_system_encrypted() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(key.encrypted, true);
|
assert!(key.encrypted);
|
||||||
assert_eq!(key.encryption_key_hash, Some("sha256:abc123".to_string()));
|
assert_eq!(key.encryption_key_hash, Some("sha256:abc123".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ async fn test_update_encrypted_status() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(key.encrypted, false);
|
assert!(!key.encrypted);
|
||||||
|
|
||||||
let input = UpdateKeyInput {
|
let input = UpdateKeyInput {
|
||||||
encrypted: Some(true),
|
encrypted: Some(true),
|
||||||
@@ -438,7 +438,7 @@ async fn test_update_encrypted_status() {
|
|||||||
|
|
||||||
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated.encrypted, true);
|
assert!(updated.encrypted);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
updated.encryption_key_hash,
|
updated.encryption_key_hash,
|
||||||
Some("sha256:xyz789".to_string())
|
Some("sha256:xyz789".to_string())
|
||||||
@@ -468,7 +468,7 @@ async fn test_update_multiple_fields() {
|
|||||||
|
|
||||||
assert_eq!(updated.name, new_name);
|
assert_eq!(updated.name, new_name);
|
||||||
assert_eq!(updated.value, "updated_value");
|
assert_eq!(updated.value, "updated_value");
|
||||||
assert_eq!(updated.encrypted, true);
|
assert!(updated.encrypted);
|
||||||
assert_eq!(updated.encryption_key_hash, Some("hash123".to_string()));
|
assert_eq!(updated.encryption_key_hash, Some("hash123".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,10 +768,10 @@ async fn test_key_encrypted_flag() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(plain_key.encrypted, false);
|
assert!(!plain_key.encrypted);
|
||||||
assert_eq!(plain_key.encryption_key_hash, None);
|
assert_eq!(plain_key.encryption_key_hash, None);
|
||||||
|
|
||||||
assert_eq!(encrypted_key.encrypted, true);
|
assert!(encrypted_key.encrypted);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
encrypted_key.encryption_key_hash,
|
encrypted_key.encryption_key_hash,
|
||||||
Some("sha256:abc".to_string())
|
Some("sha256:abc".to_string())
|
||||||
@@ -788,7 +788,7 @@ async fn test_update_encryption_status() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(key.encrypted, false);
|
assert!(!key.encrypted);
|
||||||
|
|
||||||
// Encrypt it
|
// Encrypt it
|
||||||
let input = UpdateKeyInput {
|
let input = UpdateKeyInput {
|
||||||
@@ -800,7 +800,7 @@ async fn test_update_encryption_status() {
|
|||||||
|
|
||||||
let encrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
let encrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(encrypted.encrypted, true);
|
assert!(encrypted.encrypted);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
encrypted.encryption_key_hash,
|
encrypted.encryption_key_hash,
|
||||||
Some("sha256:newkey".to_string())
|
Some("sha256:newkey".to_string())
|
||||||
@@ -817,7 +817,7 @@ async fn test_update_encryption_status() {
|
|||||||
|
|
||||||
let decrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
let decrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(decrypted.encrypted, false);
|
assert!(!decrypted.encrypted);
|
||||||
assert_eq!(decrypted.value, "plain_value");
|
assert_eq!(decrypted.value, "plain_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -892,7 +892,7 @@ async fn test_port_range() {
|
|||||||
|
|
||||||
let worker = WorkerRepository::create(&pool, input)
|
let worker = WorkerRepository::create(&pool, input)
|
||||||
.await
|
.await
|
||||||
.expect(&format!("Failed to create worker with port {}", port));
|
.unwrap_or_else(|_| panic!("Failed to create worker with port {}", port));
|
||||||
|
|
||||||
assert_eq!(worker.port, Some(port));
|
assert_eq!(worker.port, Some(port));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ async fn test_create_rule() {
|
|||||||
rule.conditions,
|
rule.conditions,
|
||||||
json!({"equals": {"event.status": "success"}})
|
json!({"equals": {"event.status": "success"}})
|
||||||
);
|
);
|
||||||
assert_eq!(rule.enabled, true);
|
assert!(rule.enabled);
|
||||||
assert!(rule.created.timestamp() > 0);
|
assert!(rule.created.timestamp() > 0);
|
||||||
assert!(rule.updated.timestamp() > 0);
|
assert!(rule.updated.timestamp() > 0);
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ async fn test_create_rule_disabled() {
|
|||||||
|
|
||||||
let rule = RuleRepository::create(&pool, input).await.unwrap();
|
let rule = RuleRepository::create(&pool, input).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(rule.enabled, false);
|
assert!(!rule.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -759,7 +759,7 @@ async fn test_update_rule_enabled() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated.enabled, false);
|
assert!(!updated.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -816,7 +816,7 @@ async fn test_update_rule_multiple_fields() {
|
|||||||
assert_eq!(updated.label, "New Label");
|
assert_eq!(updated.label, "New Label");
|
||||||
assert_eq!(updated.description, "New Description");
|
assert_eq!(updated.description, "New Description");
|
||||||
assert_eq!(updated.conditions, json!({"updated": true}));
|
assert_eq!(updated.conditions, json!({"updated": true}));
|
||||||
assert_eq!(updated.enabled, false);
|
assert!(!updated.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ async fn test_create_sensor_minimal() {
|
|||||||
assert_eq!(sensor.runtime_ref, runtime.r#ref);
|
assert_eq!(sensor.runtime_ref, runtime.r#ref);
|
||||||
assert_eq!(sensor.trigger, trigger.id);
|
assert_eq!(sensor.trigger, trigger.id);
|
||||||
assert_eq!(sensor.trigger_ref, trigger.r#ref);
|
assert_eq!(sensor.trigger_ref, trigger.r#ref);
|
||||||
assert_eq!(sensor.enabled, true);
|
assert!(sensor.enabled);
|
||||||
assert_eq!(sensor.param_schema, None);
|
assert_eq!(sensor.param_schema, None);
|
||||||
assert!(sensor.created.timestamp() > 0);
|
assert!(sensor.created.timestamp() > 0);
|
||||||
assert!(sensor.updated.timestamp() > 0);
|
assert!(sensor.updated.timestamp() > 0);
|
||||||
@@ -796,7 +796,7 @@ async fn test_update_enabled_status() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(sensor.enabled, true);
|
assert!(sensor.enabled);
|
||||||
|
|
||||||
let input = UpdateSensorInput {
|
let input = UpdateSensorInput {
|
||||||
enabled: Some(false),
|
enabled: Some(false),
|
||||||
@@ -807,7 +807,7 @@ async fn test_update_enabled_status() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated.enabled, false);
|
assert!(!updated.enabled);
|
||||||
|
|
||||||
// Enable it again
|
// Enable it again
|
||||||
let input = UpdateSensorInput {
|
let input = UpdateSensorInput {
|
||||||
@@ -819,7 +819,7 @@ async fn test_update_enabled_status() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(updated.enabled, true);
|
assert!(updated.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -924,7 +924,7 @@ async fn test_update_multiple_fields() {
|
|||||||
assert_eq!(updated.label, "Multi Update");
|
assert_eq!(updated.label, "Multi Update");
|
||||||
assert_eq!(updated.description, "Updated multiple fields");
|
assert_eq!(updated.description, "Updated multiple fields");
|
||||||
assert_eq!(updated.entrypoint, "sensors/multi.py");
|
assert_eq!(updated.entrypoint, "sensors/multi.py");
|
||||||
assert_eq!(updated.enabled, false);
|
assert!(!updated.enabled);
|
||||||
assert_eq!(updated.param_schema, Some(json!({"type": "object"})));
|
assert_eq!(updated.param_schema, Some(json!({"type": "object"})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ async fn test_create_trigger() {
|
|||||||
assert_eq!(trigger.pack, Some(pack.id));
|
assert_eq!(trigger.pack, Some(pack.id));
|
||||||
assert_eq!(trigger.pack_ref, Some(pack.r#ref));
|
assert_eq!(trigger.pack_ref, Some(pack.r#ref));
|
||||||
assert_eq!(trigger.label, "Webhook Trigger");
|
assert_eq!(trigger.label, "Webhook Trigger");
|
||||||
assert_eq!(trigger.enabled, true);
|
assert!(trigger.enabled);
|
||||||
assert!(trigger.created.timestamp() > 0);
|
assert!(trigger.created.timestamp() > 0);
|
||||||
assert!(trigger.updated.timestamp() > 0);
|
assert!(trigger.updated.timestamp() > 0);
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ async fn test_create_trigger_disabled() {
|
|||||||
|
|
||||||
let trigger = TriggerRepository::create(&pool, input).await.unwrap();
|
let trigger = TriggerRepository::create(&pool, input).await.unwrap();
|
||||||
|
|
||||||
assert_eq!(trigger.enabled, false);
|
assert!(!trigger.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -478,7 +478,7 @@ async fn test_update_trigger() {
|
|||||||
assert_eq!(updated.r#ref, trigger.r#ref); // Ref should not change
|
assert_eq!(updated.r#ref, trigger.r#ref); // Ref should not change
|
||||||
assert_eq!(updated.label, "Updated Label");
|
assert_eq!(updated.label, "Updated Label");
|
||||||
assert_eq!(updated.description, Some("Updated description".to_string()));
|
assert_eq!(updated.description, Some("Updated description".to_string()));
|
||||||
assert_eq!(updated.enabled, false);
|
assert!(!updated.enabled);
|
||||||
assert!(updated.updated > original_updated);
|
assert!(updated.updated > original_updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ mod tests {
|
|||||||
let manager = TimerManager::new(api_client).await.unwrap();
|
let manager = TimerManager::new(api_client).await.unwrap();
|
||||||
|
|
||||||
// Test various valid cron expressions
|
// Test various valid cron expressions
|
||||||
let expressions = vec![
|
let expressions = [
|
||||||
"0 0 * * * *", // Every hour
|
"0 0 * * * *", // Every hour
|
||||||
"0 */15 * * * *", // Every 15 minutes
|
"0 */15 * * * *", // Every 15 minutes
|
||||||
"0 0 0 * * *", // Daily at midnight
|
"0 0 0 * * *", // Daily at midnight
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ impl TimerConfig {
|
|||||||
/// Rule lifecycle event types
|
/// Rule lifecycle event types
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(tag = "event_type", rename_all = "PascalCase")]
|
#[serde(tag = "event_type", rename_all = "PascalCase")]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum RuleLifecycleEvent {
|
pub enum RuleLifecycleEvent {
|
||||||
RuleCreated {
|
RuleCreated {
|
||||||
rule_id: i64,
|
rule_id: i64,
|
||||||
|
|||||||
@@ -116,13 +116,7 @@ impl CompletionListener {
|
|||||||
// Verify execution exists in database
|
// Verify execution exists in database
|
||||||
let execution = ExecutionRepository::find_by_id(pool, execution_id).await?;
|
let execution = ExecutionRepository::find_by_id(pool, execution_id).await?;
|
||||||
|
|
||||||
if execution.is_none() {
|
if let Some(ref exec) = execution {
|
||||||
warn!(
|
|
||||||
"Execution {} not found in database, but still releasing queue slot",
|
|
||||||
execution_id
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let exec = execution.as_ref().unwrap();
|
|
||||||
debug!(
|
debug!(
|
||||||
"Execution {} found with status: {:?}",
|
"Execution {} found with status: {:?}",
|
||||||
execution_id, exec.status
|
execution_id, exec.status
|
||||||
@@ -180,6 +174,11 @@ impl CompletionListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Execution {} not found in database, but still releasing queue slot",
|
||||||
|
execution_id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release queue slot for this action
|
// Release queue slot for this action
|
||||||
|
|||||||
@@ -77,8 +77,7 @@ impl DeadLetterHandler {
|
|||||||
info!("Dead letter handler stopping, rejecting message");
|
info!("Dead letter handler stopping, rejecting message");
|
||||||
return Err(attune_common::mq::MqError::Consume(
|
return Err(attune_common::mq::MqError::Consume(
|
||||||
"Handler is shutting down".to_string(),
|
"Handler is shutting down".to_string(),
|
||||||
)
|
));
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ impl EventProcessor {
|
|||||||
let payload_dict = payload
|
let payload_dict = payload
|
||||||
.as_object()
|
.as_object()
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| serde_json::Map::new());
|
.unwrap_or_else(serde_json::Map::new);
|
||||||
|
|
||||||
// Resolve action parameters using the template resolver
|
// Resolve action parameters using the template resolver
|
||||||
let resolved_params = Self::resolve_action_params(pool, rule, event, &payload).await?;
|
let resolved_params = Self::resolve_action_params(pool, rule, event, &payload).await?;
|
||||||
@@ -248,7 +248,7 @@ impl EventProcessor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// If rule has no conditions, it always matches
|
// If rule has no conditions, it always matches
|
||||||
if rule.conditions.is_null() || rule.conditions.as_array().map_or(true, |a| a.is_empty()) {
|
if rule.conditions.is_null() || rule.conditions.as_array().is_none_or(|a| a.is_empty()) {
|
||||||
debug!("Rule {} has no conditions, matching by default", rule.r#ref);
|
debug!("Rule {} has no conditions, matching by default", rule.r#ref);
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ impl EventProcessor {
|
|||||||
let action_params = &rule.action_params;
|
let action_params = &rule.action_params;
|
||||||
|
|
||||||
// If there are no action params, return empty
|
// If there are no action params, return empty
|
||||||
if action_params.is_null() || action_params.as_object().map_or(true, |o| o.is_empty()) {
|
if action_params.is_null() || action_params.as_object().is_none_or(|o| o.is_empty()) {
|
||||||
return Ok(serde_json::Map::new());
|
return Ok(serde_json::Map::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -257,7 +257,6 @@ mod tests {
|
|||||||
fn test_execution_manager_creation() {
|
fn test_execution_manager_creation() {
|
||||||
// This is a placeholder test
|
// This is a placeholder test
|
||||||
// Real tests will require database and message queue setup
|
// Real tests will require database and message queue setup
|
||||||
assert!(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ use crate::queue_manager::ExecutionQueueManager;
|
|||||||
|
|
||||||
/// Policy violation type
|
/// Policy violation type
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum PolicyViolation {
|
pub enum PolicyViolation {
|
||||||
/// Rate limit exceeded
|
/// Rate limit exceeded
|
||||||
RateLimitExceeded {
|
RateLimitExceeded {
|
||||||
@@ -78,7 +79,7 @@ impl std::fmt::Display for PolicyViolation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execution policy configuration
|
/// Execution policy configuration
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct ExecutionPolicy {
|
pub struct ExecutionPolicy {
|
||||||
/// Rate limit: maximum executions per time window
|
/// Rate limit: maximum executions per time window
|
||||||
pub rate_limit: Option<RateLimit>,
|
pub rate_limit: Option<RateLimit>,
|
||||||
@@ -88,16 +89,6 @@ pub struct ExecutionPolicy {
|
|||||||
pub quotas: Option<HashMap<String, u64>>,
|
pub quotas: Option<HashMap<String, u64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExecutionPolicy {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
rate_limit: None,
|
|
||||||
concurrency_limit: None,
|
|
||||||
quotas: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rate limit configuration
|
/// Rate limit configuration
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct RateLimit {
|
pub struct RateLimit {
|
||||||
|
|||||||
@@ -78,11 +78,7 @@ fn apply_param_defaults(params: JsonValue, param_schema: &Option<JsonValue>) ->
|
|||||||
if let Some(schema_obj) = schema.as_object() {
|
if let Some(schema_obj) = schema.as_object() {
|
||||||
for (key, prop) in schema_obj {
|
for (key, prop) in schema_obj {
|
||||||
// Only fill in missing / null parameters
|
// Only fill in missing / null parameters
|
||||||
let needs_default = match obj.get(key) {
|
let needs_default = matches!(obj.get(key), None | Some(JsonValue::Null));
|
||||||
None => true,
|
|
||||||
Some(JsonValue::Null) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if needs_default {
|
if needs_default {
|
||||||
if let Some(default_val) = prop.get("default") {
|
if let Some(default_val) = prop.get("default") {
|
||||||
debug!("Applying default for parameter '{}': {}", key, default_val);
|
debug!("Applying default for parameter '{}': {}", key, default_val);
|
||||||
@@ -411,6 +407,7 @@ impl ExecutionScheduler {
|
|||||||
/// `triggered_by` is the name of the predecessor task whose completion
|
/// `triggered_by` is the name of the predecessor task whose completion
|
||||||
/// caused this task to be scheduled. Pass `None` for entry-point tasks
|
/// caused this task to be scheduled. Pass `None` for entry-point tasks
|
||||||
/// dispatched at workflow start.
|
/// dispatched at workflow start.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn dispatch_workflow_task(
|
async fn dispatch_workflow_task(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
publisher: &Publisher,
|
publisher: &Publisher,
|
||||||
@@ -496,10 +493,8 @@ impl ExecutionScheduler {
|
|||||||
let task_config: Option<JsonValue> =
|
let task_config: Option<JsonValue> =
|
||||||
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
||||||
Some(rendered_input.clone())
|
Some(rendered_input.clone())
|
||||||
} else if let Some(parent_config) = &parent_execution.config {
|
|
||||||
Some(parent_config.clone())
|
|
||||||
} else {
|
} else {
|
||||||
None
|
parent_execution.config.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build workflow task metadata
|
// Build workflow task metadata
|
||||||
@@ -660,10 +655,8 @@ impl ExecutionScheduler {
|
|||||||
let task_config: Option<JsonValue> =
|
let task_config: Option<JsonValue> =
|
||||||
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
|
||||||
Some(rendered_input.clone())
|
Some(rendered_input.clone())
|
||||||
} else if let Some(parent_config) = &parent_execution.config {
|
|
||||||
Some(parent_config.clone())
|
|
||||||
} else {
|
} else {
|
||||||
None
|
parent_execution.config.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let workflow_task = WorkflowTaskMetadata {
|
let workflow_task = WorkflowTaskMetadata {
|
||||||
@@ -1108,19 +1101,19 @@ impl ExecutionScheduler {
|
|||||||
let mut task_results_map: HashMap<String, JsonValue> = HashMap::new();
|
let mut task_results_map: HashMap<String, JsonValue> = HashMap::new();
|
||||||
for child in &child_executions {
|
for child in &child_executions {
|
||||||
if let Some(ref wt) = child.workflow_task {
|
if let Some(ref wt) = child.workflow_task {
|
||||||
if wt.workflow_execution == workflow_execution_id {
|
if wt.workflow_execution == workflow_execution_id
|
||||||
if matches!(
|
&& matches!(
|
||||||
child.status,
|
child.status,
|
||||||
ExecutionStatus::Completed
|
ExecutionStatus::Completed
|
||||||
| ExecutionStatus::Failed
|
| ExecutionStatus::Failed
|
||||||
| ExecutionStatus::Timeout
|
| ExecutionStatus::Timeout
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
|
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
|
||||||
task_results_map.insert(wt.task_name.clone(), result_val);
|
task_results_map.insert(wt.task_name.clone(), result_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let mut wf_ctx = WorkflowContext::rebuild(
|
let mut wf_ctx = WorkflowContext::rebuild(
|
||||||
workflow_params,
|
workflow_params,
|
||||||
@@ -1514,7 +1507,7 @@ impl ExecutionScheduler {
|
|||||||
// Filter by heartbeat freshness (only workers with recent heartbeats)
|
// Filter by heartbeat freshness (only workers with recent heartbeats)
|
||||||
let fresh_workers: Vec<_> = active_workers
|
let fresh_workers: Vec<_> = active_workers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|w| Self::is_worker_heartbeat_fresh(w))
|
.filter(Self::is_worker_heartbeat_fresh)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if fresh_workers.is_empty() {
|
if fresh_workers.is_empty() {
|
||||||
@@ -1749,27 +1742,23 @@ mod tests {
|
|||||||
fn test_scheduler_creation() {
|
fn test_scheduler_creation() {
|
||||||
// This is a placeholder test
|
// This is a placeholder test
|
||||||
// Real tests will require database and message queue setup
|
// Real tests will require database and message queue setup
|
||||||
assert!(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_concurrency_limit_dispatch_count() {
|
fn test_concurrency_limit_dispatch_count() {
|
||||||
// Verify the dispatch_count calculation used by dispatch_with_items_task
|
// Verify the dispatch_count calculation used by dispatch_with_items_task
|
||||||
let total = 20usize;
|
let total = 20usize;
|
||||||
let concurrency: Option<usize> = Some(3);
|
let concurrency_limit = 3usize;
|
||||||
let concurrency_limit = concurrency.unwrap_or(total);
|
|
||||||
let dispatch_count = total.min(concurrency_limit);
|
let dispatch_count = total.min(concurrency_limit);
|
||||||
assert_eq!(dispatch_count, 3);
|
assert_eq!(dispatch_count, 3);
|
||||||
|
|
||||||
// No concurrency limit → default to serial (1 at a time)
|
// No concurrency limit → default to serial (1 at a time)
|
||||||
let concurrency: Option<usize> = None;
|
let concurrency_limit = 1usize;
|
||||||
let concurrency_limit = concurrency.unwrap_or(1);
|
|
||||||
let dispatch_count = total.min(concurrency_limit);
|
let dispatch_count = total.min(concurrency_limit);
|
||||||
assert_eq!(dispatch_count, 1);
|
assert_eq!(dispatch_count, 1);
|
||||||
|
|
||||||
// Concurrency exceeds total → dispatch all
|
// Concurrency exceeds total → dispatch all
|
||||||
let concurrency: Option<usize> = Some(50);
|
let concurrency_limit = 50usize;
|
||||||
let concurrency_limit = concurrency.unwrap_or(total);
|
|
||||||
let dispatch_count = total.min(concurrency_limit);
|
let dispatch_count = total.min(concurrency_limit);
|
||||||
assert_eq!(dispatch_count, 20);
|
assert_eq!(dispatch_count, 20);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ impl WorkerHealthProbe {
|
|||||||
|
|
||||||
runtime_array.iter().any(|v| {
|
runtime_array.iter().any(|v| {
|
||||||
v.as_str()
|
v.as_str()
|
||||||
.map_or(false, |s| s.eq_ignore_ascii_case(runtime_name))
|
.is_some_and(|s| s.eq_ignore_ascii_case(runtime_name))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1129,7 +1129,7 @@ mod tests {
|
|||||||
let mut publish_map = HashMap::new();
|
let mut publish_map = HashMap::new();
|
||||||
publish_map.insert("flag".to_string(), JsonValue::Bool(true));
|
publish_map.insert("flag".to_string(), JsonValue::Bool(true));
|
||||||
publish_map.insert("count".to_string(), json!(42));
|
publish_map.insert("count".to_string(), json!(42));
|
||||||
publish_map.insert("ratio".to_string(), json!(3.14));
|
publish_map.insert("ratio".to_string(), json!(3.15));
|
||||||
publish_map.insert("nothing".to_string(), JsonValue::Null);
|
publish_map.insert("nothing".to_string(), JsonValue::Null);
|
||||||
publish_map.insert(
|
publish_map.insert(
|
||||||
"template".to_string(),
|
"template".to_string(),
|
||||||
@@ -1152,7 +1152,7 @@ mod tests {
|
|||||||
assert!(ctx.get_var("count").unwrap().is_number());
|
assert!(ctx.get_var("count").unwrap().is_number());
|
||||||
|
|
||||||
// Float preserved
|
// Float preserved
|
||||||
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.14));
|
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.15));
|
||||||
|
|
||||||
// Null preserved
|
// Null preserved
|
||||||
assert_eq!(ctx.get_var("nothing").unwrap(), json!(null));
|
assert_eq!(ctx.get_var("nothing").unwrap(), json!(null));
|
||||||
|
|||||||
@@ -965,7 +965,7 @@ tasks:
|
|||||||
publish:
|
publish:
|
||||||
- validation_passed: true
|
- validation_passed: true
|
||||||
- count: 42
|
- count: 42
|
||||||
- ratio: 3.14
|
- ratio: 3.15
|
||||||
- label: "hello"
|
- label: "hello"
|
||||||
- template_val: "{{ result().data }}"
|
- template_val: "{{ result().data }}"
|
||||||
- nothing: null
|
- nothing: null
|
||||||
@@ -1003,7 +1003,7 @@ tasks:
|
|||||||
assert_eq!(publish_map["count"], &serde_json::json!(42));
|
assert_eq!(publish_map["count"], &serde_json::json!(42));
|
||||||
|
|
||||||
// Float is preserved as a JSON number
|
// Float is preserved as a JSON number
|
||||||
assert_eq!(publish_map["ratio"], &serde_json::json!(3.14));
|
assert_eq!(publish_map["ratio"], &serde_json::json!(3.15));
|
||||||
|
|
||||||
// Plain string stays as a string
|
// Plain string stays as a string
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -357,7 +357,7 @@ impl SensorManager {
|
|||||||
let mut child = cmd
|
let mut child = cmd
|
||||||
.env("ATTUNE_API_URL", &self.inner.api_url)
|
.env("ATTUNE_API_URL", &self.inner.api_url)
|
||||||
.env("ATTUNE_API_TOKEN", &token_response.token)
|
.env("ATTUNE_API_TOKEN", &token_response.token)
|
||||||
.env("ATTUNE_SENSOR_ID", &sensor.id.to_string())
|
.env("ATTUNE_SENSOR_ID", sensor.id.to_string())
|
||||||
.env("ATTUNE_SENSOR_REF", &sensor.r#ref)
|
.env("ATTUNE_SENSOR_REF", &sensor.r#ref)
|
||||||
.env("ATTUNE_SENSOR_TRIGGERS", &trigger_instances_json)
|
.env("ATTUNE_SENSOR_TRIGGERS", &trigger_instances_json)
|
||||||
.env("ATTUNE_MQ_URL", &self.inner.mq_url)
|
.env("ATTUNE_MQ_URL", &self.inner.mq_url)
|
||||||
@@ -731,6 +731,7 @@ impl SensorInstance {
|
|||||||
|
|
||||||
/// Sensor status information
|
/// Sensor status information
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[derive(Default)]
|
||||||
pub struct SensorStatus {
|
pub struct SensorStatus {
|
||||||
/// Whether the sensor is running
|
/// Whether the sensor is running
|
||||||
pub running: bool,
|
pub running: bool,
|
||||||
@@ -745,16 +746,6 @@ pub struct SensorStatus {
|
|||||||
pub last_poll: Option<chrono::DateTime<chrono::Utc>>,
|
pub last_poll: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SensorStatus {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
running: false,
|
|
||||||
failed: false,
|
|
||||||
failure_count: 0,
|
|
||||||
last_poll: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
@@ -381,14 +381,12 @@ async fn setup_environments_for_pack(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
// Also set up version-specific environments
|
// Also set up version-specific environments
|
||||||
let versions = match RuntimeVersionRepository::find_available_by_runtime(
|
let versions: Vec<attune_common::models::RuntimeVersion> =
|
||||||
|
RuntimeVersionRepository::find_available_by_runtime(
|
||||||
db_pool, runtime_id,
|
db_pool, runtime_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
.unwrap_or_default();
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => Vec::new(),
|
|
||||||
};
|
|
||||||
setup_version_environments_from_list(
|
setup_version_environments_from_list(
|
||||||
&versions,
|
&versions,
|
||||||
&rt_name,
|
&rt_name,
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ fn normalize_api_url(raw_url: &str) -> String {
|
|||||||
|
|
||||||
impl ActionExecutor {
|
impl ActionExecutor {
|
||||||
/// Create a new action executor
|
/// Create a new action executor
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
runtime_registry: RuntimeRegistry,
|
runtime_registry: RuntimeRegistry,
|
||||||
@@ -359,8 +360,7 @@ impl ActionExecutor {
|
|||||||
|
|
||||||
// Add context data as environment variables from config
|
// Add context data as environment variables from config
|
||||||
if let Some(config) = &execution.config {
|
if let Some(config) = &execution.config {
|
||||||
if let Some(context) = config.get("context") {
|
if let Some(JsonValue::Object(map)) = config.get("context") {
|
||||||
if let JsonValue::Object(map) = context {
|
|
||||||
for (key, value) in map {
|
for (key, value) in map {
|
||||||
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
|
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
|
||||||
let env_value = match value {
|
let env_value = match value {
|
||||||
@@ -373,7 +373,6 @@ impl ActionExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch secrets (passed securely via stdin, not environment variables)
|
// Fetch secrets (passed securely via stdin, not environment variables)
|
||||||
let secrets = match self.secret_manager.fetch_secrets_for_action(action).await {
|
let secrets = match self.secret_manager.fetch_secrets_for_action(action).await {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ impl WorkerRegistration {
|
|||||||
let worker_type = config
|
let worker_type = config
|
||||||
.worker
|
.worker
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|w| w.worker_type.clone())
|
.and_then(|w| w.worker_type)
|
||||||
.unwrap_or(WorkerType::Local);
|
.unwrap_or(WorkerType::Local);
|
||||||
|
|
||||||
let worker_role = WorkerRole::Action;
|
let worker_role = WorkerRole::Action;
|
||||||
@@ -180,8 +180,8 @@ impl WorkerRegistration {
|
|||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(&self.worker_name)
|
.bind(&self.worker_name)
|
||||||
.bind(&self.worker_type)
|
.bind(self.worker_type)
|
||||||
.bind(&self.worker_role)
|
.bind(self.worker_role)
|
||||||
.bind(self.runtime_id)
|
.bind(self.runtime_id)
|
||||||
.bind(&self.host)
|
.bind(&self.host)
|
||||||
.bind(self.port)
|
.bind(self.port)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ impl NativeRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a native binary with parameters and environment variables
|
/// Execute a native binary with parameters and environment variables
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn execute_binary(
|
async fn execute_binary(
|
||||||
&self,
|
&self,
|
||||||
binary_path: PathBuf,
|
binary_path: PathBuf,
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ pub fn create_parameter_file(
|
|||||||
) -> Result<NamedTempFile, RuntimeError> {
|
) -> Result<NamedTempFile, RuntimeError> {
|
||||||
let formatted = format_parameters(parameters, format)?;
|
let formatted = format_parameters(parameters, format)?;
|
||||||
|
|
||||||
let mut temp_file = NamedTempFile::new().map_err(|e| RuntimeError::IoError(e))?;
|
let mut temp_file = NamedTempFile::new().map_err(RuntimeError::IoError)?;
|
||||||
|
|
||||||
// Set restrictive permissions (owner read-only)
|
// Set restrictive permissions (owner read-only)
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
@@ -126,20 +126,20 @@ pub fn create_parameter_file(
|
|||||||
let mut perms = temp_file
|
let mut perms = temp_file
|
||||||
.as_file()
|
.as_file()
|
||||||
.metadata()
|
.metadata()
|
||||||
.map_err(|e| RuntimeError::IoError(e))?
|
.map_err(RuntimeError::IoError)?
|
||||||
.permissions();
|
.permissions();
|
||||||
perms.set_mode(0o400); // Read-only for owner
|
perms.set_mode(0o400); // Read-only for owner
|
||||||
temp_file
|
temp_file
|
||||||
.as_file()
|
.as_file()
|
||||||
.set_permissions(perms)
|
.set_permissions(perms)
|
||||||
.map_err(|e| RuntimeError::IoError(e))?;
|
.map_err(RuntimeError::IoError)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_file
|
temp_file
|
||||||
.write_all(formatted.as_bytes())
|
.write_all(formatted.as_bytes())
|
||||||
.map_err(|e| RuntimeError::IoError(e))?;
|
.map_err(RuntimeError::IoError)?;
|
||||||
|
|
||||||
temp_file.flush().map_err(|e| RuntimeError::IoError(e))?;
|
temp_file.flush().map_err(RuntimeError::IoError)?;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Created parameter file at {:?} with format {:?}",
|
"Created parameter file at {:?} with format {:?}",
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ pub async fn execute_streaming(
|
|||||||
/// * `max_stderr_bytes` - Maximum stderr size before truncation
|
/// * `max_stderr_bytes` - Maximum stderr size before truncation
|
||||||
/// * `output_format` - How to parse stdout (Text, Json, Yaml, Jsonl)
|
/// * `output_format` - How to parse stdout (Text, Json, Yaml, Jsonl)
|
||||||
/// * `cancel_token` - Optional cancellation token for graceful process termination
|
/// * `cancel_token` - Optional cancellation token for graceful process termination
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn execute_streaming_cancellable(
|
pub async fn execute_streaming_cancellable(
|
||||||
mut cmd: Command,
|
mut cmd: Command,
|
||||||
secrets: &HashMap<String, String>,
|
secrets: &HashMap<String, String>,
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ impl ShellRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute with streaming and bounded log collection
|
/// Execute with streaming and bounded log collection
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn execute_with_streaming(
|
async fn execute_with_streaming(
|
||||||
&self,
|
&self,
|
||||||
mut cmd: Command,
|
mut cmd: Command,
|
||||||
@@ -383,6 +384,7 @@ impl ShellRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute shell script from file
|
/// Execute shell script from file
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn execute_shell_file(
|
async fn execute_shell_file(
|
||||||
&self,
|
&self,
|
||||||
script_path: PathBuf,
|
script_path: PathBuf,
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ impl SecretManager {
|
|||||||
.map_err(|e| Error::Internal(format!("Encryption failed: {}", e)))?;
|
.map_err(|e| Error::Internal(format!("Encryption failed: {}", e)))?;
|
||||||
|
|
||||||
// Format: "nonce:ciphertext" (both base64-encoded)
|
// Format: "nonce:ciphertext" (both base64-encoded)
|
||||||
let nonce_b64 = BASE64.encode(&nonce);
|
let nonce_b64 = BASE64.encode(nonce);
|
||||||
let ciphertext_b64 = BASE64.encode(&ciphertext);
|
let ciphertext_b64 = BASE64.encode(&ciphertext);
|
||||||
|
|
||||||
Ok(format!("{}:{}", nonce_b64, ciphertext_b64))
|
Ok(format!("{}:{}", nonce_b64, ciphertext_b64))
|
||||||
|
|||||||
@@ -443,6 +443,7 @@ impl WorkerService {
|
|||||||
/// 3. Wait for in-flight tasks with timeout
|
/// 3. Wait for in-flight tasks with timeout
|
||||||
/// 4. Close MQ connection
|
/// 4. Close MQ connection
|
||||||
/// 5. Close DB connection
|
/// 5. Close DB connection
|
||||||
|
///
|
||||||
/// Verify which runtime versions are available on this host/container.
|
/// Verify which runtime versions are available on this host/container.
|
||||||
///
|
///
|
||||||
/// Runs each version's verification commands (from `distributions` JSONB)
|
/// Runs each version's verification commands (from `distributions` JSONB)
|
||||||
@@ -634,7 +635,7 @@ impl WorkerService {
|
|||||||
shutdown_timeout
|
shutdown_timeout
|
||||||
);
|
);
|
||||||
|
|
||||||
let timeout_duration = Duration::from_secs(shutdown_timeout as u64);
|
let timeout_duration = Duration::from_secs(shutdown_timeout);
|
||||||
match tokio::time::timeout(timeout_duration, self.wait_for_in_flight_tasks()).await {
|
match tokio::time::timeout(timeout_duration, self.wait_for_in_flight_tasks()).await {
|
||||||
Ok(_) => info!("All in-flight tasks completed"),
|
Ok(_) => info!("All in-flight tasks completed"),
|
||||||
Err(_) => warn!("Shutdown timeout reached - some tasks may have been interrupted"),
|
Err(_) => warn!("Shutdown timeout reached - some tasks may have been interrupted"),
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ pub async fn verify_all_runtime_versions(
|
|||||||
let rt_base_name = version
|
let rt_base_name = version
|
||||||
.runtime_ref
|
.runtime_ref
|
||||||
.split('.')
|
.split('.')
|
||||||
.last()
|
.next_back()
|
||||||
.unwrap_or(&version.runtime_ref)
|
.unwrap_or(&version.runtime_ref)
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://unpkg.com/knip@latest/schema.json",
|
"$schema": "https://unpkg.com/knip@latest/schema.json",
|
||||||
"entry": ["src/main.tsx", "vite.config.ts"],
|
"entry": ["src/**/*.{ts,tsx}", "vite.config.ts"],
|
||||||
"project": ["src/**/*.{ts,tsx}", "scripts/**/*.js"],
|
"project": ["src/**/*.{ts,tsx}", "scripts/**/*.js"],
|
||||||
"ignore": ["src/api/**", "dist/**", "node_modules/**"]
|
"ignore": ["src/api/**", "dist/**", "node_modules/**"]
|
||||||
}
|
}
|
||||||
|
|||||||
272
web/package-lock.json
generated
272
web/package-lock.json
generated
@@ -15,8 +15,7 @@
|
|||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0"
|
||||||
"zustand": "^5.0.10"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
@@ -1100,9 +1099,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
|
||||||
"integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==",
|
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1114,9 +1113,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==",
|
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1128,9 +1127,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==",
|
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1142,9 +1141,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==",
|
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1156,9 +1155,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==",
|
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1170,9 +1169,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==",
|
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1184,9 +1183,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==",
|
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1198,9 +1197,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
|
||||||
"integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==",
|
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@@ -1212,9 +1211,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==",
|
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1226,9 +1225,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==",
|
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1240,9 +1239,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==",
|
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -1254,9 +1253,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==",
|
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
@@ -1268,9 +1267,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==",
|
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -1282,9 +1281,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==",
|
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
@@ -1296,9 +1295,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==",
|
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -1310,9 +1309,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==",
|
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@@ -1324,9 +1323,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==",
|
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
@@ -1338,9 +1337,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==",
|
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1352,9 +1351,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
|
||||||
"integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==",
|
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1366,9 +1365,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
|
||||||
"integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==",
|
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1380,9 +1379,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
|
||||||
"integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==",
|
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1394,9 +1393,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==",
|
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -1408,9 +1407,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==",
|
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@@ -1422,9 +1421,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
|
||||||
"integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==",
|
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1436,9 +1435,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
|
||||||
"integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==",
|
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -1557,7 +1556,7 @@
|
|||||||
"version": "19.2.10",
|
"version": "19.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
||||||
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1774,13 +1773,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
@@ -1890,9 +1889,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2000,13 +1999,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios": {
|
"node_modules/axios": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.6",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||||
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
|
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"follow-redirects": "^1.15.6",
|
"follow-redirects": "^1.15.11",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.5",
|
||||||
"proxy-from-env": "^1.1.0"
|
"proxy-from-env": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2322,7 +2321,7 @@
|
|||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
@@ -3430,9 +3429,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4036,9 +4035,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.57.0",
|
"version": "4.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||||
"integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==",
|
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4052,31 +4051,31 @@
|
|||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.57.0",
|
"@rollup/rollup-android-arm-eabi": "4.59.0",
|
||||||
"@rollup/rollup-android-arm64": "4.57.0",
|
"@rollup/rollup-android-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-arm64": "4.57.0",
|
"@rollup/rollup-darwin-arm64": "4.59.0",
|
||||||
"@rollup/rollup-darwin-x64": "4.57.0",
|
"@rollup/rollup-darwin-x64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.57.0",
|
"@rollup/rollup-freebsd-arm64": "4.59.0",
|
||||||
"@rollup/rollup-freebsd-x64": "4.57.0",
|
"@rollup/rollup-freebsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.57.0",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.57.0",
|
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.57.0",
|
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.57.0",
|
"@rollup/rollup-linux-arm64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.57.0",
|
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-loong64-musl": "4.57.0",
|
"@rollup/rollup-linux-loong64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.57.0",
|
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-ppc64-musl": "4.57.0",
|
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.57.0",
|
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.57.0",
|
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.57.0",
|
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.57.0",
|
"@rollup/rollup-linux-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.57.0",
|
"@rollup/rollup-linux-x64-musl": "4.59.0",
|
||||||
"@rollup/rollup-openbsd-x64": "4.57.0",
|
"@rollup/rollup-openbsd-x64": "4.59.0",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.57.0",
|
"@rollup/rollup-openharmony-arm64": "4.59.0",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.57.0",
|
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.57.0",
|
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.57.0",
|
"@rollup/rollup-win32-x64-gnu": "4.59.0",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.57.0",
|
"@rollup/rollup-win32-x64-msvc": "4.59.0",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4699,35 +4698,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"zod": "^3.25.0 || ^4.0.0"
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/zustand": {
|
|
||||||
"version": "5.0.10",
|
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
|
|
||||||
"integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.20.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": ">=18.0.0",
|
|
||||||
"immer": ">=9.0.6",
|
|
||||||
"react": ">=18.0.0",
|
|
||||||
"use-sync-external-store": ">=1.2.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"immer": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"use-sync-external-store": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-router-dom": "^7.12.0",
|
"react-router-dom": "^7.12.0"
|
||||||
"zustand": "^5.0.10"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import "./lib/api-config";
|
import "./lib/api-config";
|
||||||
import { initializeApiWrapper } from "./lib/api-wrapper";
|
import { initializeApiWrapper } from "./lib/api-wrapper";
|
||||||
import App from "./App.tsx";
|
import App from "./App";
|
||||||
|
|
||||||
// Initialize API wrapper for token refresh
|
// Initialize API wrapper for token refresh
|
||||||
initializeApiWrapper();
|
initializeApiWrapper();
|
||||||
|
|||||||
Reference in New Issue
Block a user