Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions helix-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ path = "src/main.rs"
normal = ["helix-db/server"]
ingestion = ["helix-db/full"]
default = ["normal"]


[[bin]]
name = "helix_test_env_parser"
path = "src/test_env_parser.rs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Missing newline at end of file

Prompt To Fix With AI
This is a comment left during a code review.
Path: helix-cli/Cargo.toml
Line: 40:40

Comment:
**style:** Missing newline at end of file

How can I resolve this? If you propose a fix, please make it concise.

1 change: 1 addition & 0 deletions helix-cli/src/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub async fn run(deployment_type: CloudDeploymentTypeCommand) -> Result<()> {
let local_config = LocalInstanceConfig {
port: None, // Let the system assign a port
build_mode: BuildMode::Debug,
env_file: None,
db_config: DbConfig::default(),
};

Expand Down
2 changes: 1 addition & 1 deletion helix-cli/src/commands/integrations/fly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ impl<'a> FlyManager<'a> {
docker.push(image_name, FLY_REGISTRY_URL)?;

// Get environment variables first to ensure they live long enough
let env_vars = docker.environment_variables(instance_name);
let env_vars = docker.environment_variables(instance_name)?;

let mut deploy_args = vec![
"deploy",
Expand Down
1 change: 1 addition & 0 deletions helix-cli/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ fn create_v2_config(ctx: &MigrationContext) -> Result<()> {
let local_config = LocalInstanceConfig {
port: Some(ctx.port),
build_mode: BuildMode::Debug,
env_file: None,
db_config,
};

Expand Down
3 changes: 3 additions & 0 deletions helix-cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ pub struct LocalInstanceConfig {
pub port: Option<u16>,
#[serde(default = "default_dev_build_mode")]
pub build_mode: BuildMode,
#[serde(default)]
pub env_file: Option<PathBuf>,
#[serde(flatten)]
pub db_config: DbConfig,
}
Expand Down Expand Up @@ -378,6 +380,7 @@ impl HelixConfig {
LocalInstanceConfig {
port: Some(6969),
build_mode: BuildMode::Debug,
env_file: None,
db_config: DbConfig::default(),
},
);
Expand Down
60 changes: 37 additions & 23 deletions helix-cli/src/docker.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::config::{BuildMode, InstanceInfo};
use crate::env_utils::load_env_variables;
use crate::project::ProjectContext;
use crate::utils::print_status;
use eyre::{Result, eyre};
Expand Down Expand Up @@ -44,25 +45,35 @@ impl<'a> DockerManager<'a> {
}

/// Get environment variables for an instance
pub(crate) fn environment_variables(&self, instance_name: &str) -> Vec<String> {
vec![
{
let port = self
.project
.config
.get_instance(instance_name)
.unwrap()
.port()
.unwrap_or(6969);
format!("HELIX_PORT={port}")
},
pub(crate) fn environment_variables(&self, instance_name: &str) -> Result<Vec<String>> {
let instance = self.project.config.get_instance(instance_name)?;

// Start with Helix built-in variables (highest priority)
let mut env_vars = vec![
format!("HELIX_PORT={}", instance.port().unwrap_or(6969)),
format!("HELIX_DATA_DIR={HELIX_DATA_DIR}"),
format!("HELIX_INSTANCE={instance_name}"),
{
let project_name = &self.project.config.project.name;
format!("HELIX_PROJECT={project_name}")
},
]
format!("HELIX_PROJECT={}", self.project.config.project.name),
];

// Load user-defined environment variables if this is a local instance
if let InstanceInfo::Local(local_config) = instance {
if let Some(env_file) = &local_config.env_file {
let user_vars = load_env_variables(
Some(env_file.as_path()),
&self.project.root,
)?;

// Add user variables that don't conflict with Helix built-ins
for (key, value) in user_vars {
if !key.starts_with("HELIX_") {
env_vars.push(format!("{key}={value}"));
}
}
}
}

Ok(env_vars)
}

/// Get the container name for an instance
Expand Down Expand Up @@ -220,6 +231,14 @@ CMD ["helix-container"]
let container_name = self.container_name(instance_name);
let network_name = self.network_name(instance_name);

// Get environment variables including user-defined ones
let env_vars = self.environment_variables(instance_name)?;
let env_section = env_vars
.iter()
.map(|var| format!(" - {var}"))
.collect::<Vec<_>>()
.join("\n");

let compose = format!(
r#"# Generated docker-compose.yml for Helix instance: {instance_name}
services:
Expand All @@ -235,10 +254,7 @@ services:
volumes:
- ../.volumes/{instance_name}:/data
environment:
- HELIX_PORT={port}
- HELIX_DATA_DIR={data_dir}
- HELIX_INSTANCE={instance_name}
- HELIX_PROJECT={project_name}
{env_section}
restart: unless-stopped
networks:
- {network_name}
Expand All @@ -250,8 +266,6 @@ networks:
platform = instance_config
.docker_build_target()
.map_or("".to_string(), |p| format!("platforms:\n - {p}")),
project_name = self.project.config.project.name,
data_dir = HELIX_DATA_DIR,
);

Ok(compose)
Expand Down
70 changes: 70 additions & 0 deletions helix-cli/src/env_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use eyre::Result;
use std::collections::HashMap;
use std::path::Path;

/// Load environment variables from .env file and shell environment
///
/// Precedence order (highest to lowest):
/// 1. Shell environment variables
/// 2. .env file variables
pub fn load_env_variables(
env_file_path: Option<&Path>,
project_root: &Path,
) -> Result<HashMap<String, String>> {
let mut vars = HashMap::new();

// Load from .env file if specified
if let Some(env_path) = env_file_path {
let full_path = project_root.join(env_path);
if full_path.exists() {
// Parse the .env file with improved handling
let env_content = std::fs::read_to_string(&full_path)?;
for (line_num, line) in env_content.lines().enumerate() {
let trimmed = line.trim();
// Skip empty lines and comments
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}

// Parse KEY=VALUE pairs (find first = to handle values with = in them)
if let Some(eq_pos) = trimmed.find('=') {
let key = trimmed[..eq_pos].trim();
let value = trimmed[eq_pos + 1..].trim();

// Validate key
if key.is_empty() {
eprintln!("Warning: Skipping empty key at line {} in {}",
line_num + 1, full_path.display());
continue;
}

// Remove surrounding quotes if present (both single and double)
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\'')) {
&value[1..value.len() - 1]
} else {
value
};
Comment on lines +42 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: quote removal logic could fail on single-character quoted strings due to potential underflow in value.len() - 1

Suggested change
let value = if (value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\'')) {
&value[1..value.len() - 1]
} else {
value
};
let value = if value.len() >= 2 && ((value.starts_with('"') && value.ends_with('"'))
|| (value.starts_with('\'') && value.ends_with('\''))) {
&value[1..value.len() - 1]
} else {
value
};
Prompt To Fix With AI
This is a comment left during a code review.
Path: helix-cli/src/env_utils.rs
Line: 42:47

Comment:
**logic:** quote removal logic could fail on single-character quoted strings due to potential underflow in `value.len() - 1`

```suggestion
                    let value = if value.len() >= 2 && ((value.starts_with('"') && value.ends_with('"'))
                        || (value.starts_with('\'') && value.ends_with('\''))) {
                        &value[1..value.len() - 1]
                    } else {
                        value
                    };
```

How can I resolve this? If you propose a fix, please make it concise.


vars.insert(key.to_string(), value.to_string());
} else if !trimmed.starts_with("export ") {
// Skip lines that start with "export " (shell syntax)
eprintln!("Warning: Skipping malformed line {} in {}: {}",
line_num + 1, full_path.display(), trimmed);
}
}
} else {
eprintln!("Warning: env_file '{}' not found, skipping", full_path.display());
}
}

// Add shell environment variables (higher priority - overwrites .env)
for (key, value) in std::env::vars() {
// Skip HELIX_ prefixed variables as they're managed internally
if !key.starts_with("HELIX_") {
vars.insert(key, value);
}
}

Ok(vars)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: missing newline at end of file

Prompt To Fix With AI
This is a comment left during a code review.
Path: helix-cli/src/env_utils.rs
Line: 70:70

Comment:
**style:** missing newline at end of file

How can I resolve this? If you propose a fix, please make it concise.

1 change: 1 addition & 0 deletions helix-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use eyre::Result;
mod commands;
mod config;
mod docker;
mod env_utils;
mod errors;
mod metrics_sender;
mod project;
Expand Down
Loading