Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
},
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {}
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {},
"ghcr.io/devcontainers/features/common-utils": {
"installOhMyZsh": true,
"configureZshAsDefaultShell": true
}
},
"remoteUser": "node",
"updateContentCommand": "npm install -g @devcontainers/cli"
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-feature feature:
rm -rf .devcontainer/{{feature}}
mkdir -p .devcontainer/{{feature}}
cp src/{{feature}}/devcontainer-feature.json .devcontainer/{{feature}}/
cp src/{{feature}}/install.sh .devcontainer/{{feature}}/
cp src/{{feature}}/*.sh .devcontainer/{{feature}}/
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The wildcard copy pattern cp src/{{feature}}/*.sh will fail if there are no .sh files in the directory (in shells without nullglob set), or could inadvertently copy unintended files if additional shell scripts are added to the feature directory in the future.

Consider being more explicit about which files to copy, such as:

cp src/{{feature}}/install.sh src/{{feature}}/configure-shell.sh .devcontainer/{{feature}}/
Suggested change
cp src/{{feature}}/*.sh .devcontainer/{{feature}}/
cp src/{{feature}}/install.sh src/{{feature}}/configure-shell.sh .devcontainer/{{feature}}/

Copilot uses AI. Check for mistakes.
echo "✓ Feature copied to .devcontainer/{{feature}}"

# Build Oh My Posh feature
Expand Down
86 changes: 86 additions & 0 deletions src/ohmyposh/configure-shell.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/bash
set -e

# This script ensures Oh My Posh configuration is at the end of shell RC files
# to avoid being overwritten by other features (like shell-history).

move_to_end() {
local rc_file="$1"
local start_marker="# region Oh My Posh configuration"
local end_marker="# endregion Oh My Posh configuration"

if [ -f "$rc_file" ]; then
# Check for the region markers
if grep -q "$start_marker" "$rc_file"; then
echo "Ensuring Oh My Posh configuration is at the end of $rc_file..."

# Extract the block
local block=$(sed -n "/$start_marker/,/$end_marker/p" "$rc_file")

if [ -n "$block" ]; then
# Create a temp file
local temp_file=$(mktemp)

# Write file content WITHOUT the block to temp file
# We use awk to exclude the range
if ! awk -v start="$start_marker" -v end="$end_marker" '
$0 ~ start {skip=1; next}
$0 ~ end {skip=0; next}
!skip {print}
' "$rc_file" > "$temp_file"; then
echo "Error: Failed to process $rc_file"
rm -f "$temp_file"
return 1
fi

# Append the block to the end
echo "" >> "$temp_file"
echo "$block" >> "$temp_file"

# Replace original file
mv "$temp_file" "$rc_file"

echo "Moved Oh My Posh configuration to the end of $rc_file"
fi
# Fallback: Check for old configuration without markers
elif grep -q "oh-my-posh init" "$rc_file"; then
echo "Found legacy Oh My Posh configuration in $rc_file. Moving to end and adding markers..."

# Create a temp file
local temp_file=$(mktemp)

# Replace lines containing "oh-my-posh init" with ":" (no-op) to disable old config
# while preserving syntax validity of surrounding if/else blocks.
sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"
Comment on lines +52 to +54
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The sed command preserves indentation with \1 but doesn't account for the case where the matched line might be inside a complex if/else/fi block. If the eval line is the only statement in an if/else branch, replacing it with : preserves syntax but leaves behind empty-looking conditional blocks (e.g., if [...]; then :; fi), which could be confusing.

Consider also removing or commenting out the surrounding conditional comments (lines containing "Use custom theme if mounted", "Use built-in theme") to make it clearer that this is a no-op block.

Suggested change
# Replace lines containing "oh-my-posh init" with ":" (no-op) to disable old config
# while preserving syntax validity of surrounding if/else blocks.
sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"
# Comment out the entire legacy oh-my-posh block (including comments and conditionals) for clarity.
# This avoids leaving behind confusing empty if/else/fi blocks.
awk '
BEGIN {in_block=0}
/Use custom theme if mounted/ {in_block=1; print "# [oh-my-posh legacy config commented out] " $0; next}
/Use built-in theme/ {if (in_block) {print "# [oh-my-posh legacy config commented out] " $0; next}}
/oh-my-posh init/ {if (in_block) {print "# [oh-my-posh legacy config commented out] " $0; next}}
/fi/ {if (in_block) {print "# [oh-my-posh legacy config commented out] " $0; in_block=0; next}}
{if (in_block) {print "# [oh-my-posh legacy config commented out] " $0} else {print $0}}
' "$rc_file" > "$temp_file"

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The regex pattern in sed uses .*oh-my-posh init.* which could match unintended lines. For example, if a user has a comment like # Don't use oh-my-posh init directly, this line would also be replaced with :.

Consider making the pattern more specific to match only actual eval statements, such as:

sed 's/^\([[:space:]]*\)eval.*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"
Suggested change
sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"
sed 's/^\([[:space:]]*\)eval.*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

Missing error handling for the sed command. If the sed operation fails, the temp file will still be moved over the original RC file (line 57), potentially corrupting the user's configuration.

Consider checking the exit status of sed before moving the temp file, similar to the error handling in the region marker path (lines 26-34).

Suggested change
sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"
if ! sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" > "$temp_file"; then
echo "Error: Failed to process $rc_file with sed"
rm -f "$temp_file"
return 1
fi

Copilot uses AI. Check for mistakes.

# Replace original file with cleaned content
mv "$temp_file" "$rc_file"

# Append new block
# Determine shell from filename
local shell_name="bash"
if [[ "$rc_file" == *".zshrc" ]]; then
shell_name="zsh"
fi

# Use echo to append to avoid issues with cat and EOF in some environments or if file is empty
echo "" >> "$rc_file"
echo "$start_marker" >> "$rc_file"
echo "if [ -s ~/.ohmyposh.json ]; then" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config ~/.ohmyposh.json)\"" >> "$rc_file"
echo "else" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config jandedobbeleer)\"" >> "$rc_file"
Comment on lines +66 to +72
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The theme is hardcoded to "jandedobbeleer" in the legacy migration logic, but the feature supports custom themes via the theme option in devcontainer-feature.json. When migrating legacy configurations, users who had configured a custom theme will lose that configuration and get the hardcoded theme instead.

Consider passing the configured theme value to this script (perhaps via an environment variable or config file) and using it instead of the hardcoded value.

Suggested change
# Use echo to append to avoid issues with cat and EOF in some environments or if file is empty
echo "" >> "$rc_file"
echo "$start_marker" >> "$rc_file"
echo "if [ -s ~/.ohmyposh.json ]; then" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config ~/.ohmyposh.json)\"" >> "$rc_file"
echo "else" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config jandedobbeleer)\"" >> "$rc_file"
# Determine theme from environment variable or default to "jandedobbeleer"
local oh_my_posh_theme="${OH_MY_POSH_THEME:-jandedobbeleer}"
# Use echo to append to avoid issues with cat and EOF in some environments or if file is empty
echo "" >> "$rc_file"
echo "$start_marker" >> "$rc_file"
echo "if [ -s ~/.ohmyposh.json ]; then" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config ~/.ohmyposh.json)\"" >> "$rc_file"
echo "else" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config \$oh_my_posh_theme)\"" >> "$rc_file"

Copilot uses AI. Check for mistakes.
echo "fi" >> "$rc_file"
echo "$end_marker" >> "$rc_file"

echo "Updated legacy configuration in $rc_file"
fi
fi
}

# Check bash
move_to_end "$HOME/.bashrc"

# Check zsh
move_to_end "$HOME/.zshrc"

1 change: 1 addition & 0 deletions src/ohmyposh/devcontainer-feature.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils"
],
"postCreateCommand": "/usr/local/bin/oh-my-posh-configure-shell",
"customizations": {
"vscode": {
"settings": {
Expand Down
8 changes: 7 additions & 1 deletion src/ohmyposh/install.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ mkdir -p "$INSTALL_PATH"
mv "$TEMP_FILE" "$INSTALL_PATH/oh-my-posh"
chmod +x "$INSTALL_PATH/oh-my-posh"

# Install the configure script
HELPER_SCRIPT="/usr/local/bin/oh-my-posh-configure-shell"
cp "$(dirname "$0")/configure-shell.sh" "$HELPER_SCRIPT"
chmod +x "$HELPER_SCRIPT"
Comment on lines +81 to +84
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The configure-shell.sh script is copied to /usr/local/bin/oh-my-posh-configure-shell, but there's no mechanism to actually invoke it after the feature installation completes. The PR description mentions adding a postCreateCommand hook, but this is not present in the feature configuration.

Without a lifecycle hook or explicit invocation mechanism, this script will never run automatically, meaning the legacy configuration migration and the "ensure prompt loads last" functionality won't work as intended. Users would need to manually run the script.

Copilot uses AI. Check for mistakes.

echo -e "${GREEN}Oh My Posh binary installed to $INSTALL_PATH/oh-my-posh${NC}"

# Verify installation
Expand Down Expand Up @@ -146,14 +151,15 @@ for SHELL_NAME in "${SHELL_ARRAY[@]}"; do
# Add Oh My Posh initialization
cat >> "$RC_FILE" << EOF

# Oh My Posh configuration
# region Oh My Posh configuration
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The comment marker has been changed from "# Oh My Posh configuration" to "# region Oh My Posh configuration" to support the new migration logic. However, existing users who already have the old marker will end up with both the old configuration block (with "# Oh My Posh configuration") and the new one (with "# region Oh My Posh configuration") since the check on line 146 uses grep -q "oh-my-posh init" which will match the old block and skip adding a new one.

The migration logic in configure-shell.sh is supposed to handle this, but it won't run automatically without a lifecycle hook.

Copilot uses AI. Check for mistakes.
if [ -s ~/.ohmyposh.json ]; then
# Use custom theme if mounted
eval "\$(oh-my-posh init $SHELL_CMD --config ~/.ohmyposh.json)"
else
# Use built-in theme
eval "\$(oh-my-posh init $SHELL_CMD --config $THEME)"
fi
# endregion Oh My Posh configuration
EOF

chown "$USER_NAME:$USER_NAME" "$RC_FILE" 2>/dev/null || true
Expand Down
72 changes: 72 additions & 0 deletions test/ohmyposh/legacy_config_migration.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash

set -e

source dev-container-features-test-lib

# 1. Verify the helper script is installed
check "helper script installed" test -x /usr/local/bin/oh-my-posh-configure-shell

# 2. Simulate legacy configuration (no markers)
cat > ~/.bashrc << EOF
# Some initial content
export PATH=\$PATH:/something

# Oh My Posh configuration
if [ -s ~/.ohmyposh.json ]; then
# Use custom theme if mounted
eval "\$(oh-my-posh init bash --config ~/.ohmyposh.json)"
else
# Use built-in theme
eval "\$(oh-my-posh init bash --config jandedobbeleer)"
fi

# Some other content that should be preserved
export FOO=bar
EOF

# 3. Run the helper script
# We need to set USERNAME to root because the test runs as root
# and the helper script defaults to 'vscode' if not set.
export USERNAME=root
/usr/local/bin/oh-my-posh-configure-shell

# 4. Verify the result
# Read the last few lines of .bashrc
LAST_LINES=$(tail -n 10 ~/.bashrc)
echo "Last lines of .bashrc:"
echo "$LAST_LINES"

# Check if "oh-my-posh init" is in the last lines
check "oh-my-posh is at the end" bash -c "tail -n 10 ~/.bashrc | grep -q 'oh-my-posh init'"

# Check if markers are present
check "markers are present" grep -q "# region Oh My Posh configuration" ~/.bashrc

# Check if old config is gone (or at least the comment)
# We removed lines with "oh-my-posh init" and "# Oh My Posh configuration"
# But we added them back at the end.
Comment on lines +47 to +48
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

The comment on line 47 states "Check if old config is gone (or at least the comment)" but the actual migration in configure-shell.sh does not remove the old "# Oh My Posh configuration" comment - it only replaces the oh-my-posh init lines with :. This means the old comment will remain, making this test comment misleading.

Consider updating the comment to accurately reflect what the migration does, or update the migration logic to also remove the old comment.

Suggested change
# We removed lines with "oh-my-posh init" and "# Oh My Posh configuration"
# But we added them back at the end.
# We removed lines with "oh-my-posh init" (replaced with ":") but the old "# Oh My Posh configuration" comment may remain.
# But we added the new block (with markers) at the end.

Copilot uses AI. Check for mistakes.
# So we check if there is only ONE occurrence of the block (or markers)
COUNT=$(grep -c "# region Oh My Posh configuration" ~/.bashrc)
if [ "$COUNT" -eq 1 ]; then
echo "✅ Only one configuration block found"
else
echo "❌ Found $COUNT configuration blocks"
exit 1
fi

# Check if FOO=bar is preserved and BEFORE the new block
POS_FOO=$(grep -n "export FOO=bar" ~/.bashrc | cut -d: -f1)
POS_OMP=$(grep -n "# region Oh My Posh configuration" ~/.bashrc | cut -d: -f1)

echo "FOO position: $POS_FOO"
echo "OMP position: $POS_OMP"

if [ "$POS_OMP" -gt "$POS_FOO" ]; then
echo "✅ Oh My Posh config is after existing content"
else
echo "❌ Oh My Posh config is NOT after existing content"
exit 1
fi

reportResults
130 changes: 130 additions & 0 deletions test/ohmyposh/repro_legacy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/bin/bash
set -e

# Mock environment
USER_HOME=$(pwd)
RC_FILE="$USER_HOME/.bashrc_repro"

# Create legacy file
cat > "$RC_FILE" << EOF
# Some initial content
export PATH=\$PATH:/something

# Oh My Posh configuration
if [ -s ~/.ohmyposh.json ]; then
# Use custom theme if mounted
eval "\$(oh-my-posh init bash --config ~/.ohmyposh.json)"
else
# Use built-in theme
eval "\$(oh-my-posh init bash --config jandedobbeleer)"
fi

# Some other content that should be preserved
export FOO=bar
EOF

echo "--- Initial Content ---"
cat "$RC_FILE"
echo "-----------------------"

# Define the function exactly as in the file
move_to_end() {
local rc_file="$1"
local start_marker="# region Oh My Posh configuration"
local end_marker="# endregion Oh My Posh configuration"

if [ -f "$rc_file" ]; then
# Check for the region markers
if grep -q "$start_marker" "$rc_file"; then
echo "Ensuring Oh My Posh configuration is at the end of $rc_file..."

# Extract the block
local block=$(sed -n "/$start_marker/,/$end_marker/p" "$rc_file")

if [ -n "$block" ]; then
# Create a temp file
local temp_file=$(mktemp)

# Write file content WITHOUT the block to temp file
# We use awk to exclude the range
awk -v start="$start_marker" -v end="$end_marker" '
$0 ~ start {skip=1; next}
$0 ~ end {skip=0; next}
!skip {print}
' "$rc_file" > "$temp_file"

# Append the block to the end
echo "" >> "$temp_file"
echo "$block" >> "$temp_file"

# Replace original file
mv "$temp_file" "$rc_file"

echo "Moved Oh My Posh configuration to the end of $rc_file"
fi
# Fallback: Check for old configuration without markers
elif grep -q "oh-my-posh init" "$rc_file"; then
echo "Found legacy Oh My Posh configuration in $rc_file. Moving to end and adding markers..."

# Create a temp file
local temp_file=$(mktemp)

# Remove lines containing "oh-my-posh init" and surrounding if/else block if possible
# This is harder to do reliably with regex, so we'll just comment out the old init line
# and add a new block at the end.

# Actually, let's just append the new block and assume the old one will be overridden or harmless
# if it's not setting PROMPT_COMMAND in a conflicting way.
# But the old one DOES set PROMPT_COMMAND via eval.

# Let's try to remove the old block if it matches the standard pattern
# Standard pattern:
# # Oh My Posh configuration
# if [ -s ~/.ohmyposh.json ]; then
# ...
# fi

# We'll use a simpler approach: Remove any lines containing "oh-my-posh init"
# and the specific comment "# Oh My Posh configuration"

# Use a temp file for grep output to avoid issues with reading/writing same file
sed 's/^\([[:space:]]*\).*oh-my-posh init.*/\1:/' "$rc_file" | grep -v "# Oh My Posh configuration" > "$temp_file"

