Skip to content

feat(env): support array access for multiple tool versions in tera templates#8129

Merged
jdx merged 1 commit intomainfrom
feat/tools-tera-array
Feb 12, 2026
Merged

feat(env): support array access for multiple tool versions in tera templates#8129
jdx merged 1 commit intomainfrom
feat/tools-tera-array

Conversation

@jdx
Copy link
Owner

@jdx jdx commented Feb 12, 2026

Summary

  • When multiple versions of a tool are installed, tools.<name> is now serialized as an array, enabling access to individual versions via tools.<name>[0].path, tools.<name>[1].path, etc.
  • Single-version tools remain plain objects (tools.<name>.path) for backwards compatibility.
  • Uses #[serde(untagged)] enum to dynamically serialize as either object or array.

Closes #8128

Test plan

  • E2e test test_env_tools extended with multi-version array access test
  • Existing single-version tests still pass (backwards compatible)
  • Lint passes

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it changes the tools value shape in Tera context when multiple versions are installed, which could affect existing templates that assume a single object; single-version behavior is preserved and covered by e2e tests.

Overview
Tera tools variable now supports multi-version access. Toolset::build_tools_tera_map collects all installed versions per tool and serializes tools.<name> as either a single ToolInfo object or an array of ToolInfo via a new untagged ToolInfos enum.

Docs are updated to describe the object-vs-array behavior, and e2e/env/test_env_tools adds coverage for indexing into tools.tiny[0/1].version and .path when two versions are installed.

Written by Cursor Bugbot for commit ebe8089. This will update automatically on new commits. Configure here.

…mplates

When multiple versions of a tool are installed, `tools.<name>` is now an
array so individual versions can be accessed via `tools.<name>[0].path`,
`tools.<name>[1].path`, etc. Single-version tools remain objects for
backwards compatibility.

Closes #8128

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings February 12, 2026 14:46
@jdx jdx enabled auto-merge (squash) February 12, 2026 14:46
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jdx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the mise environment by introducing support for array access to tool versions within Tera templates. Previously, tools.<name> always represented a single tool object. Now, if multiple versions of a tool are installed, tools.<name> will be serialized as an array of tool information, enabling users to access specific versions by index. This change significantly improves the flexibility of Tera templates for managing diverse tool environments, while carefully preserving backward compatibility for existing single-version configurations.

Highlights

  • Tera Template Array Access: Enabled array access for multiple tool versions within Tera templates, allowing tools.<name>[0].path for individual versions.
  • Dynamic Serialization: Modified tools.<name> to serialize as an array when multiple versions are installed, while single-version tools remain plain objects for backward compatibility.
  • Implementation Detail: Implemented dynamic serialization using a new #[serde(untagged)] enum (ToolInfos) to handle both single objects and arrays.
  • Documentation Update: Updated documentation to clearly describe the new tools variable behavior in templates.
  • Test Coverage: Extended end-to-end tests to validate the new multi-version array access functionality.
Changelog
  • docs/templates.md
    • Updated the description of the tools variable to clarify its serialization as either a ToolInfo object or an array of ToolInfo objects depending on the number of installed versions.
    • Added examples for accessing tools.<name>[0].version and tools.<name>[0].path for multi-version scenarios.
  • e2e/env/test_env_tools
    • Added a new test block to configure mise.toml with multiple tiny tool versions.
    • Verified that environment variables can correctly access specific versions and paths using array indexing (e.g., tools.tiny[0].version).
  • src/toolset/mod.rs
    • Defined a new ToolInfos enum, marked with #[serde(untagged)], to encapsulate either a Single(ToolInfo) or Multiple(Vec<ToolInfo>).
    • Modified the build_tools_tera_map function to return HashMap<String, ToolInfos>, dynamically converting Vec<ToolInfo> into ToolInfos::Single or ToolInfos::Multiple based on the count of installed versions for a given tool.
Activity
  • E2e test test_env_tools was extended with multi-version array access tests.
  • Existing single-version tests were confirmed to still pass, ensuring backward compatibility.
  • Linting checks passed.
  • The PR summary was generated by Claude Code and Cursor Bugbot.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for exposing multiple installed versions of a tool to Tera templates by serializing tools.<name> as either an object (single version) or an array (multiple versions), while keeping single-version behavior backward compatible.

