Skip to content

Commit 5af5e1b

Browse files
authored
Merge pull request #84 from schlich/copilot/fix-b7bddb92-b365-4e3a-b5aa-2bac8330c08f
Add version bumping infrastructure for devcontainer features (Nushell with nu_plugin_inc)
2 parents 136a2c3 + 7c2b8c1 commit 5af5e1b

File tree

12 files changed

+1177
-1
lines changed

12 files changed

+1177
-1
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
name: "Validate Version Bump"
3+
on:
4+
pull_request:
5+
paths:
6+
- 'src/**/devcontainer-feature.json'
7+
8+
jobs:
9+
validate-version-bump:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
15+
steps:
16+
- name: Checkout PR branch
17+
uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Validate version changes
22+
run: |
23+
set -e
24+
25+
echo "Checking for version changes..."
26+
27+
# Get changed files
28+
CHANGED_FILES=$(git diff --name-only \
29+
origin/${{ github.base_ref }}...HEAD | \
30+
grep 'devcontainer-feature.json$' || true)
31+
32+
if [ -z "$CHANGED_FILES" ]; then
33+
echo "No devcontainer-feature.json files changed"
34+
exit 0
35+
fi
36+
37+
HAS_ERROR=0
38+
39+
for FILE in $CHANGED_FILES; do
40+
echo ""
41+
echo "Checking $FILE..."
42+
43+
# Get old version
44+
OLD_VERSION=$(git show \
45+
origin/${{ github.base_ref }}:$FILE | \
46+
jq -r '.version' 2>/dev/null || echo "")
47+
NEW_VERSION=$(jq -r '.version' $FILE 2>/dev/null || echo "")
48+
FEATURE_ID=$(jq -r '.id' $FILE 2>/dev/null || echo "unknown")
49+
50+
if [ -z "$OLD_VERSION" ]; then
51+
echo " New feature: $FEATURE_ID (version: $NEW_VERSION)"
52+
continue
53+
fi
54+
55+
if [ -z "$NEW_VERSION" ]; then
56+
echo " Error: Could not read version from $FILE"
57+
HAS_ERROR=1
58+
continue
59+
fi
60+
61+
# Validate semantic versioning format
62+
if ! echo "$NEW_VERSION" | \
63+
grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
64+
echo " Error: Version does not follow semver"
65+
HAS_ERROR=1
66+
continue
67+
fi
68+
69+
if [ "$OLD_VERSION" = "$NEW_VERSION" ]; then
70+
echo " Warning: Version unchanged ($OLD_VERSION)"
71+
else
72+
echo " Version bumped: $OLD_VERSION -> $NEW_VERSION"
73+
74+
# Parse versions
75+
OLD_MAJOR=$(echo $OLD_VERSION | cut -d. -f1)
76+
OLD_MINOR=$(echo $OLD_VERSION | cut -d. -f2)
77+
OLD_PATCH=$(echo $OLD_VERSION | cut -d. -f3)
78+
79+
NEW_MAJOR=$(echo $NEW_VERSION | cut -d. -f1)
80+
NEW_MINOR=$(echo $NEW_VERSION | cut -d. -f2)
81+
NEW_PATCH=$(echo $NEW_VERSION | cut -d. -f3)
82+
83+
# Check if version increased
84+
if [ $NEW_MAJOR -lt $OLD_MAJOR ]; then
85+
echo " Error: Major version decreased"
86+
HAS_ERROR=1
87+
elif [ $NEW_MAJOR -eq $OLD_MAJOR ]; then
88+
if [ $NEW_MINOR -lt $OLD_MINOR ]; then
89+
echo " Error: Minor version decreased"
90+
HAS_ERROR=1
91+
elif [ $NEW_MINOR -eq $OLD_MINOR ]; then
92+
if [ $NEW_PATCH -lt $OLD_PATCH ]; then
93+
echo " Error: Patch version decreased"
94+
HAS_ERROR=1
95+
fi
96+
fi
97+
fi
98+
fi
99+
done
100+
101+
if [ $HAS_ERROR -eq 1 ]; then
102+
echo ""
103+
echo "Version validation failed"
104+
exit 1
105+
fi
106+
107+
echo ""
108+
echo "All version changes are valid"
109+

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,11 @@ dist
174174
# Finder (MacOS) folder config
175175
.DS_Store
176176
.direnv
177+
178+
# Python
179+
__pycache__/
180+
*.py[cod]
181+
*$py.class
182+
*.so
183+
.Python
184+

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,9 @@ A collection of devcontainer features.
2121
| [powerlevel10k](./src/powerlevel10k) | PowerLevel10k is a theme for Zsh. |
2222
| [rye](./src/rye) | A Hassle-Free Python Experience. |
2323
| [starship](./src/starship) | The minimal, blazing-fast, and infinitely customizable prompt for any shell! |
24+
25+
## Development
26+
27+
### Version Bumping
28+
29+
See [docs/VERSION_BUMPING.md](./docs/VERSION_BUMPING.md) for information on bumping feature versions.

docs/NUSHELL_INC_COMMAND.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Note on Nushell's `inc` Command
2+
3+
## Background
4+
5+
The Nushell documentation references an `inc` command that was designed to increment values, including semantic version strings:
6+
- Documentation: https://www.nushell.sh/commands/docs/inc.html
7+
- Supported flags: `--major`, `--minor`, `--patch`
8+
9+
## Current Status
10+
11+
The `inc` command was available in Nushell versions prior to 0.80 but has been **moved to a plugin** in current versions (0.107.0+).
12+
13+
### nu_plugin_inc
14+
15+
The `inc` functionality is now available as an official Nushell plugin: `nu_plugin_inc`
16+
17+
## Installation
18+
19+
### Step 1: Install the Plugin
20+
21+
```bash
22+
cargo install nu_plugin_inc
23+
```
24+
25+
### Step 2: Register the Plugin with Nushell
26+
27+
```bash
28+
# Start nushell
29+
nu
30+
31+
# Register the plugin
32+
plugin add ~/.cargo/bin/nu_plugin_inc
33+
34+
# Or in one command:
35+
nu -c "plugin add ~/.cargo/bin/nu_plugin_inc"
36+
```
37+
38+
### Step 3: Verify Installation
39+
40+
```bash
41+
nu -c '"1.2.3" | inc --patch'
42+
# Should output: 1.2.4
43+
```
44+
45+
## Implementation in bump-version.nu
46+
47+
The `scripts/bump-version.nu` script now uses a **hybrid approach**:
48+
49+
1. **First, try to use the `inc` plugin** if it's installed and registered
50+
2. **Fallback to custom implementation** if the plugin is not available
51+
52+
This ensures the script works whether or not the plugin is installed, while preferring the official plugin when available.
53+
54+
### Code Implementation:
55+
56+
```nushell
57+
def bump_semver [
58+
version: string
59+
bump_type: string
60+
]: nothing -> string {
61+
# Try to use inc plugin if available
62+
let inc_result = try {
63+
match $bump_type {
64+
"major" => { $version | inc --major }
65+
"minor" => { $version | inc --minor }
66+
"patch" => { $version | inc --patch }
67+
}
68+
} catch {
69+
null
70+
}
71+
72+
# If inc plugin worked, return the result
73+
if $inc_result != null {
74+
return $inc_result
75+
}
76+
77+
# Fallback to custom implementation
78+
# [custom implementation code]
79+
}
80+
```
81+
82+
## Benefits
83+
84+
1. **Plugin-first approach**: Uses official nu_plugin_inc when available
85+
2. **Graceful fallback**: Works without the plugin for compatibility
86+
3. **No manual configuration**: Script detects and uses the plugin automatically
87+
4. **Best of both worlds**: Official plugin + guaranteed functionality
88+
89+
## Advantages of Using the Plugin
90+
91+
When the plugin is installed:
92+
- ✅ Uses official, maintained code from the Nushell team
93+
- ✅ Consistent with Nushell ecosystem
94+
- ✅ Automatically updated with plugin updates
95+
- ✅ Better integration with Nushell's type system
96+
97+
## Compatibility
98+
99+
- **With plugin**: Uses `nu_plugin_inc` (recommended)
100+
- **Without plugin**: Uses custom implementation (automatic fallback)
101+
- **Both approaches**: Produce identical results
102+
103+
## Setup in CI/CD
104+
105+
To use the plugin in CI/CD environments, add to your setup:
106+
107+
```yaml
108+
- name: Install nu_plugin_inc
109+
run: |
110+
cargo install nu_plugin_inc
111+
nu -c "plugin add ~/.cargo/bin/nu_plugin_inc"
112+
```
113+
114+
## Conclusion
115+
116+
The script now follows best practices by:
117+
1. Preferring the official `nu_plugin_inc` when available
118+
2. Providing a fallback for environments without the plugin
119+
3. Automatically detecting and using the appropriate method
120+
4. Requiring no changes to the command-line interface