# Replace original file with cleaned content
mv "$temp_file" "$rc_file"

# Append new block
# Determine shell from filename
local shell_name="bash"
if [[ "$rc_file" == *".zshrc" ]]; then
shell_name="zsh"
fi

# Use echo to append to avoid issues with cat and EOF in some environments or if file is empty
echo "" >> "$rc_file"
echo "$start_marker" >> "$rc_file"
echo "if [ -s ~/.ohmyposh.json ]; then" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config ~/.ohmyposh.json)\"" >> "$rc_file"
echo "else" >> "$rc_file"
echo " eval \"\$(oh-my-posh init $shell_name --config jandedobbeleer)\"" >> "$rc_file"
echo "fi" >> "$rc_file"
echo "$end_marker" >> "$rc_file"

echo "Updated legacy configuration in $rc_file"
fi
fi
}

# Run it
move_to_end "$RC_FILE"

echo "--- Final Content ---"
cat "$RC_FILE"
echo "---------------------"

# Check for markers
if grep -q "# region Oh My Posh configuration" "$RC_FILE"; then
echo "✅ Markers found"
else
echo "❌ Markers NOT found"
fi
6 changes: 6 additions & 0 deletions test/ohmyposh/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,11 @@
"installPath": "/usr/bin"
}
}
},
"legacy_config_migration": {
"image": "ubuntu:latest",
"features": {
"ohmyposh": {}
}
}
}
25 changes: 25 additions & 0 deletions test/ohmyposh/test_sed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

