feat: add devcontainer generator (#4355)

- Add generator to provide a devcontainer compatible with mise
- Name / image / mount are customizable
- Add "homebrew" path for **wsl** / **linux** in the e2e tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Pierre-Emmanuel Mercier 2025-02-16 12:47:59 +01:00 committed by GitHub
parent fc70f04ec8
commit 9d69158c1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 233 additions and 1 deletions

View File

@ -122,6 +122,9 @@ export const commands: { [key: string]: Command } = {
config: {
hide: false,
},
devcontainer: {
hide: false,
},
"git-pre-commit": {
hide: false,
},

View File

@ -10,6 +10,7 @@
- [`mise generate bootstrap [FLAGS]`](/cli/generate/bootstrap.md)
- [`mise generate config [-t --tool-versions <TOOL_VERSIONS>] [-o --output <OUTPUT>]`](/cli/generate/config.md)
- [`mise generate devcontainer [FLAGS]`](/cli/generate/devcontainer.md)
- [`mise generate git-pre-commit [FLAGS]`](/cli/generate/git-pre-commit.md)
- [`mise generate github-action [FLAGS]`](/cli/generate/github-action.md)
- [`mise generate task-docs [FLAGS]`](/cli/generate/task-docs.md)

View File

@ -0,0 +1,30 @@
# `mise generate devcontainer`
- **Usage**: `mise generate devcontainer [FLAGS]`
- **Source code**: [`src/cli/generate/devcontainer.rs`](https://github.com/jdx/mise/blob/main/src/cli/generate/devcontainer.rs)
[experimental] Generate a devcontainer to execute mise
## Flags
### `-n --name <NAME>`
The name of the devcontainer
### `-i --image <IMAGE>`
The image to use for the devcontainer
### `-m --mount-mise-data`
Bind the mise-data-volume to the devcontainer
### `-w --write`
write to .devcontainer/devcontainer.json
Examples:
```
mise generate devcontainer
```

View File

@ -86,6 +86,7 @@ Can also use `MISE_NO_CONFIG=1`
- [`mise generate <SUBCOMMAND>`](/cli/generate.md)
- [`mise generate bootstrap [FLAGS]`](/cli/generate/bootstrap.md)
- [`mise generate config [-t --tool-versions <TOOL_VERSIONS>] [-o --output <OUTPUT>]`](/cli/generate/config.md)
- [`mise generate devcontainer [FLAGS]`](/cli/generate/devcontainer.md)
- [`mise generate git-pre-commit [FLAGS]`](/cli/generate/git-pre-commit.md)
- [`mise generate github-action [FLAGS]`](/cli/generate/github-action.md)
- [`mise generate task-docs [FLAGS]`](/cli/generate/task-docs.md)

View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Default devcontainer
assert_json_partial_object "mise generate devcontainer" "name,description,features,mounts,container_env" "
{
\"name\": \"mise\",
\"image\": \"mcr.microsoft.com/devcontainers/base:ubuntu\",
\"features\": {
\"ghcr.io/devcontainers-extra/features/mise:1\": {}
},
\"mounts\": [],
\"container_env\": {}
}
"
# With custom name and image
assert_json_partial_object "mise generate devcontainer --name test --image testimage:latest" "name,description" "
{
\"name\": \"test\",
\"image\": \"testimage:latest\"
}
"
# With mount
assert_json_partial_object "mise generate devcontainer --mount-mise-data" "name,description,features,mounts,container_env" "
{
\"name\": \"mise\",
\"image\": \"mcr.microsoft.com/devcontainers/base:ubuntu\",
\"features\": {
\"ghcr.io/devcontainers-extra/features/mise:1\": {}
},
\"mounts\": [
{
\"source\": \"mise-data-volume\",
\"target\": \"/mnt/mise-data\",
\"type\": \"volume\"
}
],
\"container_env\": {
\"MISE_DATA_VOLUME\": \"/mnt/mise-data\"
}
}
"

View File