docs/NUSHELL_RATIONALE.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Nushell vs Python: Version Bumping Implementation
2+
3+
This document explains why Nushell was chosen as the primary scripting language for version bumping.
4+
5+
## Comparison
6+
7+
### Native JSON Support
8+
9+
**Python:**
10+
```python
11+
import json
12+
13+
with open(json_path, 'r') as f:
14+
data = json.load(f)
15+
16+
# Modify data
17+
data['version'] = new_version
18+
19+
with open(json_path, 'w') as f:
20+
json.dump(data, f, indent=4)
21+
```
22+
23+
**Nushell:**
24+
```nushell
25+
let data = open $json_path
26+
let updated_data = ($data | upsert version $new_version)
27+
$updated_data | to json --indent 4 | save --force $json_path
28+
```
29+
30+
### Structured Data Handling
31+
32+
Nushell treats JSON, CSV, and other structured data as first-class citizens, making data manipulation more natural and less error-prone.
33+
34+
### Type Safety
35+
36+
**Python:**
37+
- Dynamic typing with potential runtime errors
38+
- Manual type checking needed
39+
- Error handling via try/except
40+
41+
**Nushell:**
42+
- Strongly typed with better compile-time checks
43+
- Built-in error handling with `error make`
44+
- Type coercion with `into int`, `into string`, etc.
45+
46+
### Error Messages
47+
48+
**Python:**
49+
```
50+
Traceback (most recent call last):
51+
File "bump-version.py", line 59
52+
data = json.load(f)
53+
json.decoder.JSONDecodeError: ...
54+
```
55+
56+
**Nushell:**
57+
```
58+
Error: nu::shell::error
59+
× Error: Invalid JSON in file
60+
╭─[bump-version.nu:50:13]
61+
50 │ let data = try {
62+
· ─┬─
63+
· ╰── originates from here
64+
```
65+
66+
Nushell provides clearer, more actionable error messages with line numbers and context.
67+
68+
## Benefits of Nushell for This Project
69+
70+
1. **Native JSON Support**: No external libraries needed for JSON parsing
71+
2. **Concise Syntax**: Less boilerplate code (155 lines in Python vs ~150 in Nushell)
72+
3. **Better Error Handling**: More informative error messages
73+
4. **Type Safety**: Fewer runtime errors
74+
5. **Modern Shell**: Better integration with command-line workflows
75+
6. **Performance**: Efficient structured data processing
76+
7. **Maintainability**: Cleaner, more readable code
77+
78+
## Note on `inc` Command
79+
80+
Nushell's `inc` functionality for incrementing semantic version strings is now available as an official plugin: `nu_plugin_inc`.
81+
82+
**Our implementation uses a hybrid approach:**
83+
1. First attempts to use `nu_plugin_inc` if installed
84+
2. Falls back to custom implementation if plugin is not available
85+
86+
This provides the best of both worlds:
87+
- Uses official plugin when available (recommended)
88+
- Guarantees functionality even without the plugin
89+
- No manual configuration needed
90+
91+
**To install the plugin:**
92+
```bash
93+
cargo install nu_plugin_inc
94+
nu -c "plugin add ~/.cargo/bin/nu_plugin_inc"
95+
```
96+
97+
See [NUSHELL_INC_COMMAND.md](./NUSHELL_INC_COMMAND.md) for complete details on plugin installation and usage.
98+
99+
## Installation
100+
101+
Nushell can be installed via:
102+
- Snap: `sudo snap install nushell --classic`
103+
- Cargo: `cargo install nu`
104+
- Package managers: See https://www.nushell.sh/book/installation.html
105+
106+
## Compatibility
107+
108+
Both Python and Nushell versions are maintained:
109+
- **Nushell** (`bump-version.nu`): Primary, recommended version
110+
- **Python** (`bump-version.py`): Legacy version for compatibility
111+
112+
Both produce identical results and follow the same API.

0 commit comments

Comments
 (0)