diff --git a/.github/config/.release-please-manifest.json b/.github/config/.release-please-manifest.json index 02dba1b..e272d08 100644 --- a/.github/config/.release-please-manifest.json +++ b/.github/config/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.9.2" + ".": "0.19.0" } diff --git a/.github/config/release-please-config.json b/.github/config/release-please-config.json index 1f0c4e0..9fc3546 100644 --- a/.github/config/release-please-config.json +++ b/.github/config/release-please-config.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "release-type": "simple", + "release-type": "rust", "pull-request-footer": "> [!IMPORTANT]\n> Close and reopen this PR to trigger CI checks.", "packages": { ".": { diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 023cbba..81d1891 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,14 +17,14 @@ jobs: matrix: include: - os: ubuntu-24.04 - mise_version: v2026.4.1 - mise_sha256: c597fa1e4da76d1ea1967111d150a6a655ca51a72f4cd17fdc584be2b9eaa8bd + mise_version: v2026.4.10 + mise_sha256: 84636e19a0e5001d7499f58ae5a868cec8f6ba4f52f9028680bb7cd802564229 - os: macos-15 - mise_version: v2026.4.1 - mise_sha256: c85b387148d478dec754ded31d01798e2f4e4e9448f75682dcc6bb7c16c9a4f5 + mise_version: v2026.4.10 + mise_sha256: e09f5ae83369d3c6d44572e9f2de0bf9454718e23ccb41a4138f8f88d28cbb31 - os: windows-2025 - mise_version: v2026.4.1 - mise_sha256: "" # not published for .exe — https://github.com/jdx/mise/pull/8997 + mise_version: v2026.4.10 + mise_sha256: 2df0ce5b1f42502a4895888a0fe7aae4cf6d1959d2dbb62f29204773cff3d457 permissions: contents: read @@ -41,6 +41,7 @@ jobs: with: version: ${{ matrix.mise_version }} sha256: ${{ matrix.mise_sha256 }} + cache_key_prefix: mise-v2 - name: Install Rust lint components run: rustup component add clippy rustfmt diff --git a/Cargo.lock b/Cargo.lock index 7c52da5..6492394 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,7 +225,7 @@ dependencies = [ [[package]] name = "flint" -version = "0.20.0-alpha.1" +version = "0.20.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 2b9f596..a7ced90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flint" -version = "0.20.0-alpha.1" +version = "0.20.0" edition = "2024" description = "mise-native lint orchestrator" license = "Apache-2.0" diff --git a/README.md b/README.md index 259f1b6..79a6a15 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,7 @@ run = "flint run --fix" ```text flint run [OPTIONS] [LINTERS...] +flint update flint linters flint version ``` @@ -178,6 +179,15 @@ flint run shellcheck shfmt # run only shellcheck and shfmt flint run --fix prettier # fix only prettier ``` +`flint update` applies non-interactive migrations to `mise.toml` — replaces obsolete +tool keys with their modern equivalents, preserving the declared version. Run it when +`flint run` reports an obsolete key error: + +```text +flint: obsolete tool key in mise.toml: "npm:markdownlint-cli" (replaced by "npm:markdownlint-cli2") + Run `flint update` to apply the migration automatically. +``` + `flint linters` shows every check with its status: ```text @@ -565,6 +575,13 @@ a second inventory of the same tools in `.pre-commit-config.yaml`, with its own versioning and install lifecycle. That's friction without benefit for repos that are already mise-first. +### Why not Husky? + +Husky manages git hooks for Node.js projects and requires `npm install` to activate. +Repos that aren't Node-first still need a `package.json` and a dev dependency just to +run hooks. `flint hook install` writes a single shell script directly to `.git/hooks/` +with no install step and no language runtime dependency. + ### Why not Spotless (or other Maven formatter plugins)? Spotless runs `google-java-format` as a Maven build phase, which means format diff --git a/mise.toml b/mise.toml index 8742528..ea8a90a 100644 --- a/mise.toml +++ b/mise.toml @@ -37,10 +37,6 @@ run = "cargo run -q -- run" description = "Auto-fix lint issues" run = "cargo run -q -- run --fix" -[tasks."lint:pre-commit"] -description = "Fast auto-fix lint pass (skips slow checks like renovate) — intended for pre-commit/pre-push hooks" -run = "cargo run -q -- run --fix --fast-only" - [tasks.build] description = "Build the project" run = "cargo build" diff --git a/src/init/generation.rs b/src/init/generation.rs index 5229e53..4bbfc4b 100644 --- a/src/init/generation.rs +++ b/src/init/generation.rs @@ -118,6 +118,38 @@ fn pin_tool_via_mise(project_root: &Path, key: &str) -> bool { after != before && parse_tool_keys(&after).contains(key) } +/// Replaces obsolete tool keys in mise.toml with their modern equivalents, +/// preserving the existing version value. Returns the list of replacements made +/// as `(old_key, new_key)` pairs. No-ops if the file doesn't exist or has no +/// obsolete keys. +pub fn replace_obsolete_keys( + project_root: &Path, + obsolete: &[(&str, &str)], +) -> Result> { + let path = project_root.join("mise.toml"); + let content = match std::fs::read_to_string(&path) { + Ok(c) => c, + Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(vec![]), + Err(e) => return Err(e).with_context(|| format!("failed to read {}", path.display())), + }; + let mut doc: toml_edit::DocumentMut = content.parse().context("failed to parse mise.toml")?; + + let mut replaced = vec![]; + if let Some(tools) = doc.get_mut("tools").and_then(|t| t.as_table_mut()) { + for &(old_key, new_key) in obsolete { + if let Some(value) = tools.remove(old_key) { + tools.insert(new_key, value); + replaced.push((old_key.to_string(), new_key.to_string())); + } + } + } + + if !replaced.is_empty() { + std::fs::write(&path, doc.to_string()).context("failed to write mise.toml")?; + } + Ok(replaced) +} + pub(super) fn apply_changes( path: &Path, current_content: &str, @@ -631,6 +663,53 @@ pub(super) fn maybe_install_hook(project_root: &Path, yes: bool) -> Result<()> { Ok(()) } +#[cfg(test)] +mod replace_obsolete_tests { + use super::replace_obsolete_keys; + + #[test] + fn replaces_old_key_preserving_version() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("mise.toml"); + std::fs::write(&path, "[tools]\n\"npm:markdownlint-cli\" = \"0.39.0\"\n").unwrap(); + let replaced = replace_obsolete_keys( + dir.path(), + &[("npm:markdownlint-cli", "npm:markdownlint-cli2")], + ) + .unwrap(); + assert_eq!( + replaced, + vec![( + "npm:markdownlint-cli".to_string(), + "npm:markdownlint-cli2".to_string() + )] + ); + let result = std::fs::read_to_string(&path).unwrap(); + assert!( + result.contains("npm:markdownlint-cli2"), + "new key written: {result}" + ); + assert!( + !result.contains("\"npm:markdownlint-cli\""), + "old key removed: {result}" + ); + assert!(result.contains("0.39.0"), "version preserved: {result}"); + } + + #[test] + fn noop_when_no_obsolete_keys() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("mise.toml"); + std::fs::write(&path, "[tools]\n\"npm:markdownlint-cli2\" = \"0.17.2\"\n").unwrap(); + let replaced = replace_obsolete_keys( + dir.path(), + &[("npm:markdownlint-cli", "npm:markdownlint-cli2")], + ) + .unwrap(); + assert!(replaced.is_empty()); + } +} + #[cfg(test)] mod v1_removal_tests { use super::remove_v1_tasks; diff --git a/src/init/mod.rs b/src/init/mod.rs index c098338..cfb5eef 100644 --- a/src/init/mod.rs +++ b/src/init/mod.rs @@ -7,7 +7,7 @@ use std::path::Path; use crate::registry::{Category, Check, builtin}; mod detection; -mod generation; +pub(crate) mod generation; mod ui; use detection::{ diff --git a/src/linters/lychee.rs b/src/linters/lychee.rs index f8afd4a..efaddb7 100644 --- a/src/linters/lychee.rs +++ b/src/linters/lychee.rs @@ -142,7 +142,7 @@ async fn run_lychee_cmd( let mut stdout = format!("==> {description}\n").into_bytes(); - let result = super::spawn_command(&argv) + let result = super::spawn_command(&argv, false) .current_dir(project_root) .stdin(Stdio::null()) .output() diff --git a/src/linters/mod.rs b/src/linters/mod.rs index af3330e..8835623 100644 --- a/src/linters/mod.rs +++ b/src/linters/mod.rs @@ -10,21 +10,23 @@ pub mod renovate_deps; /// (the shim fails). We check for a PE header (MZ magic) to distinguish: /// - PE binary without extension → execute directly by full path /// - Everything else → route through `cmd.exe /C` to handle `.cmd` shims -pub fn spawn_command(argv: &[String]) -> tokio::process::Command { +/// +/// Self-executing JARs (e.g. ktlint) cannot run via cmd.exe at all. +/// When `windows_java_jar` is true the binary is resolved to its full path +/// and invoked as `java -jar `. +pub fn spawn_command(argv: &[String], windows_java_jar: bool) -> tokio::process::Command { #[cfg(windows)] { - match find_executable_in_path(&argv[0]) { - Some(WinBinary::Pe(path)) => { - let mut cmd = tokio::process::Command::new(path); - cmd.args(&argv[1..]); - return cmd; - } - Some(WinBinary::Jar(path)) => { + if windows_java_jar { + if let Some(path) = find_file_in_path(&argv[0]) { let mut cmd = tokio::process::Command::new("java"); cmd.arg("-jar").arg(path).args(&argv[1..]); return cmd; } - None => {} + } else if let Some(path) = find_pe_binary(&argv[0]) { + let mut cmd = tokio::process::Command::new(path); + cmd.args(&argv[1..]); + return cmd; } let mut cmd = tokio::process::Command::new("cmd.exe"); cmd.arg("/C").args(argv); @@ -32,27 +34,18 @@ pub fn spawn_command(argv: &[String]) -> tokio::process::Command { } #[cfg(not(windows))] { + let _ = windows_java_jar; let mut cmd = tokio::process::Command::new(&argv[0]); cmd.args(&argv[1..]); cmd } } -/// What kind of executable was found in PATH on Windows. -#[cfg(windows)] -enum WinBinary { - /// Native PE binary (MZ magic) — execute directly. - Pe(std::path::PathBuf), - /// Self-executing JAR (starts with `#!` and is large) — run via `java -jar`. - Jar(std::path::PathBuf), -} - /// On Windows, look for `binary` (exact name, no extension) in each PATH -/// directory and classify it: -/// - MZ magic → native PE, run directly -/// - `#!` magic + large file (>1 MB) → self-executing JAR (e.g. ktlint), run via `java -jar` +/// directory. If found and it starts with the PE magic bytes `MZ`, return +/// its full path so it can be executed directly via `CreateProcessW`. #[cfg(windows)] -fn find_executable_in_path(binary: &str) -> Option { +fn find_pe_binary(binary: &str) -> Option { use std::io::Read; let path_var = std::env::var("PATH").ok()?; for dir in std::env::split_paths(&path_var) { @@ -60,30 +53,32 @@ fn find_executable_in_path(binary: &str) -> Option { if !candidate.is_file() { continue; } - let mut buf = [0u8; 2]; - let read = std::fs::File::open(&candidate) - .and_then(|mut f| f.read(&mut buf).map(|n| n)) - .unwrap_or(0); - if read < 2 { - continue; - } - if buf == [b'M', b'Z'] { - return Some(WinBinary::Pe(candidate)); - } - if buf == [b'#', b'!'] { - // Self-executing JAR: shell script header prepended to a JAR. - // A real script would be tiny; a self-executing JAR is many MB. - if std::fs::metadata(&candidate) - .map(|m| m.len() > 1_000_000) - .unwrap_or(false) - { - return Some(WinBinary::Jar(candidate)); - } + let is_pe = std::fs::File::open(&candidate) + .and_then(|mut f| { + let mut buf = [0u8; 2]; + f.read_exact(&mut buf)?; + Ok(buf == [b'M', b'Z']) + }) + .unwrap_or(false); + if is_pe { + return Some(candidate); } } None } +/// On Windows, return the full path of `binary` from PATH without inspecting +/// its contents. Used for self-executing JARs where the caller already knows +/// the invocation style (i.e. `windows_java_jar` is set in the registry). +#[cfg(windows)] +fn find_file_in_path(binary: &str) -> Option { + let path_var = std::env::var("PATH").ok()?; + std::env::split_paths(&path_var).find_map(|dir| { + let candidate = dir.join(binary); + candidate.is_file().then_some(candidate) + }) +} + /// Output from a single linter run. pub struct LinterOutput { pub ok: bool, diff --git a/src/linters/renovate_deps.rs b/src/linters/renovate_deps.rs index b60e704..9549258 100644 --- a/src/linters/renovate_deps.rs +++ b/src/linters/renovate_deps.rs @@ -120,12 +120,15 @@ async fn run_renovate(project_root: &Path, config_path: &Path) -> anyhow::Result env.push(("GITHUB_COM_TOKEN".into(), token)); } - let out = super::spawn_command(&[ - "renovate".to_string(), - "--platform=local".to_string(), - "--require-config=ignored".to_string(), - "--dry-run=extract".to_string(), - ]) + let out = super::spawn_command( + &[ + "renovate".to_string(), + "--platform=local".to_string(), + "--require-config=ignored".to_string(), + "--dry-run=extract".to_string(), + ], + false, + ) .current_dir(project_root) .envs(env) .stdin(Stdio::null()) diff --git a/src/main.rs b/src/main.rs index 340710b..a2d620f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,8 @@ enum SubCommand { Linters(LintersArgs), /// Set up linters in mise.toml for this project. Init(InitArgs), + /// Apply non-interactive migrations to mise.toml (replace obsolete tool keys). + Update, /// Manage git hooks. Hook(HookArgs), /// Display the flint version. @@ -140,6 +142,17 @@ async fn main() -> Result<()> { SubCommand::Init(args) => { init::run(&project_root, args.profile, args.yes)?; } + SubCommand::Update => { + let replaced = + init::generation::replace_obsolete_keys(&project_root, registry::OBSOLETE_KEYS)?; + if replaced.is_empty() { + println!("flint: mise.toml is up to date"); + } else { + for (old, new) in &replaced { + println!(" replaced {old:?} → {new:?}"); + } + } + } SubCommand::Hook(args) => match args.command { HookCommand::Install => hook::install(&project_root)?, }, @@ -182,6 +195,11 @@ async fn run( // --fast-only filter (skipped when linters are named explicitly). // mise guarantees declared tools are on PATH, so no PATH check needed. let mise_tools = registry::read_mise_tools(project_root); + if let Some((old, new)) = registry::find_obsolete_key(&mise_tools) { + eprintln!("flint: obsolete tool key in mise.toml: {old:?} (replaced by {new:?})"); + eprintln!(" Run `flint update` to apply the migration automatically."); + std::process::exit(1); + } let active: Vec<®istry::Check> = { let mut out = vec![]; for c in checks { diff --git a/src/registry.rs b/src/registry.rs index 67a5f53..cc673f3 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -85,6 +85,9 @@ pub struct Check { /// (e.g. `"clippy,rustfmt"` for the `rust` toolchain). Produces an inline-table /// entry: `rust = { version = "latest", components = "clippy,rustfmt" }`. pub mise_install_components: Option<&'static str>, + /// On Windows, the binary is a self-executing JAR that cannot be run directly + /// or via cmd.exe — invoke as `java -jar ` instead. + pub windows_java_jar: bool, pub kind: CheckKind, /// Binary name format when the backend installs with a versioned name (e.g. `"shfmt_{version}"` /// → `"shfmt_v3.12.0"`). `{version}` is replaced with the version declared in mise.toml. @@ -166,6 +169,7 @@ impl Check { full_fix_cmd: "", scope, }, + windows_java_jar: false, versioned_bin_fmt: None, desc: "", docs: "", @@ -187,6 +191,7 @@ impl Check { activate_unconditionally: false, category: Category::Default, mise_install_components: None, + windows_java_jar: false, kind: CheckKind::Special(kind), versioned_bin_fmt: None, desc: "", @@ -245,6 +250,13 @@ impl Check { self } + /// On Windows, invoke this binary via `java -jar ` rather than directly. + /// Use for self-executing JARs (e.g. ktlint) that cmd.exe cannot run. + pub fn windows_java_jar(mut self) -> Self { + self.windows_java_jar = true; + self + } + /// Mark as slow — skipped when `--fast-only` is passed; `comprehensive` init profile only. pub fn slow(mut self) -> Self { self.category = Category::Slow; @@ -539,6 +551,7 @@ fn check_ktlint() -> Check { "ktlint --format --log-level=error {ROOT}", ) .mise_tool("github:pinterest/ktlint") + .windows_java_jar() .formatter() .desc("Lint and format Kotlin code") .lang() @@ -647,8 +660,26 @@ pub const OBSOLETE_KEYS: &[(&str, &str)] = &[ // markdownlint-cli was superseded by markdownlint-cli2 (actively maintained, // faster, supports the same config files). flint only supports the cli2 variant. ("npm:markdownlint-cli", "npm:markdownlint-cli2"), + // ubi: was deprecated in mise; the github: backend is the modern replacement. + // Repos that adopted flint before this change may still have ubi: keys. + ( + "ubi:google/google-java-format", + "github:google/google-java-format", + ), + ("ubi:pinterest/ktlint", "github:pinterest/ktlint"), ]; +/// Checks whether any obsolete tool keys are present in `mise_tools`. +/// Returns the first violation found as `(obsolete_key, replacement_key)`. +pub fn find_obsolete_key( + mise_tools: &HashMap, +) -> Option<(&'static str, &'static str)> { + OBSOLETE_KEYS + .iter() + .find(|(old, _)| mise_tools.contains_key(*old)) + .copied() +} + /// Reads `[tools]` from the consuming repo's mise.toml and returns a map of /// tool name → declared version string. /// @@ -728,7 +759,10 @@ pub fn check_active(check: &Check, mise_tools: &HashMap) -> bool /// Returns the binary name to use for this check given the active mise tools. /// When `versioned_bin_fmt` is set, the version from mise.toml is substituted /// into the format string (e.g. `"shfmt_{version}"` + `"v3.12.0"` → `"shfmt_v3.12.0"`). -/// Falls back to `check.bin_name` for standard installations. +/// This is needed for shfmt because mise's `github:` backend preserves the version +/// suffix in the installed binary name. The backend's binary-name cleaning logic matches +/// binaries against the repo name (e.g. `"mvdan/sh"`), so it cannot map `"shfmt"` → +/// `"mvdan/sh"` and leaves the name as `"shfmt_v3.12.0"` rather than stripping it. pub fn resolve_bin_name(check: &Check, mise_tools: &HashMap) -> String { if let Some(fmt) = check.versioned_bin_fmt { let key = check.mise_tool_name.unwrap_or(check.bin_name); @@ -768,6 +802,24 @@ fn coerce_version(s: &str) -> Option { mod tests { use super::*; + #[test] + fn find_obsolete_key_detects_superseded_keys() { + let mut tools = HashMap::new(); + tools.insert("npm:markdownlint-cli".to_string(), "0.39.0".to_string()); + let result = find_obsolete_key(&tools); + assert_eq!( + result, + Some(("npm:markdownlint-cli", "npm:markdownlint-cli2")) + ); + } + + #[test] + fn find_obsolete_key_returns_none_for_clean_tools() { + let mut tools = HashMap::new(); + tools.insert("npm:markdownlint-cli2".to_string(), "0.17.2".to_string()); + assert_eq!(find_obsolete_key(&tools), None); + } + /// If any entry for a bin_name declares a version_range, every entry for that /// bin_name must declare one. A mix of ranged and unranged entries for the same /// binary is ambiguous — it would be impossible to guarantee exactly one activates. diff --git a/src/runner.rs b/src/runner.rs index fc25fdd..d59ffd7 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -30,6 +30,7 @@ enum PreparedCheck { Invocations { name: String, argv_list: Vec>, + windows_java_jar: bool, }, Links { name: String, @@ -62,9 +63,11 @@ impl PreparedCheck { let name = self.name().to_string(); let start = Instant::now(); let out: LinterOutput = match self { - Self::Invocations { argv_list, .. } => { - run_invocations(&name, &argv_list, project_root).await - } + Self::Invocations { + argv_list, + windows_java_jar, + .. + } => run_invocations(&name, &argv_list, windows_java_jar, project_root).await, Self::Links { cfg, file_list, @@ -184,7 +187,11 @@ fn prepare( if argv_list.is_empty() { return None; } - Some(PreparedCheck::Invocations { name, argv_list }) + Some(PreparedCheck::Invocations { + name, + argv_list, + windows_java_jar: check.windows_java_jar, + }) } CheckKind::Special(SpecialKind::Links) => Some(PreparedCheck::Links { name, @@ -400,7 +407,12 @@ fn inject_config(mut argv: Vec, config_args: &[String]) -> Vec { /// Runs all invocations for one check. /// Never prints — callers decide when and whether to flush output. -async fn run_invocations(name: &str, invocations: &[Vec], root: &Path) -> LinterOutput { +async fn run_invocations( + name: &str, + invocations: &[Vec], + windows_java_jar: bool, + root: &Path, +) -> LinterOutput { let mut all_ok = true; let mut combined_stdout = Vec::new(); let mut combined_stderr = Vec::new(); @@ -409,7 +421,7 @@ async fn run_invocations(name: &str, invocations: &[Vec], root: &Path) - if argv.is_empty() { continue; } - let result = crate::linters::spawn_command(argv) + let result = crate::linters::spawn_command(argv, windows_java_jar) .current_dir(root) .stdin(Stdio::null()) .output() @@ -675,6 +687,7 @@ mod tests { activate_unconditionally: false, category: Category::Default, mise_install_components: None, + windows_java_jar: false, kind: CheckKind::Template { check_cmd: "run-it", fix_cmd: "", diff --git a/tests/cases/general/hook-install/files/.gitkeep b/tests/cases/general/hook-install/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/general/hook-install/test.toml b/tests/cases/general/hook-install/test.toml new file mode 100644 index 0000000..00ba029 --- /dev/null +++ b/tests/cases/general/hook-install/test.toml @@ -0,0 +1,11 @@ +[expected] +args = "hook install" +exit = 0 +stdout = "installed pre-commit hook (.git/hooks/pre-commit)\n" + +[expected.files] +".git/hooks/pre-commit" = ''' +#!/bin/sh +# Installed by flint — run `flint hook install` to reinstall +mise exec -- flint run --fix --fast-only +''' diff --git a/tests/cases/general/update-no-op/files/mise.toml b/tests/cases/general/update-no-op/files/mise.toml new file mode 100644 index 0000000..8550653 --- /dev/null +++ b/tests/cases/general/update-no-op/files/mise.toml @@ -0,0 +1,3 @@ +[tools] +"npm:markdownlint-cli2" = "0.17.2" +shellcheck = "v0.11.0" diff --git a/tests/cases/general/update-no-op/test.toml b/tests/cases/general/update-no-op/test.toml new file mode 100644 index 0000000..9961032 --- /dev/null +++ b/tests/cases/general/update-no-op/test.toml @@ -0,0 +1,4 @@ +[expected] +args = "update" +exit = 0 +stdout = "flint: mise.toml is up to date\n" diff --git a/tests/cases/general/update-obsolete-key/files/mise.toml b/tests/cases/general/update-obsolete-key/files/mise.toml new file mode 100644 index 0000000..117d0a9 --- /dev/null +++ b/tests/cases/general/update-obsolete-key/files/mise.toml @@ -0,0 +1,3 @@ +[tools] +"npm:markdownlint-cli" = "0.39.0" +shellcheck = "v0.11.0" diff --git a/tests/cases/general/update-obsolete-key/test.toml b/tests/cases/general/update-obsolete-key/test.toml new file mode 100644 index 0000000..adf98ef --- /dev/null +++ b/tests/cases/general/update-obsolete-key/test.toml @@ -0,0 +1,11 @@ +[expected] +args = "update" +exit = 0 +stdout = " replaced \"npm:markdownlint-cli\" → \"npm:markdownlint-cli2\"\n" + +[expected.files] +"mise.toml" = ''' +[tools] +shellcheck = "v0.11.0" +"npm:markdownlint-cli2" = "0.39.0" +''' diff --git a/tests/cases/general/version/files/.gitkeep b/tests/cases/general/version/files/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/cases/general/version/test.toml b/tests/cases/general/version/test.toml new file mode 100644 index 0000000..a8777bc --- /dev/null +++ b/tests/cases/general/version/test.toml @@ -0,0 +1,4 @@ +[expected] +args = "version" +exit = 0 +stdout = "flint 0.20.0\n"