From 2cb1f1625fb3c247c48705f88cf3a0f45e991c85 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 23 Mar 2026 18:42:47 +0000 Subject: [PATCH 1/7] feat: add Java integration via Chicory WebAssembly runtime - Make cli and lib compile for wasm32-wasip1: gate xx dependency behind cfg(not(wasm32)), replace xx::file/xx::regex with std::fs and regex::Regex + LazyLock, add wasm32 stubs for shell execution - Add [profile.wasm] Cargo profile (opt-level=z, strip=symbols) for smaller WASM output (4.5M vs 6.2M default) - Migrate cli/Makefile targets to mise wasm:* tasks - Add integrations/java/ with Usage wrapper that runs the usage CLI compiled to WASM via Chicory with build-time AOT compilation - Add CI workflow for Java integration (mvn test + jbang example) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test-java.yml | 32 +++++ .gitignore | 4 + Cargo.toml | 5 + cli/Cargo.toml | 1 + cli/src/cli/complete_word.rs | 17 ++- cli/src/cli/generate/fig.rs | 6 +- cli/src/cli/generate/manpage.rs | 6 +- cli/src/cli/generate/markdown.rs | 8 +- cli/src/lib.rs | 1 + integrations/java/README.md | 108 +++++++++++++++++ integrations/java/example/Main.java | 110 +++++++++++++++++ integrations/java/pom.xml | 112 ++++++++++++++++++ .../dev/jdx/usage/WasmResource.java | 8 ++ .../src/main/java/dev/jdx/usage/Usage.java | 98 +++++++++++++++ .../main/java/dev/jdx/usage/UsageResult.java | 39 ++++++ .../test/java/dev/jdx/usage/UsageTest.java | 99 ++++++++++++++++ lib/Cargo.toml | 1 + lib/src/docs/markdown/renderer.rs | 12 +- lib/src/error.rs | 1 + lib/src/sh.rs | 12 ++ lib/src/spec/mod.rs | 17 +-- mise.toml | 36 ++++++ 22 files changed, 714 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/test-java.yml create mode 100644 integrations/java/README.md create mode 100644 integrations/java/example/Main.java create mode 100644 integrations/java/pom.xml create mode 100644 integrations/java/src/main/java-templates/dev/jdx/usage/WasmResource.java create mode 100644 integrations/java/src/main/java/dev/jdx/usage/Usage.java create mode 100644 integrations/java/src/main/java/dev/jdx/usage/UsageResult.java create mode 100644 integrations/java/src/test/java/dev/jdx/usage/UsageTest.java diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml new file mode 100644 index 00000000..b4c2870f --- /dev/null +++ b/.github/workflows/test-java.yml @@ -0,0 +1,32 @@ +name: test-java +on: + push: + branches: ["main"] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + MISE_EXPERIMENTAL: 1 + +jobs: + test-java: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + submodules: recursive + - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 + with: + shared-key: wasm + - uses: jdx/mise-action@e79ddf65a11cec7b0e882bedced08d6e976efb2d # v3 + - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4 + with: + distribution: temurin + java-version: 21 + - run: mise run wasm:build + - run: cd integrations/java && mvn clean install + - uses: jbangdev/setup-jbang@6109ef573a4a3e8e21df68dcc3e0e29e3c5e48ea # v3 + - run: cd integrations/java && jbang example/Main.java diff --git a/.gitignore b/.gitignore index 878a0ac5..d86dfe8f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ #/target megalinter-reports/ tasks/fig/build + +cli/wasi-sdk/ +cli/binaryen/ +integrations/java/target/ diff --git a/Cargo.toml b/Cargo.toml index 83473aa3..5d639abc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,8 @@ usage-lib = { path = "./lib", version = "3.2.0", features = ["clap"] } [workspace.metadata.release] allow-branch = ["main"] + +[profile.wasm] +inherits = "release" +opt-level = "z" +strip = "symbols" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b92e015b..5ad17ea0 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -40,6 +40,7 @@ serde_with = "3" tera = "1" thiserror = "2" usage-lib = { workspace = true, features = ["clap", "docs", "unstable_choices_env"] } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] xx = "2" [target.'cfg(unix)'.dependencies] diff --git a/cli/src/cli/complete_word.rs b/cli/src/cli/complete_word.rs index 115a7676..9d2958f4 100644 --- a/cli/src/cli/complete_word.rs +++ b/cli/src/cli/complete_word.rs @@ -2,15 +2,15 @@ use std::collections::BTreeMap; use std::env; use std::fmt::Debug; use std::path::{Path, PathBuf}; +#[cfg(not(target_arch = "wasm32"))] use std::process::Command; use std::sync::Arc; use clap::Args; use itertools::Itertools; use miette::IntoDiagnostic; +use regex::Regex; use std::sync::LazyLock; -use xx::process::check_status; -use xx::{regex, XXError, XXResult}; use usage::{Spec, SpecArg, SpecCommand, SpecComplete, SpecFlag}; @@ -283,7 +283,8 @@ impl CompleteWord { trace!("run: {run}"); let stdout = sh(&run)?; // trace!("stdout: {stdout}"); - let re = regex!(r"[^\\]:"); + static COLON_RE: LazyLock = LazyLock::new(|| Regex::new(r"[^\\]:").unwrap()); + let re = &*COLON_RE; return Ok(stdout .lines() .map(|l| { @@ -372,7 +373,10 @@ fn zsh_escape(s: &str) -> String { .replace(']', "\\]") } -fn sh(script: &str) -> XXResult { +#[cfg(not(target_arch = "wasm32"))] +fn sh(script: &str) -> xx::XXResult { + use xx::process::check_status; + use xx::XXError; let output = Command::new("sh") .arg("-c") .arg(script) @@ -387,3 +391,8 @@ fn sh(script: &str) -> XXResult { let stdout = String::from_utf8(output.stdout).expect("stdout is not utf-8"); Ok(stdout) } + +#[cfg(target_arch = "wasm32")] +fn sh(_script: &str) -> miette::Result { + Err(miette::miette!("shell execution is not supported on wasm")) +} diff --git a/cli/src/cli/generate/fig.rs b/cli/src/cli/generate/fig.rs index 7d0896a9..45f73673 100644 --- a/cli/src/cli/generate/fig.rs +++ b/cli/src/cli/generate/fig.rs @@ -7,6 +7,7 @@ use itertools::Itertools; use usage::{SpecArg, SpecCommand, SpecComplete, SpecFlag}; use crate::cli::generate; +use miette::IntoDiagnostic; use serde::{Deserialize, Serialize, Serializer}; use serde_with::{serde_as, OneOrMany}; @@ -356,7 +357,10 @@ impl Fig { pub fn run(&self) -> miette::Result<()> { let write = |path: &PathBuf, md: &str| -> miette::Result<()> { println!("writing to {}", path.display()); - xx::file::write(path, format!("{}\n", md.trim()))?; + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).into_diagnostic()?; + } + std::fs::write(path, format!("{}\n", md.trim())).into_diagnostic()?; Ok(()) }; let spec = generate::file_or_spec(&self.file, &self.spec)?; diff --git a/cli/src/cli/generate/manpage.rs b/cli/src/cli/generate/manpage.rs index 797e36be..6d0d4868 100644 --- a/cli/src/cli/generate/manpage.rs +++ b/cli/src/cli/generate/manpage.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use super::parse_file_or_stdin; use clap::Args; +use miette::IntoDiagnostic; use usage::docs::manpage::ManpageRenderer; #[derive(Args)] @@ -34,7 +35,10 @@ impl Manpage { if let Some(out_file) = &self.out_file { println!("writing to {}", out_file.display()); - xx::file::write(out_file, &manpage)?; + if let Some(parent) = out_file.parent() { + std::fs::create_dir_all(parent).into_diagnostic()?; + } + std::fs::write(out_file, &manpage).into_diagnostic()?; } else { print!("{}", manpage); } diff --git a/cli/src/cli/generate/markdown.rs b/cli/src/cli/generate/markdown.rs index 6938386f..f5e038cf 100644 --- a/cli/src/cli/generate/markdown.rs +++ b/cli/src/cli/generate/markdown.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use super::parse_file_or_stdin; use clap::Args; +use miette::IntoDiagnostic; use usage::docs::markdown::MarkdownRenderer; /// Generate markdown documentation from usage specs @@ -43,13 +44,16 @@ impl Markdown { pub fn run(&self) -> miette::Result<()> { let write = |path: &PathBuf, md: &str| -> miette::Result<()> { println!("writing to {}", path.display()); - xx::file::write( + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).into_diagnostic()?; + } + std::fs::write( path, format!( "\n{}\n", md.trim() ), - )?; + ).into_diagnostic()?; Ok(()) }; let spec = parse_file_or_stdin(&self.file)?; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index eeef3bff..b4502c7f 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] extern crate log; extern crate miette; +#[cfg(not(target_arch = "wasm32"))] extern crate xx; use miette::Result; diff --git a/integrations/java/README.md b/integrations/java/README.md new file mode 100644 index 00000000..ec02b584 --- /dev/null +++ b/integrations/java/README.md @@ -0,0 +1,108 @@ +# usage-java + +Run the [usage](https://usage.jdx.dev) CLI from Java via [Chicory](https://github.com/nicholasgasior/chicory), a pure-Java WebAssembly runtime. + +The `usage` CLI is compiled to WebAssembly (WASI) and executed at runtime through Chicory with build-time AOT compilation for fast execution. + +## Prerequisites + +Build the WASM binary (requires wasi-sdk): + +```bash +mise run wasm:build +``` + +## Build + +```bash +cd integrations/java +mvn compile +``` + +## Quick Start + +```java +import dev.jdx.usage.Usage; +import dev.jdx.usage.UsageResult; + +String spec = + "name \"mycli\"\n" + + "bin \"mycli\"\n" + + "version \"1.0.0\"\n" + + "flag \"-v --verbose\" help=\"Verbose output\"\n" + + "cmd \"run\" help=\"Run the thing\" {\n" + + " arg \n" + + "}\n"; + +// Generate bash completions +UsageResult result = Usage.builder() + .withStdin(spec) + .withArgs("generate", "completion", "bash", "mycli", "-f", "-") + .run(); + +System.out.println(result.stdoutAsString()); +``` + +## API + +### `Usage.builder()` + +Creates a builder for running usage CLI commands. + +| Method | Description | +| --- | --- | +| `.withStdin(String)` | Pipe a string to stdin (typically a usage spec) | +| `.withStdin(byte[])` | Pipe bytes to stdin | +| `.withArgs(String...)` | Append CLI arguments (the `usage` program name is added automatically) | +| `.withDirectory(Path)` | Pre-open a directory for WASI filesystem access (needed for `--out-file`) | +| `.run()` | Execute and return a `UsageResult` | + +### `UsageResult` + +| Method | Description | +| --- | --- | +| `.stdout()` | Raw stdout bytes | +| `.stderr()` | Raw stderr bytes | +| `.stdoutAsString()` | Stdout as UTF-8 string | +| `.stderrAsString()` | Stderr as UTF-8 string | +| `.exitCode()` | Process exit code | +| `.success()` | `true` if exit code is 0 | + +## Common Commands + +```java +// Generate shell completions +Usage.builder().withStdin(spec).withArgs("generate", "completion", "bash", "mybinary", "-f", "-").run(); +Usage.builder().withStdin(spec).withArgs("generate", "completion", "zsh", "mybinary", "-f", "-").run(); +Usage.builder().withStdin(spec).withArgs("generate", "completion", "fish", "mybinary", "-f", "-").run(); + +// Generate man page +Usage.builder().withStdin(spec).withArgs("generate", "manpage", "-f", "-").run(); + +// Generate JSON spec +Usage.builder().withStdin(spec).withArgs("generate", "json", "-f", "-").run(); + +// Generate markdown (requires a directory for --out-file, use ZeroFs for in-memory) +FileSystem fs = ZeroFs.newFileSystem(Configuration.unix().toBuilder().setAttributeViews("unix").build()); +Path outDir = fs.getPath("out"); +Files.createDirectory(outDir); +Usage.builder() + .withStdin(spec) + .withArgs("generate", "markdown", "-f", "-", "--out-file", outDir.resolve("docs.md").toString()) + .withDirectory(outDir) + .run(); +String markdown = new String(Files.readAllBytes(outDir.resolve("docs.md"))); +``` + +## Example + +See [`example/Main.java`](example/Main.java) for a complete example mirroring the [cobra integration example](../cobra/example/main.go). + +Run it with [jbang](https://www.jbang.dev/): + +```bash +mise run wasm:build +cd integrations/java +mvn install +jbang example/Main.java +``` diff --git a/integrations/java/example/Main.java b/integrations/java/example/Main.java new file mode 100644 index 00000000..07e3cda9 --- /dev/null +++ b/integrations/java/example/Main.java @@ -0,0 +1,110 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//DEPS dev.jdx:usage-chicory:0.1.0-SNAPSHOT +//DEPS io.roastedroot:zerofs:0.1.0 + +// Example demonstrating the usage-java integration. +// +// Run with jbang: +// mise run wasm:build +// cd integrations/java && mvn install +// jbang example/Main.java +// +// See the test suite for more examples. + +import dev.jdx.usage.Usage; +import dev.jdx.usage.UsageResult; +import io.roastedroot.zerofs.Configuration; +import io.roastedroot.zerofs.ZeroFs; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Main { + + private static final String SPEC = + "name \"deploy-tool\"\n" + + "bin \"deploy-tool\"\n" + + "version \"0.1.0\"\n" + + "about \"A deployment management tool\"\n" + + "flag \"-v --verbose\" help=\"Enable verbose output\" global=#true\n" + + "cmd \"deploy\" help=\"Deploy a service\" {\n" + + " flag \"-e --env\" help=\"Target environment\" {\n" + + " arg default=\"staging\"\n" + + " }\n" + + " flag \"--force\" help=\"Force deployment without confirmation\"\n" + + " flag \"--dry-run\" help=\"Show what would be deployed\"\n" + + " arg help=\"Service to deploy\"\n" + + "}\n" + + "cmd \"rollback\" help=\"Rollback a service to a previous version\" {\n" + + " arg help=\"Service to rollback\"\n" + + " arg \"[version]\" help=\"Target version\" required=#false\n" + + "}\n" + + "cmd \"status\" help=\"Show deployment status\" {\n" + + " alias \"st\" \"info\"\n" + + " arg \"[service]\" help=\"Service to check\" required=#false\n" + + "}\n"; + + public static void main(String[] args) throws Exception { + // Generate JSON spec + System.out.println("=== JSON spec ==="); + UsageResult jsonResult = + Usage.builder() + .withStdin(SPEC) + .withArgs("generate", "json", "-f", "-") + .run(); + System.out.println(jsonResult.stdoutAsString()); + + // Generate bash completions + System.out.println("=== Bash completions ==="); + UsageResult bashResult = + Usage.builder() + .withStdin(SPEC) + .withArgs( + "generate", + "completion", + "bash", + "deploy-tool", + "-f", + "-") + .run(); + System.out.println(bashResult.stdoutAsString()); + + // Generate markdown documentation (requires a filesystem for --out-file) + System.out.println("=== Markdown docs ==="); + try (FileSystem fs = + ZeroFs.newFileSystem( + Configuration.unix().toBuilder().setAttributeViews("unix").build())) { + Path outDir = fs.getPath("out"); + Files.createDirectory(outDir); + Path outFile = outDir.resolve("docs.md"); + + UsageResult mdResult = + Usage.builder() + .withStdin(SPEC) + .withArgs( + "generate", + "markdown", + "-f", + "-", + "--out-file", + outFile.toString()) + .withDirectory(outDir) + .run(); + + if (mdResult.success()) { + System.out.println(new String(Files.readAllBytes(outFile))); + } else { + System.err.println(mdResult.stderrAsString()); + } + } + + // Generate man page + System.out.println("=== Man page ==="); + UsageResult manResult = + Usage.builder() + .withStdin(SPEC) + .withArgs("generate", "manpage", "-f", "-") + .run(); + System.out.println(manResult.stdoutAsString()); + } +} diff --git a/integrations/java/pom.xml b/integrations/java/pom.xml new file mode 100644 index 00000000..579042d7 --- /dev/null +++ b/integrations/java/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + dev.jdx + usage-chicory + 0.1.0-SNAPSHOT + jar + + usage-chicory + Run the usage CLI from Java via Chicory WebAssembly runtime + + + UTF-8 + 11 + + 1.7.3 + 5.14.3 + 3.15.0 + 3.1.0 + + + + + com.dylibso.chicory + annotations + ${chicory.version} + + + com.dylibso.chicory + runtime + ${chicory.version} + + + com.dylibso.chicory + wasi + ${chicory.version} + + + + io.roastedroot + zerofs + 0.1.0 + test + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + + + com.dylibso.chicory + chicory-compiler-maven-plugin + ${chicory.version} + + + usage + + compile + + + dev.jdx.usage.UsageModule + ${project.basedir}/../../target/wasm32-wasip1/wasm/usage.wasm + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + true + ${maven.compiler.release} + true + + + com.dylibso.chicory + annotations-processor + ${chicory.version} + + + + + + org.codehaus.mojo + templating-maven-plugin + ${templating-maven-plugin.version} + + + filtering-java-templates + + filter-sources + + + + + + + + diff --git a/integrations/java/src/main/java-templates/dev/jdx/usage/WasmResource.java b/integrations/java/src/main/java-templates/dev/jdx/usage/WasmResource.java new file mode 100644 index 00000000..e83f0e5b --- /dev/null +++ b/integrations/java/src/main/java-templates/dev/jdx/usage/WasmResource.java @@ -0,0 +1,8 @@ +package dev.jdx.usage; + +public final class WasmResource { + public static final String absoluteFile = + "file://${project.basedir}/../../target/wasm32-wasip1/wasm/usage.wasm"; + + private WasmResource() {} +} diff --git a/integrations/java/src/main/java/dev/jdx/usage/Usage.java b/integrations/java/src/main/java/dev/jdx/usage/Usage.java new file mode 100644 index 00000000..f95dd421 --- /dev/null +++ b/integrations/java/src/main/java/dev/jdx/usage/Usage.java @@ -0,0 +1,98 @@ +package dev.jdx.usage; + +import com.dylibso.chicory.annotations.WasmModuleInterface; +import com.dylibso.chicory.runtime.ImportValues; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasi.WasiExitException; +import com.dylibso.chicory.wasi.WasiOptions; +import com.dylibso.chicory.wasi.WasiPreview1; +import com.dylibso.chicory.wasm.WasmModule; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +@WasmModuleInterface(WasmResource.absoluteFile) +public final class Usage { + private static final WasmModule MODULE = UsageModule.load(); + + public static Builder builder() { + return new Builder(); + } + + private static UsageResult exec(byte[] stdin, List args, Path directory) { + try (ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream()) { + + WasiOptions.Builder wasiOptsBuilder = + WasiOptions.builder() + .withStdout(stdout) + .withStderr(stderr) + .withStdin( + new ByteArrayInputStream( + stdin != null ? stdin : new byte[0])) + .withArguments(args); + + if (directory != null) { + wasiOptsBuilder.withDirectory(directory.toString(), directory); + } + + try (WasiPreview1 wasi = + WasiPreview1.builder().withOptions(wasiOptsBuilder.build()).build()) { + Instance.builder(MODULE) + .withMachineFactory(UsageModule::create) + .withImportValues( + ImportValues.builder() + .addFunction(wasi.toHostFunctions()) + .build()) + .build(); + } catch (WasiExitException e) { + return new UsageResult(stdout.toByteArray(), stderr.toByteArray(), e.exitCode()); + } + + return new UsageResult(stdout.toByteArray(), stderr.toByteArray(), 0); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static final class Builder { + private byte[] stdin; + private final List args = new ArrayList<>(); + private Path directory; + + private Builder() { + args.add("usage"); + } + + public Builder withStdin(byte[] stdin) { + this.stdin = stdin; + return this; + } + + public Builder withStdin(String stdin) { + this.stdin = stdin.getBytes(StandardCharsets.UTF_8); + return this; + } + + public Builder withArgs(String... args) { + for (String arg : args) { + this.args.add(arg); + } + return this; + } + + public Builder withDirectory(Path directory) { + this.directory = directory; + return this; + } + + public UsageResult run() { + return exec(stdin, args, directory); + } + } +} diff --git a/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java b/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java new file mode 100644 index 00000000..d206d533 --- /dev/null +++ b/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java @@ -0,0 +1,39 @@ +package dev.jdx.usage; + +import java.nio.charset.StandardCharsets; + +public class UsageResult { + private final byte[] stdout; + private final byte[] stderr; + private final int exitCode; + + public UsageResult(byte[] stdout, byte[] stderr, int exitCode) { + this.stdout = stdout; + this.stderr = stderr; + this.exitCode = exitCode; + } + + public byte[] stdout() { + return stdout; + } + + public byte[] stderr() { + return stderr; + } + + public String stdoutAsString() { + return new String(stdout, StandardCharsets.UTF_8); + } + + public String stderrAsString() { + return new String(stderr, StandardCharsets.UTF_8); + } + + public int exitCode() { + return exitCode; + } + + public boolean success() { + return exitCode == 0; + } +} diff --git a/integrations/java/src/test/java/dev/jdx/usage/UsageTest.java b/integrations/java/src/test/java/dev/jdx/usage/UsageTest.java new file mode 100644 index 00000000..0585a36a --- /dev/null +++ b/integrations/java/src/test/java/dev/jdx/usage/UsageTest.java @@ -0,0 +1,99 @@ +package dev.jdx.usage; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.roastedroot.zerofs.Configuration; +import io.roastedroot.zerofs.ZeroFs; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +class UsageTest { + + private static final String SPEC = + "name \"test-cli\"\n" + + "bin \"test-cli\"\n" + + "version \"1.0.0\"\n" + + "flag \"-v --verbose\" help=\"Enable verbose output\" global=#true\n" + + "arg \"\" help=\"Input file\"\n" + + "cmd \"sub\" help=\"A subcommand\" {\n" + + " flag \"--force\" help=\"Force operation\"\n" + + "}\n"; + + @Test + void generateJson() { + UsageResult result = + Usage.builder() + .withStdin(SPEC) + .withArgs("generate", "json", "-f", "-") + .run(); + + assertTrue(result.success(), "stderr: " + result.stderrAsString()); + String output = result.stdoutAsString(); + assertTrue(output.contains("\"name\": \"test-cli\"")); + assertTrue(output.contains("\"version\": \"1.0.0\"")); + } + + @Test + void generateManpage() { + UsageResult result = + Usage.builder() + .withStdin(SPEC) + .withArgs("generate", "manpage", "-f", "-") + .run(); + + assertTrue(result.success(), "stderr: " + result.stderrAsString()); + String output = result.stdoutAsString(); + assertTrue(output.contains("TEST-CLI")); + } + + @Test + void generateMarkdown() throws Exception { + try (FileSystem fs = + ZeroFs.newFileSystem( + Configuration.unix().toBuilder().setAttributeViews("unix").build())) { + Path outDir = fs.getPath("out"); + Files.createDirectory(outDir); + Path outFile = outDir.resolve("docs.md"); + + UsageResult result = + Usage.builder() + .withStdin(SPEC) + .withArgs( + "generate", + "markdown", + "-f", + "-", + "--out-file", + outFile.toString()) + .withDirectory(outDir) + .run(); + + assertTrue(result.success(), "stderr: " + result.stderrAsString()); + String markdown = new String(Files.readAllBytes(outFile)); + assertTrue(markdown.contains("test-cli")); + } + } + + @Test + void generateCompletionBash() { + UsageResult result = + Usage.builder() + .withStdin(SPEC) + .withArgs("generate", "completion", "bash", "test-cli", "-f", "-") + .run(); + + assertTrue(result.success(), "stderr: " + result.stderrAsString()); + String output = result.stdoutAsString(); + assertTrue(output.contains("test-cli")); + } + + @Test + void invalidArgs() { + UsageResult result = Usage.builder().withArgs("--nonexistent-flag").run(); + + assertEquals(2, result.exitCode()); + } +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index c6ed057e..c889b896 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -37,6 +37,7 @@ strum = { version = "0.28", features = ["derive"] } tera = { version = "1", optional = true } thiserror = "2" versions = "7" +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] xx = "2" [features] diff --git a/lib/src/docs/markdown/renderer.rs b/lib/src/docs/markdown/renderer.rs index 9772b3b9..1e22736c 100644 --- a/lib/src/docs/markdown/renderer.rs +++ b/lib/src/docs/markdown/renderer.rs @@ -4,7 +4,8 @@ use crate::error::UsageErr; use itertools::Itertools; use serde::Serialize; use std::collections::HashMap; -use xx::regex; +use std::sync::LazyLock; +use regex::Regex; #[derive(Debug, Clone)] pub struct MarkdownRenderer { @@ -88,7 +89,10 @@ impl MarkdownRenderer { return line.to_string(); } // replace '<' with '<' but not inside code blocks - xx::regex!(r"(`[^`]*`)|(<)") + { + static RE: LazyLock = LazyLock::new(|| Regex::new(r"(`[^`]*`)|(<)").unwrap()); + &RE + } .replace_all(line, |caps: ®ex::Captures| { if caps.get(1).is_some() { caps.get(1).unwrap().as_str().to_string() @@ -102,8 +106,8 @@ impl MarkdownRenderer { Ok(value.into()) }, ); - let path_re = - regex!(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/"); + static PATH_RE: LazyLock = LazyLock::new(|| Regex::new(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/").unwrap()); + let path_re = &*PATH_RE; tera.register_function("source_code_link", |args: &HashMap| { let spec = args.get("spec").unwrap().as_object().unwrap(); let cmd = args.get("cmd").unwrap().as_object().unwrap(); diff --git a/lib/src/error.rs b/lib/src/error.rs index d117d9bd..9101c680 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -50,6 +50,7 @@ pub enum UsageErr { #[diagnostic(transparent)] KdlError(#[from] kdl::KdlError), + #[cfg(not(target_arch = "wasm32"))] #[error(transparent)] #[diagnostic(transparent)] XXError(#[from] xx::error::XXError), diff --git a/lib/src/sh.rs b/lib/src/sh.rs index 3640ab04..42b1cdea 100644 --- a/lib/src/sh.rs +++ b/lib/src/sh.rs @@ -1,7 +1,11 @@ +#[cfg(not(target_arch = "wasm32"))] use std::process::Command; +#[cfg(not(target_arch = "wasm32"))] use xx::process::check_status; +#[cfg(not(target_arch = "wasm32"))] use xx::{XXError, XXResult}; +#[cfg(not(target_arch = "wasm32"))] pub(crate) fn sh(script: &str) -> XXResult { #[cfg(unix)] let (shell, flag) = ("sh", "-c"); @@ -23,3 +27,11 @@ pub(crate) fn sh(script: &str) -> XXResult { let stdout = String::from_utf8(output.stdout).expect("stdout is not utf-8"); Ok(stdout) } + +#[cfg(target_arch = "wasm32")] +pub(crate) fn sh(_script: &str) -> std::result::Result { + Err(crate::error::UsageErr::IO(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "shell execution is not supported on wasm", + ))) +} diff --git a/lib/src/spec/mod.rs b/lib/src/spec/mod.rs index f5563605..6c1b290d 100644 --- a/lib/src/spec/mod.rs +++ b/lib/src/spec/mod.rs @@ -18,7 +18,8 @@ use std::fmt::{Display, Formatter}; use std::iter::once; use std::path::Path; use std::str::FromStr; -use xx::file; +use std::sync::LazyLock; +use regex::Regex; use crate::error::UsageErr; use crate::spec::cmd::{SpecCommand, SpecExample}; @@ -102,7 +103,7 @@ impl Spec { /// If `bin` is not specified in the spec, it defaults to the filename. #[must_use = "parsing result should be used"] pub fn parse_script(file: &Path) -> Result { - let raw = extract_usage_from_comments(&file::read_to_string(file)?); + let raw = extract_usage_from_comments(&std::fs::read_to_string(file)?); let ctx = ParsingContext::new(file, &raw); let mut spec = Self::parse(&ctx, &raw)?; if spec.bin.is_empty() { @@ -303,11 +304,11 @@ fn check_usage_version(version: &str) { } fn split_script(file: &Path) -> Result { - let full = file::read_to_string(file)?; + let full = std::fs::read_to_string(file)?; // If file has a shebang and USAGE comments, extract the spec from comments if full.starts_with("#!") { - let usage_regex = xx::regex!(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])"); - if full.lines().any(|l| usage_regex.is_match(l)) { + static USAGE_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])").unwrap()); + if full.lines().any(|l| USAGE_RE.is_match(l)) { return Ok(extract_usage_from_comments(&full)); } } @@ -316,8 +317,10 @@ fn split_script(file: &Path) -> Result { } fn extract_usage_from_comments(full: &str) -> String { - let usage_regex = xx::regex!(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$"); - let blank_comment_regex = xx::regex!(r"^(?:#|//|::)\s*$"); + static USAGE_CAPTURE_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$").unwrap()); + static BLANK_COMMENT_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)\s*$").unwrap()); + let usage_regex = &*USAGE_CAPTURE_RE; + let blank_comment_regex = &*BLANK_COMMENT_RE; let mut usage = vec![]; let mut found = false; for line in full.lines() { diff --git a/mise.toml b/mise.toml index 8fb1b9b2..c63db468 100644 --- a/mise.toml +++ b/mise.toml @@ -124,3 +124,39 @@ includes = ['tasks'] [tasks.install-dev] run = "cargo install --path cli --debug" + +[tasks."wasm:clean"] +dir = "cli" +run = "rm -rf target wasi-sdk binaryen" + +[tasks."wasm:get-wasisdk"] +dir = "cli" +run = """ +if [ ! -d "wasi-sdk" ]; then + wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-29/wasi-sdk-29.0-x86_64-linux.tar.gz + tar xvf wasi-sdk-29.0-x86_64-linux.tar.gz + rm wasi-sdk-29.0-x86_64-linux.tar.gz + mv wasi-sdk-29.0-x86_64-linux wasi-sdk +fi +""" + +[tasks."wasm:get-binaryen"] +dir = "cli" +run = """ +if [ ! -d "binaryen" ]; then + wget https://github.com/WebAssembly/binaryen/releases/download/version_125/binaryen-version_125-x86_64-linux.tar.gz + tar xvf binaryen-version_125-x86_64-linux.tar.gz + rm binaryen-version_125-x86_64-linux.tar.gz + mv binaryen-version_125 binaryen +fi +""" + +[tasks."wasm:build"] +dir = "cli" +depends = ["wasm:get-wasisdk"] +run = """ +WASI_SDK_PATH=$(pwd)/wasi-sdk \ +CC_wasm32_wasip1=$(pwd)/wasi-sdk/bin/clang \ +CFLAGS_wasm32_wasip1="--sysroot=$(pwd)/wasi-sdk/share/wasi-sysroot" \ +cargo build --target wasm32-wasip1 --profile wasm +""" From 383095bbe075e9f7cd999380d71ad8e1954e4c54 Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Mon, 23 Mar 2026 18:58:15 +0000 Subject: [PATCH 2/7] Update integrations/java/README.md Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- integrations/java/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/java/README.md b/integrations/java/README.md index ec02b584..6279b9ec 100644 --- a/integrations/java/README.md +++ b/integrations/java/README.md @@ -1,6 +1,6 @@ # usage-java -Run the [usage](https://usage.jdx.dev) CLI from Java via [Chicory](https://github.com/nicholasgasior/chicory), a pure-Java WebAssembly runtime. +Run the [usage](https://usage.jdx.dev) CLI from Java via [Chicory](https://github.com/dylibso/chicory), a pure-Java WebAssembly runtime. The `usage` CLI is compiled to WebAssembly (WASI) and executed at runtime through Chicory with build-time AOT compilation for fast execution. From be532578160399ac17d2b69664756c971e606579 Mon Sep 17 00:00:00 2001 From: andreatp Date: Mon, 23 Mar 2026 19:00:06 +0000 Subject: [PATCH 3/7] fix: address PR review feedback - Fix wasm:clean to remove workspace-root target/wasm32-wasip1 instead of cli/target which doesn't exist in a Cargo workspace - Remove unused wasm:get-binaryen task Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 1 - mise.toml | 13 +------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index d86dfe8f..859cac59 100644 --- a/.gitignore +++ b/.gitignore @@ -12,5 +12,4 @@ megalinter-reports/ tasks/fig/build cli/wasi-sdk/ -cli/binaryen/ integrations/java/target/ diff --git a/mise.toml b/mise.toml index c63db468..7ba90ab4 100644 --- a/mise.toml +++ b/mise.toml @@ -126,8 +126,7 @@ includes = ['tasks'] run = "cargo install --path cli --debug" [tasks."wasm:clean"] -dir = "cli" -run = "rm -rf target wasi-sdk binaryen" +run = "rm -rf target/wasm32-wasip1 cli/wasi-sdk" [tasks."wasm:get-wasisdk"] dir = "cli" @@ -140,16 +139,6 @@ if [ ! -d "wasi-sdk" ]; then fi """ -[tasks."wasm:get-binaryen"] -dir = "cli" -run = """ -if [ ! -d "binaryen" ]; then - wget https://github.com/WebAssembly/binaryen/releases/download/version_125/binaryen-version_125-x86_64-linux.tar.gz - tar xvf binaryen-version_125-x86_64-linux.tar.gz - rm binaryen-version_125-x86_64-linux.tar.gz - mv binaryen-version_125 binaryen -fi -""" [tasks."wasm:build"] dir = "cli" From 738c2feb2e6c4c903e675aba3d30e2c341143660 Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 24 Mar 2026 10:00:03 +0000 Subject: [PATCH 4/7] fix(ci): use correct setup-jbang action SHA The previous SHA didn't exist. Pin to v0.1.1. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test-java.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml index b4c2870f..9bbbe589 100644 --- a/.github/workflows/test-java.yml +++ b/.github/workflows/test-java.yml @@ -28,5 +28,5 @@ jobs: java-version: 21 - run: mise run wasm:build - run: cd integrations/java && mvn clean install - - uses: jbangdev/setup-jbang@6109ef573a4a3e8e21df68dcc3e0e29e3c5e48ea # v3 + - uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1 - run: cd integrations/java && jbang example/Main.java From 542fd86786adfa85b01918b0b3342a5e02d8f753 Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 24 Mar 2026 10:04:41 +0000 Subject: [PATCH 5/7] fix: address greptile review comments - Pin maven-surefire-plugin to 3.5.2 for reliable JUnit 5 discovery - Return defensive copies from UsageResult.stdout()/stderr() - Catch RuntimeException (WASM traps) and surface as exit code 1 Co-Authored-By: Claude Opus 4.6 (1M context) --- integrations/java/pom.xml | 6 ++++++ .../java/src/main/java/dev/jdx/usage/UsageResult.java | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/integrations/java/pom.xml b/integrations/java/pom.xml index 579042d7..098390d4 100644 --- a/integrations/java/pom.xml +++ b/integrations/java/pom.xml @@ -17,6 +17,7 @@ 1.7.3 5.14.3 3.15.0 + 3.5.2 3.1.0 @@ -93,6 +94,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + org.codehaus.mojo templating-maven-plugin diff --git a/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java b/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java index d206d533..32eb7381 100644 --- a/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java +++ b/integrations/java/src/main/java/dev/jdx/usage/UsageResult.java @@ -14,11 +14,11 @@ public UsageResult(byte[] stdout, byte[] stderr, int exitCode) { } public byte[] stdout() { - return stdout; + return stdout.clone(); } public byte[] stderr() { - return stderr; + return stderr.clone(); } public String stdoutAsString() { From 7630327f35e5665ed319974b7c22b41ef5dc5a9a Mon Sep 17 00:00:00 2001 From: andreatp Date: Tue, 24 Mar 2026 10:17:30 +0000 Subject: [PATCH 6/7] fix(ci): add wasm32-wasip1 rustup target in test-java workflow Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/test-java.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-java.yml b/.github/workflows/test-java.yml index 9bbbe589..04721817 100644 --- a/.github/workflows/test-java.yml +++ b/.github/workflows/test-java.yml @@ -26,6 +26,7 @@ jobs: with: distribution: temurin java-version: 21 + - run: rustup target add wasm32-wasip1 - run: mise run wasm:build - run: cd integrations/java && mvn clean install - uses: jbangdev/setup-jbang@2b1b465a7b75f4222b81426f23a01e013aa7b95c # v0.1.1 From 1dd488aab08ab661ae51fdb8a75c5da5a37f19d0 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:19:03 +0000 Subject: [PATCH 7/7] [autofix.ci] apply automated fixes --- cli/src/cli/generate/markdown.rs | 3 +- integrations/java/README.md | 28 ++++++++--------- lib/src/docs/markdown/renderer.rs | 26 +++++++++------- lib/src/spec/mod.rs | 11 ++++--- mise.lock | 52 +++++++++++++++++++++++++------ 5 files changed, 80 insertions(+), 40 deletions(-) diff --git a/cli/src/cli/generate/markdown.rs b/cli/src/cli/generate/markdown.rs index f5e038cf..ae65a84e 100644 --- a/cli/src/cli/generate/markdown.rs +++ b/cli/src/cli/generate/markdown.rs @@ -53,7 +53,8 @@ impl Markdown { "\n{}\n", md.trim() ), - ).into_diagnostic()?; + ) + .into_diagnostic()?; Ok(()) }; let spec = parse_file_or_stdin(&self.file)?; diff --git a/integrations/java/README.md b/integrations/java/README.md index 6279b9ec..ac192ffc 100644 --- a/integrations/java/README.md +++ b/integrations/java/README.md @@ -49,24 +49,24 @@ System.out.println(result.stdoutAsString()); Creates a builder for running usage CLI commands. -| Method | Description | -| --- | --- | -| `.withStdin(String)` | Pipe a string to stdin (typically a usage spec) | -| `.withStdin(byte[])` | Pipe bytes to stdin | -| `.withArgs(String...)` | Append CLI arguments (the `usage` program name is added automatically) | +| Method | Description | +| ---------------------- | ------------------------------------------------------------------------- | +| `.withStdin(String)` | Pipe a string to stdin (typically a usage spec) | +| `.withStdin(byte[])` | Pipe bytes to stdin | +| `.withArgs(String...)` | Append CLI arguments (the `usage` program name is added automatically) | | `.withDirectory(Path)` | Pre-open a directory for WASI filesystem access (needed for `--out-file`) | -| `.run()` | Execute and return a `UsageResult` | +| `.run()` | Execute and return a `UsageResult` | ### `UsageResult` -| Method | Description | -| --- | --- | -| `.stdout()` | Raw stdout bytes | -| `.stderr()` | Raw stderr bytes | -| `.stdoutAsString()` | Stdout as UTF-8 string | -| `.stderrAsString()` | Stderr as UTF-8 string | -| `.exitCode()` | Process exit code | -| `.success()` | `true` if exit code is 0 | +| Method | Description | +| ------------------- | ------------------------ | +| `.stdout()` | Raw stdout bytes | +| `.stderr()` | Raw stderr bytes | +| `.stdoutAsString()` | Stdout as UTF-8 string | +| `.stderrAsString()` | Stderr as UTF-8 string | +| `.exitCode()` | Process exit code | +| `.success()` | `true` if exit code is 0 | ## Common Commands diff --git a/lib/src/docs/markdown/renderer.rs b/lib/src/docs/markdown/renderer.rs index 1e22736c..9879afb5 100644 --- a/lib/src/docs/markdown/renderer.rs +++ b/lib/src/docs/markdown/renderer.rs @@ -2,10 +2,10 @@ use crate::docs::markdown::tera::TERA; use crate::docs::models::Spec; use crate::error::UsageErr; use itertools::Itertools; +use regex::Regex; use serde::Serialize; use std::collections::HashMap; use std::sync::LazyLock; -use regex::Regex; #[derive(Debug, Clone)] pub struct MarkdownRenderer { @@ -90,23 +90,27 @@ impl MarkdownRenderer { } // replace '<' with '<' but not inside code blocks { - static RE: LazyLock = LazyLock::new(|| Regex::new(r"(`[^`]*`)|(<)").unwrap()); + static RE: LazyLock = + LazyLock::new(|| Regex::new(r"(`[^`]*`)|(<)").unwrap()); &RE } - .replace_all(line, |caps: ®ex::Captures| { - if caps.get(1).is_some() { - caps.get(1).unwrap().as_str().to_string() - } else { - "<".to_string() - } - }) - .to_string() + .replace_all(line, |caps: ®ex::Captures| { + if caps.get(1).is_some() { + caps.get(1).unwrap().as_str().to_string() + } else { + "<".to_string() + } + }) + .to_string() }) .join("\n"); Ok(value.into()) }, ); - static PATH_RE: LazyLock = LazyLock::new(|| Regex::new(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/").unwrap()); + static PATH_RE: LazyLock = LazyLock::new(|| { + Regex::new(r"https://(github.com/[^/]+/[^/]+|gitlab.com/[^/]+/[^/]+/-)/blob/[^/]+/") + .unwrap() + }); let path_re = &*PATH_RE; tera.register_function("source_code_link", |args: &HashMap| { let spec = args.get("spec").unwrap().as_object().unwrap(); diff --git a/lib/src/spec/mod.rs b/lib/src/spec/mod.rs index 6c1b290d..ec1879dd 100644 --- a/lib/src/spec/mod.rs +++ b/lib/src/spec/mod.rs @@ -13,13 +13,13 @@ pub mod mount; use indexmap::IndexMap; use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue}; use log::{info, warn}; +use regex::Regex; use serde::Serialize; use std::fmt::{Display, Formatter}; use std::iter::once; use std::path::Path; use std::str::FromStr; use std::sync::LazyLock; -use regex::Regex; use crate::error::UsageErr; use crate::spec::cmd::{SpecCommand, SpecExample}; @@ -307,7 +307,8 @@ fn split_script(file: &Path) -> Result { let full = std::fs::read_to_string(file)?; // If file has a shebang and USAGE comments, extract the spec from comments if full.starts_with("#!") { - static USAGE_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])").unwrap()); + static USAGE_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])").unwrap()); if full.lines().any(|l| USAGE_RE.is_match(l)) { return Ok(extract_usage_from_comments(&full)); } @@ -317,8 +318,10 @@ fn split_script(file: &Path) -> Result { } fn extract_usage_from_comments(full: &str) -> String { - static USAGE_CAPTURE_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$").unwrap()); - static BLANK_COMMENT_RE: LazyLock = LazyLock::new(|| Regex::new(r"^(?:#|//|::)\s*$").unwrap()); + static USAGE_CAPTURE_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^(?:#|//|::)(?:USAGE| ?\[USAGE\])(.*)$").unwrap()); + static BLANK_COMMENT_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^(?:#|//|::)\s*$").unwrap()); let usage_regex = &*USAGE_CAPTURE_RE; let blank_comment_regex = &*BLANK_COMMENT_RE; let mut usage = vec![]; diff --git a/mise.lock b/mise.lock index 584a3dda..91083bfc 100644 --- a/mise.lock +++ b/mise.lock @@ -1,14 +1,28 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + [[tools.actionlint]] version = "1.7.10" backend = "aqua:rhysd/actionlint" -"platforms.linux-x64" = { checksum = "sha256:f4c76b71db5755a713e6055cbb0857ed07e103e028bda117817660ebadb4386f", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.10/actionlint_1.7.10_linux_amd64.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:004ca87b367b37f4d75c55ab6cf80f9b8c043adbfbd440f31c604d417939c442", url = "https://github.com/rhysd/actionlint/releases/download/v1.7.10/actionlint_1.7.10_darwin_arm64.tar.gz"} + +[tools.actionlint."platforms.linux-x64"] +checksum = "sha256:f4c76b71db5755a713e6055cbb0857ed07e103e028bda117817660ebadb4386f" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.10/actionlint_1.7.10_linux_amd64.tar.gz" + +[tools.actionlint."platforms.macos-arm64"] +checksum = "sha256:004ca87b367b37f4d75c55ab6cf80f9b8c043adbfbd440f31c604d417939c442" +url = "https://github.com/rhysd/actionlint/releases/download/v1.7.10/actionlint_1.7.10_darwin_arm64.tar.gz" [[tools.cargo-binstall]] version = "1.17.4" backend = "aqua:cargo-bins/cargo-binstall" -"platforms.linux-x64" = { checksum = "sha256:8822793dd22a1fcfba6d7581ed130ec935fb26a0db58fb9b0f01f0fae17d28f8", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.4/cargo-binstall-x86_64-unknown-linux-musl.tgz"} -"platforms.macos-arm64" = { checksum = "sha256:c689061413598fa32b77b6fd2a7300c6392faf008137194ab8a4cdb93c12040a", url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.4/cargo-binstall-aarch64-apple-darwin.zip"} + +[tools.cargo-binstall."platforms.linux-x64"] +checksum = "sha256:8822793dd22a1fcfba6d7581ed130ec935fb26a0db58fb9b0f01f0fae17d28f8" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.4/cargo-binstall-x86_64-unknown-linux-musl.tgz" + +[tools.cargo-binstall."platforms.macos-arm64"] +checksum = "sha256:c689061413598fa32b77b6fd2a7300c6392faf008137194ab8a4cdb93c12040a" +url = "https://github.com/cargo-bins/cargo-binstall/releases/download/v1.17.4/cargo-binstall-aarch64-apple-darwin.zip" [[tools."cargo:cargo-edit"]] version = "0.13.8" @@ -37,8 +51,14 @@ backend = "github:jdx/communique" [[tools.gh]] version = "2.86.0" backend = "aqua:cli/cli" -"platforms.linux-x64" = { checksum = "sha256:f3b08bd6a28420cc2229b0a1a687fa25f2b838d3f04b297414c1041ca68103c7", url = "https://github.com/cli/cli/releases/download/v2.86.0/gh_2.86.0_linux_amd64.tar.gz"} -"platforms.macos-arm64" = { checksum = "sha256:bde753978a352c5ae6c4abec47334d516e82807af20c9dbbd45507a5a0aedaaa", url = "https://github.com/cli/cli/releases/download/v2.86.0/gh_2.86.0_macOS_arm64.zip"} + +[tools.gh."platforms.linux-x64"] +checksum = "sha256:f3b08bd6a28420cc2229b0a1a687fa25f2b838d3f04b297414c1041ca68103c7" +url = "https://github.com/cli/cli/releases/download/v2.86.0/gh_2.86.0_linux_amd64.tar.gz" + +[tools.gh."platforms.macos-arm64"] +checksum = "sha256:bde753978a352c5ae6c4abec47334d516e82807af20c9dbbd45507a5a0aedaaa" +url = "https://github.com/cli/cli/releases/download/v2.86.0/gh_2.86.0_macOS_arm64.zip" [[tools."npm:prettier"]] version = "3.8.1" @@ -47,11 +67,23 @@ backend = "npm:prettier" [[tools.pnpm]] version = "10.29.3" backend = "aqua:pnpm/pnpm" -"platforms.linux-x64" = { checksum = "sha256:2fc98db127c611be0c110af11b5b72759f7d736893dddc81df73b7b59b30f15a", url = "https://github.com/pnpm/pnpm/releases/download/v10.29.3/pnpm-linux-x64"} -"platforms.macos-arm64" = { checksum = "sha256:a248c56b0ab20443e18d0c8ee3207c6c564ee342bd3fc8694b3569a060cecece", url = "https://github.com/pnpm/pnpm/releases/download/v10.29.3/pnpm-macos-arm64"} + +[tools.pnpm."platforms.linux-x64"] +checksum = "sha256:2fc98db127c611be0c110af11b5b72759f7d736893dddc81df73b7b59b30f15a" +url = "https://github.com/pnpm/pnpm/releases/download/v10.29.3/pnpm-linux-x64" + +[tools.pnpm."platforms.macos-arm64"] +checksum = "sha256:a248c56b0ab20443e18d0c8ee3207c6c564ee342bd3fc8694b3569a060cecece" +url = "https://github.com/pnpm/pnpm/releases/download/v10.29.3/pnpm-macos-arm64" [[tools.shellcheck]] version = "0.11.0" backend = "aqua:koalaman/shellcheck" -"platforms.linux-x64" = { checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz"} -"platforms.macos-arm64" = { checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79", url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz"} + +[tools.shellcheck."platforms.linux-x64"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" + +[tools.shellcheck."platforms.macos-arm64"] +checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz"