Skip to content

Commit cca580e

Browse files
committed
feat(mpm): rename binary to mows with busybox-style subcommands
Transform mpm into a busybox-style `mows` binary with `package-manager` subcommand grouping. The `mpm` symlink is preserved as a backward-compatible alias via argv[0] detection. Key changes: - Binary renamed from `mpm` to `mows` (crate: `mows-cli`) - CLI restructured: `mows package-manager compose ...` with `mpm compose ...` alias - Source moved: `src/compose/` -> `src/package_manager/compose/` - Config: `mows.yaml` (primary) with `mpm.yaml` legacy fallback - Env var: `MOWS_CONFIG_PATH` (primary) with `MPM_CONFIG_PATH` fallback - Error type: `MpmError` -> `MowsError` with structured variants - Self-update: tags use `mows-cli-v*`, creates mpm symlink after update - CI: `publish-mpm.yml` -> `publish-mows.yml` with updated triggers - Shell completions and man pages generated for both `mows` and `mpm` - All docs, tests, build scripts, Dockerfile, and install script updated - HTTP errors now use proper `Network` variant instead of generic `Message` - Symlink creation improved with TOCTOU mitigation and proper error handling - Integration test fixes for config path priority and arithmetic under set -e
1 parent ac8c59a commit cca580e

59 files changed

Lines changed: 2031 additions & 1631 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
name: Publish mpm binary
1+
name: Publish mows binary
22

33
on:
44
push:
55
tags:
6-
- 'mpm-v*'
6+
- 'mows-cli-v*'
77
paths:
88
- 'utils/mpm/**'
99
- 'utils/mows-common-rust/**'
10-
- '.github/workflows/publish-mpm.yml'
10+
- '.github/workflows/publish-mows.yml'
1111
pull_request:
1212
paths:
1313
- 'utils/mpm/**'
1414
- 'utils/mows-common-rust/**'
15-
- '.github/workflows/publish-mpm.yml'
15+
- '.github/workflows/publish-mows.yml'
1616
workflow_dispatch:
1717
inputs:
1818
dry_run:
@@ -25,15 +25,15 @@ jobs:
2525
test:
2626
runs-on: ubuntu-latest
2727
steps:
28-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
28+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
2929

3030
- name: Install Rust
3131
uses: dtolnay/rust-toolchain@f7ccc83f9ed1e5b9c81d8a67d7ad1a747e22a561 # stable
3232
with:
3333
toolchain: stable
3434

3535
- name: Cache cargo registry
36-
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v4
36+
uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v4
3737
with:
3838
path: |
3939
~/.cargo/registry
@@ -53,13 +53,18 @@ jobs:
5353
cd utils/mpm
5454
cargo build --release
5555
56+
- name: Create mpm symlink for e2e tests
57+
run: |
58+
ln -sf mows utils/mpm/target/release/mpm
59+
5660
- name: Run e2e tests
5761
run: |
5862
cd utils/mpm/tests
5963
# Use mock Docker client for tests that would otherwise need Docker daemon
6064
# Skip test-self-update which requires network access to GitHub API
6165
SKIP_TESTS="test-self-update" ./run-all.sh
6266
env:
67+
MOWS_BIN: ${{ github.workspace }}/utils/mpm/target/release/mows
6368
MPM_BIN: ${{ github.workspace }}/utils/mpm/target/release/mpm
6469
MPM_MOCK_DOCKER: "1"
6570

@@ -76,7 +81,7 @@ jobs:
7681
- arch: arm64
7782
os_name: linux
7883
steps:
79-
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
84+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
8085

