feat: mise g bootstrap (#3792)

This commit is contained in:
jdx authored and GitHub committed 2024-12-22 19:43:34 +00:00
1 parent 40a13f8e70
commit 0d07383bb9
19 files changed
+427 -6

No files matched your search

Generated
+7
View File
@@ -2572,6 +2572,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "minisign-verify"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
@@ -2648,6 +2654,7 @@ dependencies = [
"junction",
"log",
"md-5",
"minisign-verify",
"nix",
"num_cpus",
"number_prefix",
+1
View File
@@ -88,6 +88,7 @@ indoc = "2"
itertools = "0.13"
junction = "1"
log = "0.4"
minisign-verify = "0.2"
md-5 = "0.10"
num_cpus = "1"
number_prefix = "0.4"
+6
View File
@@ -116,6 +116,9 @@ export const commands: { [key: string]: Command } = {
generate: {
hide: false,
subcommands: {
bootstrap: {
hide: false,
},
config: {
hide: false,
},
@@ -128,6 +131,9 @@ export const commands: { [key: string]: Command } = {
"task-docs": {
hide: false,
},
"task-stubs": {
hide: false,
},
},
},
global: {
+2
View File
@@ -8,7 +8,9 @@
## Subcommands
- [`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 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)
- [`mise generate task-stubs [-m --mise-bin <MISE_BIN>] [-d --dir <DIR>]`](/cli/generate/task-stubs.md)
+36
View File
@@ -0,0 +1,36 @@
# `mise generate bootstrap`
- **Usage**: `mise generate bootstrap [FLAGS]`
- **Source code**: [`src/cli/generate/bootstrap.rs`](https://github.com/jdx/mise/blob/main/src/cli/generate/bootstrap.rs)
[experimental] Generate a script to download+execute mise
This is designed to be used in a project where contributors may not have mise installed.
## Flags
### `-l --localize`
Sandboxes mise internal directories like MISE_DATA_DIR and MISE_CACHE_DIR into a `.mise` directory in the project
This is necessary if users may use a different version of mise outside the project.
### `--localized-dir <LOCALIZED_DIR>`
Directory to put localized data into
### `-V --version <VERSION>`
Specify mise version to fetch
### `-w --write <WRITE>`
instead of outputting the script to stdout, write to a file and make it executable
Examples:
```
mise generate bootstrap >./bin/mise
chmod +x ./bin/mise
./bin/mise install – automatically downloads mise to .mise if not already installed
```
+30
View File
@@ -0,0 +1,30 @@
# `mise generate task-stubs`
- **Usage**: `mise generate task-stubs [-m --mise-bin <MISE_BIN>] [-d --dir <DIR>]`
- **Source code**: [`src/cli/generate/task-stubs.rs`](https://github.com/jdx/mise/blob/main/src/cli/generate/task-stubs.rs)
[experimental] Generates shims to run mise tasks
By default, this will build shims like ./bin/&lt;task>. These can be paired with `mise generate bootstrap`
so contributors to a project can execute mise tasks without installing mise into their system.
## Flags
### `-m --mise-bin <MISE_BIN>`
Path to a mise bin to use when running the task stub.
Use `--mise-bin=./bin/mise` to use a mise bin generated from `mise generate bootstrap`
### `-d --dir <DIR>`
Directory to create task stubs inside of
Examples:
```
$ mise task add test -- echo 'running tests'
$ mise generate task-stubs
$ ./bin/test
running tests
```
+2
View File
@@ -84,10 +84,12 @@ Can also use `MISE_NO_CONFIG=1`
- [`mise exec [FLAGS] [TOOL@VERSION]... [-- COMMAND]...`](/cli/exec.md)
- [`mise fmt [-a --all]`](/cli/fmt.md)
- [`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 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)
- [`mise generate task-stubs [-m --mise-bin <MISE_BIN>] [-d --dir <DIR>]`](/cli/generate/task-stubs.md)
- [`mise implode [--config] [-n --dry-run]`](/cli/implode.md)
- [`mise install [FLAGS] [TOOL@VERSION]...`](/cli/install.md)
- [`mise install-into <TOOL@VERSION> <PATH>`](/cli/install-into.md)
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# TODO: enable this after next release
#assert "mise generate bootstrap -w"
#assert "./bin/mise -v" "xx"
#
#assert "mise task add xxx -- echo 'running xxx'"
#assert "mise generate task-stubs --mise-bin ./bin/mise"
#assert "./bin/xxx" "running xxx"
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
assert "mise task add xxx -- echo 'running xxx'"
assert "mise generate task-stubs"
assert "./bin/xxx" "running xxx"
+2 -2
View File
@@ -1,2 +1,2 @@
untrusted comment: minisign public key E97C65762BC3EF8B
RWSL78MrdmV86brusnKIy6M7tCyiwUecPBqZ1w7U2vFPH40DuA2lKA1x
untrusted comment: minisign public key 64113EDF160FDEC2
RWTC3g8W3z4RZK3V3qv7fa1QY4JEWyBtqIHW+85QlJpZc5yG+uNYNBSZ
+43
View File
@@ -478,6 +478,29 @@ Sorts keys and cleans up whitespace in mise.toml"
cmd "generate" subcommand_required=true help="[experimental] Generate files for various tools/services" {
alias "gen"
alias "g" hide=true
cmd "bootstrap" help="[experimental] Generate a script to download+execute mise" {
long_help r"[experimental] Generate a script to download+execute mise
This is designed to be used in a project where contributors may not have mise installed."
after_long_help r"Examples:
$ mise generate bootstrap >./bin/mise
$ chmod +x ./bin/mise
$ ./bin/mise install – automatically downloads mise to .mise if not already installed
"
flag "-l --localize" help="Sandboxes mise internal directories like MISE_DATA_DIR and MISE_CACHE_DIR into a `.mise` directory in the project" {
long_help "Sandboxes mise internal directories like MISE_DATA_DIR and MISE_CACHE_DIR into a `.mise` directory in the project\n\nThis is necessary if users may use a different version of mise outside the project."
}
flag "--localized-dir" help="Directory to put localized data into" {
arg "<LOCALIZED_DIR>"
}
flag "-V --version" help="Specify mise version to fetch" {
arg "<VERSION>"
}
flag "-w --write" help="instead of outputting the script to stdout, write to a file and make it executable" {
arg "<WRITE>"
}
}
cmd "config" help="[experimental] Generate a mise.toml file" {
alias "g"
after_long_help r"Examples:
@@ -554,6 +577,26 @@ when you push changes to your repository."
}
}
}
cmd "task-stubs" help="[experimental] Generates shims to run mise tasks" {
long_help r"[experimental] Generates shims to run mise tasks
By default, this will build shims like ./bin/<task>. These can be paired with `mise generate bootstrap`
so contributors to a project can execute mise tasks without installing mise into their system."
after_long_help r"Examples:
$ mise task add test -- echo 'running tests'
$ mise generate task-stubs
$ ./bin/test
running tests
"
flag "-m --mise-bin" help="Path to a mise bin to use when running the task stub." {
long_help "Path to a mise bin to use when running the task stub.\n\nUse `--mise-bin=./bin/mise` to use a mise bin generated from `mise generate bootstrap`"
arg "<MISE_BIN>"
}
flag "-d --dir" help="Directory to create task stubs inside of" {
arg "<DIR>"
}
}
}
cmd "global" hide=true help="Sets/gets the global tool version(s)" {
long_help r"Sets/gets the global tool version(s)
+3 -4
View File
@@ -193,21 +193,18 @@ after_finish_help() {
info "mise: run the following to activate mise in your shell:"
info "echo \"eval \\\"\\\$($install_path activate zsh)\\\"\" >> \"${ZDOTDIR-$HOME}/.zshrc\""
info ""
info "mise: this must be run in order to use mise in the terminal"
info "mise: run \`mise doctor\` to verify this is setup correctly"
;;
*/bash)
info "mise: run the following to activate mise in your shell:"
info "echo \"eval \\\"\\\$($install_path activate bash)\\\"\" >> ~/.bashrc"
info ""
info "mise: this must be run in order to use mise in the terminal"
info "mise: run \`mise doctor\` to verify this is setup correctly"
;;
*/fish)
info "mise: run the following to activate mise in your shell:"
info "echo \"$install_path activate fish | source\" >> ~/.config/fish/config.fish"
info ""
info "mise: this must be run in order to use mise in the terminal"
info "mise: run \`mise doctor\` to verify this is setup correctly"
;;
*)
@@ -217,4 +214,6 @@ after_finish_help() {
}
install_mise
after_finish_help
if [ "${MISE_INSTALL_HELP-}" != 0 ]; then
after_finish_help
fi
+118
View File
@@ -0,0 +1,118 @@
use crate::config::SETTINGS;
use crate::file;
use crate::http::HTTP;
use crate::ui::info;
use crate::Result;
use clap::ValueHint;
use std::path::PathBuf;
use xx::file::display_path;
use xx::regex;
/// [experimental] Generate a script to download+execute mise
///
/// This is designed to be used in a project where contributors may not have mise installed.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct Bootstrap {
/// Sandboxes mise internal directories like MISE_DATA_DIR and MISE_CACHE_DIR into a `.mise` directory in the project
///
/// This is necessary if users may use a different version of mise outside the project.
#[clap(long, short, verbatim_doc_comment)]
localize: bool,
/// Directory to put localized data into
#[clap(long, verbatim_doc_comment, default_value=".mise", value_hint=ValueHint::DirPath)]
localized_dir: PathBuf,
/// Specify mise version to fetch
#[clap(long, short = 'V', verbatim_doc_comment)]
version: Option<String>,
/// instead of outputting the script to stdout, write to a file and make it executable
#[clap(long, short, verbatim_doc_comment, num_args=0..=1, default_missing_value = "./bin/mise")]
write: Option<PathBuf>,
}
impl Bootstrap {
pub fn run(self) -> eyre::Result<()> {
SETTINGS.ensure_experimental("generate bootstrap")?;
let output = self.generate()?;
if let Some(bin) = &self.write {
if let Some(parent) = bin.parent() {
file::create_dir_all(parent)?;
}
file::write(bin, &output)?;
file::make_executable(bin)?;
miseprintln!("Wrote to {}", display_path(bin));
} else {
miseprintln!("{output}");
}
Ok(())
}
fn generate(&self) -> Result<String> {
let url = if let Some(v) = &self.version {
format!("https://mise.jdx.dev/v{v}/install.sh")
} else {
"https://mise.jdx.dev/install.sh".into()
};
let install = HTTP.get_text(&url)?;
// let install_sig = HTTP.get_text(format!("{url}.minisig"))?;
// minisign::verify(minisign::MISE_PUB_KEY, &install, &install_sig)?;
let install = info::indent_by(install, " ");
let version = regex!(r#"version="\$\{MISE_VERSION:-v([0-9.]+)\}""#)
.captures(&install)
.unwrap()
.get(1)
.unwrap()
.as_str();
let vars = if self.localize {
// TODO: this will only work right if it is in the base directory, not an absolute path or has a subdirectory
let localized_dir = self.localized_dir.to_string_lossy();
format!(
r#"
local script_dir=$( cd -- "$( dirname -- "${{BASH_SOURCE[0]}}" )" &> /dev/null && pwd )
local project_dir=$( cd -- "$( dirname -- "$script_dir" )" &> /dev/null && pwd )
local localized_dir="$project_dir/{localized_dir}"
export MISE_DATA_DIR="$localized_dir"
export MISE_CONFIG_DIR="$localized_dir"
export MISE_CACHE_DIR="$localized_dir/cache"
export MISE_STATE_DIR="$localized_dir/state"
export MISE_INSTALL_PATH="$localized_dir/mise-{version}"
"#
)
} else {
format!(
r#"
local cache_home="${{XDG_CACHE_HOME:-$HOME/.cache}}/mise"
export MISE_INSTALL_PATH="$cache_home/mise-{version}"
"#
)
};
let vars = info::indent_by(vars.trim(), " ");
let script = format!(
r#"
#!/bin/sh
set -euxo pipefail
__mise_bootstrap() {{
{vars}
install() {{
{install}
}}
local MISE_INSTALL_HELP=0
test -f "$MISE_INSTALL_PATH" || install
}}
__mise_bootstrap
exec "$MISE_INSTALL_PATH" "$@"
"#
);
Ok(script.trim().to_string())
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise generate bootstrap >./bin/mise</bold>
$ <bold>chmod +x ./bin/mise</bold>
$ <bold>./bin/mise install</bold> – automatically downloads mise to .mise if not already installed
"#
);
+6
View File
@@ -1,9 +1,11 @@
use clap::Subcommand;
mod bootstrap;
mod config;
mod git_pre_commit;
mod github_action;
mod task_docs;
mod task_stubs;
/// [experimental] Generate files for various tools/services
#[derive(Debug, clap::Args)]
@@ -15,19 +17,23 @@ pub struct Generate {
#[derive(Debug, Subcommand)]
enum Commands {
Bootstrap(bootstrap::Bootstrap),
Config(config::Config),
GitPreCommit(git_pre_commit::GitPreCommit),
GithubAction(github_action::GithubAction),
TaskDocs(task_docs::TaskDocs),
TaskStubs(task_stubs::TaskStubs),
}
impl Commands {
pub fn run(self) -> eyre::Result<()> {
match self {
Self::Bootstrap(cmd) => cmd.run(),
Self::Config(cmd) => cmd.run(),
Self::GitPreCommit(cmd) => cmd.run(),
Self::GithubAction(cmd) => cmd.run(),
Self::TaskDocs(cmd) => cmd.run(),
Self::TaskStubs(cmd) => cmd.run(),
}
}
}
+66
View File
@@ -0,0 +1,66 @@
use crate::config::{Config, SETTINGS};
use crate::file;
use crate::task::Task;
use crate::Result;
use clap::ValueHint;
use std::path::PathBuf;
use xx::file::display_path;
/// [experimental] Generates shims to run mise tasks
///
/// By default, this will build shims like ./bin/<task>. These can be paired with `mise generate bootstrap`
/// so contributors to a project can execute mise tasks without installing mise into their system.
#[derive(Debug, clap::Args)]
#[clap(verbatim_doc_comment, after_long_help = AFTER_LONG_HELP)]
pub struct TaskStubs {
/// Path to a mise bin to use when running the task stub.
///
/// Use `--mise-bin=./bin/mise` to use a mise bin generated from `mise generate bootstrap`
#[clap(long, short, verbatim_doc_comment, default_value = "mise")]
mise_bin: PathBuf,
/// Directory to create task stubs inside of
#[clap(long, short, verbatim_doc_comment, default_value="bin", value_hint=ValueHint::DirPath)]
dir: PathBuf,
}
impl TaskStubs {
pub fn run(self) -> Result<()> {
SETTINGS.ensure_experimental("generate task-stubs")?;
let config = Config::get();
for task in config.tasks()?.values() {
let bin = self.dir.join(task.name_to_path());
let output = self.generate(task)?;
if let Some(parent) = bin.parent() {
file::create_dir_all(parent)?;
}
file::write(&bin, &output)?;
file::make_executable(&bin)?;
miseprintln!("Wrote to {}", display_path(&bin));
}
Ok(())
}
fn generate(&self, task: &Task) -> Result<String> {
let mise_bin = self.mise_bin.to_string_lossy();
let mise_bin = shell_words::quote(&mise_bin);
let display_name = task.display_name();
let script = format!(
r#"
#!/bin/sh
exec {mise_bin} run {display_name} "$@"
"#
);
Ok(script.trim().to_string())
}
}
static AFTER_LONG_HELP: &str = color_print::cstr!(
r#"<bold><underline>Examples:</underline></bold>
$ <bold>mise task add test -- echo 'running tests'</bold>
$ <bold>mise generate task-stubs</bold>
$ <bold>./bin/test</bold>
running tests
"#
);
+1
View File
@@ -50,6 +50,7 @@ mod lockfile;
pub(crate) mod logger;
pub(crate) mod maplit;
mod migrate;
mod minisign;
mod path_env;
mod plugins;
mod rand;
+12
View File
@@ -0,0 +1,12 @@
use crate::*;
use minisign_verify::*;
#[allow(dead_code)]
pub const MISE_PUB_KEY: &str = include_str!("../minisign.pub");
#[allow(dead_code)]
pub fn verify(pub_key: &str, data: &str, sig: &str) -> Result<()> {
let public_key = PublicKey::from_base64(pub_key)?;
let signature = Signature::decode(sig)?;
public_key.verify(data.as_bytes(), &signature, false)?;
Ok(())
}
+4
View File
@@ -476,6 +476,10 @@ impl Task {
Ok(())
}
pub fn name_to_path(&self) -> PathBuf {
self.name.replace(':', path::MAIN_SEPARATOR_STR).into()
}
pub fn render_env(&self, ts: &Toolset) -> Result<EnvMap> {
let config = Config::get();
let mut tera = get_tera(self.config_root.as_deref());
+74
View File
@@ -961,6 +961,51 @@ const completionSpec: Fig.Spec = {
],
"description": "[experimental] Generate files for various tools/services",
"subcommands": [
{
"name": "bootstrap",
"description": "[experimental] Generate a script to download+execute mise",
"options": [
{
"name": [
"-l",
"--localize"
],
"description": "Sandboxes mise internal directories like MISE_DATA_DIR and MISE_CACHE_DIR into a `.mise` directory in the project",
"isRepeatable": false
},
{
"name": "--localized-dir",
"description": "Directory to put localized data into",
"isRepeatable": false,
"args": {
"name": "localized_dir",
"template": "folders"
}
},
{
"name": [
"-V",
"--version"
],
"description": "Specify mise version to fetch",
"isRepeatable": false,
"args": {
"name": "version"
}
},
{
"name": [
"-w",
"--write"
],
"description": "Instead of outputting the script to stdout, write to a file and make it executable",
"isRepeatable": false,
"args": {
"name": "write"
}
}
]
},
{
"name": [
"config",
@@ -1128,6 +1173,35 @@ const completionSpec: Fig.Spec = {
}
}
]
},
{
"name": "task-stubs",
"description": "[experimental] Generates shims to run mise tasks",
"options": [
{
"name": [
"-m",
"--mise-bin"
],
"description": "Path to a mise bin to use when running the task stub.",
"isRepeatable": false,
"args": {
"name": "mise_bin"
}
},
{
"name": [
"-d",
"--dir"
],
"description": "Directory to create task stubs inside of",
"isRepeatable": false,
"args": {
"name": "dir",
"template": "folders"
}
}
]
}
]
},