diff --git a/CHANGELOG.md b/CHANGELOG.md index ee91d94e62..ecf3fbe3e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## [2026.6.15](https://github.com/jdx/mise/compare/v2026.6.14..v2026.6.15) - 2026-06-26 + +### 🚀 Features + +- **(bootstrap)** prune unmanaged brew formulae by @jdx in [#10618](https://github.com/jdx/mise/pull/10618) + +### 🐛 Bug Fixes + +- **(brew-cask)** handle raw binaries, $APPDIR paths, and app bundle copying by @arthurh4 in [#10626](https://github.com/jdx/mise/pull/10626) +- **(hooks)** set MISE_INSTALLED_TOOLS to [] on no-op install (keep running postinstall) by @JamBalaya56562 in [#10615](https://github.com/jdx/mise/pull/10615) +- **(install)** respect lockfile backend during locked installs by @risu729 in [#10599](https://github.com/jdx/mise/pull/10599) +- **(install)** suggest source install for unsupported arches by @risu729 in [#10627](https://github.com/jdx/mise/pull/10627) +- **(oci)** resolve host install symlinks and symlinked paths during `PATH` rebasing by @salim-b in [#10624](https://github.com/jdx/mise/pull/10624) +- stop forcing no-yjit ruby on older glibc by @jdx in [#10620](https://github.com/jdx/mise/pull/10620) + +### New Contributors + +- @arthurh4 made their first contribution in [#10626](https://github.com/jdx/mise/pull/10626) + +### 📦 Aqua Registry Updates + +#### New Packages (4) + +- [`callumalpass/tasknotes-tui`](https://github.com/callumalpass/tasknotes-tui) +- `glossia.ai/cli` +- [`ogulcancelik/herdr`](https://github.com/ogulcancelik/herdr) +- [`pvolok/dekit`](https://github.com/pvolok/dekit) + ## [2026.6.14](https://github.com/jdx/mise/compare/v2026.6.13..v2026.6.14) - 2026-06-25 ### 🚀 Features diff --git a/Cargo.lock b/Cargo.lock index adccf997c7..ce71bbddb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5296,7 +5296,7 @@ dependencies = [ [[package]] name = "mise" -version = "2026.6.14" +version = "2026.6.15" dependencies = [ "age", "aho-corasick", diff --git a/Cargo.toml b/Cargo.toml index 3c82c61aed..538486179d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ [package] name = "mise" -version = "2026.6.14" +version = "2026.6.15" edition = "2024" description = "Dev tools, env vars, and tasks in one CLI" authors = ["Jeff Dickey (@jdx)"] diff --git a/README.md b/README.md index 8c7915857a..bb4b81c1ae 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ $ ~/.local/bin/mise --version / / / / / / (__ ) __/_____/ __/ / / /_____/ /_/ / / /_/ / /__/ __/ /_/ /_/ /_/_/____/\___/ \___/_/ /_/ / .___/_/\__,_/\___/\___/ /_/ by @jdx -2026.6.14 macos-arm64 (2026-06-25) +2026.6.15 macos-arm64 (2026-06-26) ``` Hook mise into your shell (pick the right one for your shell): diff --git a/completions/_mise b/completions/_mise index 19008e5139..ee166cfc50 100644 --- a/completions/_mise +++ b/completions/_mise @@ -24,7 +24,7 @@ _mise() { return 1 fi - local spec_file="${TMPDIR:-/tmp}/usage__usage_spec_mise_2026_6_14.spec" + local spec_file="${TMPDIR:-/tmp}/usage__usage_spec_mise_2026_6_15.spec" if [[ ! -f "$spec_file" ]]; then mise usage >| "$spec_file" fi diff --git a/completions/mise.bash b/completions/mise.bash index bac3171e0d..0adaf632c1 100644 --- a/completions/mise.bash +++ b/completions/mise.bash @@ -9,7 +9,7 @@ _mise() { local cur prev words cword was_split comp_args _comp_initialize -n : -- "$@" || return - local spec_file="${TMPDIR:-/tmp}/usage__usage_spec_mise_2026_6_14.spec" + local spec_file="${TMPDIR:-/tmp}/usage__usage_spec_mise_2026_6_15.spec" if [[ ! -f "$spec_file" ]]; then mise usage >| "$spec_file" fi diff --git a/completions/mise.fish b/completions/mise.fish index 7fc7b18c5d..a2baa0c4a8 100644 --- a/completions/mise.fish +++ b/completions/mise.fish @@ -8,7 +8,7 @@ if ! type -p usage &> /dev/null return 1 end set -l tmpdir (if set -q TMPDIR; echo $TMPDIR; else; echo /tmp; end) -set -l spec_file "$tmpdir/usage__usage_spec_mise_2026_6_14.spec" +set -l spec_file "$tmpdir/usage__usage_spec_mise_2026_6_15.spec" if not test -f "$spec_file" mise usage | string collect > "$spec_file" end diff --git a/completions/mise.ps1 b/completions/mise.ps1 index fae39e6aa3..2eeee67324 100644 --- a/completions/mise.ps1 +++ b/completions/mise.ps1 @@ -10,7 +10,7 @@ Register-ArgumentCompleter -Native -CommandName 'mise' -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) $tmpDir = if ($env:TEMP) { $env:TEMP } else { [System.IO.Path]::GetTempPath() } - $specFile = Join-Path $tmpDir "usage__usage_spec_mise_2026_6_14.kdl" + $specFile = Join-Path $tmpDir "usage__usage_spec_mise_2026_6_15.kdl" if (-not (Test-Path $specFile)) { mise usage | Out-File -FilePath $specFile -Encoding utf8 diff --git a/default.nix b/default.nix index 12c6d216c1..7a0e2ab9f4 100644 --- a/default.nix +++ b/default.nix @@ -2,7 +2,7 @@ rustPlatform.buildRustPackage { pname = "mise"; - version = "2026.6.14"; + version = "2026.6.15"; src = lib.cleanSource ./.; diff --git a/docs/hooks.md b/docs/hooks.md index 6672f061be..9f18cbf80b 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -59,7 +59,9 @@ postinstall = { run = "echo installed", run_windows = "Write-Output installed" } For `preinstall` and `postinstall`, `script = ...` is a legacy alias for `run = ...`. If a `shell` is also set on a `script`/`scripts` hook, mise warns that the shell is ignored and still runs the script with the default inline shell. Use `run = ...` with `shell = "bash -c"` to choose the inline shell command. The `script` alias for install hooks is deprecated. -The `postinstall` hook receives a `MISE_INSTALLED_TOOLS` environment variable containing a JSON array of the tools that were just installed: +A `mise install` that finds nothing to install (all configured tools are already present) still runs the `postinstall` hook — it is not skipped on a no-op install. + +The `postinstall` hook receives a `MISE_INSTALLED_TOOLS` environment variable containing a JSON array of the tools that were just installed, or `[]` when nothing was installed (e.g. a no-op install). Hooks that should only act on real installs can guard on `MISE_INSTALLED_TOOLS != "[]"`: ```toml [hooks] diff --git a/e2e/config/test_hooks b/e2e/config/test_hooks index 081478e266..6ba086de5b 100644 --- a/e2e/config/test_hooks +++ b/e2e/config/test_hooks @@ -12,8 +12,10 @@ preinstall = { run = 'echo PREINSTALL' } postinstall = { run = 'echo POSTINSTALL' } EOF +# preinstall + postinstall hooks fire when installing assert_contains "mise i 2>&1" "PREINSTALL" assert_contains "mise i dummy@1 2>&1" "POSTINSTALL" +# `mise i` with nothing missing still runs postinstall (MISE_INSTALLED_TOOLS=[], #10574) assert_contains "mise i 2>&1" "POSTINSTALL" assert_contains "mise i dummy@1 2>&1" "POSTINSTALL" eval "$(mise hook-env)" diff --git a/e2e/config/test_hooks_installed_tools b/e2e/config/test_hooks_installed_tools index 2f94bfa217..edc913f605 100644 --- a/e2e/config/test_hooks_installed_tools +++ b/e2e/config/test_hooks_installed_tools @@ -33,3 +33,14 @@ else echo "Output: $output" exit 1 fi + +# A no-op `mise install` (everything already installed) still runs postinstall, +# now with an empty JSON array rather than an unset variable (#10574) +output2=$(mise install 2>&1) +if [[ $output2 == *'INSTALLED_TOOLS=[]'* ]]; then + echo "✓ MISE_INSTALLED_TOOLS is [] on a no-op install" +else + echo "✗ MISE_INSTALLED_TOOLS should be [] on a no-op install" + echo "Output: $output2" + exit 1 +fi diff --git a/packaging/rpm/mise.spec b/packaging/rpm/mise.spec index d587c173ca..89cb25170b 100644 --- a/packaging/rpm/mise.spec +++ b/packaging/rpm/mise.spec @@ -1,6 +1,6 @@ Summary: Dev tools, env vars, and tasks in one CLI Name: mise -Version: 2026.6.14 +Version: 2026.6.15 Release: 1 URL: https://github.com/jdx/mise/ Group: System diff --git a/packaging/standalone/install.envsubst b/packaging/standalone/install.envsubst index e4a6ae22a0..3678c235c8 100644 --- a/packaging/standalone/install.envsubst +++ b/packaging/standalone/install.envsubst @@ -30,6 +30,16 @@ error() { echo "$@" >&2 exit 1 } + +unsupported_arch() { + arch="$1" + warn "unsupported architecture: $arch" + warn "" + warn "mise does not provide prebuilt binaries for this platform." + warn "If Rust/Cargo is available, install from source with:" + warn " cargo install --locked mise" + exit 1 +} #endregion #region environment setup @@ -67,7 +77,7 @@ get_arch() { elif [ "$arch" = armv7l ]; then echo "armv7$musl" else - error "unsupported architecture: $arch" + unsupported_arch "$arch" fi } diff --git a/snapcraft.yaml b/snapcraft.yaml index 3f05f46415..29119224d8 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -9,7 +9,7 @@ name: mise title: mise-en-place -version: "2026.6.14" +version: "2026.6.15" summary: Dev tools, env vars, and tasks in one CLI description: | mise-en-place prepares your development environment before each command runs. diff --git a/src/cli/install.rs b/src/cli/install.rs index d9a42dbdb4..ef27a64bde 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -422,11 +422,15 @@ impl Install { let (versions, install_error) = if missing.is_empty() { measure!("run_postinstall_hook", { info!("all tools are installed"); - hooks::run_one_hook( + // Nothing was installed, but postinstall still runs (idempotent + // project setup relies on it); MISE_INSTALLED_TOOLS is [] so hooks + // can guard on actual installs. (#10574) + hooks::run_one_hook_with_context( &config, config.get_toolset().await?, Hooks::Postinstall, None, + Some(&[]), ) .await; (vec![], Ok(())) diff --git a/src/system/packages/brew/cask.rs b/src/system/packages/brew/cask.rs index 488edda74f..7d085e2829 100644 --- a/src/system/packages/brew/cask.rs +++ b/src/system/packages/brew/cask.rs @@ -267,6 +267,13 @@ async fn fetch_archive(cask: &Cask, pr: Option<&dyn SingleReport>) -> Result {} @@ -291,22 +298,30 @@ fn extract_archive(cask: &Cask, archive: &Path, pr: Option<&dyn SingleReport>) - file::un_dmg(archive, &extract_dir)?; } else { let format = ExtractionFormat::from_file_name(filename); - if !format.is_archive() { + if format == ExtractionFormat::Raw { + // Raw executable binary — copy it using the original URL filename so find_artifact + // can match against the binary stanza source name (e.g. "claude"). + let url_filename = archive_filename(&cask.url).unwrap_or_else(|| filename.to_string()); + let dest = extract_dir.join(&url_filename); + file::copy(archive, &dest)?; + file::make_executable(&dest)?; + } else if !format.is_archive() { bail!( "brew-cask:{}: unsupported archive type for {}", cask.token, filename ); + } else { + file::extract_archive( + archive, + &extract_dir, + format, + &ExtractOptions { + pr, + ..Default::default() + }, + )?; } - file::extract_archive( - archive, - &extract_dir, - format, - &ExtractOptions { - pr, - ..Default::default() - }, - )?; } Ok(extract_dir) } @@ -316,7 +331,7 @@ fn install_app(stage: &Path, caskroom: &Path, app: &AppArtifact) -> Result<()> { .ok_or_else(|| eyre!("brew-cask: app artifact '{}' was not found", app.source))?; let caskroom_app = caskroom.join(app_bundle_name(app.target_name())?); file::remove_all(&caskroom_app)?; - file::copy_dir_all(&source, &caskroom_app)?; + ditto(&source, &caskroom_app)?; let target = app_target_path(app.target_name())?; if let Some(parent) = target.parent() { file::create_dir_all(parent)?; @@ -326,9 +341,50 @@ fn install_app(stage: &Path, caskroom: &Path, app: &AppArtifact) -> Result<()> { crate::hash::hash_to_str(&target.display().to_string()) )); file::remove_all(&tmp_target)?; - file::copy_dir_all(&caskroom_app, &tmp_target)?; - file::remove_all(&target)?; - file::rename(&tmp_target, &target)?; + ditto(&caskroom_app, &tmp_target)?; + // Atomic swap: rename existing target aside before putting the new one in place so that + // a failure during rename leaves the old app intact rather than leaving nothing. + let old_target = target.with_extension(format!( + "mise-old-{}", + crate::hash::hash_to_str(&target.display().to_string()) + )); + file::remove_all(&old_target)?; + if target.exists() { + file::rename(&target, &old_target)?; + } + if let Err(e) = file::rename(&tmp_target, &target) { + // Restore the old app if the swap failed. + if old_target.exists() { + let _ = file::rename(&old_target, &target); + } + return Err(e); + } + file::remove_all(&old_target)?; + // Remove macOS quarantine attribute so Gatekeeper doesn't block the app. + let _ = std::process::Command::new("xattr") + .args(["-r", "-d", "com.apple.quarantine"]) + .arg(&target) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status(); + Ok(()) +} + +/// Copy a directory using macOS `ditto`, which preserves resource forks, extended attributes, +/// and HFS+ metadata that a plain recursive copy would strip. +fn ditto(from: &Path, to: &Path) -> Result<()> { + let status = std::process::Command::new("ditto") + .arg(from) + .arg(to) + .status() + .wrap_err("failed to run ditto")?; + if !status.success() { + bail!( + "ditto failed copying {} to {}", + from.display(), + to.display() + ); + } Ok(()) } @@ -345,21 +401,41 @@ fn install_pkg(stage: &Path, pkg: &PkgArtifact) -> Result<()> { } fn stage_binary(stage: &Path, caskroom: &Path, binary: &BinaryArtifact) -> Result<()> { - let source = find_artifact(stage, &binary.source) - .filter(|path| path.is_file()) + let caskroom_binary = caskroom_binary_path(caskroom, binary)?; + file::remove_all(&caskroom_binary)?; + if let Some(parent) = caskroom_binary.parent() { + file::create_dir_all(parent)?; + } + if binary.source.contains("$APPDIR") { + // $APPDIR is the Applications directory where install_app placed the bundle. + // Symlink into the installed app so the CLI wrapper can trace back to find the app. + // Check both /Applications and $HOMEBREW_PREFIX/Applications per app_target_path(). + let app_binary = [ + PathBuf::from("/Applications"), + prefix::prefix().join("Applications"), + ] + .iter() + .map(|appdir| PathBuf::from(binary.source.replace("$APPDIR", &appdir.to_string_lossy()))) + .find(|p| p.is_file()) .ok_or_else(|| { eyre!( "brew-cask: binary artifact '{}' was not found", binary.source ) })?; - let caskroom_binary = caskroom_binary_path(caskroom, binary)?; - file::remove_all(&caskroom_binary)?; - if let Some(parent) = caskroom_binary.parent() { - file::create_dir_all(parent)?; + file::make_symlink(&app_binary, &caskroom_binary)?; + } else { + let source = find_artifact(stage, &binary.source) + .filter(|path| path.is_file()) + .ok_or_else(|| { + eyre!( + "brew-cask: binary artifact '{}' was not found", + binary.source + ) + })?; + file::copy(&source, &caskroom_binary)?; + file::make_executable(&caskroom_binary)?; } - file::copy(&source, &caskroom_binary)?; - file::make_executable(&caskroom_binary)?; Ok(()) } diff --git a/vendor/aqua-registry/metadata.json b/vendor/aqua-registry/metadata.json index dbbfd70301..0092a05af6 100644 --- a/vendor/aqua-registry/metadata.json +++ b/vendor/aqua-registry/metadata.json @@ -1,4 +1,4 @@ { "repository": "aquaproj/aqua-registry", - "tag": "92c705cff220bcc5c3e545eed7f4923281b51942" + "tag": "8b9d2c6527205b4a73227cb6d82918408a73f9e5" } diff --git a/vendor/aqua-registry/registry.yml b/vendor/aqua-registry/registry.yml index cfa8925151..77200c3461 100644 --- a/vendor/aqua-registry/registry.yml +++ b/vendor/aqua-registry/registry.yml @@ -22483,6 +22483,50 @@ packages: - darwin - windows - amd64 + - type: github_release + repo_owner: callumalpass + repo_name: tasknotes-tui + description: Terminal interface for managing markdown-based tasks + files: + - name: tasknotes-tui + src: tasknotes-tui-{{.Version}}-{{.OS}}-{{.Arch}}/tasknotes-tui + version_constraint: "false" + version_overrides: + - version_constraint: Version == "v0.1.0" + asset: tasknotes-tui-{{.Version}}-{{.OS}}-{{.Arch}}.{{.Format}} + format: tar.gz + windows_arm_emulation: true + replacements: + amd64: x86_64 + darwin: macos + overrides: + - goos: darwin + replacements: + amd64: amd64 + arm64: aarch64 + - goos: windows + format: zip + supported_envs: + - linux/amd64 + - darwin/arm64 + - windows/amd64 + - version_constraint: "true" + asset: tasknotes-tui-{{.Version}}-{{.OS}}-{{.Arch}}.{{.Format}} + format: tar.gz + windows_arm_emulation: true + replacements: + amd64: x86_64 + darwin: macos + overrides: + - goos: darwin + replacements: + arm64: aarch64 + - goos: windows + format: zip + supported_envs: + - darwin + - windows + - amd64 - type: github_release repo_owner: cameron-martin repo_name: bazel-lsp @@ -41734,21 +41778,25 @@ packages: github_artifact_attestations: predicate_type: https://spdx.dev/Document/v2.3 signer_workflow: gleam-lang/gleam/\.github/workflows/release\.yaml - - type: github_release - repo_owner: glossia - repo_name: cli + - type: http + name: glossia.ai/cli aliases: - - name: glossia.ai/cli + - name: glossia/cli + link: https://glossia.ai/en/docs/reference/cli/ description: Localize like you ship software files: - name: glossia version_constraint: "false" version_overrides: - - version_constraint: semver("< 0.15.1") - version_prefix: cli-v - type: http + - version_constraint: Version startsWith "cli-v" or Version startsWith "v" + error_message: Remove the prefix "cli-v" from the version + - version_constraint: Version startsWith "v" + error_message: Remove the prefix "v" from the version + - version_constraint: semver("<= 0.14.0") + error_message: The version is too old. Please upgrade to 0.16.0 or later. + - version_constraint: "true" windows_arm_emulation: true - url: https://glossia-releases.t3.storage.dev/cli/{{.SemVer}}/glossia-{{.OS}}-{{.Arch}}.{{.Format}} + url: https://releases.glossia.ai/cli/{{.Version}}/glossia-{{.OS}}-{{.Arch}}.{{.Format}} replacements: amd64: x64 format: tar.gz @@ -41757,21 +41805,8 @@ packages: format: zip checksum: type: http - url: https://glossia-releases.t3.storage.dev/cli/{{.SemVer}}/SHA256SUMS - algorithm: sha256 - - version_constraint: "true" - asset: glossia-{{.OS}}-{{.Arch}}.{{.Format}} - format: tar.gz - windows_arm_emulation: true - replacements: - amd64: x64 - checksum: - type: github_release - asset: SHA256SUMS + url: https://releases.glossia.ai/cli/{{.Version}}/SHA256SUMS algorithm: sha256 - overrides: - - goos: windows - format: zip - type: github_release repo_owner: go-acme repo_name: lego @@ -68956,6 +68991,31 @@ packages: files: - name: exa src: bin/exa + - type: github_release + repo_owner: ogulcancelik + repo_name: herdr + description: agent multiplexer that lives in your terminal + version_constraint: "false" + version_overrides: + - version_constraint: Version == "v0.4.0" + asset: herdr-{{.OS}}-{{.Arch}} + format: raw + replacements: + amd64: x86_64 + arm64: aarch64 + darwin: macos + supported_envs: + - darwin + - version_constraint: "true" + asset: herdr-{{.OS}}-{{.Arch}} + format: raw + replacements: + amd64: x86_64 + arm64: aarch64 + darwin: macos + supported_envs: + - linux + - darwin - type: github_release repo_owner: ohkrab repo_name: krab @@ -75540,8 +75600,12 @@ packages: - darwin - type: github_release repo_owner: pvolok - repo_name: mprocs + repo_name: dekit + aliases: + - name: pvolok/mprocs description: Run multiple commands in parallel + files: + - name: mprocs version_constraint: "false" version_overrides: - version_constraint: semver("<= 0.0.1")