8186
- name: Set up Docker Buildx
8287
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
@@ -102,7 +107,7 @@ jobs:
102107
run: |
103108
if [[ "${{ github.ref_type }}" == "tag" ]]; then
104109
VERSION="${{ github.ref_name }}"
105-
VERSION="${VERSION#mpm-v}"
110+
VERSION="${VERSION#mows-cli-v}"
106111
else
107112
VERSION="dev"
108113
fi
@@ -111,13 +116,15 @@ jobs:
111116
- name: Rename binary with version and platform
112117
run: |
113118
cd utils/mpm/dist
114-
mv mpm mpm-${{ steps.get_version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}
119+
# Remove symlink, keep only the real binary
120+
rm -f mpm
121+
mv mows mows-${{ steps.get_version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}
115122
116123
- name: Upload artifact
117124
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4
118125
with:
119-
name: mpm-${{ matrix.os_name }}-${{ matrix.arch }}
120-
path: utils/mpm/dist/mpm-*
126+
name: mows-${{ matrix.os_name }}-${{ matrix.arch }}
127+
path: utils/mpm/dist/mows-*
121128
retention-days: 7
122129

123130
release:
@@ -137,22 +144,22 @@ jobs:
137144
id: get_version
138145
run: |
139146
VERSION="${{ github.ref_name }}"
140-
VERSION="${VERSION#mpm-v}"
147+
VERSION="${VERSION#mows-cli-v}"
141148
echo "version=${VERSION}" >> $GITHUB_OUTPUT
142149
143150
- name: Create checksums
144151
run: |
145152
cd artifacts
146-
for f in mpm-*; do
153+
for f in mows-*; do
147154
sha256sum "$f" > "${f}-checksum-sha256.txt"
148155
done
149156
150157
- name: Create GitHub Release
151158
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
152159
with:
153-
name: mpm v${{ steps.get_version.outputs.version }}
160+
name: mows v${{ steps.get_version.outputs.version }}
154161
draft: false
155162
prerelease: false
156163
files: |
157-
artifacts/mpm-*
164+
artifacts/mows-*
158165
generate_release_notes: true

Cargo.lock

Lines changed: 36 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

utils/mpm/CLAUDE.md

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,41 @@
1-
# MPM (MOWS Package Manager)
1+
# MOWS CLI (mows-cli)
2+
3+
MOWS uses a busybox-style binary called `mows` with subcommands:
4+
- `mows package-manager compose ...` - Package manager compose operations
5+
- `mows tools ...` - Utility tools (json-to-yaml, jq, etc.)
6+
- `mows template ...` - Template rendering
7+
- `mows self-update` - Self-update
8+
9+
A symlink `mpm` is provided as an alias for `mows package-manager`, so `mpm compose ...` is equivalent to `mows package-manager compose ...`.
210

311
## Testing Guidelines
412

513
### Config File Isolation
614

7-
**CRITICAL**: All tests that call `MpmConfig::load()` or `MpmConfig::save()` MUST use proper isolation to prevent modifying the user's actual config file at `~/.config/mows.cloud/mpm.yaml`.
15+
**CRITICAL**: All tests that call `MowsConfig::load()` or `MowsConfig::save()` MUST use proper isolation to prevent modifying the user's actual config file at `~/.config/mows.cloud/mows.yaml`.
816

917
#### Recommended: Use TestConfigGuard (for unit tests)
1018

11-
The `TestConfigGuard` helper in `src/compose/config.rs` provides RAII-based isolation:
19+
The `TestConfigGuard` helper in `src/package_manager/compose/config.rs` provides RAII-based isolation:
1220

1321
```rust
1422
#[test]
1523
fn test_config_persistence() {
1624
let _guard = TestConfigGuard::new(); // Sets up isolated environment
1725

18-
let mut config = MpmConfig::default();
26+
let mut config = MowsConfig::default();
1927
config.set_update_available("1.0.0".to_string());
2028
config.save().unwrap();
2129

22-
let loaded = MpmConfig::load().unwrap();
30+
let loaded = MowsConfig::load().unwrap();
2331
assert_eq!(loaded.update.unwrap().available_version, "1.0.0");
2432
} // Guard automatically cleans up when dropped
2533
```
2634

2735
The guard:
2836
1. Acquires a mutex to prevent concurrent test interference
2937
2. Creates a temporary file for the config
30-
3. Sets `MPM_CONFIG_PATH` to the temp file
38+
3. Sets `MOWS_CONFIG_PATH` to the temp file
3139
4. Automatically cleans up when dropped
3240

3341
#### Alternative: Manual Setup (if needed outside config module)
@@ -39,20 +47,20 @@ use std::env;
3947
#[test]
4048
fn test_example() {
4149
let temp_config = NamedTempFile::new().unwrap();
42-
env::set_var("MPM_CONFIG_PATH", temp_config.path());
50+
env::set_var("MOWS_CONFIG_PATH", temp_config.path());
4351

44-
// ... run test that uses MpmConfig ...
52+
// ... run test that uses MowsConfig ...
4553

46-
env::remove_var("MPM_CONFIG_PATH");
54+
env::remove_var("MOWS_CONFIG_PATH");
4755
}
4856
```
4957

5058
#### For integration tests (bash):
5159

5260
```bash
53-
export MPM_CONFIG_PATH=$(mktemp)
54-
# ... run mpm commands ...
55-
rm -f "$MPM_CONFIG_PATH"
61+
export MOWS_CONFIG_PATH=$(mktemp)
62+
# ... run mows/mpm commands ...
63+
rm -f "$MOWS_CONFIG_PATH"
5664
```
5765

5866
### Why This Matters
@@ -67,20 +75,27 @@ rm -f "$MPM_CONFIG_PATH"
6775
### Unit Tests
6876

6977
```bash
70-
cargo test # Run all unit tests
71-
cargo test config::tests # Run specific module tests
72-
cargo test -- --nocapture # Show test output
78+
cargo test -p mows-cli # Run all unit tests
79+
cargo test -p mows-cli config::tests # Run specific module tests
80+
cargo test -p mows-cli -- --nocapture # Show test output
7381
```
7482

7583
### Integration Tests (E2E)
7684

85+
The E2E tests use two environment variables to locate the binaries:
86+
- `MOWS_BIN` - Path to the `mows` binary (default: `target/release/mows`)
87+
- `MPM_BIN` - Path to the `mpm` symlink (default: alongside the `mows` binary)
88+
7789
```bash
7890
cd tests
7991
./run-all.sh # Run all integration tests
8092
./run-all.sh test-cli # Run specific test
8193
VERBOSE=1 ./run-all.sh # Verbose output
8294
```
8395

96+
Tests that use `mows` directly (tools, template, self-update, cli) reference `$MOWS_BIN`.
97+
Tests that use the package manager via the symlink (compose) reference `$MPM_BIN`.
98+
8499
### Mock Docker Client for CI
85100

86101
Tests that require Docker can run in CI using the mock Docker client:
@@ -96,7 +111,7 @@ The mock client (`MPM_MOCK_DOCKER=1`) simulates Docker operations:
96111
- `compose logs` returns empty logs
97112
- Container inspections return minimal mock data
98113

99-
This allows testing the full mpm workflow without a Docker daemon.
114+
This allows testing the full mows workflow without a Docker daemon.
100115

101116
### Tests Skipped in CI
102117

@@ -108,17 +123,19 @@ Note: `test-compose-up` now runs in CI using `MPM_MOCK_DOCKER=1`.
108123

109124
## Building
110125

126+
The crate name is `mows-cli`, producing a binary called `mows`. A `mpm` symlink is created alongside it.
127+
111128
### Development
112129

113130
```bash
114-
cargo build # Debug build
115-
cargo build --release # Release build (local)
131+
cargo build -p mows-cli # Debug build (target/debug/mows)
132+
cargo build -p mows-cli --release # Release build (target/release/mows)
116133
```
117134

118135
### Static Binary (Docker)
119136

120137
```bash
121-
bash build.sh # Build static binary to dist/mpm
138+
bash build.sh # Build static binary to dist/mows
122139
TARGETARCH=arm64 bash build.sh # Cross-compile for ARM64
123140
PROFILE=dev bash build.sh # Faster dev build
124141
```

utils/mpm/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
[package]
2-
name = "mpm"
2+
name = "mows-cli"
33
version = "0.6.0"
44
edition = { workspace = true }
55

6+
[[bin]]
7+
name = "mows"
8+
path = "src/main.rs"
9+
610
[dependencies]
711
mows-common-rust = { workspace = true }
812

utils/mpm/Dockerfile

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ARG PROFILE="release"
2-
ARG SERVICE_NAME="mpm"
2+
ARG SERVICE_NAME="mows"
33
ARG SERVICE_VERSION="0.0.0-dev"
4-
ARG BINARY_NAME="mpm"
4+
ARG BINARY_NAME="mows"
55
ARG APP_STAGE_IMAGE="scratch"
66
ARG TARGETARCH="amd64"
77

@@ -101,11 +101,13 @@ ARG SERVICE_NAME
101101
ARG SERVICE_VERSION
102102
ARG BINARY_NAME
103103
WORKDIR /app
104-
COPY --from=builder /${BINARY_NAME} ./mpm
104+
COPY --from=builder /${BINARY_NAME} ./${BINARY_NAME}
105105
ENV SERVICE_NAME=${SERVICE_NAME}
106106
ENV SERVICE_VERSION=${SERVICE_VERSION}
107107
STOPSIGNAL SIGTERM
108108
# Healthcheck verifies the binary is functional (uses exec form for scratch compatibility)
109-
# Note: On scratch images, exec form runs directly without a shell
110-
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["./mpm", "--help"]
111-
ENTRYPOINT ["./mpm"]
109+
# Note: On scratch images there is no shell, so we must use exec form with literal values.
110+
# Docker does not expand ARG/ENV variables inside exec-form JSON arrays for CMD/ENTRYPOINT.
111+
# BINARY_NAME is used in COPY above (where Docker does expand it), but must be literal here.
112+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 CMD ["./mows", "--help"]
113+
ENTRYPOINT ["./mows"]

0 commit comments

Comments
 (0)