Missing set -e directive. If any command in this test script fails (e.g., the sed command or bash syntax check), the script will continue executing instead of stopping at the failure point, potentially masking errors. Add set -e at the beginning of the script after the shebang.

Suggested change
#!/bin/bash
#!/bin/bash
set -e

Copilot uses AI. Check for mistakes.
set -e

cat > test.sh << EOF
# Oh My Posh configuration
if [ -s ~/.ohmyposh.json ]; then
# Use custom theme if mounted
eval "\$(oh-my-posh init bash --config ~/.ohmyposh.json)"
else
# Use built-in theme
eval "\$(oh-my-posh init bash --config jandedobbeleer)"
fi
EOF

sed 's/.*oh-my-posh init.*/ :/' test.sh > test.sh.tmp
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The sed command preserves indentation but the result will have inconsistent indentation. The original code has eval statements indented with 4 spaces inside the if/else block, but after replacement with :, these become lines with just whitespace followed by :. While syntactically correct, this creates odd-looking code.

Consider if this test should verify that the indentation is preserved correctly, or if the migration strategy should be adjusted.

Suggested change
sed 's/.*oh-my-posh init.*/ :/' test.sh > test.sh.tmp
sed 's/^\([[:space:]]*\)oh-my-posh init.*/\1:/' test.sh > test.sh.tmp

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 24, 2025

Choose a reason for hiding this comment

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

[nitpick] The regex pattern .*oh-my-posh init.* could match unintended lines, such as comments that mention "oh-my-posh init". For consistency and safety, consider using a more specific pattern that matches only actual eval statements, as this is a test verifying the migration behavior.

Suggested change
sed 's/.*oh-my-posh init.*/ :/' test.sh > test.sh.tmp
sed 's/^[[:space:]]*eval[[:space:]]*"\$\(oh-my-posh init[^"]*\)"/ :/' test.sh > test.sh.tmp

Copilot uses AI. Check for mistakes.
mv test.sh.tmp test.sh

cat test.sh

# Check syntax
if bash -n test.sh; then
echo "Syntax OK"
else
echo "Syntax Error"
fi