@ -75,7 +75,7 @@ within_isolated_env() {
MISE_TRUSTED_CONFIG_PATHS="$TEST_ISOLATED_DIR" \
MISE_USE_VERSIONS_HOST="${MISE_USE_VERSIONS_HOST:-1}" \
MISE_YES=1 \
PATH="${CARGO_TARGET_DIR:-$ROOT/target}/debug:$HOME/mise/bin:$CARGO_HOME/bin:$TEST_HOME/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" \
PATH="${CARGO_TARGET_DIR:-$ROOT/target}/debug:$HOME/mise/bin:$CARGO_HOME/bin:$TEST_HOME/bin:/opt/homebrew/bin:/home/linuxbrew/.linuxbrew/bin:/usr/local/bin:/usr/bin:/bin" \
ROOT="$ROOT" \
RUSTUP_HOME="$RUSTUP_HOME" \
RUST_BACKTRACE="${RUST_BACKTRACE:-1}" \

View File

@ -305,6 +305,17 @@ cmd generate subcommand_required=#true help="[experimental] Generate files for v
arg <OUTPUT>
}
}
cmd devcontainer help="[experimental] Generate a devcontainer to execute mise" {
after_long_help "Examples:\n\n $ mise generate devcontainer\n"
flag "-n --name" help="The name of the devcontainer" {
arg <NAME>
}
flag "-i --image" help="The image to use for the devcontainer" {
arg <IMAGE>
}
flag "-m --mount-mise-data" help="Bind the mise-data-volume to the devcontainer"
flag "-w --write" help="write to .devcontainer/devcontainer.json"
}
cmd git-pre-commit help="[experimental] Generate a git pre-commit hook" {
alias pre-commit
long_help "[experimental] Generate a git pre-commit hook\n\nThis command generates a git pre-commit hook that runs a mise task like `mise run pre-commit`\nwhen you commit changes to your repository.\n\nStaged files are passed to the task as `STAGED`."

View File

@ -0,0 +1,107 @@
use std::collections::HashMap;
use crate::{
config::SETTINGS,
file::{self, display_path},
git::Git,
};
use serde::Serialize;
/// [experimental] Generate a devcontainer to execute mise
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Devcontainer {
/// The name of the devcontainer
#[clap(long, short, verbatim_doc_comment)]
name: Option<String>,
/// The image to use for the devcontainer
#[clap(long, short, verbatim_doc_comment)]
image: Option<String>,
/// Bind the mise-data-volume to the devcontainer
#[clap(long, short, verbatim_doc_comment)]
mount_mise_data: bool,
/// write to .devcontainer/devcontainer.json
#[clap(long, short)]
write: bool,
}
#[derive(Serialize)]
struct DevcontainerTemplate {
name: String,
image: String,
features: HashMap<String, HashMap<String, String>>,
mounts: Vec<DevcontainerMount>,
container_env: HashMap<String, String>,
}
#[derive(Serialize)]
struct DevcontainerMount {
source: String,
target: String,
#[serde(rename = "type")]
type_field: String,
}
impl Devcontainer {
pub fn run(self) -> eyre::Result<()> {
SETTINGS.ensure_experimental("generate devcontainer")?;
let output = self.generate()?;
if self.write {
let path = Git::get_root()?.join(".devcontainer/devcontainer.json");
file::write(&path, &output)?;
miseprintln!("Wrote to {}", display_path(&path));
} else {
miseprintln!("{output}");
}
Ok(())
}
fn generate(&self) -> eyre::Result<String> {
let name = self.name.as_deref().unwrap_or("mise");
let image = self
.image
.as_deref()
.unwrap_or("mcr.microsoft.com/devcontainers/base:ubuntu");
let mut mounts = vec![];
let mut container_env = HashMap::new();
if self.mount_mise_data {
mounts.push(DevcontainerMount {
source: "mise-data-volume".to_string(),
target: "/mnt/mise-data".to_string(),
type_field: "volume".to_string(),
});
container_env.insert("MISE_DATA_VOLUME".to_string(), "/mnt/mise-data".to_string());
}
let mut features = HashMap::new();
features.insert(
"ghcr.io/devcontainers-extra/features/mise:1".to_string(),
HashMap::new(),
);
let template = DevcontainerTemplate {
name: name.to_string(),
image: image.to_string(),
features,
mounts,
container_env,
};
let output = serde_json::to_string_pretty(&template)?;
Ok(output)
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise generate devcontainer</bold>
"#
);

View File

@ -2,6 +2,7 @@ use clap::Subcommand;
mod bootstrap;
mod config;
mod devcontainer;
mod git_pre_commit;
mod github_action;
mod task_docs;
@ -19,6 +20,7 @@ pub struct Generate {
enum Commands {
Bootstrap(bootstrap::Bootstrap),
Config(config::Config),
Devcontainer(devcontainer::Devcontainer),
GitPreCommit(git_pre_commit::GitPreCommit),
GithubAction(github_action::GithubAction),
TaskDocs(task_docs::TaskDocs),
@ -30,6 +32,7 @@ impl Commands {
match self {
Self::Bootstrap(cmd) => cmd.run(),
Self::Config(cmd) => cmd.run(),
Self::Devcontainer(cmd) => cmd.run(),
Self::GitPreCommit(cmd) => cmd.run(),
Self::GithubAction(cmd) => cmd.run(),
Self::TaskDocs(cmd) => cmd.run(),

View File

@ -1060,6 +1060,38 @@ const completionSpec: Fig.Spec = {
},
],
},
{
name: "devcontainer",
description: "[experimental] Generate a devcontainer to execute mise",
options: [
{
name: ["-n", "--name"],
description: "The name of the devcontainer",
isRepeatable: false,
args: {
name: "name",
},
},
{
name: ["-i", "--image"],
description: "The image to use for the devcontainer",
isRepeatable: false,
args: {
name: "image",
},
},
{
name: ["-m", "--mount-mise-data"],
description: "Bind the mise-data-volume to the devcontainer",
isRepeatable: false,
},
{
name: ["-w", "--write"],
description: "Write to .devcontainer/devcontainer.json",
isRepeatable: false,
},
],
},
{
name: ["git-pre-commit", "pre-commit"],
description: "[experimental] Generate a git pre-commit hook",