Changes:

  • Introduces an untagged ToolInfos enum to serialize tools.<name> as ToolInfo or Vec<ToolInfo>.
  • Updates build_tools_tera_map to accumulate multiple ToolInfo entries per tool name.
  • Extends e2e coverage and documents array access in templates.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/toolset/mod.rs Serializes tool info as object vs array depending on number of versions.
e2e/env/test_env_tools Adds e2e assertions for array indexing when multiple versions are configured.
docs/templates.md Documents tools.<name> as object-or-array with indexing examples.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

tools_map.entry(short).or_insert(info);
tools_map.entry(short).or_default().push(info);
}
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The array index semantics (tools.<name>[0], [1], etc.) depend on the iteration order of list_current_installed_versions(config). If that iterator isn’t guaranteed to be stable and aligned with configuration order, template indexing (and the added e2e test) can become non-deterministic. Consider enforcing a deterministic ordering per tool before returning (e.g., preserve configured order from the toolset definition, or explicitly sort by a defined key like the requested version order).

Suggested change
}
}
// Ensure deterministic ordering of versions per tool so that
// template index semantics (tools.<name>[0], [1], ...) are stable.
for versions in tools_map.values_mut() {
versions.sort_by(|a, b| a.version.cmp(&b.version));
}

Copilot uses AI. Check for mistakes.
Comment on lines 326 to 336
let info = ToolInfo {
version: tv.version.clone(),
path: tv.install_path().to_string_lossy().to_string(),
};
tools_map.entry(tool_name.clone()).or_insert(info.clone());
tools_map
.entry(tool_name.clone())
.or_default()
.push(info.clone());
if short != tool_name {
tools_map.entry(short).or_insert(info);
tools_map.entry(short).or_default().push(info);
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This always clones info for the primary tool name even when there is no short alias, which creates unnecessary allocations when populating the map. You can avoid the unconditional clone by pushing info into the primary vec and only cloning when also pushing to short (or by constructing two infos only in the alias case).

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +46
assert_contains "mise hook-env -s bash" "export TINY_V0=3"
assert_contains "mise hook-env -s bash" "export TINY_P0=$TINY_PATH0"
assert_contains "mise hook-env -s bash" "export TINY_V1=2"
assert_contains "mise hook-env -s bash" "export TINY_P1=$TINY_PATH1"
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

This test assumes tools.tiny[0] corresponds to the first configured version (\"3\") and [1] to the second (\"2\"). If the production code doesn’t explicitly guarantee and enforce that ordering, this will be flaky across platforms or internal iteration changes. Either (a) make the code guarantee config-order indexing (preferred, since indexing implies order), or (b) adjust the test to not depend on a specific order.

Suggested change
assert_contains "mise hook-env -s bash" "export TINY_V0=3"
assert_contains "mise hook-env -s bash" "export TINY_P0=$TINY_PATH0"
assert_contains "mise hook-env -s bash" "export TINY_V1=2"
assert_contains "mise hook-env -s bash" "export TINY_P1=$TINY_PATH1"
HOOK_ENV_OUTPUT=$(mise hook-env -s bash)
# Ensure version 3 is present in either TINY_V0 or TINY_V1
if [[ "$HOOK_ENV_OUTPUT" != *"export TINY_V0=3"* && "$HOOK_ENV_OUTPUT" != *"export TINY_V1=3"* ]]; then
echo "expected version 3 in either TINY_V0 or TINY_V1"
exit 1
fi
# Ensure version 2 is present in either TINY_V0 or TINY_V1
if [[ "$HOOK_ENV_OUTPUT" != *"export TINY_V0=2"* && "$HOOK_ENV_OUTPUT" != *"export TINY_V1=2"* ]]; then
echo "expected version 2 in either TINY_V0 or TINY_V1"
exit 1
fi
# Ensure the tiny@3 path is present in either TINY_P0 or TINY_P1
if [[ "$HOOK_ENV_OUTPUT" != *"export TINY_P0=$TINY_PATH0"* && "$HOOK_ENV_OUTPUT" != *"export TINY_P1=$TINY_PATH0"* ]]; then
echo "expected tiny@3 path in either TINY_P0 or TINY_P1"
exit 1
fi
# Ensure the tiny@2 path is present in either TINY_P0 or TINY_P1
if [[ "$HOOK_ENV_OUTPUT" != *"export TINY_P0=$TINY_PATH1"* && "$HOOK_ENV_OUTPUT" != *"export TINY_P1=$TINY_PATH1"* ]]; then
echo "expected tiny@2 path in either TINY_P0 or TINY_P1"
exit 1
fi

Copilot uses AI. Check for mistakes.
- When multiple versions are installed, it becomes an array:
- `tools.<name>[0].version: String` – The first version
- `tools.<name>[0].path: String` – The first install path
- `tools.<name>[1].version: String` – The second version, etc.
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The docs describe indexing but don’t define what determines the array order (e.g., config order, resolution order, semantic version sort, installation time). Since users will rely on stable indexing, it would help to explicitly document the ordering contract (and ideally match whatever deterministic ordering is implemented in build_tools_tera_map).

Suggested change
- `tools.<name>[1].version: String` – The second version, etc.
- `tools.<name>[1].version: String` – The second version, etc. The array is
ordered so that index `0` is the version that mise would select as the active
version for that tool name, with higher indices following the same resolution
priority.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable feature for accessing multiple tool versions within Tera templates. The implementation, which uses an untagged enum for serialization, is clean and effective. The accompanying documentation updates are clear, and the new end-to-end test provides good coverage for the new functionality. I have one suggestion to enhance the determinism of the tool map by using IndexMap. Overall, this is a solid contribution.

Comment on lines +321 to +322
pub fn build_tools_tera_map(&self, config: &Arc<Config>) -> HashMap<String, ToolInfos> {
let mut tools_map: HashMap<String, Vec<ToolInfo>> = HashMap::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To ensure a deterministic order of tools in the tools map, which can be important for templates that might iterate over it, consider using IndexMap instead of HashMap. This will preserve the insertion order, which is based on the order tools are defined in your configuration.

The list_current_installed_versions function provides tools in a specific order, but HashMap does not preserve this order.

Suggested change
pub fn build_tools_tera_map(&self, config: &Arc<Config>) -> HashMap<String, ToolInfos> {
let mut tools_map: HashMap<String, Vec<ToolInfo>> = HashMap::new();
pub fn build_tools_tera_map(&self, config: &Arc<Config>) -> IndexMap<String, ToolInfos> {
let mut tools_map: IndexMap<String, Vec<ToolInfo>> = IndexMap::new();

@jdx jdx merged commit f655777 into main Feb 12, 2026
38 checks passed
@jdx jdx deleted the feat/tools-tera-array branch February 12, 2026 14:58
@github-actions
Copy link

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.10 x -- echo 22.0 ± 0.4 21.2 24.4 1.00
mise x -- echo 22.6 ± 0.6 21.9 27.7 1.02 ± 0.03

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.10 env 21.7 ± 0.2 21.0 22.5 1.00
mise env 21.9 ± 0.2 21.4 22.8 1.01 ± 0.01

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.10 hook-env 22.1 ± 0.4 21.5 24.8 1.00
mise hook-env 22.7 ± 0.5 21.8 24.2 1.03 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2026.2.10 ls 20.4 ± 0.4 19.7 23.8 1.00
mise ls 20.8 ± 0.5 19.9 22.3 1.02 ± 0.03

xtasks/test/perf

Command mise-2026.2.10 mise Variance
install (cached) 120ms 119ms +0%
ls (cached) 74ms 74ms +0%
bin-paths (cached) 78ms 78ms +0%
task-ls (cached) 780ms 791ms -1%

lucasew pushed a commit to lucasew/CONTRIB-mise that referenced this pull request Feb 18, 2026
…mplates (jdx#8129)

## Summary
- When multiple versions of a tool are installed, `tools.<name>` is now
serialized as an array, enabling access to individual versions via
`tools.<name>[0].path`, `tools.<name>[1].path`, etc.
- Single-version tools remain plain objects (`tools.<name>.path`) for
backwards compatibility.
- Uses `#[serde(untagged)]` enum to dynamically serialize as either
object or array.

Closes jdx#8128

## Test plan
- [x] E2e test `test_env_tools` extended with multi-version array access
test
- [x] Existing single-version tests still pass (backwards compatible)
- [x] Lint passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> <sup>[Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) is
generating a summary for commit
ebe8089. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants