diff --git a/AGENTS.md b/AGENTS.md index 34e77642ff..b10e71bac7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -261,7 +261,7 @@ If you think you need to pick "the newest installed version" at a new call site, ### Configuration Parsing The configuration system supports multiple file formats and environment-specific configs. Changes to settings require updating `settings.toml` and running `mise run render:schema`. The `Settings` types are generated from `settings.toml` in `crates/mise-settings`; loading them (config discovery, trust, CLI flags) stays in `src/config/settings.rs`, whose `SettingsExt` trait holds the methods that need the rest of mise. -When changing configuration syntax or tool options, update `schema/mise.json` and run `mise run render:schema`. The render task also regenerates `schema/mise-task.json` from the main schema. Sections whose types live in `crates/mise-config` (so far `[bootstrap]`, `[daemons_settings]`, `[daemon_groups]`, `[doctor]`, `[history]`, `[monorepo]`, `[oci]`, `[task_config]`, `[tasks]`, `[task_templates]`, `[tool_config]` and `[wrappers]`) are generated from those types and their doc comments: edit the Rust, not the JSON, then run `mise run render:schema`. `[env]`, `[vars]`, `[[watch_files]]`, `[tool_alias]` and `min_version` accept shapes a derive cannot describe, so their schema is a hand-written `JsonSchema` in the same crate: `crates/mise-config/src/env/schema.rs` next to the `env.rs` parser, and the impls in `watch_files.rs`, `alias.rs` and `min_version.rs`. Task fields with several accepted shapes take theirs from `crates/mise-config/src/task/schema.rs`. Change it together with the parser, then run `mise run render:schema`. `[tools]` picks per-backend options by key pattern, so its schema is hand-written in `crates/mise-config/src/tools/schema.rs`; change it together with `ToolVersionOptions::insert_option` and the backend's option reader, then run `mise run render:schema`. A field that stays raw TOML keeps a hand-written `$defs` entry that the generated part references. +`schema/mise.json`, `schema/mise-task.json` and `schema/miserc.json` are generated from `crates/mise-config` and `settings.toml`: never edit the JSON. Change the Rust, then run `mise run render:schema`; a `mise-config` test fails while the committed files are stale. The top-level keys are the fields of `MiseTomlDecl` in `crates/mise-config/src/mise_toml.rs`, and the `schema_keys_match_parser` test fails until `MiseToml` reads the same keys, so a new key goes in both. Sections whose types derive `JsonSchema` (so far `[bootstrap]`, `[daemons_settings]`, `[daemon_groups]`, `[doctor]`, `[history]`, `[monorepo]`, `[oci]`, `[task_config]`, `[tasks]`, `[task_templates]`, `[tool_config]` and `[wrappers]`) are generated from those types and their doc comments. `[env]`, `[vars]`, `[[watch_files]]`, `[tool_alias]` and `min_version` accept shapes a derive cannot describe, so their schema is a hand-written `JsonSchema` in the same crate: `crates/mise-config/src/env/schema.rs` next to the `env.rs` parser, and the impls in `watch_files.rs`, `alias.rs` and `min_version.rs`. Task fields with several accepted shapes take theirs from `crates/mise-config/src/task/schema.rs`. Change it together with the parser. `[tools]` picks per-backend options by key pattern, so its schema is hand-written in `crates/mise-config/src/tools/schema.rs`; change it together with `ToolVersionOptions::insert_option` and the backend's option reader. `[hooks]` and `[deps]` are written by hand next to their types (`hooks/schema.rs`, `deps/schema.rs`), and so are the values that stay raw TOML so newer shapes warn instead of failing: `[dotfiles]`, `[dotfile_groups]`, `[daemons]`, `[daemon_providers]`, `[secrets]` and parts of `[bootstrap]` (`dotfiles/schema.rs`, `daemons/schema.rs`, `mise_toml.rs`, `bootstrap/schema.rs`). `[settings]` and `miserc.json` come from `settings.toml` through `crates/mise-config/src/schema/settings.rs`. The `schema_matches_parser` unit test (`src/config/config_file/mise_toml/schema_conformance.rs`) validates every `toml` block in `docs/` and every mise config an e2e heredoc writes against both the schema and `MiseToml`'s parser, and fails when they disagree. Fix the schema rather than the corpus. Mark a config that mise refuses on purpose with `# schema: invalid` (e2e) or `` (docs) on the line above it, and an excerpt that is not a whole config with `schema: skip`. List deliberate differences in its `GAPS` with a reason. diff --git a/crates/mise-config/examples/render-schema.rs b/crates/mise-config/examples/render-schema.rs index d838e3895c..b5bced2a1a 100644 --- a/crates/mise-config/examples/render-schema.rs +++ b/crates/mise-config/examples/render-schema.rs @@ -1,11 +1,14 @@ -//! Prints the generated schema fragments as one JSON object, keyed by JSON pointer. +//! Writes `schema/mise.json`, `schema/mise-task.json` and `schema/miserc.json`. //! -//! `mise run render:schema` runs this and writes each fragment into `schema/mise.json`. +//! `mise run render:schema` runs this, then formats the files. + +use std::path::Path; fn main() { - let fragments: serde_json::Map = mise_config::schema::fragments() - .into_iter() - .map(|(pointer, schema)| (pointer.to_string(), schema)) - .collect(); - println!("{}", serde_json::to_string_pretty(&fragments).unwrap()); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let settings = std::fs::read_to_string(root.join("settings.toml")).unwrap(); + for (path, schema) in mise_config::schema::render(&settings) { + let json = serde_json::to_string_pretty(&schema).unwrap(); + std::fs::write(root.join(path), json + "\n").unwrap(); + } } diff --git a/crates/mise-config/src/bootstrap.rs b/crates/mise-config/src/bootstrap.rs index 397e9655ff..5d54a063d1 100644 --- a/crates/mise-config/src/bootstrap.rs +++ b/crates/mise-config/src/bootstrap.rs @@ -1,3 +1,5 @@ +pub(crate) mod schema; + use indexmap::IndexMap; use schemars::JsonSchema; use serde::Deserialize; diff --git a/crates/mise-config/src/bootstrap/schema.rs b/crates/mise-config/src/bootstrap/schema.rs new file mode 100644 index 0000000000..c3e15a05a1 --- /dev/null +++ b/crates/mise-config/src/bootstrap/schema.rs @@ -0,0 +1,369 @@ +//! The JSON schema for the `[bootstrap]` fields that stay raw TOML, written by hand. +//! +//! Hooks, shell activation, the macOS defaults domains and the apt and dnf repositories are kept +//! as TOML values so a shape from a newer mise warns instead of failing the whole file, so there +//! is no Rust type to derive them from. The derived `[bootstrap]` schema points at these through +//! [`refs`](crate::schema::refs). Change them together with the code that reads those values, then +//! run `mise run render:schema`. + +use schemars::{Schema, json_schema}; + +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { + vec![ + ("bootstrap_file_phase", bootstrap_file_phase()), + ("bootstrap_hook", bootstrap_hook()), + ("bootstrap_shell_activation", bootstrap_shell_activation()), + ("bootstrap_apt_sources", bootstrap_apt_sources()), + ("bootstrap_dnf_repos", bootstrap_dnf_repos()), + ("bootstrap_hooks", bootstrap_hooks()), + ("bootstrap_macos_defaults", bootstrap_macos_defaults()), + ("bootstrap_macos_dock", bootstrap_macos_dock()), + ("bootstrap_macos_finder", bootstrap_macos_finder()), + ("bootstrap_macos_keyboard", bootstrap_macos_keyboard()), + ("bootstrap_macos_trackpad", bootstrap_macos_trackpad()), + ( + "bootstrap_mise_shell_activate", + bootstrap_mise_shell_activate(), + ), + ("macos_defaults_value", macos_defaults_value()), + ] +} + +fn bootstrap_file_phase() -> Schema { + json_schema!({ + "type": "string", + "enum": ["pre-packages", "post-packages"], + "default": "post-packages", + "description": "When to apply the managed path during bootstrap; pre-packages runs before the pre-packages hook" + }) +} + +fn bootstrap_hook() -> Schema { + json_schema!({ + "description": "command or commands to run for a bootstrap phase", + "oneOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": "string"}}, + { + "type": "object", + "properties": { + "run": { + "oneOf": [ + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} + ] + } + }, + "required": ["run"], + "additionalProperties": true + } + ] + }) +} + +fn bootstrap_shell_activation() -> Schema { + json_schema!({ + "oneOf": [ + { + "type": "boolean", + "description": "enable the target with its default mode, or disable it" + }, + { + "type": "string", + "enum": ["activate", "shims", "auto"], + "description": "enable the target with the selected activation mode" + }, + { + "type": "object", + "description": "shell activation settings for the target", + "properties": { + "enabled": { + "type": "boolean", + "description": "whether to manage the activation block" + }, + "mode": { + "type": "string", + "enum": ["activate", "shims", "auto"], + "description": "activation mode; defaults according to the target" + } + }, + "required": ["enabled"], + "additionalProperties": false + } + ] + }) +} + +fn bootstrap_apt_sources() -> Schema { + json_schema!({ + "type": "object", + "description": "apt repositories added before apt packages are installed, keyed by a name used in the files mise writes", + "propertyNames": {"pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$"}, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "ppa": { + "type": "string", + "description": "Launchpad PPA as owner/archive, such as git-core/ppa; added on Ubuntu and Ubuntu-based releases only" + }, + "uris": {"type": "string", "description": "repository URL(s), space-separated"}, + "suites": { + "type": "string", + "description": "suite(s), space-separated; defaults to the release codename" + }, + "components": { + "type": "string", + "description": "component(s), space-separated; defaults to main, or none when suites is a path ending in /" + }, + "architectures": { + "type": "string", + "description": "architecture(s) to fetch, space-separated" + }, + "key": { + "type": "string", + "description": "http(s) URL of the repository's signing key, armored or binary" + }, + "fingerprint": { + "description": "full primary key fingerprint(s) the signing key must have; required with key", + "oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}] + } + }, + "oneOf": [ + { + "required": ["ppa"], + "properties": {"fingerprint": {"type": "string"}}, + "not": { + "anyOf": [ + {"required": ["uris"]}, + {"required": ["suites"]}, + {"required": ["components"]}, + {"required": ["architectures"]}, + {"required": ["key"]} + ] + } + }, + {"required": ["uris", "key", "fingerprint"], "not": {"required": ["ppa"]}} + ] + } + }) +} + +fn bootstrap_dnf_repos() -> Schema { + json_schema!({ + "type": "object", + "description": "dnf repositories enabled before dnf packages are installed, keyed by a name for the entry", + "propertyNames": {"pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$"}, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["copr"], + "properties": { + "copr": { + "type": "string", + "description": "COPR project as owner/project or @group/project, such as scottames/ghostty; enabled as dnf copr enable would" + } + } + } + }) +} + +fn bootstrap_hooks() -> Schema { + json_schema!({ + "type": "object", + "description": "commands to run before and after bootstrap phases", + "properties": { + "pre-packages": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_packages": {"$ref": "#/$defs/bootstrap_hook"}, + "post-packages": {"$ref": "#/$defs/bootstrap_hook"}, + "post_packages": {"$ref": "#/$defs/bootstrap_hook"}, + "pre-repos": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_repos": {"$ref": "#/$defs/bootstrap_hook"}, + "post-repos": {"$ref": "#/$defs/bootstrap_hook"}, + "post_repos": {"$ref": "#/$defs/bootstrap_hook"}, + "pre-dotfiles": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_dotfiles": {"$ref": "#/$defs/bootstrap_hook"}, + "post-dotfiles": {"$ref": "#/$defs/bootstrap_hook"}, + "post_dotfiles": {"$ref": "#/$defs/bootstrap_hook"}, + "pre-defaults": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_defaults": {"$ref": "#/$defs/bootstrap_hook"}, + "post-defaults": {"$ref": "#/$defs/bootstrap_hook"}, + "post_defaults": {"$ref": "#/$defs/bootstrap_hook"}, + "pre-user": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_user": {"$ref": "#/$defs/bootstrap_hook"}, + "post-user": {"$ref": "#/$defs/bootstrap_hook"}, + "post_user": {"$ref": "#/$defs/bootstrap_hook"}, + "pre-tools": {"$ref": "#/$defs/bootstrap_hook"}, + "pre_tools": {"$ref": "#/$defs/bootstrap_hook"}, + "post-tools": {"$ref": "#/$defs/bootstrap_hook"}, + "post_tools": {"$ref": "#/$defs/bootstrap_hook"}, + "final": {"$ref": "#/$defs/bootstrap_hook"} + }, + "additionalProperties": false + }) +} + +fn bootstrap_macos_defaults() -> Schema { + json_schema!({ + "type": "object", + "description": "macOS user defaults to apply with `mise bootstrap macos defaults apply`, keyed by preferences domain (e.g. \"com.apple.dock\", \"NSGlobalDomain\")", + "additionalProperties": { + "type": "object", + "description": "defaults keys and their desired values for this domain", + "additionalProperties": { + "$ref": "#/$defs/macos_defaults_value", + "description": "desired value, written with the matching `defaults write` type; arrays and tables become property list arrays and dictionaries" + } + } + }) +} + +fn bootstrap_macos_dock() -> Schema { + json_schema!({ + "type": "object", + "description": "curated Dock preferences that compile into macOS defaults", + "properties": { + "apps": { + "type": "array", + "items": {"type": "string", "pattern": "^(?:/|~/(?!/)).+\\.[aA][pP][pP]/*$"}, + "uniqueItems": true, + "description": "Pinned application paths in order; absolute or ~/ paths ending in .app. An empty array removes application tiles." + }, + "autohide_delay": {"type": "number", "description": "delay before showing the Dock"}, + "autohide_time_modifier": { + "type": "number", + "description": "Dock autohide animation time modifier" + }, + "autohide": {"type": "boolean", "description": "hide and show the Dock automatically"}, + "orientation": { + "type": "string", + "enum": ["bottom", "left", "right"], + "description": "Dock screen edge" + }, + "tilesize": {"type": "integer", "description": "Dock icon size"}, + "magnification": {"type": "boolean", "description": "enable Dock magnification"}, + "largesize": {"type": "integer", "description": "Dock magnified icon size"}, + "show_recents": { + "type": "boolean", + "description": "show recent applications in the Dock" + }, + "mru_spaces": { + "type": "boolean", + "description": "automatically rearrange Spaces based on most recent use" + } + }, + "additionalProperties": false + }) +} + +fn bootstrap_macos_finder() -> Schema { + json_schema!({ + "type": "object", + "description": "curated Finder preferences that compile into macOS defaults", + "properties": { + "sort_folders_first": { + "type": "boolean", + "description": "keep folders first when sorting by name" + }, + "save_new_documents_to_cloud": { + "type": "boolean", + "description": "save new documents to iCloud by default (applies globally to application save dialogs)" + }, + "show_all_files": {"type": "boolean", "description": "show hidden files in Finder"}, + "show_pathbar": {"type": "boolean", "description": "show the Finder path bar"}, + "show_status_bar": {"type": "boolean", "description": "show the Finder status bar"}, + "show_extensions_warning": { + "type": "boolean", + "description": "show the warning when changing file extensions" + }, + "preferred_view_style": { + "type": "string", + "enum": ["icon", "list", "column", "gallery"], + "description": "Finder preferred view style" + } + }, + "additionalProperties": false + }) +} + +fn bootstrap_macos_keyboard() -> Schema { + json_schema!({ + "type": "object", + "description": "curated keyboard preferences that compile into macOS defaults", + "properties": { + "automatic_capitalization": { + "type": "boolean", + "description": "automatically capitalize words" + }, + "automatic_spelling_correction": { + "type": "boolean", + "description": "automatically correct spelling" + }, + "key_repeat": {"type": "integer", "description": "keyboard repeat interval"}, + "initial_key_repeat": { + "type": "integer", + "description": "delay before key repeat starts" + }, + "press_and_hold": { + "type": "boolean", + "description": "enable press-and-hold accent picker" + }, + "fn_state": { + "type": "boolean", + "description": "use F1, F2, etc. as standard function keys" + } + }, + "additionalProperties": false + }) +} + +fn bootstrap_macos_trackpad() -> Schema { + json_schema!({ + "type": "object", + "description": "curated trackpad preferences that compile into macOS defaults", + "properties": { + "tap_to_click": { + "type": "boolean", + "description": "enable tap to click for built-in and Bluetooth trackpads" + }, + "three_finger_drag": { + "type": "boolean", + "description": "enable three finger drag for built-in and Bluetooth trackpads" + } + }, + "additionalProperties": false + }) +} + +fn bootstrap_mise_shell_activate() -> Schema { + json_schema!({ + "type": "object", + "description": "declarative mise shell activation snippets, applied with `mise bootstrap mise-shell-activate apply`", + "properties": { + "bash": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "zsh": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "fish": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "bash_profile": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "bashrc": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "zshenv": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "zprofile": {"$ref": "#/$defs/bootstrap_shell_activation"}, + "zshrc": {"$ref": "#/$defs/bootstrap_shell_activation"} + }, + "additionalProperties": false + }) +} + +fn macos_defaults_value() -> Schema { + json_schema!({ + "description": "a property list value: boolean, integer, real, string, or an array or dictionary of these", + "anyOf": [ + {"type": "boolean"}, + {"type": "integer"}, + {"type": "number"}, + {"type": "string"}, + {"type": "array", "items": {"$ref": "#/$defs/macos_defaults_value"}}, + {"type": "object", "additionalProperties": {"$ref": "#/$defs/macos_defaults_value"}} + ] + }) +} diff --git a/crates/mise-config/src/daemons.rs b/crates/mise-config/src/daemons.rs index a4018e706a..ea2bd2c27d 100644 --- a/crates/mise-config/src/daemons.rs +++ b/crates/mise-config/src/daemons.rs @@ -1,5 +1,7 @@ //! `[daemons]`, `[daemons_settings]` and `[daemon_groups]`. +pub(crate) mod schema; + use schemars::JsonSchema; use serde::Deserialize; @@ -7,7 +9,7 @@ use serde::Deserialize; // to users. Notes for Rust readers go in plain comments. // A `[daemons]` entry. A definition stays a raw table that mise-daemons checks when it builds the -// daemon, so its schema is still written by hand in schema/mise.json. +// daemon, so its schema is written by hand in `schema.rs`. #[derive(Debug, Clone, Deserialize)] #[serde(untagged)] pub enum Declaration { diff --git a/crates/mise-config/src/daemons/schema.rs b/crates/mise-config/src/daemons/schema.rs new file mode 100644 index 0000000000..5f74c57232 --- /dev/null +++ b/crates/mise-config/src/daemons/schema.rs @@ -0,0 +1,261 @@ +//! The JSON schema for `[daemons]` and `[daemon_providers]`, written by hand. +//! +//! A daemon definition and a provider stay raw tables that mise-daemons checks when it builds the +//! daemon, so there is no Rust type to derive them from. Change them together with mise-daemons, +//! then run `mise run render:schema`. + +use schemars::{Schema, json_schema}; + +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { + vec![ + ("daemon_port", daemon_port()), + ("daemon_proxy", daemon_proxy()), + ("daemon_proxy_tls", daemon_proxy_tls()), + ("daemon_proxy_idle_timeout", daemon_proxy_idle_timeout()), + ("daemon_init", daemon_init()), + ] +} + +fn daemon_port() -> Schema { + json_schema!({ + "description": "A fixed port, or \"auto\" to derive one from the project root so the primary checkout keeps the base port and each linked git worktree gets its own.", + "oneOf": [ + {"type": "integer", "minimum": 1, "maximum": 65535}, + { + "type": "string", + "const": "auto", + "description": "Offset the preset's default port by this project root's worktree slot." + }, + { + "type": "object", + "required": ["auto"], + "properties": { + "auto": {"const": true, "description": "Enable per-worktree port allocation."}, + "base": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "description": "Port used by the primary checkout. Defaults to the preset's port; required for custom daemons." + }, + "stride": { + "type": "integer", + "minimum": 1, + "maximum": 65535, + "description": "Distance between consecutive worktree slots. Defaults to 1." + } + }, + "additionalProperties": false + } + ] + }) +} + +fn daemon_proxy() -> Schema { + json_schema!({ + "description": "Hostname label the reverse proxy routes to this daemon, false to opt out, or true to use the daemon's name. Only a daemon with a port is routed. The hostname is ... in a linked git worktree and .. in the primary checkout.", + "oneOf": [ + {"type": "string", "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", "maxLength": 63}, + { + "type": "boolean", + "description": "false opts out of the reverse proxy; true uses the daemon's name, which re-enables routing for a preset that opts out." + } + ] + }) +} + +fn daemon_proxy_tls() -> Schema { + json_schema!({ + "description": "Whether the proxy terminates TLS for this daemon or passes the connection through to it.", + "type": "string", + "enum": ["terminate", "passthrough"] + }) +} + +fn daemon_proxy_idle_timeout() -> Schema { + json_schema!({ + "description": "Stop this daemon after this long without proxy traffic when the proxy started it, for example \"30m\". false exempts it from idle shutdown even when pitchfork's proxy.idle_timeout is set; \"0\" does the same. Idle shutdown is off unless this or pitchfork's proxy.idle_timeout is set, and a daemon started with `mise daemons start` is never stopped for inactivity. Requires pitchfork 2.27.0.", + "oneOf": [{"type": "string", "pattern": "^\\s*[0-9]"}, {"type": "boolean", "enum": [false]}] + }) +} + +fn daemon_init() -> Schema { + json_schema!({ + "description": "idempotent setup commands run before the daemon's long-running process", + "oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}] + }) +} + +pub(crate) fn daemons() -> Schema { + json_schema!({ + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "description": "Version request for a preset matching the daemon name." + }, + { + "type": "object", + "required": ["run"], + "not": { + "anyOf": [ + {"required": ["preset"]}, + {"required": ["project"]}, + {"required": ["task"]}, + {"required": ["args"]}, + {"required": ["provider"]}, + {"required": ["resource"]} + ] + }, + "properties": { + "run": {"type": "string"}, + "init": {"$ref": "#/$defs/daemon_init"}, + "mise": {"type": "boolean"}, + "port": { + "oneOf": [ + {"$ref": "#/$defs/daemon_port"}, + { + "type": "object", + "description": "Pitchfork's structured port configuration, forwarded verbatim.", + "not": {"required": ["auto"]} + } + ] + }, + "proxy": {"$ref": "#/$defs/daemon_proxy"}, + "proxy_tls": {"$ref": "#/$defs/daemon_proxy_tls"}, + "proxy_idle_timeout": {"$ref": "#/$defs/daemon_proxy_idle_timeout"} + }, + "additionalProperties": true + }, + { + "type": "object", + "required": ["task"], + "not": { + "anyOf": [ + {"required": ["run"]}, + {"required": ["preset"]}, + {"required": ["project"]}, + {"required": ["provider"]}, + {"required": ["resource"]} + ] + }, + "properties": { + "task": { + "description": "mise task run as this daemon's long-running process", + "type": "string" + }, + "args": { + "description": "arguments passed to the task after `--`", + "type": "array", + "items": {"type": "string"} + }, + "init": {"$ref": "#/$defs/daemon_init"}, + "proxy": {"$ref": "#/$defs/daemon_proxy"}, + "proxy_tls": {"$ref": "#/$defs/daemon_proxy_tls"}, + "proxy_idle_timeout": {"$ref": "#/$defs/daemon_proxy_idle_timeout"} + }, + "additionalProperties": true + }, + { + "type": "object", + "required": ["preset", "version"], + "not": { + "anyOf": [ + {"required": ["project"]}, + {"required": ["task"]}, + {"required": ["args"]}, + {"required": ["provider"]}, + {"required": ["resource"]} + ] + }, + "properties": { + "data_dir": { + "type": "string", + "minLength": 1, + "description": "Persistent preset data directory, relative to the declaring project root or absolute. Defaults to mise state storage." + }, + "preset": { + "type": "string", + "enum": ["cockroachdb", "nats", "postgres", "redis", "spicedb"] + }, + "version": {"type": "string"}, + "port": {"$ref": "#/$defs/daemon_port"}, + "ports": { + "type": "object", + "description": "Overrides for the preset's additional named ports.", + "additionalProperties": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + } + }, + "options": {"type": "object"}, + "run": {"type": "string"}, + "init": {"$ref": "#/$defs/daemon_init"}, + "proxy": {"$ref": "#/$defs/daemon_proxy"}, + "proxy_tls": {"$ref": "#/$defs/daemon_proxy_tls"}, + "proxy_idle_timeout": {"$ref": "#/$defs/daemon_proxy_idle_timeout"} + }, + "additionalProperties": true + }, + { + "type": "object", + "description": "Daemon declared by another project, run under that project's namespace and root.", + "required": ["project"], + "properties": { + "project": { + "type": "string", + "description": "Directory of a project whose mise configuration declares the daemon. A relative path resolves against this project root." + }, + "name": { + "type": "string", + "description": "Daemon name inside the referenced project, when it differs from this key." + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": ["provider"], + "additionalProperties": false, + "properties": { + "provider": { + "type": "string", + "description": "Name of a globally defined shared server provider." + }, + "resource": { + "type": "string", + "pattern": "^[a-z][a-z0-9_]{0,62}$", + "description": "Database or NATS account shared by consumers choosing this name. Omit for a checkout-specific resource." + } + } + } + ] + } + }) +} + +pub(crate) fn daemon_providers() -> Schema { + json_schema!({ + "type": "object", + "propertyNames": {"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"}, + "additionalProperties": { + "type": "object", + "required": ["preset", "version"], + "additionalProperties": false, + "properties": { + "preset": {"enum": ["postgres", "cockroachdb", "nats"]}, + "version": {"type": "string"}, + "port": {"$ref": "#/$defs/daemon_port"}, + "ports": { + "type": "object", + "additionalProperties": {"type": "integer", "minimum": 1, "maximum": 65535} + }, + "options": {"type": "object"}, + "data_dir": {"type": "string", "minLength": 1}, + "tool": {"type": "string"} + } + } + }) +} diff --git a/crates/mise-config/src/deps.rs b/crates/mise-config/src/deps.rs index 63d734b533..fd43d47ce2 100644 --- a/crates/mise-config/src/deps.rs +++ b/crates/mise-config/src/deps.rs @@ -1,5 +1,7 @@ //! `[deps]`: the providers that bring a project's dependencies up to date. +pub(crate) mod schema; + use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; diff --git a/crates/mise-config/src/deps/schema.rs b/crates/mise-config/src/deps/schema.rs new file mode 100644 index 0000000000..271619ccde --- /dev/null +++ b/crates/mise-config/src/deps/schema.rs @@ -0,0 +1,83 @@ +//! The JSON schema for `[deps]`, written by hand next to its declaration types. +//! +//! [`DepsDecl`](super::DepsDecl) flattens every provider into one map, but the schema lists the +//! built-in providers by name so an editor can offer them. Change it together with the types in +//! `super` and mise's built-in providers, then run `mise run render:schema`. + +use schemars::{Schema, json_schema}; + +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { + vec![("deps_provider", deps_provider())] +} + +pub(crate) fn deps() -> Schema { + json_schema!({ + "type": "object", + "properties": { + "disable": { + "type": "array", + "items": {"type": "string"}, + "description": "Disable specific deps providers" + }, + "npm": {"$ref": "#/$defs/deps_provider"}, + "yarn": {"$ref": "#/$defs/deps_provider"}, + "pnpm": {"$ref": "#/$defs/deps_provider"}, + "bun": {"$ref": "#/$defs/deps_provider"}, + "aube": {"$ref": "#/$defs/deps_provider"}, + "deno": {"$ref": "#/$defs/deps_provider"}, + "go": {"$ref": "#/$defs/deps_provider"}, + "pip": {"$ref": "#/$defs/deps_provider"}, + "poetry": {"$ref": "#/$defs/deps_provider"}, + "uv": {"$ref": "#/$defs/deps_provider"}, + "bundler": {"$ref": "#/$defs/deps_provider"}, + "composer": {"$ref": "#/$defs/deps_provider"}, + "dart": {"$ref": "#/$defs/deps_provider"}, + "flutter": {"$ref": "#/$defs/deps_provider"}, + "git-submodule": {"$ref": "#/$defs/deps_provider"} + }, + "additionalProperties": {"$ref": "#/$defs/deps_provider"} + }) +} + +fn deps_provider() -> Schema { + json_schema!({ + "type": "object", + "properties": { + "auto": { + "type": "boolean", + "default": false, + "description": "Auto-run before `mise x` and `mise run`" + }, + "sources": { + "type": "array", + "items": {"type": "string"}, + "description": "Files/patterns to check for changes" + }, + "outputs": { + "type": "array", + "items": {"type": "string"}, + "description": "Files/directories that should be newer than sources" + }, + "run": {"type": "string", "description": "Command to run when stale"}, + "env": { + "type": "object", + "additionalProperties": {"type": "string"}, + "description": "Environment variables to set" + }, + "dir": {"type": "string", "description": "Working directory for the command"}, + "description": {"type": "string", "description": "Description shown in output"}, + "depends": { + "type": "array", + "items": {"type": "string"}, + "description": "Other deps providers that must complete before this one runs" + }, + "timeout": { + "type": "string", + "description": "Timeout for the run command (e.g., \"30s\", \"5m\", \"1h\")" + } + }, + "unevaluatedProperties": false, + "description": "Prepare provider configuration" + }) +} diff --git a/crates/mise-config/src/dotfiles.rs b/crates/mise-config/src/dotfiles.rs index e7f5ec1bfd..f7afadb159 100644 --- a/crates/mise-config/src/dotfiles.rs +++ b/crates/mise-config/src/dotfiles.rs @@ -1,3 +1,5 @@ +pub(crate) mod schema; + use indexmap::IndexMap; use serde::Deserialize; diff --git a/crates/mise-config/src/dotfiles/schema.rs b/crates/mise-config/src/dotfiles/schema.rs new file mode 100644 index 0000000000..8a99d98378 --- /dev/null +++ b/crates/mise-config/src/dotfiles/schema.rs @@ -0,0 +1,405 @@ +//! The JSON schema for `[dotfiles]` and `[dotfile_groups]`, written by hand. +//! +//! Each entry stays a TOML value until mise expands it, so a malformed one warns and is skipped +//! instead of failing the whole file, and there is no Rust type to derive the schema from. Change +//! it together with mise's dotfiles expansion, then run `mise run render:schema`. + +use schemars::{Schema, json_schema}; + +pub(crate) fn dotfiles() -> Schema { + json_schema!({ + "type": "object", + "additionalProperties": { + "oneOf": [ + {"type": "string", "description": "source path for a whole-file dotfile entry"}, + entry() + ] + } + }) +} + +/// A table entry: a whole file, or an edit to part of one. +fn entry() -> Schema { + json_schema!({ + "type": "object", + "description": "whole-file dotfile entry or file edit entry", + "properties": { + "encrypt": { + "type": "boolean", + "default": false, + "description": "encrypt tracked file contents before storing them in Git; the live file remains plaintext" + }, + "allow_plaintext": { + "type": "boolean", + "default": false, + "description": "allow this directly tracked credential-named file to be saved in plaintext history and any connected origin" + }, + "source": {"type": "string", "description": "source file or directory"}, + "content": { + "type": "string", + "description": "literal inline file content; alternative to source" + }, + "mode": { + "type": "string", + "description": "how to apply a whole-file source to the target; omitted uses dotfiles.default_mode. \"absent\" removes a file or symlink at the target and takes no source. \"track\" saves the file's history where it is, and \"track-local\" saves it in this machine's local-only history, which is never shared", + "anyOf": [ + { + "enum": [ + "symlink", + "symlink-each", + "copy", + "template", + "absent", + "track", + "track-local" + ] + }, + {"type": "string"} + ] + }, + "exclude": { + "type": "array", + "description": "glob patterns to skip when the mode walks a directory (symlink-each, copy with a directory source, or a tracked directory, where they are relative to the tracked path); a pattern without \"/\" matches any path component, one with \"/\" is anchored to the root", + "items": {"type": "string"} + }, + "include": { + "type": "array", + "description": "history only (mode = \"track\"): the only paths of the tracked directory that are captured, relative to it and matched like \"exclude\"; without it the whole tree is captured with the builtin credential filtering, and an explicit \"exclude\" still wins. A selected file is captured even when it looks like a credential store, in plaintext unless the entry sets encrypt", + "items": {"type": "string"} + }, + "manifest": { + "type": "string", + "description": "source manifest used to select managed files from a directory", + "enum": ["git"] + }, + "remove_empty": { + "type": "boolean", + "default": false, + "description": "template mode only: remove the target when the template renders empty or whitespace-only; a target that changed since mise last wrote it needs --force" + }, + "dot_prefix": { + "type": "boolean", + "default": false, + "description": "symlink-each and directory copy only: deploy each source path component named \"dot-\" as \".\", like GNU Stow's --dotfiles; exclude still matches source names" + }, + "relative": { + "type": "boolean", + "description": "symlink and symlink-each only: link by a path relative to the link's directory instead of an absolute one; overrides the dotfiles.relative_symlinks setting (ignored on Windows)" + }, + "permissions": { + "type": "string", + "description": "octal permissions for the target, such as \"0600\"; overrides what copy, template, and inline content entries set, and on its own manages only the permissions of an existing target (Unix only)", + "pattern": "^(0o)?[0-7]{1,4}$" + }, + "block": { + "type": "string", + "description": "block content, maintained between marker comments" + }, + "template": { + "type": "string", + "description": "template engine to render block content with; currently only \"tera\"", + "anyOf": [{"enum": ["tera"]}, {"type": "string"}] + }, + "line": {"type": "string", "description": "exact line to ensure exists"}, + "position": { + "type": "string", + "default": "append", + "description": "where to insert a missing line", + "anyOf": [{"enum": ["append", "prepend"]}, {"type": "string"}] + }, + "comment": { + "type": "string", + "description": "comment prefix for edit marker lines; inferred from the file extension when omitted" + }, + "merge": { + "oneOf": [{"type": "boolean"}, {"const": "missing"}], + "description": "edit entry: true sets the keys of source (default: the target's path under dotfiles.root) on a JSON, TOML, or YAML target and leaves the target's other keys to the application that also writes it; \"missing\" sets only the keys the target has no value for; the format comes from the target's extension" + }, + "variants": { + "type": "array", + "description": "OS/profile variants: history streams for track mode, or optional destinations for deployment modes", + "items": variant() + } + }, + "allOf": [ + { + "if": {"required": ["merge"], "properties": {"merge": {"enum": [true, "missing"]}}}, + "then": { + "properties": { + "mode": false, + "content": false, + "permissions": false, + "exclude": false, + "include": false, + "manifest": false, + "autosave": false, + "encrypt": false, + "allow_plaintext": false, + "variants": false, + "enabled": false, + "remove_empty": false, + "relative": false, + "dot_prefix": false, + "group": false, + "block": false, + "line": false, + "position": false, + "comment": false + } + } + }, + { + "anyOf": [ + {"properties": {"relative": {"const": false}}}, + { + "required": ["mode"], + "properties": {"mode": {"enum": ["symlink", "symlink-each"]}} + }, + { + "properties": { + "mode": false, + "content": false, + "permissions": false, + "block": false, + "line": false + } + } + ] + }, + { + "anyOf": [ + {"properties": {"remove_empty": {"const": false}}}, + { + "properties": { + "mode": {"const": "template"}, + "content": false, + "block": false, + "line": false + }, + "anyOf": [ + {"required": ["source"]}, + {"required": ["mode"]}, + {"properties": {"permissions": false}} + ] + } + ], + "if": {"required": ["mode"], "properties": {"mode": {"const": "absent"}}}, + "then": { + "properties": { + "source": false, + "content": false, + "exclude": false, + "manifest": false, + "permissions": false, + "remove_empty": false, + "dot_prefix": {"const": false}, + "encrypt": {"const": false}, + "block": false, + "line": false, + "position": false, + "template": false, + "comment": false + } + } + }, + { + "oneOf": [ + { + "required": ["manifest"], + "properties": { + "source": {}, + "content": false, + "permissions": false, + "mode": {"enum": ["copy", "symlink-each"]}, + "exclude": {}, + "manifest": {}, + "block": false, + "line": false, + "template": false, + "comment": false + } + }, + {"properties": {"manifest": false}} + ] + }, + { + "if": {"required": ["dot_prefix"], "properties": {"dot_prefix": {"const": true}}}, + "then": { + "properties": { + "content": false, + "mode": {"enum": ["copy", "symlink-each"]}, + "block": false, + "line": false, + "template": false, + "comment": false + }, + "anyOf": [ + {"required": ["source"]}, + {"required": ["mode"]}, + {"properties": {"permissions": false}} + ] + } + }, + { + "oneOf": [ + { + "required": ["permissions"], + "properties": { + "mode": {"enum": ["copy", "template"]}, + "block": false, + "template": false, + "comment": false + }, + "anyOf": [ + {"required": ["source"]}, + {"required": ["content"]}, + {"required": ["mode"]}, + {"properties": {"exclude": false, "manifest": false, "encrypt": false}} + ] + }, + {"properties": {"permissions": false}} + ] + }, + { + "oneOf": [ + { + "required": ["content"], + "properties": { + "source": false, + "manifest": false, + "mode": false, + "exclude": false, + "block": false, + "line": false, + "position": false, + "template": false, + "comment": false + } + }, + { + "required": ["line"], + "properties": { + "content": false, + "permissions": false, + "mode": false, + "exclude": false, + "source": false, + "block": false, + "template": false, + "comment": false + } + }, + {"properties": {"content": false, "line": false, "position": false}} + ] + }, + { + "oneOf": [ + { + "required": ["include", "mode"], + "properties": {"mode": {"enum": ["track", "track-local"]}} + }, + {"properties": {"include": false}} + ] + }, + { + "oneOf": [ + { + "required": ["allow_plaintext", "mode"], + "properties": {"mode": {"enum": ["track", "track-local"]}} + }, + {"properties": {"allow_plaintext": false}} + ] + } + ], + "anyOf": [ + {"properties": {"encrypt": {"const": false}}}, + {"properties": {"content": false, "block": false, "line": false, "template": false}} + ] + }) +} + +/// One OS or profile variant of an entry. +fn variant() -> Schema { + json_schema!({ + "type": "object", + "additionalProperties": false, + "properties": { + "os": {"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]}, + "profile": { + "type": "string", + "description": "mise environment selected with -E or MISE_ENV" + }, + "default": {"type": "boolean"}, + "machine": { + "type": "boolean", + "description": "track mode only: a separate stream on every machine, never applied on another machine; must be the only variant and cannot be combined with encrypt" + }, + "target": { + "type": "string", + "description": "deployment destination override; requires either an explicit source or a safe relative entry key when every variant has a target, and is not supported in track mode" + } + } + }) +} + +pub(crate) fn dotfile_groups() -> Schema { + json_schema!({ + "type": "object", + "description": "named directory trees of dotfiles, applied with `mise dotfiles apply` or `mise bootstrap` while [bootstrap] dotfile_groups selects them (or is unset)", + "propertyNames": {"pattern": "^[A-Za-z0-9_.-]+$"}, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["root"], + "properties": { + "root": { + "type": "string", + "description": "the directory tree the group deploys; a relative path resolves against dotfiles.root" + }, + "target": { + "type": "string", + "description": "target directory, absolute or starting with ~/", + "default": "~" + }, + "mode": { + "type": "string", + "description": "how the tree is deployed", + "enum": ["symlink-each", "copy", "symlink"], + "default": "symlink-each" + }, + "exclude": { + "type": "array", + "description": "glob patterns of source paths to skip, as for [dotfiles] exclude", + "items": {"type": "string"} + }, + "dot_prefix": { + "type": "boolean", + "default": false, + "description": "deploy each source path component named \"dot-\" as \".\", like GNU Stow's --dotfiles" + }, + "manifest": { + "type": "string", + "description": "source manifest used to select managed files from the tree", + "enum": ["git"] + }, + "relative": { + "type": "boolean", + "description": "link by a path relative to the link's directory; overrides the dotfiles.relative_symlinks setting (ignored on Windows)" + }, + "entries": { + "type": "object", + "description": "whole-file entries for parts of the tree, keyed by target path inside the group's target and written like [dotfiles] entries; each is cut out of the walk. A missing source is found under the root at the entry's path inside the target, and a relative source starts at the root", + "additionalProperties": { + "oneOf": [ + { + "type": "string", + "description": "source path, relative to the group root" + }, + {"type": "object", "description": "whole-file entry, as in [dotfiles]"} + ] + } + } + } + } + }) +} diff --git a/crates/mise-config/src/env.rs b/crates/mise-config/src/env.rs index 38eef27b8a..5cf020ea96 100644 --- a/crates/mise-config/src/env.rs +++ b/crates/mise-config/src/env.rs @@ -12,7 +12,7 @@ use serde::de::Visitor; use serde::{Deserialize, Deserializer, de}; mod schema; -pub(crate) use schema::fragments as schema_fragments; +pub(crate) use schema::defs as schema_defs; static DISPLAY_PATH: OnceLock String> = OnceLock::new(); diff --git a/crates/mise-config/src/env/schema.rs b/crates/mise-config/src/env/schema.rs index 8cdd07084c..d61c457a1d 100644 --- a/crates/mise-config/src/env/schema.rs +++ b/crates/mise-config/src/env/schema.rs @@ -7,24 +7,24 @@ use schemars::{Schema, json_schema}; -/// Each definition, keyed by the JSON pointer it replaces in `schema/mise.json`. -pub(crate) fn fragments() -> Vec<(&'static str, Schema)> { +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { vec![ - ("/$defs/env", env()), - ("/$defs/vars", vars()), - ("/$defs/env_directive", env_directive()), - ("/$defs/env_value", env_value()), - ("/$defs/env_path", env_path()), - ("/$defs/env_paths", env_paths()), - ("/$defs/env_tools", env_tools()), - ("/$defs/env_file_expand", env_file_expand()), - ("/$defs/env_file_find_up", env_file_find_up()), - ("/$defs/env_redact", env_redact()), - ("/$defs/env_required", env_required()), - ("/$defs/env_default", env_default()), - ("/$defs/env_age", env_age()), - ("/$defs/env_age_value", env_age_value()), - ("/$defs/env_age_format", env_age_format()), + ("env", env()), + ("vars", vars()), + ("env_directive", env_directive()), + ("env_value", env_value()), + ("env_path", env_path()), + ("env_paths", env_paths()), + ("env_tools", env_tools()), + ("env_file_expand", env_file_expand()), + ("env_file_find_up", env_file_find_up()), + ("env_redact", env_redact()), + ("env_required", env_required()), + ("env_default", env_default()), + ("env_age", env_age()), + ("env_age_value", env_age_value()), + ("env_age_format", env_age_format()), ] } diff --git a/crates/mise-config/src/hooks.rs b/crates/mise-config/src/hooks.rs new file mode 100644 index 0000000000..50f2240034 --- /dev/null +++ b/crates/mise-config/src/hooks.rs @@ -0,0 +1,125 @@ +//! `[hooks]`: commands, scripts and tasks run on `cd`, on entering or leaving a project, and +//! around tool installs. + +mod schema; +pub(crate) use schema::defs as schema_defs; + +use serde::Deserialize; + +/// The event a hook runs on. +#[derive( + Debug, + Clone, + Copy, + serde::Serialize, + Deserialize, + strum::Display, + Ord, + PartialOrd, + Eq, + PartialEq, + Hash, +)] +#[serde(rename_all = "lowercase")] +pub enum Hooks { + Enter, + Leave, + Cd, + Preinstall, + Postinstall, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum HookScripts { + One(String), + Many(Vec), +} + +impl HookScripts { + pub fn into_script(self) -> String { + match self { + Self::One(script) => script, + Self::Many(scripts) => scripts.join("\n"), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum HookDef { + /// Array of hook definitions: `enter = ["echo hello", { task = "setup" }]` + Array(Vec), + One(HookDefItem), +} + +impl HookDef { + /// This definition followed by the hooks of `later`, so both run. + pub fn then(self, later: HookDef) -> HookDef { + let mut all = self.into_items(); + all.extend(later.into_items()); + HookDef::Array(all) + } + + pub fn into_items(self) -> Vec { + match self { + HookDef::Array(items) => items, + HookDef::One(item) => vec![item], + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged, deny_unknown_fields)] +pub enum HookDefItem { + /// Simple run string: `enter = "echo hello"` + RunString(String), + /// Table with run: `enter = { run = "echo hello" }` + Run(HookRunTable), + /// Table with script and optional shell: `enter = { script = "echo hello", shell = "bash" }` + ScriptTable { + script: HookScripts, + shell: Option, + }, + /// Table with scripts and optional shell: `enter = { scripts = ["echo hello"], shell = "bash" }` + ScriptsTable { + scripts: Vec, + shell: Option, + }, + /// Task reference: `enter = { task = "setup" }` + TaskRef { task: String }, +} + +#[derive(Debug, Clone)] +pub struct HookRunTable { + pub run: Option, + pub run_windows: Option, + pub shell: Option, +} + +impl<'de> Deserialize<'de> for HookRunTable { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(deny_unknown_fields)] + struct Helper { + run: Option, + run_windows: Option, + shell: Option, + } + + let helper = ::deserialize(deserializer)?; + if helper.run.is_none() && helper.run_windows.is_none() { + return Err(serde::de::Error::custom( + "hook run table must define `run` or `run_windows`", + )); + } + Ok(Self { + run: helper.run, + run_windows: helper.run_windows, + shell: helper.shell, + }) + } +} diff --git a/crates/mise-config/src/hooks/schema.rs b/crates/mise-config/src/hooks/schema.rs new file mode 100644 index 0000000000..94232109b9 --- /dev/null +++ b/crates/mise-config/src/hooks/schema.rs @@ -0,0 +1,256 @@ +//! The JSON schema for `[hooks]`, written by hand next to its declaration types. +//! +//! What an untagged [`HookDefItem`](super::HookDefItem) means depends on the event: a `script` +//! with a `shell` is evaluated in the active shell for `enter`, `leave` and `cd`, while the install +//! hooks ignore the `shell`. A derive cannot tell those apart. Change these together with the types +//! in `super` and mise's `hooks` module, then run `mise run render:schema`. + +use schemars::{Schema, json_schema}; + +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { + vec![ + ("hook_run_string", hook_run_string()), + ("hook_run_table", hook_run_table()), + ("hook_script_value", hook_script_value()), + ("hook_scripts_value", hook_scripts_value()), + ( + "hook_current_shell_script_table", + hook_current_shell_script_table(), + ), + ( + "hook_current_shell_scripts_table", + hook_current_shell_scripts_table(), + ), + ("hook_spawned_script_table", hook_spawned_script_table()), + ("hook_spawned_scripts_table", hook_spawned_scripts_table()), + ("hook_install_script_table", hook_install_script_table()), + ("hook_install_scripts_table", hook_install_scripts_table()), + ("hook_task_ref", hook_task_ref()), + ("hook_definition_item", hook_definition_item()), + ("hook_definition", hook_definition()), + ( + "hook_install_definition_item", + hook_install_definition_item(), + ), + ("hook_install_definition", hook_install_definition()), + ("hooks", hooks()), + ] +} + +fn hook_run_string() -> Schema { + json_schema!({"description": "inline hook command to run", "type": "string"}) +} + +fn hook_run_table() -> Schema { + json_schema!({ + "unevaluatedProperties": false, + "properties": { + "run": {"description": "inline hook command to run", "type": "string"}, + "run_windows": { + "description": "inline hook command to run on Windows", + "type": "string" + }, + "shell": { + "description": "inline shell command to use for run hooks, such as \"bash -c\"", + "type": "string" + } + }, + "anyOf": [{"required": ["run"]}, {"required": ["run_windows"]}], + "type": "object" + }) +} + +fn hook_script_value() -> Schema { + json_schema!({"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]}) +} + +fn hook_scripts_value() -> Schema { + json_schema!({"type": "array", "items": {"type": "string"}}) +} + +fn hook_current_shell_script_table() -> Schema { + json_schema!({ + "unevaluatedProperties": false, + "properties": { + "script": { + "description": "script or scripts to evaluate in the active shell for enter/leave/cd hooks", + "$ref": "#/$defs/hook_script_value" + }, + "shell": { + "description": "active shell name for current-shell hooks, such as \"bash\"", + "type": "string" + } + }, + "required": ["script", "shell"], + "type": "object" + }) +} + +fn hook_current_shell_scripts_table() -> Schema { + json_schema!({ + "unevaluatedProperties": false, + "properties": { + "scripts": { + "description": "scripts to evaluate in the active shell for enter/leave/cd hooks", + "$ref": "#/$defs/hook_scripts_value" + }, + "shell": { + "description": "active shell name for current-shell hooks, such as \"bash\"", + "type": "string" + } + }, + "required": ["scripts", "shell"], + "type": "object" + }) +} + +fn hook_spawned_script_table() -> Schema { + json_schema!({ + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "script": { + "deprecated": true, + "description": "deprecated alias for run when an enter/leave/cd hook has no shell", + "$ref": "#/$defs/hook_script_value" + } + }, + "required": ["script"], + "type": "object" + }) +} + +fn hook_spawned_scripts_table() -> Schema { + json_schema!({ + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "scripts": { + "deprecated": true, + "description": "deprecated alias for run when an enter/leave/cd hook has no shell", + "$ref": "#/$defs/hook_scripts_value" + } + }, + "required": ["scripts"], + "type": "object" + }) +} + +fn hook_install_script_table() -> Schema { + json_schema!({ + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "script": { + "deprecated": true, + "description": "deprecated alias for run in preinstall/postinstall hooks; executes in a subprocess", + "$ref": "#/$defs/hook_script_value" + }, + "shell": { + "deprecated": true, + "description": "ignored for preinstall/postinstall hooks that use script; use a run hook to select an inline shell", + "type": "string" + } + }, + "required": ["script"], + "type": "object" + }) +} + +fn hook_install_scripts_table() -> Schema { + json_schema!({ + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "scripts": { + "deprecated": true, + "description": "deprecated alias for run in preinstall/postinstall hooks; joins the scripts and executes them in a subprocess", + "$ref": "#/$defs/hook_scripts_value" + }, + "shell": { + "deprecated": true, + "description": "ignored for preinstall/postinstall hooks that use scripts; use a run hook to select an inline shell", + "type": "string" + } + }, + "required": ["scripts"], + "type": "object" + }) +} + +fn hook_task_ref() -> Schema { + json_schema!({ + "unevaluatedProperties": false, + "properties": {"task": {"description": "mise task to run", "type": "string"}}, + "required": ["task"], + "type": "object" + }) +} + +fn hook_definition_item() -> Schema { + json_schema!({ + "oneOf": [ + {"$ref": "#/$defs/hook_run_string"}, + {"$ref": "#/$defs/hook_run_table"}, + {"$ref": "#/$defs/hook_current_shell_script_table"}, + {"$ref": "#/$defs/hook_current_shell_scripts_table"}, + {"$ref": "#/$defs/hook_spawned_script_table"}, + {"$ref": "#/$defs/hook_spawned_scripts_table"}, + {"$ref": "#/$defs/hook_task_ref"} + ] + }) +} + +fn hook_definition() -> Schema { + json_schema!({ + "oneOf": [ + {"$ref": "#/$defs/hook_definition_item"}, + { + "description": "scripts/tasks to run", + "items": {"$ref": "#/$defs/hook_definition_item"}, + "type": "array" + } + ] + }) +} + +fn hook_install_definition_item() -> Schema { + json_schema!({ + "oneOf": [ + {"$ref": "#/$defs/hook_run_string"}, + {"$ref": "#/$defs/hook_run_table"}, + {"$ref": "#/$defs/hook_install_script_table"}, + {"$ref": "#/$defs/hook_install_scripts_table"}, + {"$ref": "#/$defs/hook_task_ref"} + ] + }) +} + +fn hook_install_definition() -> Schema { + json_schema!({ + "oneOf": [ + {"$ref": "#/$defs/hook_install_definition_item"}, + { + "description": "install hooks to run", + "items": {"$ref": "#/$defs/hook_install_definition_item"}, + "type": "array" + } + ] + }) +} + +fn hooks() -> Schema { + json_schema!({ + "description": "hooks to run", + "type": "object", + "properties": { + "enter": {"$ref": "#/$defs/hook_definition"}, + "leave": {"$ref": "#/$defs/hook_definition"}, + "cd": {"$ref": "#/$defs/hook_definition"}, + "preinstall": {"$ref": "#/$defs/hook_install_definition"}, + "postinstall": {"$ref": "#/$defs/hook_install_definition"} + }, + "additionalProperties": false + }) +} diff --git a/crates/mise-config/src/lib.rs b/crates/mise-config/src/lib.rs index 12621672f1..f567a557c0 100644 --- a/crates/mise-config/src/lib.rs +++ b/crates/mise-config/src/lib.rs @@ -1,8 +1,10 @@ //! The `mise.toml` declaration types: plain data parsed from the file, with no templating, //! settings, or filesystem access. So far this covers `[bootstrap]`, the `[daemons]` sections, -//! `[doctor]`, `[dotfiles]`, `[env]`, `[dotfile_groups]`, `[history]`, `[oci]`, `[tool_config]`, -//! `[vars]`, `[watch_files]`, `[wrappers]`, `[deps]`, `[task_config]`, `[tool_alias]`, -//! `[monorepo]`, `min_version`, `[tools]`, and the `[tasks]` and `[task_templates]` declarations. +//! `[doctor]`, `[dotfiles]`, `[env]`, `[dotfile_groups]`, `[history]`, `[hooks]`, `[oci]`, +//! `[tool_config]`, `[vars]`, `[watch_files]`, `[wrappers]`, `[deps]`, `[task_config]`, +//! `[tool_alias]`, `[monorepo]`, `min_version`, `[tools]`, and the `[tasks]` and +//! `[task_templates]` declarations. [`mise_toml::MiseTomlDecl`] names every top-level key, and +//! [`schema`] generates mise's JSON schemas from these types. //! //! mise's config layer stores these as parsed, and the system modules interpret them. Keeping //! the plain data here, below both, is what lets those two layers be separate crates. @@ -22,10 +24,12 @@ pub mod env; pub mod env_value; pub mod firewall; pub mod history; +pub mod hooks; pub mod launchd; pub mod login_items; pub mod managed_files; pub mod min_version; +pub mod mise_toml; pub mod monorepo; pub mod oci; pub mod packages; diff --git a/crates/mise-config/src/min_version.rs b/crates/mise-config/src/min_version.rs index b245ea5e88..fe5c44c775 100644 --- a/crates/mise-config/src/min_version.rs +++ b/crates/mise-config/src/min_version.rs @@ -138,8 +138,7 @@ impl JsonSchema for MinVersionSpec { } fn json_schema(_: &mut SchemaGenerator) -> Schema { - json_schema!({ - "description": "minimum version of mise required to use this config", + crate::schema::hand_written(json_schema!({ "oneOf": [ {"pattern": "^\\d+\\.\\d+\\.\\d+$", "type": "string"}, { @@ -159,6 +158,6 @@ impl JsonSchema for MinVersionSpec { } } ] - }) + })) } } diff --git a/crates/mise-config/src/mise_toml.rs b/crates/mise-config/src/mise_toml.rs new file mode 100644 index 0000000000..aee3c3f4f5 --- /dev/null +++ b/crates/mise-config/src/mise_toml.rs @@ -0,0 +1,245 @@ +//! The top level of a `mise.toml`: every key mise reads from it. +//! +//! `schema/mise.json` is generated from [`MiseTomlDecl`]. mise still parses a file into its own +//! `MiseToml`, and a test in mise checks that the two accept the same keys; parsing through this +//! type is the next step. + +use indexmap::IndexMap; +use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema}; + +use crate::alias::Alias; +use crate::bootstrap::BootstrapTomlConfig; +use crate::daemons::{DaemonSettings, GroupDeclaration}; +use crate::history::HistoryTomlConfig; +use crate::min_version::MinVersionSpec; +use crate::oci::OciConfig; +use crate::schema::{hand_written, refs}; +use crate::wrappers::CommandWrapper; + +// The doc comments on these fields are their descriptions in schema/mise.json, which editors show +// to users. Notes for Rust readers go in plain comments. +// +// A field whose schema is a `$ref`, or written by hand, takes it from `schema_with`; until mise +// parses through this struct, such a field's type is just the value it holds. Every field is +// optional, and the struct-level `default` keeps any of them from being required. + +/// config file for mise version manager (mise.toml) +#[derive(Debug, Default, JsonSchema)] +#[schemars(default)] +pub struct MiseTomlDecl { + /// custom shorthands + #[schemars(schema_with = "alias_map", extend("deprecated" = true))] + pub alias: Option>, + /// Tool version aliases + #[schemars(schema_with = "alias_map")] + pub tool_alias: Option>, + /// shell aliases + #[schemars(schema_with = "shell_alias")] + pub shell_alias: Option>, + /// commands that intercept a binary name before delegating to the active toolset + pub wrappers: Option>, + /// environment variables to set + #[schemars(schema_with = "env")] + pub env: Option, + /// config files merged into this one, ranking just below it: a local path (relative to this file), git:://.toml?ref= or oci::/[:tag|@sha256:] (the artifact holds a mise.toml). Paranoid mode requires a commit sha or digest for remote includes and rejects local ones; otherwise a branch or tag is refreshed after fetch_remote_versions_cache + #[schemars(schema_with = "include")] + pub include: Option>, + /// dotenv file(s) to load. Deprecated; use env._.file instead. This will be removed in mise 2027.4.0. + #[schemars(schema_with = "dotenv_files", extend("deprecated" = true))] + pub env_file: Option>, + /// dotenv file(s) to load. Deprecated; use env._.file instead. This will be removed in mise 2027.4.0. + #[schemars(schema_with = "dotenv_files", extend("deprecated" = true))] + pub dotenv: Option>, + /// PATH entries to add. Deprecated; use env._.path instead. This will be removed in mise 2027.4.0. + #[schemars(schema_with = "path_entries", extend("deprecated" = true))] + pub env_path: Option>, + /// marks this config as a monorepo root for task path syntax + #[schemars(schema_with = "refs::monorepo_root")] + pub monorepo_root: Option, + /// deprecated alias of monorepo_root + #[schemars(extend("deprecated" = true))] + pub experimental_monorepo_root: Option, + /// configuration for monorepo task discovery + #[schemars(schema_with = "refs::monorepo")] + pub monorepo: Option, + /// minimum version of mise required to use this config + pub min_version: Option, + /// [experimental] Sources for mise secrets: values resolved only when mise starts a task or `mise x` command that was granted them. Project config only. + #[schemars(schema_with = "secrets")] + pub secrets: Option, + /// env or vars keys to redact from logs + pub redactions: Option>, + /// plugins to use + #[schemars(schema_with = "plugins")] + pub plugins: Option>, + /// configure deps providers + #[schemars(schema_with = "deps")] + pub deps: Option, + pub oci: Option, + /// mise settings + #[schemars(schema_with = "settings")] + pub settings: Option, + /// dotfiles applied with `mise dotfiles apply` or `mise bootstrap`, keyed by target path or a logical name when every variant overrides the target; an omitted source for the latter resolves the relative key under dotfiles.root + #[schemars(schema_with = "dotfiles")] + pub dotfiles: Option, + // The schema has no description for `[dotfile_groups]` yet. + #[schemars(schema_with = "dotfile_groups")] + pub dotfile_groups: Option, + pub bootstrap: Option, + pub history: Option, + /// configuration for task execution and management + #[schemars(schema_with = "refs::task_config")] + pub task_config: Option, + /// policy for tools declared by this config root + #[schemars(schema_with = "refs::tool_config")] + pub tool_config: Option, + /// task runner tasks + #[schemars(schema_with = "task_map")] + pub tasks: Option, + /// task templates that can be extended by tasks via extends + #[schemars(schema_with = "task_template_map")] + pub task_templates: Option, + /// dev tools to use + #[schemars(schema_with = "tools")] + pub tools: Option, + /// hooks to run on events like cd, enter, leave + #[schemars(schema_with = "refs::hooks")] + pub hooks: Option, + /// variables to set for use in config templates + #[schemars(schema_with = "refs::vars")] + pub vars: Option, + /// files to watch for changes and run scripts when modified + #[schemars(schema_with = "refs::watch_files")] + pub watch_files: Option, + // `[_]` holds the user's own data, which mise never reads. + #[schemars(rename = "_", schema_with = "custom")] + pub custom: Option, + #[schemars(schema_with = "refs::doctor")] + pub doctor: Option, + /// Experimental project daemons: custom pitchfork commands or managed presets. + #[schemars(schema_with = "daemons")] + pub daemons: Option, + /// Experimental shared servers owned by the user. Only allowed in global configuration. + #[schemars(schema_with = "daemon_providers")] + pub daemon_providers: Option, + pub daemons_settings: Option, + /// Experimental named groups of project daemons. Members are daemon names or other group names declared in the same project. + pub daemon_groups: Option>, +} + +/// The `$defs` entries for top-level keys that other sections do not share. +pub(crate) fn schema_defs() -> Vec<(&'static str, Schema)> { + vec![( + "monorepo_root", + json_schema!({ + "description": "Marks this config as a monorepo root, enabling target path syntax for tasks. When enabled, tasks can be referenced with paths like //projects/frontend:build", + "type": "boolean" + }), + )] +} + +fn alias_map(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "object", "additionalProperties": {"$ref": "#/$defs/tool_alias_entry"}}) +} + +fn task_map(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "object", "additionalProperties": {"$ref": "#/$defs/task"}}) +} + +fn task_template_map(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "object", "additionalProperties": {"$ref": "#/$defs/task_template"}}) +} + +fn settings(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "object", "$ref": "#/$defs/settings"}) +} + +fn env(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "oneOf": [{"$ref": "#/$defs/env"}, {"type": "array", "items": {"$ref": "#/$defs/env"}}] + }) +} + +fn shell_alias(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "object", + "additionalProperties": {"description": "command to run", "type": "string"} + }) +} + +fn plugins(_: &mut SchemaGenerator) -> Schema { + json_schema!({ + "type": "object", + "additionalProperties": {"description": "url to plugin repository", "type": "string"} + }) +} + +fn custom(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "object", "additionalProperties": true}) +} + +fn include(_: &mut SchemaGenerator) -> Schema { + one_or_many("a config fragment", "config fragments") +} + +fn dotenv_files(_: &mut SchemaGenerator) -> Schema { + one_or_many("dotenv file to load", "dotenv files to load") +} + +fn path_entries(_: &mut SchemaGenerator) -> Schema { + one_or_many("PATH entry to add", "PATH entries to add") +} + +/// A string, or a list of them, as `mise_config::de::deserialize_arr` accepts. +fn one_or_many(one: &str, many: &str) -> Schema { + json_schema!({ + "oneOf": [ + {"description": one, "type": "string"}, + {"description": many, "type": "array", "items": {"type": "string"}} + ] + }) +} + +// Raw on purpose in mise: a malformed `[secrets]` must not fail the whole file. +fn secrets(_: &mut SchemaGenerator) -> Schema { + hand_written(json_schema!({ + "type": "object", + "properties": { + "fnox": { + "description": "Use fnox (fnox.toml discovered from this config's directory). https://mise.jdx.dev/environments/secrets/fnox.html", + "type": "object", + "additionalProperties": false, + "properties": { + "profile": { + "type": "string", + "description": "fnox profile (fnox -P); defaults to FNOX_PROFILE, then \"default\"" + } + } + } + } + })) +} + +fn tools(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::tools::schema::tools()) +} + +fn deps(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::deps::schema::deps()) +} + +fn dotfiles(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::dotfiles::schema::dotfiles()) +} + +fn dotfile_groups(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::dotfiles::schema::dotfile_groups()) +} + +fn daemons(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::daemons::schema::daemons()) +} + +fn daemon_providers(_: &mut SchemaGenerator) -> Schema { + hand_written(crate::daemons::schema::daemon_providers()) +} diff --git a/crates/mise-config/src/schema.rs b/crates/mise-config/src/schema.rs index ff5901c6c5..03645e7bc3 100644 --- a/crates/mise-config/src/schema.rs +++ b/crates/mise-config/src/schema.rs @@ -1,121 +1,184 @@ -//! The JSON schema for the `mise.toml` sections whose types live in this crate. +//! The JSON schemas mise publishes: `schema/mise.json`, `schema/mise-task.json` and +//! `schema/miserc.json`, all generated by `mise run render:schema` through the `render-schema` +//! example, and checked by the test below. //! -//! `schema/mise.json` is still partly written by hand. `mise run render:schema` replaces each -//! fragment listed here at its JSON pointer, the way it already fills in `$defs.settings` from -//! `settings.toml`. A section moves out of the hand-written part when its types gain a -//! `JsonSchema` derive here, and the test below fails while the committed file is stale. -//! -//! A field whose value stays raw TOML (so newer shapes warn instead of failing) has no Rust type -//! to describe it. Its schema stays hand-written under `$defs`, and the generated tree points at -//! it through [`refs`]. +//! `mise.json`'s top-level keys come from [`MiseTomlDecl`] and the `[settings]` from mise's +//! `settings.toml`. A section whose types derive `JsonSchema` is generated from them; one whose +//! parser accepts shapes a derive cannot describe has its schema written by hand next to that +//! parser, as does a field whose value stays raw TOML (so newer shapes warn instead of failing). +//! The generated tree points at shared definitions through [`refs`]. + +mod settings; +mod shared; + +use std::collections::{BTreeMap, VecDeque}; use schemars::generate::SchemaSettings; -use schemars::transform::RecursiveTransform; +use schemars::transform::{Transform, transform_subschemas}; use schemars::{JsonSchema, Schema, SchemaGenerator}; -use serde_json::Value; +use serde_json::{Map, Value, json}; use crate::alias::Alias; -use crate::bootstrap::BootstrapTomlConfig; -use crate::daemons::{DaemonSettings, GroupDeclaration}; use crate::doctor::DoctorConfig; -use crate::history::HistoryTomlConfig; use crate::launchd::LaunchdCalendarInterval; -use crate::min_version::MinVersionSpec; +use crate::mise_toml::MiseTomlDecl; use crate::monorepo::MonorepoConfig; -use crate::oci::OciConfig; use crate::remote::InstallMiseTomlConfig; use crate::task::{TaskConfig, TaskDecl}; use crate::tool_config::ToolConfig; use crate::watch_files::WatchFile; -use crate::wrappers::CommandWrapper; -/// Each generated fragment, keyed by the JSON pointer it replaces in `schema/mise.json`. -pub fn fragments() -> Vec<(&'static str, Value)> { +pub(crate) const DRAFT: &str = "https://json-schema.org/draft/2020-12/schema"; + +/// Each schema file, keyed by its path from the repository root, given the text of mise's +/// `settings.toml`. +pub fn render(settings_toml: &str) -> Vec<(&'static str, Value)> { + let settings: toml::Table = toml::from_str(settings_toml).expect("settings.toml parses"); + let mise = mise_json(&settings); + let task = task_json(&mise); + vec![ + ("schema/mise.json", mise), + ("schema/mise-task.json", task), + ("schema/miserc.json", settings::miserc(&settings)), + ] +} + +fn mise_json(settings: &toml::Table) -> Value { let mut generator = generator(); - let derived = vec![ - ( - "/properties/bootstrap", - fragment::(&mut generator), - ), - ( - "/properties/history", - fragment::(&mut generator), - ), - ( - "/properties/wrappers/additionalProperties", - fragment::(&mut generator), - ), - ( - "/properties/daemons_settings", - fragment::(&mut generator), - ), - ( - "/properties/daemon_groups/additionalProperties", - fragment::(&mut generator), - ), - ("/properties/oci", fragment::(&mut generator)), - ("/$defs/doctor", fragment::(&mut generator)), - ( - "/$defs/monorepo", - fragment::(&mut generator), - ), - ("/$defs/tool_config", fragment::(&mut generator)), - ( - "/$defs/watch_files/items", - fragment::(&mut generator), - ), - ( - "/$defs/bootstrap_remote_install_mise", - fragment::(&mut generator), - ), - ( - "/$defs/launchd_calendar_interval", - fragment::(&mut generator), - ), - ]; + let decl = fragment::(&mut generator); + let settings_def = json!({ + "type": "object", + "unevaluatedProperties": false, + "properties": settings::properties(settings) + }); + // Sorted by name, so a section's definitions sit together whichever module writes them. + let mut defs = BTreeMap::new(); + for (name, schema) in derived_defs(&mut generator) + .into_iter() + .chain(hand_written_defs(&mut generator)) + .chain([("settings", settings_def)]) + { + assert!( + defs.insert(name, schema).is_none(), + "$defs.{name} is defined twice" + ); + } + let mut mise = json!({ + "$id": "https://mise.jdx.dev/schema/mise.json", + "$schema": DRAFT, + "title": "mise", + "type": "object", + "$defs": Map::from_iter(defs.into_iter().map(|(name, schema)| (name.to_string(), schema))), + "unevaluatedProperties": false, + "description": decl["description"], + "properties": decl["properties"] + }); + strip_hand_written(&mut mise); + mise +} + +/// The `$defs` entries generated from this crate's types. +fn derived_defs(generator: &mut SchemaGenerator) -> Vec<(&'static str, Value)> { // `task_props` is composed with `allOf` in `task` and `task_template`, which close it with // `unevaluatedProperties`; closing it here as well would reject every key they add. - let mut task_props = fragment::(&mut generator); + let mut task_props = fragment::(generator); if let Some(object) = task_props.as_object_mut() { object.remove("additionalProperties"); } let task_template = crate::task::template_schema(&task_props); - let tasks = vec![ - ("/$defs/task_props", task_props), - ("/$defs/task_template", task_template), - ("/$defs/task_config", fragment::(&mut generator)), - ]; - // Written by hand next to their parser, so they skip the generator's transforms and are - // emitted exactly as written. - let hand_written = crate::env::schema_fragments() + let mut watch_files = fragment::>(generator); + watch_files["description"] = "files to watch for changes".into(); + vec![ + ("task_props", task_props), + ("task_template", task_template), + ("task_config", fragment::(generator)), + ("tool_config", fragment::(generator)), + ("monorepo", fragment::(generator)), + ("watch_files", watch_files), + ("doctor", fragment::(generator)), + ( + "launchd_calendar_interval", + fragment::(generator), + ), + ( + "bootstrap_remote_install_mise", + fragment::(generator), + ), + ] +} + +/// The `$defs` entries written by hand next to their parsers. They skip the generator's +/// transforms and are emitted exactly as written. +fn hand_written_defs(generator: &mut SchemaGenerator) -> Vec<(&'static str, Value)> { + shared::defs() .into_iter() - .chain(crate::task::schema_fragments()) - .chain(crate::tools::schema_fragments()) + .chain(crate::env::schema_defs()) + .chain(crate::task::schema_defs()) + .chain(crate::tools::schema_defs()) .chain([ - ( - "/$defs/tool_alias_entry", - Alias::json_schema(&mut generator), - ), - ( - "/$defs/tool_alias_versions", - crate::alias::versions_schema(), - ), - ( - "/properties/min_version", - MinVersionSpec::json_schema(&mut generator), - ), + ("tool_alias_entry", Alias::json_schema(generator)), + ("tool_alias_versions", crate::alias::versions_schema()), ]) - .map(|(pointer, schema)| (pointer, schema.to_value())); - derived - .into_iter() - .chain(tasks) - .chain(hand_written) + .chain(crate::hooks::schema_defs()) + .chain(crate::mise_toml::schema_defs()) + .chain(crate::deps::schema::defs()) + .chain(crate::daemons::schema::defs()) + .chain(crate::bootstrap::schema::defs()) + .map(|(name, schema)| (name, schema.to_value())) .collect() } +/// `schema/mise-task.json`, for the task files `task_config.includes` loads: a table of tasks, +/// with the `$defs` of `mise.json` that a task refers to. +fn task_json(mise: &Value) -> Value { + let mut task = json!({ + "$id": "https://mise.jdx.dev/schema/mise-task.json", + "$schema": DRAFT, + "title": "mise-task-schema", + "type": "object", + "description": "Config file for included mise tasks (https://mise.jdx.dev/tasks/task-configuration.html)", + "additionalProperties": {"$ref": "#/$defs/task"} + }); + task["$defs"] = referenced_defs(&mise["$defs"], &task).into(); + task +} + +/// Every entry of `defs` that `root` refers to, directly or through another entry, in the order +/// a breadth-first walk reaches them. +fn referenced_defs(defs: &Value, root: &Value) -> Map { + let mut picked = Map::new(); + let mut queue = VecDeque::from([root]); + while let Some(value) = queue.pop_front() { + match value { + Value::Array(items) => queue.extend(items), + Value::Object(object) => { + if let Some(Value::String(reference)) = object.get("$ref") { + let Some(path) = reference.strip_prefix("#/$defs/") else { + assert!( + !reference.starts_with("#/"), + "unsupported local JSON schema ref: {reference}" + ); + queue.extend(object.values()); + continue; + }; + let name = path.split('/').next().unwrap_or(path); + let def = defs + .get(name) + .unwrap_or_else(|| panic!("schema/mise.json is missing $defs.{name}")); + if !picked.contains_key(name) { + picked.insert(name.to_string(), def.clone()); + queue.push_back(def); + } + } + queue.extend(object.values()); + } + _ => {} + } + } + picked +} + /// `schema_with` functions that point at a `$defs` entry, for fields that share one definition. -/// -/// Some entries are generated by [`fragments`]; the rest are still written by hand. pub(crate) mod refs { use schemars::{Schema, SchemaGenerator, json_schema}; @@ -139,16 +202,23 @@ pub(crate) mod refs { bootstrap_macos_trackpad, bootstrap_mise_shell_activate, bootstrap_remote_install_mise, + doctor, env, + hooks, launchd_calendar_interval, + monorepo, + monorepo_root, os_filter, + task_config, task_watch, + tool_config, vars, + watch_files, ); } /// Closes a `deny_unknown_fields` struct with `unevaluatedProperties`, the form the hand-written -/// file uses, in place of the `additionalProperties: false` schemars writes. The two accept the +/// schemas use, in place of the `additionalProperties: false` schemars writes. The two accept the /// same tables when the schema has no subschemas. pub(crate) fn unevaluated(schema: &mut Schema) { if schema.get("additionalProperties") == Some(&Value::Bool(false)) { @@ -157,7 +227,7 @@ pub(crate) fn unevaluated(schema: &mut Schema) { } } -/// Writes an untagged enum's alternatives as `oneOf`, the form the hand-written file uses. +/// Writes an untagged enum's alternatives as `oneOf`, the form the hand-written schemas use. /// /// schemars writes `anyOf`. The alternatives of these enums never overlap, so the two accept /// the same values. @@ -170,18 +240,67 @@ pub(crate) fn one_of(schema: &mut Schema) { fn generator() -> SchemaGenerator { SchemaSettings::draft2020_12() .with(|settings| { - // Every fragment is written in place, so a nested type cannot point at a `$defs` - // entry that the hand-written file does not have. + // Every section is written in place, so a nested type cannot point at a `$defs` + // entry that is not listed in `mise_json`. settings.inline_subschemas = true; }) - .with_transform(RecursiveTransform(drop_null)) - .with_transform(RecursiveTransform(drop_integer_format)) - .with_transform(RecursiveTransform(drop_empty_default)) - .with_transform(RecursiveTransform(reject_unknown_keys)) - .with_transform(RecursiveTransform(describe_first)) + .with_transform(Generated(drop_null)) + .with_transform(Generated(drop_integer_format)) + .with_transform(Generated(drop_empty_default)) + .with_transform(Generated(reject_unknown_keys)) + .with_transform(Generated(describe_first)) .into_generator() } +const HAND_WRITTEN: &str = "x-mise-hand-written"; + +/// Marks a schema written by hand, so the generator's transforms leave it as written when a +/// derived type embeds it. +pub(crate) fn hand_written(mut schema: Schema) -> Schema { + schema.insert(HAND_WRITTEN.into(), true.into()); + schema +} + +/// Applies a transform to a schema and its subschemas, stopping at a [`hand_written`] one. +#[derive(Clone)] +struct Generated(T); + +impl Transform for Generated { + fn transform(&mut self, schema: &mut Schema) { + if schema.get(HAND_WRITTEN).is_some() { + return; + } + self.0.transform(schema); + // `drop_null` can merge a hand-written schema into its `Option` wrapper. + if schema.get(HAND_WRITTEN).is_none() { + transform_subschemas(self, schema); + } + } +} + +/// Removes the [`hand_written`] markers, with the empty `default` the derive gives an optional +/// field: TOML has no null, and `{}` or `[]` says nothing an editor can use. +fn strip_hand_written(value: &mut Value) { + match value { + Value::Object(object) => { + if object.remove(HAND_WRITTEN).is_some() { + let empty = match object.get("default") { + Some(Value::Null) => true, + Some(Value::Array(values)) => values.is_empty(), + Some(Value::Object(values)) => values.is_empty(), + _ => false, + }; + if empty { + object.remove("default"); + } + } + object.values_mut().for_each(strip_hand_written); + } + Value::Array(items) => items.iter_mut().for_each(strip_hand_written), + _ => {} + } +} + fn fragment(generator: &mut SchemaGenerator) -> Value { let mut schema = generator.root_schema_for::(); let object = schema.ensure_object(); @@ -280,17 +399,44 @@ fn describe_first(schema: &mut Schema) { mod tests { use super::*; + const ROOT: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../.."); + #[test] - fn schema_json_is_up_to_date() { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../schema/mise.json"); - let committed: Value = - serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); - for (pointer, generated) in fragments() { - assert_eq!( - committed.pointer(pointer), - Some(&generated), - "schema/mise.json{pointer} differs from the mise-config types; run `mise run render:schema`" - ); + fn schema_files_are_up_to_date() { + let settings = std::fs::read_to_string(format!("{ROOT}/settings.toml")).unwrap(); + for (path, generated) in render(&settings) { + let committed: Value = + serde_json::from_str(&std::fs::read_to_string(format!("{ROOT}/{path}")).unwrap()) + .unwrap(); + if let Some(pointer) = first_difference(&committed, &generated, String::new()) { + panic!( + "{path} differs from mise-config at {pointer}; run `mise run render:schema`" + ); + } + } + } + + /// The JSON pointer of the first value that differs, ignoring key order. + fn first_difference(a: &Value, b: &Value, pointer: String) -> Option { + match (a, b) { + (Value::Object(a), Value::Object(b)) => { + // Each key once: comparing a shared key from both sides would double the work + // at every level of nesting. + let only_b = b.keys().filter(|key| !a.contains_key(*key)); + a.keys().chain(only_b).find_map(|key| { + let at = format!("{pointer}/{}", key.replace('~', "~0").replace('/', "~1")); + match (a.get(key), b.get(key)) { + (Some(a), Some(b)) => first_difference(a, b, at), + _ => Some(at), + } + }) + } + (Value::Array(a), Value::Array(b)) if a.len() == b.len() => a + .iter() + .zip(b) + .enumerate() + .find_map(|(i, (a, b))| first_difference(a, b, format!("{pointer}/{i}"))), + _ => (a != b).then_some(pointer), } } } diff --git a/crates/mise-config/src/schema/settings.rs b/crates/mise-config/src/schema/settings.rs new file mode 100644 index 0000000000..7abea3c4af --- /dev/null +++ b/crates/mise-config/src/schema/settings.rs @@ -0,0 +1,154 @@ +//! The `[settings]` schema and `schema/miserc.json`, built from mise's `settings.toml`. +//! +//! Each leaf table of `settings.toml` (one with a `type`) describes a setting; every other table +//! is a group of settings. mise-settings generates the Rust types from the same file, so a setting +//! and its schema cannot drift apart. + +use serde_json::{Map, Value, json}; + +/// The `properties` of `$defs.settings`, one per top-level setting or group. +pub(crate) fn properties(settings: &toml::Table) -> Map { + group(settings, "") +} + +/// `schema/miserc.json`: the top-level settings marked `rc`, which mise reads from +/// `.miserc.toml` before it loads any config file. +pub(crate) fn miserc(settings: &toml::Table) -> Value { + let properties: Map = settings + .iter() + .filter_map(|(key, node)| { + let props = leaf(node)?; + (props.get("rc").and_then(toml::Value::as_bool) == Some(true)) + .then(|| (key.clone(), element(key, props))) + }) + .collect(); + json!({ + "$schema": super::DRAFT, + "title": "mise rc config", + "description": "Early initialization settings for mise. These settings are loaded before the main config files.", + "type": "object", + "unevaluatedProperties": false, + "properties": properties + }) +} + +fn leaf(node: &toml::Value) -> Option<&toml::Table> { + node.as_table().filter(|table| table.contains_key("type")) +} + +fn group(table: &toml::Table, prefix: &str) -> Map { + table + .iter() + .map(|(key, node)| { + let name = if prefix.is_empty() { + key.clone() + } else { + format!("{prefix}.{key}") + }; + let schema = match leaf(node) { + Some(props) => element(&name, props), + None => { + let table = node + .as_table() + .unwrap_or_else(|| panic!("settings.toml: {name} is not a table")); + json!({ + "type": "object", + "unevaluatedProperties": false, + "properties": group(table, &name) + }) + } + }; + (key.clone(), schema) + }) + .collect() +} + +/// One setting's schema. `name` is its dotted path, for error messages. +fn element(name: &str, props: &toml::Table) -> Value { + let str_prop = |key: &str| props.get(key).and_then(toml::Value::as_str); + let setting_type = str_prop("type").unwrap_or_default(); + let json_type = match setting_type { + "String" | "Path" | "Url" | "Duration" => "string", + "Bool" => "boolean", + "Integer" => "integer", + "ListString" | "ListPath" | "SetString" => "array", + "IndexMap" => "object", + // Written as `oneOf` below, which AJV's strictTypes accepts where a list of types fails. + "BoolOrString" | "BoolOrList" => "", + other => panic!("settings.toml: unknown type {other:?} for {name}"), + }; + let description = str_prop("description") + .unwrap_or_else(|| panic!("settings.toml: missing description for {name}")); + let choices: Option = props + .get("enum") + .and_then(toml::Value::as_array) + .map(|items| { + items + .iter() + // An entry is a bare value or `{ value, description }`. + .map(|item| to_json(item.as_table().and_then(|t| t.get("value")).unwrap_or(item))) + .collect() + }); + let strings = || { + let mut items = json!({"type": "string"}); + if let Some(choices) = &choices { + items["enum"] = choices.clone(); + } + items + }; + + let mut element = Map::new(); + if let Some(default) = props.get("default") { + element.insert("default".into(), to_json(default)); + } + element.insert("description".into(), description.into()); + if !json_type.is_empty() { + element.insert("type".into(), json_type.into()); + } + if props.contains_key("deprecated") { + element.insert("deprecated".into(), true.into()); + } + if json_type == "integer" && str_prop("rust_type").is_some_and(|t| t.starts_with('u')) { + element.insert("minimum".into(), 0.into()); + } + if json_type == "array" { + element.insert("items".into(), strings()); + } else if setting_type != "BoolOrList" + && let Some(choices) = &choices + { + element.insert("enum".into(), choices.clone()); + } + if json_type == "object" { + element.insert("additionalProperties".into(), json!({"type": "string"})); + } + match setting_type { + // A list setting that also takes a single value. + _ if json_type == "array" && str_prop("deserialize_with") == Some("path_or_paths") => { + element.remove("type"); + let items = element.remove("items"); + element.insert( + "oneOf".into(), + json!([{"type": "string"}, {"type": "array", "items": items}]), + ); + } + "BoolOrString" => { + element.insert( + "oneOf".into(), + json!([{"type": "boolean"}, {"type": "string"}]), + ); + } + // A boolean, or a list of the strings the setting's `enum` allows. + "BoolOrList" => { + element.insert( + "oneOf".into(), + json!([{"type": "boolean"}, {"type": "array", "items": strings()}]), + ); + } + _ => {} + } + element.into() +} + +fn to_json(value: &toml::Value) -> Value { + serde_json::to_value(value).expect("a settings.toml value converts to JSON") +} diff --git a/crates/mise-config/src/schema/shared.rs b/crates/mise-config/src/schema/shared.rs new file mode 100644 index 0000000000..5fcf3a0a98 --- /dev/null +++ b/crates/mise-config/src/schema/shared.rs @@ -0,0 +1,30 @@ +//! Definitions written by hand that several sections share. + +use schemars::{Schema, json_schema}; + +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { + vec![ + ("os_filter", os_filter()), + ("os_filter_item", os_filter_item()), + ] +} + +fn os_filter() -> Schema { + json_schema!({ + "description": "operating system filters to install on", + "oneOf": [ + {"$ref": "#/$defs/os_filter_item"}, + {"type": "array", "items": {"$ref": "#/$defs/os_filter_item"}, "minItems": 1} + ] + }) +} + +fn os_filter_item() -> Schema { + json_schema!({ + "description": "operating system or os/arch pair to install on", + "type": "string", + "pattern": "^[A-Za-z0-9_.+-]+(/[A-Za-z0-9_.+-]+)?$", + "examples": ["linux", "macos", "windows", "unix", "macos/arm64", "linux/x64"] + }) +} diff --git a/crates/mise-config/src/task.rs b/crates/mise-config/src/task.rs index aefa386a60..2e22f47d29 100644 --- a/crates/mise-config/src/task.rs +++ b/crates/mise-config/src/task.rs @@ -19,7 +19,7 @@ pub use confirm::TaskConfirm; pub use decl::{TaskDecl, TaskTemplate}; pub use dep::TaskDep; pub use outputs::TaskOutputs; -pub(crate) use schema::{fragments as schema_fragments, task_template as template_schema}; +pub(crate) use schema::{defs as schema_defs, task_template as template_schema}; pub use secrets::{G7_TEXT, TaskSecrets}; // A `{ version = "..." }`-style tool table, shared by `[tools]` and a task's `tools`: exactly one diff --git a/crates/mise-config/src/task/schema.rs b/crates/mise-config/src/task/schema.rs index fcf44397ed..ef30cb5e26 100644 --- a/crates/mise-config/src/task/schema.rs +++ b/crates/mise-config/src/task/schema.rs @@ -15,14 +15,14 @@ use serde_json::{Map, Value}; use super::TaskTemplate; -/// The hand-written definitions, keyed by the JSON pointer each replaces in `schema/mise.json`. -pub(crate) fn fragments() -> Vec<(&'static str, Schema)> { +/// The hand-written definitions, keyed by their names under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { vec![ - ("/$defs/task", task()), - ("/$defs/task_dependency_item", task_dependency_item()), - ("/$defs/task_dependency_table", task_dependency_table()), - ("/$defs/task_run_entry", task_run_entry()), - ("/$defs/task_watch", task_watch()), + ("task", task()), + ("task_dependency_item", task_dependency_item()), + ("task_dependency_table", task_dependency_table()), + ("task_run_entry", task_run_entry()), + ("task_watch", task_watch()), ] } diff --git a/crates/mise-config/src/tools.rs b/crates/mise-config/src/tools.rs index cb6a5e6140..bf0b99a52d 100644 --- a/crates/mise-config/src/tools.rs +++ b/crates/mise-config/src/tools.rs @@ -12,8 +12,8 @@ use serde::{Deserialize, Deserializer}; use crate::env_value::EnvValue; use crate::task::ParsedToolMap; -mod schema; -pub(crate) use schema::fragments as schema_fragments; +pub(crate) mod schema; +pub(crate) use schema::defs as schema_defs; /// `[tools]` as written: tool key (`node`, `"cargo:ripgrep"`, `"node[os=linux]"`) to its versions. pub type ToolsDecl = IndexMap; diff --git a/crates/mise-config/src/tools/schema.rs b/crates/mise-config/src/tools/schema.rs index 717feabb70..64de592af7 100644 --- a/crates/mise-config/src/tools/schema.rs +++ b/crates/mise-config/src/tools/schema.rs @@ -9,72 +9,65 @@ use schemars::{Schema, json_schema}; -/// Each definition, keyed by the JSON pointer it replaces in `schema/mise.json`. -pub(crate) fn fragments() -> Vec<(&'static str, Schema)> { +/// Each definition, keyed by its name under `$defs` in `schema/mise.json`. +pub(crate) fn defs() -> Vec<(&'static str, Schema)> { vec![ - ("/properties/tools", tools()), - ("/$defs/tool", tool()), - ("/$defs/tool_bool", tool_bool()), - ("/$defs/tool_bool_text", tool_bool_text()), - ("/$defs/tool_platforms", tool_platforms()), - ("/$defs/tool_forge_options", tool_forge_options()), - ("/$defs/tool_github_options", tool_github_options()), - ("/$defs/tool_forge_platforms", tool_forge_platforms()), - ("/$defs/tool_github", tool_github()), - ("/$defs/tool_gitlab_options", tool_gitlab_options()), - ("/$defs/tool_gitlab", tool_gitlab()), - ("/$defs/tool_forgejo_options", tool_forgejo_options()), - ("/$defs/tool_forgejo", tool_forgejo()), - ("/$defs/tool_ubi_options", tool_ubi_options()), - ("/$defs/tool_ubi_platforms", tool_ubi_platforms()), - ("/$defs/tool_ubi", tool_ubi()), - ("/$defs/tool_http_options", tool_http_options()), - ("/$defs/tool_http_platforms", tool_http_platforms()), - ("/$defs/tool_http", tool_http()), - ("/$defs/tool_s3_options", tool_s3_options()), - ("/$defs/tool_s3_platforms", tool_s3_platforms()), - ("/$defs/tool_s3", tool_s3()), - ("/$defs/tool_aqua_options", tool_aqua_options()), - ("/$defs/tool_aqua", tool_aqua()), - ("/$defs/tool_cargo_options", tool_cargo_options()), - ("/$defs/tool_cargo_platforms", tool_cargo_platforms()), - ("/$defs/tool_cargo", tool_cargo()), - ("/$defs/tool_npm_options", tool_npm_options()), - ("/$defs/tool_npm", tool_npm()), - ("/$defs/tool_pypi_options", tool_pypi_options()), - ("/$defs/tool_pypi", tool_pypi()), - ("/$defs/tool_gem_options", tool_gem_options()), - ("/$defs/tool_gem", tool_gem()), - ("/$defs/tool_go_options", tool_go_options()), - ("/$defs/tool_go", tool_go()), - ("/$defs/tool_conda_options", tool_conda_options()), - ("/$defs/tool_conda", tool_conda()), - ("/$defs/tool_spm_options", tool_spm_options()), - ("/$defs/tool_spm_platforms", tool_spm_platforms()), - ("/$defs/tool_spm", tool_spm()), - ("/$defs/tool_packslip_options", tool_packslip_options()), - ("/$defs/tool_packslip", tool_packslip()), - ("/$defs/tool_spinel_options", tool_spinel_options()), - ("/$defs/tool_spinel", tool_spinel()), - ( - "/$defs/tool_core_python_options", - tool_core_python_options(), - ), - ("/$defs/tool_core_python", tool_core_python()), - ("/$defs/tool_core_java_options", tool_core_java_options()), - ("/$defs/tool_core_java", tool_core_java()), - ("/$defs/tool_core_rust_options", tool_core_rust_options()), - ("/$defs/tool_core_rust", tool_core_rust()), - ( - "/$defs/tool_core_dotnet_options", - tool_core_dotnet_options(), - ), - ("/$defs/tool_core_dotnet", tool_core_dotnet()), - ("/$defs/tool_options", tool_options()), + ("tool", tool()), + ("tool_bool", tool_bool()), + ("tool_bool_text", tool_bool_text()), + ("tool_platforms", tool_platforms()), + ("tool_forge_options", tool_forge_options()), + ("tool_github_options", tool_github_options()), + ("tool_forge_platforms", tool_forge_platforms()), + ("tool_github", tool_github()), + ("tool_gitlab_options", tool_gitlab_options()), + ("tool_gitlab", tool_gitlab()), + ("tool_forgejo_options", tool_forgejo_options()), + ("tool_forgejo", tool_forgejo()), + ("tool_ubi_options", tool_ubi_options()), + ("tool_ubi_platforms", tool_ubi_platforms()), + ("tool_ubi", tool_ubi()), + ("tool_http_options", tool_http_options()), + ("tool_http_platforms", tool_http_platforms()), + ("tool_http", tool_http()), + ("tool_s3_options", tool_s3_options()), + ("tool_s3_platforms", tool_s3_platforms()), + ("tool_s3", tool_s3()), + ("tool_aqua_options", tool_aqua_options()), + ("tool_aqua", tool_aqua()), + ("tool_cargo_options", tool_cargo_options()), + ("tool_cargo_platforms", tool_cargo_platforms()), + ("tool_cargo", tool_cargo()), + ("tool_npm_options", tool_npm_options()), + ("tool_npm", tool_npm()), + ("tool_pypi_options", tool_pypi_options()), + ("tool_pypi", tool_pypi()), + ("tool_gem_options", tool_gem_options()), + ("tool_gem", tool_gem()), + ("tool_go_options", tool_go_options()), + ("tool_go", tool_go()), + ("tool_conda_options", tool_conda_options()), + ("tool_conda", tool_conda()), + ("tool_spm_options", tool_spm_options()), + ("tool_spm_platforms", tool_spm_platforms()), + ("tool_spm", tool_spm()), + ("tool_packslip_options", tool_packslip_options()), + ("tool_packslip", tool_packslip()), + ("tool_spinel_options", tool_spinel_options()), + ("tool_spinel", tool_spinel()), + ("tool_core_python_options", tool_core_python_options()), + ("tool_core_python", tool_core_python()), + ("tool_core_java_options", tool_core_java_options()), + ("tool_core_java", tool_core_java()), + ("tool_core_rust_options", tool_core_rust_options()), + ("tool_core_rust", tool_core_rust()), + ("tool_core_dotnet_options", tool_core_dotnet_options()), + ("tool_core_dotnet", tool_core_dotnet()), + ("tool_options", tool_options()), ] } -fn tools() -> Schema { +pub(crate) fn tools() -> Schema { json_schema!({ "patternProperties": { "^github:": { @@ -201,7 +194,6 @@ fn tools() -> Schema { "additionalProperties": { "oneOf": [{"items": {"$ref": "#/$defs/tool"}, "type": "array"}, {"$ref": "#/$defs/tool"}] }, - "description": "dev tools to use", "type": "object" }) } diff --git a/docs/architecture.md b/docs/architecture.md index d576f744b4..bbc7240589 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -213,25 +213,22 @@ remove it. Edit the source, then regenerate. Commit the output with the change that produced it. -| Output | Source | Regenerate with | -| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------- | -| `mise.usage.kdl`, `docs/cli/` | Command definitions in `src/cli/`, `src/assets/mise-extra.usage.kdl`, and `docs/.vitepress/cli-reference.ts` | `mise run render:usage` | -| `tasks.md` | The project's tasks in `tasks.toml`, `xtasks/`, and `mise.toml`, through `mise generate task-docs` | `mise run render:usage` | -| `man/man1/mise.1` | `mise.usage.kdl` | `mise run render:mangen` | -| `completions/` | The usage spec, through `mise completion ` | `mise run render:completions` | -| `docs/.vitepress/cli_commands.ts` and the idiomatic version file table | `src/cli/` and the `idiomatic_files` keys in `registry/*.toml` | `mise run render:help` | -| The settings part of `schema/mise.json`, `schema/miserc.json`, `schema/mise-task.json` | `settings.toml`; `mise-task.json` also copies the task definitions from `schema/mise.json` | `mise run render:schema` | -| The parts of `schema/mise.json` for sections declared in `crates/mise-config` | The types and doc comments in `crates/mise-config`, through its `render-schema` example | `mise run render:schema` | -| `docs/public/llms.txt` | Page titles and leads, and `docs/.vitepress/sidebar.ts` | `mise run render:llms` | -| `Settings` types | `settings.toml` | Every build (`crates/mise-settings/build.rs`) | -| The registry compiled into mise | `registry/*.toml` | Every build (`build.rs`) | -| The [settings reference](/configuration/settings.html) | `settings.toml` | Every docs build | +| Output | Source | Regenerate with | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| `mise.usage.kdl`, `docs/cli/` | Command definitions in `src/cli/`, `src/assets/mise-extra.usage.kdl`, and `docs/.vitepress/cli-reference.ts` | `mise run render:usage` | +| `tasks.md` | The project's tasks in `tasks.toml`, `xtasks/`, and `mise.toml`, through `mise generate task-docs` | `mise run render:usage` | +| `man/man1/mise.1` | `mise.usage.kdl` | `mise run render:mangen` | +| `completions/` | The usage spec, through `mise completion ` | `mise run render:completions` | +| `docs/.vitepress/cli_commands.ts` and the idiomatic version file table | `src/cli/` and the `idiomatic_files` keys in `registry/*.toml` | `mise run render:help` | +| `schema/mise.json`, `schema/mise-task.json`, `schema/miserc.json` | The types and doc comments in `crates/mise-config` (`MiseTomlDecl` for the top level) and `settings.toml`, through mise-config's `render-schema` example | `mise run render:schema` | +| `docs/public/llms.txt` | Page titles and leads, and `docs/.vitepress/sidebar.ts` | `mise run render:llms` | +| `Settings` types | `settings.toml` | Every build (`crates/mise-settings/build.rs`) | +| The registry compiled into mise | `registry/*.toml` | Every build (`build.rs`) | +| The [settings reference](/configuration/settings.html) | `settings.toml` | Every docs build | -`mise run render` runs every `render:*` task. The rest of `schema/mise.json` is -written by hand: change it when you change config syntax, then run -`mise run render:schema`. Sections move to `crates/mise-config` one at a time. -For those, change the types and doc comments, not the JSON; a `mise-config` -test fails while the committed file is stale. +`mise run render` runs every `render:*` task. For the schemas, change the types +and doc comments in `crates/mise-config`, not the JSON; a `mise-config` test +fails while the committed files are stale. ## Tests {#tests} diff --git a/docs/contributing.md b/docs/contributing.md index f97ed40b82..80f219724e 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -252,7 +252,8 @@ not have to change your default toolchain. ### Schema conformance {#schema-conformance} -`schema/mise.json` is written by hand, so a unit test checks it against the +`schema/mise.json` is generated from the declaration types in +`crates/mise-config`, not from the parser, so a unit test checks it against the parser: `cargo test schema_matches_parser` parses every `toml` block in `docs/` and every mise config an E2E test writes with a heredoc, validates each against the schema, and fails when the two disagree. Fix the schema, then run diff --git a/schema/mise.json b/schema/mise.json index 39c1ce255e..d64432c08e 100644 --- a/schema/mise.json +++ b/schema/mise.json @@ -4,6 +4,466 @@ "title": "mise", "type": "object", "$defs": { + "bootstrap_apt_sources": { + "type": "object", + "description": "apt repositories added before apt packages are installed, keyed by a name used in the files mise writes", + "propertyNames": { + "pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "ppa": { + "type": "string", + "description": "Launchpad PPA as owner/archive, such as git-core/ppa; added on Ubuntu and Ubuntu-based releases only" + }, + "uris": { + "type": "string", + "description": "repository URL(s), space-separated" + }, + "suites": { + "type": "string", + "description": "suite(s), space-separated; defaults to the release codename" + }, + "components": { + "type": "string", + "description": "component(s), space-separated; defaults to main, or none when suites is a path ending in /" + }, + "architectures": { + "type": "string", + "description": "architecture(s) to fetch, space-separated" + }, + "key": { + "type": "string", + "description": "http(s) URL of the repository's signing key, armored or binary" + }, + "fingerprint": { + "description": "full primary key fingerprint(s) the signing key must have; required with key", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "oneOf": [ + { + "required": ["ppa"], + "properties": { + "fingerprint": { + "type": "string" + } + }, + "not": { + "anyOf": [ + { + "required": ["uris"] + }, + { + "required": ["suites"] + }, + { + "required": ["components"] + }, + { + "required": ["architectures"] + }, + { + "required": ["key"] + } + ] + } + }, + { + "required": ["uris", "key", "fingerprint"], + "not": { + "required": ["ppa"] + } + } + ] + } + }, + "bootstrap_dnf_repos": { + "type": "object", + "description": "dnf repositories enabled before dnf packages are installed, keyed by a name for the entry", + "propertyNames": { + "pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["copr"], + "properties": { + "copr": { + "type": "string", + "description": "COPR project as owner/project or @group/project, such as scottames/ghostty; enabled as dnf copr enable would" + } + } + } + }, + "bootstrap_file_phase": { + "type": "string", + "enum": ["pre-packages", "post-packages"], + "default": "post-packages", + "description": "When to apply the managed path during bootstrap; pre-packages runs before the pre-packages hook" + }, + "bootstrap_hook": { + "description": "command or commands to run for a bootstrap phase", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "object", + "properties": { + "run": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": ["run"], + "additionalProperties": true + } + ] + }, + "bootstrap_hooks": { + "type": "object", + "description": "commands to run before and after bootstrap phases", + "properties": { + "pre-packages": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_packages": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-packages": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_packages": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre-repos": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_repos": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-repos": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_repos": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre-dotfiles": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_dotfiles": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-dotfiles": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_dotfiles": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre-defaults": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_defaults": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-defaults": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_defaults": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre-user": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_user": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-user": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_user": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre-tools": { + "$ref": "#/$defs/bootstrap_hook" + }, + "pre_tools": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post-tools": { + "$ref": "#/$defs/bootstrap_hook" + }, + "post_tools": { + "$ref": "#/$defs/bootstrap_hook" + }, + "final": { + "$ref": "#/$defs/bootstrap_hook" + } + }, + "additionalProperties": false + }, + "bootstrap_macos_defaults": { + "type": "object", + "description": "macOS user defaults to apply with `mise bootstrap macos defaults apply`, keyed by preferences domain (e.g. \"com.apple.dock\", \"NSGlobalDomain\")", + "additionalProperties": { + "type": "object", + "description": "defaults keys and their desired values for this domain", + "additionalProperties": { + "$ref": "#/$defs/macos_defaults_value", + "description": "desired value, written with the matching `defaults write` type; arrays and tables become property list arrays and dictionaries" + } + } + }, + "bootstrap_macos_dock": { + "type": "object", + "description": "curated Dock preferences that compile into macOS defaults", + "properties": { + "apps": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(?:/|~/(?!/)).+\\.[aA][pP][pP]/*$" + }, + "uniqueItems": true, + "description": "Pinned application paths in order; absolute or ~/ paths ending in .app. An empty array removes application tiles." + }, + "autohide_delay": { + "type": "number", + "description": "delay before showing the Dock" + }, + "autohide_time_modifier": { + "type": "number", + "description": "Dock autohide animation time modifier" + }, + "autohide": { + "type": "boolean", + "description": "hide and show the Dock automatically" + }, + "orientation": { + "type": "string", + "enum": ["bottom", "left", "right"], + "description": "Dock screen edge" + }, + "tilesize": { + "type": "integer", + "description": "Dock icon size" + }, + "magnification": { + "type": "boolean", + "description": "enable Dock magnification" + }, + "largesize": { + "type": "integer", + "description": "Dock magnified icon size" + }, + "show_recents": { + "type": "boolean", + "description": "show recent applications in the Dock" + }, + "mru_spaces": { + "type": "boolean", + "description": "automatically rearrange Spaces based on most recent use" + } + }, + "additionalProperties": false + }, + "bootstrap_macos_finder": { + "type": "object", + "description": "curated Finder preferences that compile into macOS defaults", + "properties": { + "sort_folders_first": { + "type": "boolean", + "description": "keep folders first when sorting by name" + }, + "save_new_documents_to_cloud": { + "type": "boolean", + "description": "save new documents to iCloud by default (applies globally to application save dialogs)" + }, + "show_all_files": { + "type": "boolean", + "description": "show hidden files in Finder" + }, + "show_pathbar": { + "type": "boolean", + "description": "show the Finder path bar" + }, + "show_status_bar": { + "type": "boolean", + "description": "show the Finder status bar" + }, + "show_extensions_warning": { + "type": "boolean", + "description": "show the warning when changing file extensions" + }, + "preferred_view_style": { + "type": "string", + "enum": ["icon", "list", "column", "gallery"], + "description": "Finder preferred view style" + } + }, + "additionalProperties": false + }, + "bootstrap_macos_keyboard": { + "type": "object", + "description": "curated keyboard preferences that compile into macOS defaults", + "properties": { + "automatic_capitalization": { + "type": "boolean", + "description": "automatically capitalize words" + }, + "automatic_spelling_correction": { + "type": "boolean", + "description": "automatically correct spelling" + }, + "key_repeat": { + "type": "integer", + "description": "keyboard repeat interval" + }, + "initial_key_repeat": { + "type": "integer", + "description": "delay before key repeat starts" + }, + "press_and_hold": { + "type": "boolean", + "description": "enable press-and-hold accent picker" + }, + "fn_state": { + "type": "boolean", + "description": "use F1, F2, etc. as standard function keys" + } + }, + "additionalProperties": false + }, + "bootstrap_macos_trackpad": { + "type": "object", + "description": "curated trackpad preferences that compile into macOS defaults", + "properties": { + "tap_to_click": { + "type": "boolean", + "description": "enable tap to click for built-in and Bluetooth trackpads" + }, + "three_finger_drag": { + "type": "boolean", + "description": "enable three finger drag for built-in and Bluetooth trackpads" + } + }, + "additionalProperties": false + }, + "bootstrap_mise_shell_activate": { + "type": "object", + "description": "declarative mise shell activation snippets, applied with `mise bootstrap mise-shell-activate apply`", + "properties": { + "bash": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "zsh": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "fish": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "bash_profile": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "bashrc": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "zshenv": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "zprofile": { + "$ref": "#/$defs/bootstrap_shell_activation" + }, + "zshrc": { + "$ref": "#/$defs/bootstrap_shell_activation" + } + }, + "additionalProperties": false + }, + "bootstrap_remote_install_mise": { + "oneOf": [ + { + "type": "boolean", + "description": "install the provisioned mise to ~/.local/bin/mise, or do not install it" + }, + { + "type": "string", + "description": "absolute or ~/-relative remote path to install the provisioned mise to" + } + ] + }, + "bootstrap_shell_activation": { + "oneOf": [ + { + "type": "boolean", + "description": "enable the target with its default mode, or disable it" + }, + { + "type": "string", + "enum": ["activate", "shims", "auto"], + "description": "enable the target with the selected activation mode" + }, + { + "type": "object", + "description": "shell activation settings for the target", + "properties": { + "enabled": { + "type": "boolean", + "description": "whether to manage the activation block" + }, + "mode": { + "type": "string", + "enum": ["activate", "shims", "auto"], + "description": "activation mode; defaults according to the target" + } + }, + "required": ["enabled"], + "additionalProperties": false + } + ] + }, + "daemon_init": { + "description": "idempotent setup commands run before the daemon's long-running process", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, "daemon_port": { "description": "A fixed port, or \"auto\" to derive one from the project root so the primary checkout keeps the base port and each linked git worktree gets its own.", "oneOf": [ @@ -56,11 +516,6 @@ } ] }, - "daemon_proxy_tls": { - "description": "Whether the proxy terminates TLS for this daemon or passes the connection through to it.", - "type": "string", - "enum": ["terminate", "passthrough"] - }, "daemon_proxy_idle_timeout": { "description": "Stop this daemon after this long without proxy traffic when the proxy started it, for example \"30m\". false exempts it from idle shutdown even when pitchfork's proxy.idle_timeout is set; \"0\" does the same. Idle shutdown is off unless this or pitchfork's proxy.idle_timeout is set, and a daemon started with `mise daemons start` is never stopped for inactivity. Requires pitchfork 2.27.0.", "oneOf": [ @@ -74,247 +529,113 @@ } ] }, - "task_dependency_item": { - "description": "task name and args", - "oneOf": [ - { - "type": "string" + "daemon_proxy_tls": { + "description": "Whether the proxy terminates TLS for this daemon or passes the connection through to it.", + "type": "string", + "enum": ["terminate", "passthrough"] + }, + "deps_provider": { + "type": "object", + "properties": { + "auto": { + "type": "boolean", + "default": false, + "description": "Auto-run before `mise x` and `mise run`" }, - { + "sources": { "type": "array", "items": { "type": "string" }, - "minItems": 1 + "description": "Files/patterns to check for changes" }, - { - "$ref": "#/$defs/task_dependency_table" - } - ] - }, - "task_dependency_table": { - "description": "task dependency with args, env, and optional flag", - "type": "object", - "properties": { - "task": { - "type": "string" - }, - "args": { + "outputs": { "type": "array", "items": { "type": "string" - } + }, + "description": "Files/directories that should be newer than sources" + }, + "run": { + "type": "string", + "description": "Command to run when stale" }, "env": { "type": "object", "additionalProperties": { "type": "string" - } + }, + "description": "Environment variables to set" }, - "optional": { - "description": "do not fail when the task name or pattern has no matches", - "type": "boolean" + "dir": { + "type": "string", + "description": "Working directory for the command" + }, + "description": { + "type": "string", + "description": "Description shown in output" + }, + "depends": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Other deps providers that must complete before this one runs" + }, + "timeout": { + "type": "string", + "description": "Timeout for the run command (e.g., \"30s\", \"5m\", \"1h\")" } }, - "required": ["task"], - "unevaluatedProperties": false + "unevaluatedProperties": false, + "description": "Prepare provider configuration" }, - "os_filter_item": { - "description": "operating system or os/arch pair to install on", - "type": "string", - "pattern": "^[A-Za-z0-9_.+-]+(/[A-Za-z0-9_.+-]+)?$", - "examples": [ - "linux", - "macos", - "windows", - "unix", - "macos/arm64", - "linux/x64" - ] - }, - "os_filter": { - "description": "operating system filters to install on", - "oneOf": [ - { - "$ref": "#/$defs/os_filter_item" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/os_filter_item" - }, - "minItems": 1 - } - ] - }, - "tool_alias_versions": { - "description": "tool version aliases", - "type": "object", - "additionalProperties": { - "description": "version alias points to", - "type": "string" - } - }, - "tool_alias_entry": { - "oneOf": [ - { - "description": "where the alias goes", - "type": "string" - }, - { - "description": "tool to set aliases for", - "type": "object", - "properties": { - "backend": { - "description": "where the alias goes", - "type": "string" - }, - "versions": { - "$ref": "#/$defs/tool_alias_versions" - } - }, - "additionalProperties": { - "description": "version alias points to", - "type": "string" - } - } - ] - }, - "env_value": { - "description": "environment variable value; false removes the variable", - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "type": "boolean" - } - ] - }, - "env_path": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "env_paths": { - "type": "array", - "items": { - "type": "string" - } - }, - "env_tools": { - "type": "boolean", - "description": "load tools before resolving" - }, - "env_file_expand": { - "type": "boolean", - "default": false, - "description": "enable shell-style variable expansion for values loaded by env._.file" - }, - "env_file_find_up": { - "type": "boolean", - "default": false, - "description": "look for a relative env._.file path in the current directory and each parent directory" - }, - "env_redact": { - "type": "boolean", - "description": "redact the value from logs" - }, - "env_required": { - "oneOf": [ - { - "type": "boolean", - "description": "require this environment variable to be defined before mise runs or in a later config file" - }, - { - "type": "string", - "description": "require this environment variable with user help text on how to set it" - } - ], - "description": "require this environment variable to be defined before mise runs or in a later config file. Can be a boolean or a help string. A variable that carries its own `value` cannot also be `required = true`." - }, - "env_default": { - "oneOf": [ - { - "type": "string", - "description": "fallback value to use when the environment variable is unset or empty" - }, - { - "type": "integer", - "description": "fallback value to use when the environment variable is unset or empty" - } - ], - "description": "fallback value to use when the environment variable is unset or empty" - }, - "env_age": { - "type": "string", - "description": "[experimental] age-encrypted value (simplified format)" - }, - "env_age_value": { - "type": "string", - "description": "[experimental] age-encrypted value" - }, - "env_age_format": { - "type": "string", - "enum": ["raw", "zstd"], - "description": "[experimental] compression format for the encrypted value" - }, - "env_directive": { + "doctor": { "type": "object", + "description": "Project diagnostic checks, run explicitly with mise doctor project.", "properties": { - "path": { - "$ref": "#/$defs/env_path" - }, - "paths": { - "$ref": "#/$defs/env_paths" - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "expand": { - "$ref": "#/$defs/env_file_expand" - }, - "find_up": { - "$ref": "#/$defs/env_file_find_up" - }, - "redact": { - "$ref": "#/$defs/env_redact" - }, - "required": { - "$ref": "#/$defs/env_required" + "checks": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "run": { + "type": "string", + "description": "Diagnostic command. Exit zero to pass; other exit codes fail.", + "minLength": 1 + }, + "description": { + "type": "string", + "description": "Human-readable requirement checked by the command." + }, + "hint": { + "type": "string", + "description": "Remediation guidance displayed on failure; never executed." + }, + "timeout": { + "type": "string", + "description": "Positive command timeout, such as 5s. Defaults to 10s." + }, + "dir": { + "type": "string", + "description": "Working directory. Relative paths resolve from the declaring config's root (the invocation directory for global and system config); a leading ~/ and absolute paths are used as given." + }, + "shell": { + "type": "string", + "description": "Shell command, including the command flag, as in tasks.", + "examples": ["bash -c", "pwsh -Command"] + }, + "os": { + "description": "OS or OS/arch selectors; omit to run everywhere.", + "$ref": "#/$defs/os_filter" + } + }, + "required": ["run"] + } } }, - "oneOf": [ - { - "type": "object", - "properties": { - "path": { - "$ref": "#/$defs/env_path" - } - }, - "required": ["path"] - }, - { - "type": "object", - "properties": { - "paths": { - "$ref": "#/$defs/env_paths" - } - }, - "required": ["paths"] - } - ] + "additionalProperties": true }, "env": { "additionalProperties": { @@ -614,6 +935,564 @@ }, "type": "object" }, + "env_age": { + "type": "string", + "description": "[experimental] age-encrypted value (simplified format)" + }, + "env_age_format": { + "type": "string", + "enum": ["raw", "zstd"], + "description": "[experimental] compression format for the encrypted value" + }, + "env_age_value": { + "type": "string", + "description": "[experimental] age-encrypted value" + }, + "env_default": { + "oneOf": [ + { + "type": "string", + "description": "fallback value to use when the environment variable is unset or empty" + }, + { + "type": "integer", + "description": "fallback value to use when the environment variable is unset or empty" + } + ], + "description": "fallback value to use when the environment variable is unset or empty" + }, + "env_directive": { + "type": "object", + "properties": { + "path": { + "$ref": "#/$defs/env_path" + }, + "paths": { + "$ref": "#/$defs/env_paths" + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "expand": { + "$ref": "#/$defs/env_file_expand" + }, + "find_up": { + "$ref": "#/$defs/env_file_find_up" + }, + "redact": { + "$ref": "#/$defs/env_redact" + }, + "required": { + "$ref": "#/$defs/env_required" + } + }, + "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "$ref": "#/$defs/env_path" + } + }, + "required": ["path"] + }, + { + "type": "object", + "properties": { + "paths": { + "$ref": "#/$defs/env_paths" + } + }, + "required": ["paths"] + } + ] + }, + "env_file_expand": { + "type": "boolean", + "default": false, + "description": "enable shell-style variable expansion for values loaded by env._.file" + }, + "env_file_find_up": { + "type": "boolean", + "default": false, + "description": "look for a relative env._.file path in the current directory and each parent directory" + }, + "env_path": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "env_paths": { + "type": "array", + "items": { + "type": "string" + } + }, + "env_redact": { + "type": "boolean", + "description": "redact the value from logs" + }, + "env_required": { + "oneOf": [ + { + "type": "boolean", + "description": "require this environment variable to be defined before mise runs or in a later config file" + }, + { + "type": "string", + "description": "require this environment variable with user help text on how to set it" + } + ], + "description": "require this environment variable to be defined before mise runs or in a later config file. Can be a boolean or a help string. A variable that carries its own `value` cannot also be `required = true`." + }, + "env_tools": { + "type": "boolean", + "description": "load tools before resolving" + }, + "env_value": { + "description": "environment variable value; false removes the variable", + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "boolean" + } + ] + }, + "hook_current_shell_script_table": { + "unevaluatedProperties": false, + "properties": { + "script": { + "description": "script or scripts to evaluate in the active shell for enter/leave/cd hooks", + "$ref": "#/$defs/hook_script_value" + }, + "shell": { + "description": "active shell name for current-shell hooks, such as \"bash\"", + "type": "string" + } + }, + "required": ["script", "shell"], + "type": "object" + }, + "hook_current_shell_scripts_table": { + "unevaluatedProperties": false, + "properties": { + "scripts": { + "description": "scripts to evaluate in the active shell for enter/leave/cd hooks", + "$ref": "#/$defs/hook_scripts_value" + }, + "shell": { + "description": "active shell name for current-shell hooks, such as \"bash\"", + "type": "string" + } + }, + "required": ["scripts", "shell"], + "type": "object" + }, + "hook_definition": { + "oneOf": [ + { + "$ref": "#/$defs/hook_definition_item" + }, + { + "description": "scripts/tasks to run", + "items": { + "$ref": "#/$defs/hook_definition_item" + }, + "type": "array" + } + ] + }, + "hook_definition_item": { + "oneOf": [ + { + "$ref": "#/$defs/hook_run_string" + }, + { + "$ref": "#/$defs/hook_run_table" + }, + { + "$ref": "#/$defs/hook_current_shell_script_table" + }, + { + "$ref": "#/$defs/hook_current_shell_scripts_table" + }, + { + "$ref": "#/$defs/hook_spawned_script_table" + }, + { + "$ref": "#/$defs/hook_spawned_scripts_table" + }, + { + "$ref": "#/$defs/hook_task_ref" + } + ] + }, + "hook_install_definition": { + "oneOf": [ + { + "$ref": "#/$defs/hook_install_definition_item" + }, + { + "description": "install hooks to run", + "items": { + "$ref": "#/$defs/hook_install_definition_item" + }, + "type": "array" + } + ] + }, + "hook_install_definition_item": { + "oneOf": [ + { + "$ref": "#/$defs/hook_run_string" + }, + { + "$ref": "#/$defs/hook_run_table" + }, + { + "$ref": "#/$defs/hook_install_script_table" + }, + { + "$ref": "#/$defs/hook_install_scripts_table" + }, + { + "$ref": "#/$defs/hook_task_ref" + } + ] + }, + "hook_install_script_table": { + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "script": { + "deprecated": true, + "description": "deprecated alias for run in preinstall/postinstall hooks; executes in a subprocess", + "$ref": "#/$defs/hook_script_value" + }, + "shell": { + "deprecated": true, + "description": "ignored for preinstall/postinstall hooks that use script; use a run hook to select an inline shell", + "type": "string" + } + }, + "required": ["script"], + "type": "object" + }, + "hook_install_scripts_table": { + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "scripts": { + "deprecated": true, + "description": "deprecated alias for run in preinstall/postinstall hooks; joins the scripts and executes them in a subprocess", + "$ref": "#/$defs/hook_scripts_value" + }, + "shell": { + "deprecated": true, + "description": "ignored for preinstall/postinstall hooks that use scripts; use a run hook to select an inline shell", + "type": "string" + } + }, + "required": ["scripts"], + "type": "object" + }, + "hook_run_string": { + "description": "inline hook command to run", + "type": "string" + }, + "hook_run_table": { + "unevaluatedProperties": false, + "properties": { + "run": { + "description": "inline hook command to run", + "type": "string" + }, + "run_windows": { + "description": "inline hook command to run on Windows", + "type": "string" + }, + "shell": { + "description": "inline shell command to use for run hooks, such as \"bash -c\"", + "type": "string" + } + }, + "anyOf": [ + { + "required": ["run"] + }, + { + "required": ["run_windows"] + } + ], + "type": "object" + }, + "hook_script_value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "hook_scripts_value": { + "type": "array", + "items": { + "type": "string" + } + }, + "hook_spawned_script_table": { + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "script": { + "deprecated": true, + "description": "deprecated alias for run when an enter/leave/cd hook has no shell", + "$ref": "#/$defs/hook_script_value" + } + }, + "required": ["script"], + "type": "object" + }, + "hook_spawned_scripts_table": { + "deprecated": true, + "unevaluatedProperties": false, + "properties": { + "scripts": { + "deprecated": true, + "description": "deprecated alias for run when an enter/leave/cd hook has no shell", + "$ref": "#/$defs/hook_scripts_value" + } + }, + "required": ["scripts"], + "type": "object" + }, + "hook_task_ref": { + "unevaluatedProperties": false, + "properties": { + "task": { + "description": "mise task to run", + "type": "string" + } + }, + "required": ["task"], + "type": "object" + }, + "hooks": { + "description": "hooks to run", + "type": "object", + "properties": { + "enter": { + "$ref": "#/$defs/hook_definition" + }, + "leave": { + "$ref": "#/$defs/hook_definition" + }, + "cd": { + "$ref": "#/$defs/hook_definition" + }, + "preinstall": { + "$ref": "#/$defs/hook_install_definition" + }, + "postinstall": { + "$ref": "#/$defs/hook_install_definition" + } + }, + "additionalProperties": false + }, + "launchd_calendar_interval": { + "type": "object", + "description": "calendar schedule fields for launchd StartCalendarInterval", + "properties": { + "minute": { + "type": "integer", + "maximum": 59, + "minimum": 0 + }, + "hour": { + "type": "integer", + "maximum": 23, + "minimum": 0 + }, + "day": { + "type": "integer", + "maximum": 31, + "minimum": 1 + }, + "weekday": { + "type": "integer", + "maximum": 7, + "minimum": 0 + }, + "month": { + "type": "integer", + "maximum": 12, + "minimum": 1 + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "macos_defaults_value": { + "description": "a property list value: boolean, integer, real, string, or an array or dictionary of these", + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/macos_defaults_value" + } + }, + { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/macos_defaults_value" + } + } + ] + }, + "monorepo": { + "type": "object", + "description": "Configuration for monorepo task discovery", + "properties": { + "config_roots": { + "type": "array", + "description": "Explicit list of config root paths for monorepo task discovery. Supports single-level glob patterns (*). When set, skips filesystem walking for better performance.", + "items": { + "type": "string" + } + }, + "path_aliases": { + "type": "object", + "description": "Short names for configured monorepo task roots. Keys are single path segments; values are paths in config_roots.", + "additionalProperties": { + "type": "string" + } + }, + "lockfile": { + "type": "boolean", + "description": "Use a single lockfile at the monorepo root for descendant config roots. true opts in now, false keeps lockfiles next to subproject configs, and unset keeps the current behavior until the scheduled default flip." + }, + "task_defaults": { + "type": "object", + "description": "Task defaults applied by task name across inferred and explicit workspace projects", + "additionalProperties": { + "$ref": "#/$defs/task_template" + } + }, + "projects": { + "type": "object", + "description": "Explicit additions, removals, and overrides applied to provider-inferred workspace projects", + "additionalProperties": { + "type": "object", + "properties": { + "remove": { + "type": "boolean", + "description": "Remove this project and every dependency edge connected to it", + "default": false + }, + "root": { + "type": "string", + "description": "Add the project at this workspace-relative root or replace its inferred root" + }, + "metadata": { + "type": "object", + "description": "Replace provider-inferred project metadata", + "additionalProperties": { + "type": "string" + } + }, + "depends": { + "type": "array", + "description": "Replace the complete provider-inferred dependency set", + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "depends_add": { + "type": "array", + "description": "Add dependency edges after an optional depends replacement", + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "depends_remove": { + "type": "array", + "description": "Remove dependency edges after an optional depends replacement", + "uniqueItems": true, + "items": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + } + } + }, + "unevaluatedProperties": false + }, + "monorepo_root": { + "description": "Marks this config as a monorepo root, enabling target path syntax for tasks. When enabled, tasks can be referenced with paths like //projects/frontend:build", + "type": "boolean" + }, + "os_filter": { + "description": "operating system filters to install on", + "oneOf": [ + { + "$ref": "#/$defs/os_filter_item" + }, + { + "type": "array", + "items": { + "$ref": "#/$defs/os_filter_item" + }, + "minItems": 1 + } + ] + }, + "os_filter_item": { + "description": "operating system or os/arch pair to install on", + "type": "string", + "pattern": "^[A-Za-z0-9_.+-]+(/[A-Za-z0-9_.+-]+)?$", + "examples": [ + "linux", + "macos", + "windows", + "unix", + "macos/arm64", + "linux/x64" + ] + }, "settings": { "type": "object", "unevaluatedProperties": false, @@ -2684,54 +3563,6 @@ } } }, - "task_run_entry": { - "oneOf": [ - { - "description": "script to run", - "type": "string" - }, - { - "type": "object", - "unevaluatedProperties": false, - "properties": { - "task": { - "description": "single task name to run", - "type": "string" - }, - "args": { - "description": "arguments to pass to the task", - "type": "array", - "items": { - "type": "string" - } - }, - "env": { - "description": "environment variables to set for the task", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["task"] - }, - { - "type": "object", - "unevaluatedProperties": false, - "properties": { - "tasks": { - "description": "parallel task group to run", - "items": { - "description": "task name and args", - "type": "string" - }, - "type": "array" - } - }, - "required": ["tasks"] - } - ] - }, "task": { "oneOf": [ { @@ -2764,204 +3595,6 @@ } ] }, - "vars": { - "description": "variables to set", - "type": "object", - "properties": { - "_": { - "description": "vars modules", - "additionalProperties": true, - "properties": { - "file": { - "oneOf": [ - { - "$ref": "#/$defs/env_directive" - }, - { - "description": "environment file to load (dotenv, json, yaml, or toml)", - "type": "string" - }, - { - "description": "environment files to load (dotenv, json, yaml, or toml)", - "items": { - "oneOf": [ - { - "description": "environment file to load (dotenv, json, yaml, or toml)", - "type": "string" - }, - { - "$ref": "#/$defs/env_directive" - } - ] - }, - "type": "array" - } - ] - }, - "source": { - "oneOf": [ - { - "$ref": "#/$defs/env_directive" - }, - { - "description": "bash script to load", - "type": "string" - }, - { - "description": "bash scripts to load", - "items": { - "oneOf": [ - { - "description": "bash script to load", - "type": "string" - }, - { - "$ref": "#/$defs/env_directive" - } - ] - }, - "type": "array" - } - ] - } - }, - "type": "object" - } - }, - "additionalProperties": { - "oneOf": [ - { - "description": "value of variable", - "type": "string" - }, - { - "description": "value of variable", - "type": "integer" - }, - { - "description": "value of variable", - "const": true - }, - { - "type": "object", - "properties": { - "value": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "integer" - }, - { - "const": true - } - ] - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "redact": { - "$ref": "#/$defs/env_redact" - } - }, - "required": ["value"], - "unevaluatedProperties": false - }, - { - "type": "object", - "properties": { - "default": { - "$ref": "#/$defs/env_default" - }, - "prompt": { - "description": "question to ask once, on a machine with no saved answer, when a command is run with a prompt flag such as mise bootstrap --prompt-vars", - "type": "string" - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "redact": { - "$ref": "#/$defs/env_redact" - } - }, - "required": ["default"], - "unevaluatedProperties": false - }, - { - "type": "object", - "properties": { - "prompt": { - "description": "question to ask once, on a machine with no saved answer, when a command is run with a prompt flag such as mise bootstrap --prompt-vars", - "type": "string" - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "redact": { - "$ref": "#/$defs/env_redact" - }, - "required": { - "oneOf": [ - { - "const": true - }, - { - "type": "string" - } - ] - } - }, - "required": ["required"], - "unevaluatedProperties": false - }, - { - "type": "object", - "properties": { - "age": { - "$ref": "#/$defs/env_age" - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "redact": { - "$ref": "#/$defs/env_redact" - } - }, - "required": ["age"], - "unevaluatedProperties": false, - "description": "[experimental] age-encrypted variable" - }, - { - "type": "object", - "properties": { - "age": { - "type": "object", - "properties": { - "value": { - "$ref": "#/$defs/env_age_value" - }, - "format": { - "$ref": "#/$defs/env_age_format" - }, - "tools": { - "$ref": "#/$defs/env_tools" - }, - "redact": { - "$ref": "#/$defs/env_redact" - } - }, - "required": ["value"], - "unevaluatedProperties": false, - "description": "[experimental] age-encrypted value (complex format)" - } - }, - "required": ["age"], - "unevaluatedProperties": false - } - ] - } - }, "task_config": { "type": "object", "description": "configuration for task execution/management", @@ -3084,2012 +3717,8 @@ }, "unevaluatedProperties": false }, - "tool_config": { - "type": "object", - "description": "policy for tools declared by configs sharing the containing config root", - "additionalProperties": false, - "properties": { - "locked": { - "type": "boolean", - "description": "require tools declared by this config root to resolve and install from their lockfiles", - "default": false - } - } - }, - "monorepo_root": { - "description": "Marks this config as a monorepo root, enabling target path syntax for tasks. When enabled, tasks can be referenced with paths like //projects/frontend:build", - "type": "boolean" - }, - "monorepo": { - "type": "object", - "description": "Configuration for monorepo task discovery", - "properties": { - "config_roots": { - "type": "array", - "description": "Explicit list of config root paths for monorepo task discovery. Supports single-level glob patterns (*). When set, skips filesystem walking for better performance.", - "items": { - "type": "string" - } - }, - "path_aliases": { - "type": "object", - "description": "Short names for configured monorepo task roots. Keys are single path segments; values are paths in config_roots.", - "additionalProperties": { - "type": "string" - } - }, - "lockfile": { - "type": "boolean", - "description": "Use a single lockfile at the monorepo root for descendant config roots. true opts in now, false keeps lockfiles next to subproject configs, and unset keeps the current behavior until the scheduled default flip." - }, - "task_defaults": { - "type": "object", - "description": "Task defaults applied by task name across inferred and explicit workspace projects", - "additionalProperties": { - "$ref": "#/$defs/task_template" - } - }, - "projects": { - "type": "object", - "description": "Explicit additions, removals, and overrides applied to provider-inferred workspace projects", - "additionalProperties": { - "type": "object", - "properties": { - "remove": { - "type": "boolean", - "description": "Remove this project and every dependency edge connected to it", - "default": false - }, - "root": { - "type": "string", - "description": "Add the project at this workspace-relative root or replace its inferred root" - }, - "metadata": { - "type": "object", - "description": "Replace provider-inferred project metadata", - "additionalProperties": { - "type": "string" - } - }, - "depends": { - "type": "array", - "description": "Replace the complete provider-inferred dependency set", - "uniqueItems": true, - "items": { - "type": "string" - } - }, - "depends_add": { - "type": "array", - "description": "Add dependency edges after an optional depends replacement", - "uniqueItems": true, - "items": { - "type": "string" - } - }, - "depends_remove": { - "type": "array", - "description": "Remove dependency edges after an optional depends replacement", - "uniqueItems": true, - "items": { - "type": "string" - } - } - }, - "unevaluatedProperties": false - } - } - }, - "unevaluatedProperties": false - }, - "tool": { - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool_options" - }, - { - "properties": { - "auto_update": { - "description": "update this tool before it runs when its check is due; true, or a check interval such as \"12h\". Only honored in global config", - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string" - } - ] - }, - "install_env": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/env_value" - }, - "description": "environment variables during tool installation" - }, - "depends": { - "oneOf": [ - { - "description": "tools that must be installed before this tool", - "type": "string" - }, - { - "description": "tools that must be installed before this tool", - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "lazy": { - "description": "defer installation until one of the tool's bootstrap shims is invoked", - "type": "boolean", - "default": false - }, - "lazy_bins": { - "description": "bootstrap shim names for lazy tools without registry bin metadata", - "oneOf": [ - { - "type": "string", - "minLength": 1, - "pattern": "^[^/\\\\]+$" - }, - { - "type": "array", - "items": { - "type": "string", - "minLength": 1, - "pattern": "^[^/\\\\]+$" - } - } - ] - }, - "postinstall": { - "description": "command to run after tool installation, or on every install when requested", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "run": { - "type": "string" - }, - "when": { - "type": "string", - "enum": ["install", "always"], - "default": "install" - } - }, - "required": ["run"], - "additionalProperties": false - } - ] - } - } - } - ], - "type": "object", - "unevaluatedProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array" - }, - { - "type": "object", - "additionalProperties": true - } - ] - } - } - ] - }, - "tool_bool": { - "description": "a boolean, also accepted as the strings true/false or the integers 1/0", - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "pattern": "^\\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|1|0)\\s*$" - }, - { - "type": "integer", - "enum": [0, 1] - } - ] - }, - "tool_bool_text": { - "description": "a boolean, also accepted as the string true or false", - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string", - "pattern": "^([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])$" - } - ] - }, - "tool_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object" - } - }, - "tool_forge_options": { - "type": "object", - "properties": { - "version_prefix": { - "description": "release tag prefix stripped from versions and re-added when resolving tags", - "type": "string" - }, - "asset_pattern": { - "description": "glob (* and ?) matched against release asset names, tera templated; replaces autodetection", - "type": "string" - }, - "additional_asset_patterns": { - "description": "extra archives from the same release extracted over the primary asset, in order", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "matching": { - "description": "substring an asset name must contain, applied before platform autodetection", - "type": "string" - }, - "matching_regex": { - "description": "regex an asset name must match, applied before platform autodetection", - "type": "string" - }, - "no_app": { - "description": "penalize macos .app bundle assets during autodetection", - "$ref": "#/$defs/tool_bool" - }, - "filter_bins": { - "description": "only expose these binaries on PATH", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "format": { - "description": "force the archive format instead of detecting it from the file name", - "type": "string", - "enum": [ - "tar.gz", - "tgz", - "gz", - "tar.xz", - "txz", - "xz", - "tar.bz2", - "tbz2", - "tbz", - "bz2", - "tar.zst", - "tzst", - "zst", - "tar", - "zip", - "vsix", - "7z", - "tar.br", - "tbr", - "br", - "tar.lz4", - "tlz4", - "lz4", - "tar.sz", - "tsz", - "sz", - "rar", - "raw" - ] - }, - "checksum": { - "description": "expected artifact digest as algo:hash (sha256, sha512, sha1, md5, blake3)", - "type": "string" - }, - "size": { - "description": "expected artifact size in bytes", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "strip_components": { - "description": "leading directory components to strip on extract (auto 1 for single-root archives)", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "bin": { - "description": "executable name (or safe relative path) to install the downloaded binary as", - "type": "string" - }, - "rename_exe": { - "description": "rename the extracted executable: a name, or a table of glob = new name", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - ] - }, - "bin_path": { - "description": "directory inside the install that contains binaries (tera templated)", - "type": "string" - } - } - }, - "tool_github_options": { - "type": "object", - "properties": { - "api_url": { - "description": "api base url, for github enterprise or self-hosted instances", - "type": "string" - }, - "github_attestations": { - "description": "verify github artifact attestations when available", - "$ref": "#/$defs/tool_bool" - }, - "slsa_signer_identity": { - "description": "expected fulcio certificate identity for slsa provenance, tera templated", - "type": "string" - }, - "slsa_signer_issuer": { - "description": "expected oidc issuer for slsa provenance; requires slsa_signer_identity", - "type": "string" - } - } - }, - "tool_forge_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "asset_pattern": { - "$ref": "#/$defs/tool_forge_options/properties/asset_pattern" - }, - "additional_asset_patterns": { - "$ref": "#/$defs/tool_forge_options/properties/additional_asset_patterns" - }, - "url": { - "description": "direct download url for this platform, tera templated; skips release asset selection", - "type": "string" - }, - "no_app": { - "$ref": "#/$defs/tool_forge_options/properties/no_app" - }, - "filter_bins": { - "$ref": "#/$defs/tool_forge_options/properties/filter_bins" - }, - "checksum": { - "$ref": "#/$defs/tool_forge_options/properties/checksum" - }, - "size": { - "$ref": "#/$defs/tool_forge_options/properties/size" - }, - "strip_components": { - "$ref": "#/$defs/tool_forge_options/properties/strip_components" - }, - "bin": { - "$ref": "#/$defs/tool_forge_options/properties/bin" - }, - "rename_exe": { - "$ref": "#/$defs/tool_forge_options/properties/rename_exe" - }, - "bin_path": { - "$ref": "#/$defs/tool_forge_options/properties/bin_path" - }, - "format": { - "$ref": "#/$defs/tool_forge_options/properties/format" - } - } - } - }, - "tool_github": { - "description": "a tool installed with the github backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_forge_options" - }, - { - "$ref": "#/$defs/tool_github_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_forge_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_forge_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_gitlab_options": { - "type": "object", - "properties": { - "api_url": { - "description": "api base url, for self-hosted gitlab instances", - "type": "string" - } - } - }, - "tool_gitlab": { - "description": "a tool installed with the gitlab backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_forge_options" - }, - { - "$ref": "#/$defs/tool_gitlab_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_forge_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_forge_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_forgejo_options": { - "type": "object", - "properties": { - "api_url": { - "description": "api base url, for self-hosted forgejo or gitea instances", - "type": "string" - } - } - }, - "tool_forgejo": { - "description": "a tool installed with the forgejo backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_forge_options" - }, - { - "$ref": "#/$defs/tool_forgejo_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_forge_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_forge_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_ubi_options": { - "type": "object", - "properties": { - "exe": { - "description": "name of the executable inside the archive to extract", - "type": "string" - }, - "rename_exe": { - "description": "name to give the extracted executable", - "type": "string" - }, - "matching": { - "description": "substring to match release filenames when several match the os and arch", - "type": "string" - }, - "matching_regex": { - "description": "regex release filenames must match before os and arch matching", - "type": "string" - }, - "provider": { - "description": "forge used to fetch releases and assets", - "type": "string", - "enum": ["github", "gitlab"] - }, - "api_url": { - "description": "provider api base url, for self-hosted instances", - "type": "string" - }, - "tag_regex": { - "description": "regex release tags must match to be listed as versions", - "type": "string" - }, - "extract_all": { - "description": "extract every file in the archive instead of only the binary", - "$ref": "#/$defs/tool_bool" - }, - "bin_path": { - "description": "directory in the install containing binaries, used with extract_all", - "type": "string" - } - } - }, - "tool_ubi_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "bin_path": { - "$ref": "#/$defs/tool_ubi_options/properties/bin_path" - } - } - } - }, - "tool_ubi": { - "description": "a tool installed with the ubi backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_ubi_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_ubi_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_ubi_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_http_options": { - "type": "object", - "properties": { - "url": { - "description": "download url, tera templated, http(s) or file://; required for each platform", - "type": "string" - }, - "checksum": { - "description": "expected artifact digest as algo:hash", - "type": "string" - }, - "checksum_url": { - "description": "checksum file or manifest url, used by mise lock without downloading", - "type": "string" - }, - "checksum_expr": { - "description": "expr-lang expression extracting algo:hash from the checksum_url manifest", - "type": "string" - }, - "headers": { - "description": "request headers sent with the download, version_list_url and checksum_url requests; values are tera templated (e.g. Bearer {{ env.TOKEN }})", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "headers_forward": { - "description": "hosts each header in headers may follow a redirect to; exact hosts or *.suffix, and a header with no entry is never forwarded", - "type": "object", - "additionalProperties": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - }, - "size": { - "description": "expected artifact size in bytes", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "format": { - "description": "force the archive format instead of detecting it from the url", - "type": "string", - "enum": [ - "tar.gz", - "tgz", - "gz", - "tar.xz", - "txz", - "xz", - "tar.bz2", - "tbz2", - "tbz", - "bz2", - "tar.zst", - "tzst", - "zst", - "tar", - "zip", - "vsix", - "7z", - "tar.br", - "tbr", - "br", - "tar.lz4", - "tlz4", - "lz4", - "tar.sz", - "tsz", - "sz", - "rar", - "raw" - ] - }, - "strip_components": { - "description": "leading directory components to strip on extract (auto 1 for single-root archives)", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "bin": { - "description": "name for a downloaded raw or compressed single binary (ignored for archives)", - "type": "string" - }, - "rename_exe": { - "description": "rename the extracted executable: a name, or a table of glob = new name", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - ] - }, - "bin_path": { - "description": "directory inside the install that contains binaries (tera templated)", - "type": "string" - }, - "shared_extraction": { - "description": "share extracted content between installs with identical artifacts", - "$ref": "#/$defs/tool_bool" - }, - "windows_script_interpreter": { - "description": "windows only: write a .cmd launcher running the raw script with this interpreter", - "type": "string" - }, - "version_list_url": { - "description": "url returning the version list (text, json or html)", - "type": "string" - }, - "version_regex": { - "description": "regex extracting versions from the list (first capture group or whole match)", - "type": "string" - }, - "version_json_path": { - "description": "jq-like path extracting versions from json, such as .[].version", - "type": "string" - }, - "version_expr": { - "description": "expr-lang expression post-processing versions (variables: body, versions)", - "type": "string" - } - } - }, - "tool_http_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "url": { - "$ref": "#/$defs/tool_http_options/properties/url" - }, - "headers": { - "$ref": "#/$defs/tool_http_options/properties/headers" - }, - "headers_forward": { - "$ref": "#/$defs/tool_http_options/properties/headers_forward" - }, - "checksum": { - "$ref": "#/$defs/tool_http_options/properties/checksum" - }, - "checksum_url": { - "$ref": "#/$defs/tool_http_options/properties/checksum_url" - }, - "size": { - "$ref": "#/$defs/tool_http_options/properties/size" - }, - "format": { - "$ref": "#/$defs/tool_http_options/properties/format" - }, - "strip_components": { - "$ref": "#/$defs/tool_http_options/properties/strip_components" - }, - "bin": { - "$ref": "#/$defs/tool_http_options/properties/bin" - }, - "rename_exe": { - "$ref": "#/$defs/tool_http_options/properties/rename_exe" - }, - "bin_path": { - "$ref": "#/$defs/tool_http_options/properties/bin_path" - }, - "windows_script_interpreter": { - "$ref": "#/$defs/tool_http_options/properties/windows_script_interpreter" - } - } - } - }, - "tool_http": { - "description": "a tool installed with the http backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_http_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_http_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_http_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_s3_options": { - "type": "object", - "properties": { - "url": { - "description": "s3://bucket/key object url, tera templated; required for each platform", - "type": "string" - }, - "checksum": { - "description": "expected artifact digest as algo:hash", - "type": "string" - }, - "size": { - "description": "expected artifact size in bytes", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "format": { - "description": "force the archive format when the object key has no usable extension", - "type": "string", - "enum": [ - "tar.gz", - "tgz", - "gz", - "tar.xz", - "txz", - "xz", - "tar.bz2", - "tbz2", - "tbz", - "bz2", - "tar.zst", - "tzst", - "zst", - "tar", - "zip", - "vsix", - "7z", - "tar.br", - "tbr", - "br", - "tar.lz4", - "tlz4", - "lz4", - "tar.sz", - "tsz", - "sz", - "rar", - "raw" - ] - }, - "strip_components": { - "description": "leading directory components to strip on extract (auto 1 for single-root archives)", - "oneOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ] - }, - "bin": { - "description": "executable name (or safe relative path) to install the downloaded binary as", - "type": "string" - }, - "rename_exe": { - "description": "rename the extracted executable: a name, or a table of glob = new name", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - ] - }, - "bin_path": { - "description": "directory inside the install that contains binaries (tera templated)", - "type": "string" - }, - "region": { - "description": "aws region used for the s3 client", - "type": "string" - }, - "endpoint": { - "description": "custom s3-compatible endpoint url (minio and similar); enables path-style addressing", - "type": "string" - }, - "version_list_url": { - "description": "s3:// url of a manifest file listing versions", - "type": "string" - }, - "version_prefix": { - "description": "s3 key prefix to list objects under for version discovery", - "type": "string" - }, - "version_regex": { - "description": "regex extracting versions from the manifest or object keys", - "type": "string" - }, - "version_json_path": { - "description": "jq-like path extracting versions from json, such as .[].version", - "type": "string" - }, - "version_expr": { - "description": "expr-lang expression post-processing versions (variables: body, versions)", - "type": "string" - } - } - }, - "tool_s3_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "url": { - "$ref": "#/$defs/tool_s3_options/properties/url" - }, - "checksum": { - "$ref": "#/$defs/tool_s3_options/properties/checksum" - }, - "size": { - "$ref": "#/$defs/tool_s3_options/properties/size" - }, - "format": { - "$ref": "#/$defs/tool_s3_options/properties/format" - }, - "strip_components": { - "$ref": "#/$defs/tool_s3_options/properties/strip_components" - }, - "bin": { - "$ref": "#/$defs/tool_s3_options/properties/bin" - }, - "rename_exe": { - "$ref": "#/$defs/tool_s3_options/properties/rename_exe" - }, - "bin_path": { - "$ref": "#/$defs/tool_s3_options/properties/bin_path" - }, - "region": { - "$ref": "#/$defs/tool_s3_options/properties/region" - }, - "endpoint": { - "$ref": "#/$defs/tool_s3_options/properties/endpoint" - }, - "version_list_url": { - "$ref": "#/$defs/tool_s3_options/properties/version_list_url" - }, - "version_prefix": { - "$ref": "#/$defs/tool_s3_options/properties/version_prefix" - }, - "version_regex": { - "$ref": "#/$defs/tool_s3_options/properties/version_regex" - }, - "version_json_path": { - "$ref": "#/$defs/tool_s3_options/properties/version_json_path" - }, - "version_expr": { - "$ref": "#/$defs/tool_s3_options/properties/version_expr" - } - } - } - }, - "tool_s3": { - "description": "a tool installed with the s3 backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_s3_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_s3_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_s3_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_aqua_options": { - "type": "object", - "properties": { - "symlink_bins": { - "description": "expose only the registry-declared binaries through a filtered bin directory", - "$ref": "#/$defs/tool_bool" - }, - "libc": { - "description": "force strict glibc or musl asset selection on linux", - "type": "string", - "enum": ["glibc", "gnu", "musl"] - }, - "slsa_signer_identity": { - "description": "expected fulcio certificate subject for slsa provenance; requires slsa_signer_issuer", - "type": "string" - }, - "slsa_signer_issuer": { - "description": "expected oidc issuer for slsa provenance; requires slsa_signer_identity", - "type": "string" - }, - "vars": { - "description": "values for aqua registry template variables", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } - }, - "tool_aqua": { - "description": "a tool installed with the aqua backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_aqua_options" - } - ], - "type": "object" - } - ] - }, - "tool_cargo_options": { - "type": "object", - "properties": { - "features": { - "description": "crate features to enable (cargo install --features); skips cargo-binstall", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "default-features": { - "description": "set to false to pass --no-default-features; skips cargo-binstall", - "$ref": "#/$defs/tool_bool" - }, - "bin": { - "description": "binary to install when a crate has several (cargo install --bin)", - "type": "string" - }, - "rename_exe": { - "description": "rename the installed executable: a name, or a table of glob = new name", - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - ] - }, - "crate": { - "description": "crate to install when a git repository contains several", - "type": "string" - }, - "locked": { - "description": "pass --locked to cargo install (default true)", - "$ref": "#/$defs/tool_bool" - } - } - }, - "tool_cargo_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "bin": { - "$ref": "#/$defs/tool_cargo_options/properties/bin" - } - } - } - }, - "tool_cargo": { - "description": "a tool installed with the cargo backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_cargo_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_cargo_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_cargo_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_npm_options": { - "type": "object", - "properties": { - "allow_builds": { - "description": "dependency packages whose build scripts are approved; true approves all", - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "trust_policy_excludes": { - "description": "packages or version ranges exempt from aube's trustPolicy no-downgrade check", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "allow_low_downloads": { - "description": "approve the requested package past aube's reputation checks", - "$ref": "#/$defs/tool_bool_text" - }, - "allow_exotic_deps": { - "description": "dependencies allowed from non-registry sources (git, file, tarball url); true allows all", - "oneOf": [ - { - "type": "boolean" - }, - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "aube_args": { - "description": "extra arguments for aube add --global (only with npm.package_manager = aube_cli)", - "type": "string" - }, - "pnpm_args": { - "description": "extra arguments for pnpm installs (shell-split)", - "type": "string" - }, - "bun_args": { - "description": "extra arguments for bun installs (shell-split)", - "type": "string" - }, - "npm_args": { - "description": "extra arguments for npm installs (shell-split)", - "type": "string" - } - } - }, - "tool_npm": { - "description": "a tool installed with the npm backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_npm_options" - } - ], - "type": "object" - } - ] - }, - "tool_pypi_options": { - "type": "object", - "properties": { - "extras": { - "description": "python package extras to install", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "with": { - "description": "additional python requirements installed into the tool environment (requires uv)", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "expose": { - "description": "extra requirements whose executables are exposed (requires uv 0.8.5+)", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "dependency_prereleases": { - "description": "uv prerelease policy for dependency resolution (requires uv)", - "type": "string", - "enum": ["disallow", "allow", "if-necessary", "explicit"] - }, - "python": { - "description": "mise-managed CPython version for this tool (minor or exact patch)", - "type": "string" - }, - "package_name": { - "description": "distribution name for git sources, used to attach extras", - "type": "string" - }, - "pipx_args": { - "description": "extra arguments for pipx install (shell-split)", - "type": "string" - }, - "uvx": { - "description": "set to false to install with pipx instead of uv", - "$ref": "#/$defs/tool_bool" - }, - "uvx_args": { - "description": "extra arguments for uv tool install (shell-split)", - "type": "string" - }, - "registry_url": { - "description": "version-listing url containing a {} placeholder for the package name", - "type": "string" - } - } - }, - "tool_pypi": { - "description": "a tool installed with the pypi backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_pypi_options" - } - ], - "type": "object" - } - ] - }, - "tool_gem_options": { - "type": "object", - "properties": { - "source": { - "description": "gem registry http(s) url used to resolve and install this gem", - "type": "string" - } - } - }, - "tool_gem": { - "description": "a tool installed with the gem backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_gem_options" - } - ], - "type": "object" - } - ] - }, - "tool_go_options": { - "type": "object", - "properties": { - "tags": { - "description": "build tags passed to go install -tags", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - } - }, - "tool_go": { - "description": "a tool installed with the go backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_go_options" - } - ], - "type": "object" - } - ] - }, - "tool_conda_options": { - "type": "object", - "properties": { - "channel": { - "description": "conda channel to resolve the package from (default conda-forge)", - "type": "string" - } - } - }, - "tool_conda": { - "description": "a tool installed with the conda backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_conda_options" - } - ], - "type": "object" - } - ] - }, - "tool_spm_options": { - "type": "object", - "properties": { - "provider": { - "description": "git host used for release info and assets", - "type": "string", - "enum": ["github", "gitlab"] - }, - "api_url": { - "description": "provider api url, for self-hosted instances", - "type": "string" - }, - "artifactbundle": { - "description": "true requires an artifactbundle, false forces a source build, unset tries the bundle first", - "$ref": "#/$defs/tool_bool" - }, - "artifactbundle_asset": { - "description": "specific *.artifactbundle.zip release asset to use", - "type": "string" - }, - "filter_bins": { - "description": "executable products to install", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "install_command": { - "description": "command run in the checkout instead of swift build; incompatible with filter_bins", - "type": "string" - } - } - }, - "tool_spm_platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "type": "object", - "propertyNames": { - "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" - }, - "additionalProperties": { - "type": "object", - "properties": { - "provider": { - "$ref": "#/$defs/tool_spm_options/properties/provider" - }, - "api_url": { - "$ref": "#/$defs/tool_spm_options/properties/api_url" - }, - "artifactbundle": { - "$ref": "#/$defs/tool_spm_options/properties/artifactbundle" - }, - "artifactbundle_asset": { - "$ref": "#/$defs/tool_spm_options/properties/artifactbundle_asset" - } - } - } - }, - "tool_spm": { - "description": "a tool installed with the spm backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_spm_options" - }, - { - "properties": { - "platforms": { - "description": "per-platform option overrides", - "$ref": "#/$defs/tool_spm_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_spm_platforms" - } - } - } - ], - "type": "object" - } - ] - }, - "tool_packslip_options": { - "type": "object", - "properties": { - "variant": { - "description": "publisher-declared alternative build to select, such as fips", - "type": "string" - }, - "pubkey": { - "description": "minisign public key line or path to a .pub file pinning the signer", - "type": "string" - }, - "identity": { - "description": "exact keyless signer certificate identity", - "type": "string" - }, - "identity_prefix": { - "description": "keyless signer certificate identity prefix", - "type": "string" - }, - "workflow": { - "description": "repository workflow file, or a list of them, that signs releases on a tag (or on the ref a name ends with, as in release.yml@refs/heads/main); github.com projects only, not valid with pubkey, identity, identity_prefix, or issuer", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - }, - "minItems": 1 - } - ] - }, - "list_identity_prefix": { - "description": "separate identity prefix for the vendor release list; needs an issuer, not valid with pubkey", - "type": "string" - }, - "issuer": { - "description": "oidc issuer for the keyless signer", - "type": "string" - }, - "allow_unlogged": { - "description": "accept key-signed bundles without transparency-log evidence", - "$ref": "#/$defs/tool_bool" - }, - "ignore_requirements": { - "description": "install despite confirmed host requirement failures", - "$ref": "#/$defs/tool_bool" - }, - "trust": { - "description": "vendor exempts this tool from configured packslip stampers", - "type": "string", - "enum": ["vendor"] - } - } - }, - "tool_packslip": { - "description": "a tool installed with the packslip backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_packslip_options" - } - ], - "type": "object" - } - ] - }, - "tool_spinel_options": { - "type": "object", - "properties": { - "entrypoint": { - "description": "ruby file to compile, relative to the repository root (default main.rb)", - "type": "string" - }, - "bin": { - "description": "name of the executable written to bin/ (default: the repository name)", - "type": "string" - }, - "tag_prefix": { - "description": "text before the version in each git tag, such as v", - "type": "string" - }, - "source_ref": { - "description": "full 40-character commit SHA to build instead of the tag", - "type": "string", - "pattern": "^[0-9a-fA-F]{40}$" - }, - "spinel": { - "description": "spinel compiler to run (default: spinel on PATH)", - "type": "string" - } - } - }, - "tool_spinel": { - "description": "a tool installed with the spinel backend", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_spinel_options" - } - ], - "type": "object" - } - ] - }, - "tool_core_python_options": { - "type": "object", - "properties": { - "patch_sysconfig": { - "description": "patch sysconfig paths of precompiled python after install (default true, unix only)", - "$ref": "#/$defs/tool_bool" - }, - "virtualenv": { - "description": "virtualenv path to activate; use env._.python.venv instead", - "type": "string", - "deprecated": true - } - } - }, - "tool_core_python": { - "description": "the core python tool", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_core_python_options" - } - ], - "type": "object" - } - ] - }, - "tool_core_java_options": { - "type": "object", - "properties": { - "release_type": { - "description": "jdk metadata channel: ga (default) or ea for early access builds", - "type": "string", - "enum": ["ga", "ea"] - } - } - }, - "tool_core_java": { - "description": "the core java tool", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_core_java_options" - } - ], - "type": "object" - } - ] - }, - "tool_core_rust_options": { - "type": "object", - "properties": { - "profile": { - "description": "rustup profile to install, such as minimal, default or complete", - "type": "string" - }, - "components": { - "description": "rustup components to install", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "targets": { - "description": "rustup targets to install, for cross-compilation", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - "mr_boxington": { - "description": "wrap cargo with mr boxington (mbx); needs mr-boxington in tools (default false)", - "$ref": "#/$defs/tool_bool" - } - } - }, - "tool_core_rust": { - "description": "the core rust tool", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_core_rust_options" - } - ], - "type": "object" - } - ] - }, - "tool_core_dotnet_options": { - "type": "object", - "properties": { - "runtime": { - "description": "install a shared runtime instead of the sdk; the version is the runtime version", - "type": "string", - "enum": ["dotnet", "aspnetcore", "windowsdesktop"] - } - } - }, - "tool_core_dotnet": { - "description": "the core dotnet tool", - "oneOf": [ - { - "description": "version of the tool to install", - "type": "string" - }, - { - "allOf": [ - { - "$ref": "#/$defs/tool" - }, - { - "$ref": "#/$defs/tool_core_dotnet_options" - } - ], - "type": "object" - } - ] - }, - "tool_options": { - "properties": { - "version": { - "description": "version of the tool to install", - "type": "string" - }, - "path": { - "description": "path to a custom compiled version", - "type": "string" - }, - "prefix": { - "description": "version prefix to resolve", - "type": "string" - }, - "ref": { - "description": "version control reference to compile", - "type": "string" - }, - "os": { - "$ref": "#/$defs/os_filter" - }, - "minimum_release_age": { - "description": "minimum time since a version's release before mise selects it, as a duration such as \"7d\" or an absolute date", - "type": "string" - }, - "install_before": { - "description": "deprecated alias for minimum_release_age", - "deprecated": true, - "type": "string" - }, - "version_order": { - "description": "ordering applied to remote versions: \"source\" keeps the backend's order, \"semver\" sorts semantic versions", - "enum": ["source", "semver"] - }, - "prerelease": { - "description": "include pre-release versions when listing versions and resolving latest", - "$ref": "#/$defs/tool_bool" - }, - "platforms": { - "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", - "$ref": "#/$defs/tool_platforms" - }, - "platform": { - "description": "per-platform option overrides (alias of platforms)", - "$ref": "#/$defs/tool_platforms" - } - }, - "oneOf": [ - { - "required": ["version"] - }, - { - "required": ["path"] - }, - { - "required": ["prefix"] - }, - { - "required": ["ref"] - } - ], - "type": "object" - }, - "hook_run_string": { - "description": "inline hook command to run", - "type": "string" - }, - "hook_run_table": { - "unevaluatedProperties": false, - "properties": { - "run": { - "description": "inline hook command to run", - "type": "string" - }, - "run_windows": { - "description": "inline hook command to run on Windows", - "type": "string" - }, - "shell": { - "description": "inline shell command to use for run hooks, such as \"bash -c\"", - "type": "string" - } - }, - "anyOf": [ - { - "required": ["run"] - }, - { - "required": ["run_windows"] - } - ], - "type": "object" - }, - "hook_script_value": { + "task_dependency_item": { + "description": "task name and args", "oneOf": [ { "type": "string" @@ -5098,342 +3727,40 @@ "type": "array", "items": { "type": "string" - } + }, + "minItems": 1 + }, + { + "$ref": "#/$defs/task_dependency_table" } ] }, - "hook_scripts_value": { - "type": "array", - "items": { - "type": "string" - } - }, - "hook_current_shell_script_table": { - "unevaluatedProperties": false, - "properties": { - "script": { - "description": "script or scripts to evaluate in the active shell for enter/leave/cd hooks", - "$ref": "#/$defs/hook_script_value" - }, - "shell": { - "description": "active shell name for current-shell hooks, such as \"bash\"", - "type": "string" - } - }, - "required": ["script", "shell"], - "type": "object" - }, - "hook_current_shell_scripts_table": { - "unevaluatedProperties": false, - "properties": { - "scripts": { - "description": "scripts to evaluate in the active shell for enter/leave/cd hooks", - "$ref": "#/$defs/hook_scripts_value" - }, - "shell": { - "description": "active shell name for current-shell hooks, such as \"bash\"", - "type": "string" - } - }, - "required": ["scripts", "shell"], - "type": "object" - }, - "hook_spawned_script_table": { - "deprecated": true, - "unevaluatedProperties": false, - "properties": { - "script": { - "deprecated": true, - "description": "deprecated alias for run when an enter/leave/cd hook has no shell", - "$ref": "#/$defs/hook_script_value" - } - }, - "required": ["script"], - "type": "object" - }, - "hook_spawned_scripts_table": { - "deprecated": true, - "unevaluatedProperties": false, - "properties": { - "scripts": { - "deprecated": true, - "description": "deprecated alias for run when an enter/leave/cd hook has no shell", - "$ref": "#/$defs/hook_scripts_value" - } - }, - "required": ["scripts"], - "type": "object" - }, - "hook_install_script_table": { - "deprecated": true, - "unevaluatedProperties": false, - "properties": { - "script": { - "deprecated": true, - "description": "deprecated alias for run in preinstall/postinstall hooks; executes in a subprocess", - "$ref": "#/$defs/hook_script_value" - }, - "shell": { - "deprecated": true, - "description": "ignored for preinstall/postinstall hooks that use script; use a run hook to select an inline shell", - "type": "string" - } - }, - "required": ["script"], - "type": "object" - }, - "hook_install_scripts_table": { - "deprecated": true, - "unevaluatedProperties": false, - "properties": { - "scripts": { - "deprecated": true, - "description": "deprecated alias for run in preinstall/postinstall hooks; joins the scripts and executes them in a subprocess", - "$ref": "#/$defs/hook_scripts_value" - }, - "shell": { - "deprecated": true, - "description": "ignored for preinstall/postinstall hooks that use scripts; use a run hook to select an inline shell", - "type": "string" - } - }, - "required": ["scripts"], - "type": "object" - }, - "hook_task_ref": { - "unevaluatedProperties": false, + "task_dependency_table": { + "description": "task dependency with args, env, and optional flag", + "type": "object", "properties": { "task": { - "description": "mise task to run", "type": "string" - } - }, - "required": ["task"], - "type": "object" - }, - "hook_definition_item": { - "oneOf": [ - { - "$ref": "#/$defs/hook_run_string" }, - { - "$ref": "#/$defs/hook_run_table" - }, - { - "$ref": "#/$defs/hook_current_shell_script_table" - }, - { - "$ref": "#/$defs/hook_current_shell_scripts_table" - }, - { - "$ref": "#/$defs/hook_spawned_script_table" - }, - { - "$ref": "#/$defs/hook_spawned_scripts_table" - }, - { - "$ref": "#/$defs/hook_task_ref" - } - ] - }, - "hook_definition": { - "oneOf": [ - { - "$ref": "#/$defs/hook_definition_item" - }, - { - "description": "scripts/tasks to run", + "args": { + "type": "array", "items": { - "$ref": "#/$defs/hook_definition_item" - }, - "type": "array" - } - ] - }, - "hook_install_definition_item": { - "oneOf": [ - { - "$ref": "#/$defs/hook_run_string" - }, - { - "$ref": "#/$defs/hook_run_table" - }, - { - "$ref": "#/$defs/hook_install_script_table" - }, - { - "$ref": "#/$defs/hook_install_scripts_table" - }, - { - "$ref": "#/$defs/hook_task_ref" - } - ] - }, - "hook_install_definition": { - "oneOf": [ - { - "$ref": "#/$defs/hook_install_definition_item" - }, - { - "description": "install hooks to run", - "items": { - "$ref": "#/$defs/hook_install_definition_item" - }, - "type": "array" - } - ] - }, - "hooks": { - "description": "hooks to run", - "type": "object", - "properties": { - "enter": { - "$ref": "#/$defs/hook_definition" - }, - "leave": { - "$ref": "#/$defs/hook_definition" - }, - "cd": { - "$ref": "#/$defs/hook_definition" - }, - "preinstall": { - "$ref": "#/$defs/hook_install_definition" - }, - "postinstall": { - "$ref": "#/$defs/hook_install_definition" - } - }, - "additionalProperties": false - }, - "watch_files": { - "description": "files to watch for changes", - "type": "array", - "items": { - "type": "object", - "description": "file to watch for changes", - "oneOf": [ - { - "unevaluatedProperties": false, - "properties": { - "run": { - "type": "string", - "description": "script to run when file changes" - }, - "shell": { - "type": "string", - "description": "inline shell command used to run script when file changes" - }, - "patterns": { - "type": "array", - "description": "patterns to watch for", - "items": { - "type": "string" - } - } - }, - "required": ["run", "patterns"] - }, - { - "unevaluatedProperties": false, - "properties": { - "task": { - "type": "string", - "description": "mise task to run when file changes" - }, - "patterns": { - "type": "array", - "description": "patterns to watch for", - "items": { - "type": "string" - } - } - }, - "required": ["task", "patterns"] + "type": "string" } - ] - } - }, - "deps_provider": { - "type": "object", - "properties": { - "auto": { - "type": "boolean", - "default": false, - "description": "Auto-run before `mise x` and `mise run`" - }, - "sources": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Files/patterns to check for changes" - }, - "outputs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Files/directories that should be newer than sources" - }, - "run": { - "type": "string", - "description": "Command to run when stale" }, "env": { "type": "object", "additionalProperties": { "type": "string" - }, - "description": "Environment variables to set" + } }, - "dir": { - "type": "string", - "description": "Working directory for the command" - }, - "description": { - "type": "string", - "description": "Description shown in output" - }, - "depends": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Other deps providers that must complete before this one runs" - }, - "timeout": { - "type": "string", - "description": "Timeout for the run command (e.g., \"30s\", \"5m\", \"1h\")" - } - }, - "unevaluatedProperties": false, - "description": "Prepare provider configuration" - }, - "task_watch": { - "description": "options used by `mise watch` for this task", - "type": "object", - "unevaluatedProperties": false, - "properties": { - "no_vcs_ignore": { - "default": false, - "description": "do not load VCS ignore files such as `.gitignore` when watching this task", + "optional": { + "description": "do not fail when the task name or pattern has no matches", "type": "boolean" } - } - }, - "daemon_init": { - "description": "idempotent setup commands run before the daemon's long-running process", - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] + }, + "required": ["task"], + "unevaluatedProperties": false }, "task_props": { "type": "object", @@ -5923,6 +4250,54 @@ } } }, + "task_run_entry": { + "oneOf": [ + { + "description": "script to run", + "type": "string" + }, + { + "type": "object", + "unevaluatedProperties": false, + "properties": { + "task": { + "description": "single task name to run", + "type": "string" + }, + "args": { + "description": "arguments to pass to the task", + "type": "array", + "items": { + "type": "string" + } + }, + "env": { + "description": "environment variables to set for the task", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "required": ["task"] + }, + { + "type": "object", + "unevaluatedProperties": false, + "properties": { + "tasks": { + "description": "parallel task group to run", + "items": { + "description": "task name and args", + "type": "string" + }, + "type": "array" + } + }, + "required": ["tasks"] + } + ] + }, "task_template": { "description": "task template that can be extended by tasks", "allOf": [ @@ -5943,157 +4318,997 @@ "unevaluatedProperties": false, "type": "object" }, - "launchd_calendar_interval": { + "task_watch": { + "description": "options used by `mise watch` for this task", "type": "object", - "description": "calendar schedule fields for launchd StartCalendarInterval", + "unevaluatedProperties": false, "properties": { - "minute": { - "type": "integer", - "maximum": 59, - "minimum": 0 - }, - "hour": { - "type": "integer", - "maximum": 23, - "minimum": 0 - }, - "day": { - "type": "integer", - "maximum": 31, - "minimum": 1 - }, - "weekday": { - "type": "integer", - "maximum": 7, - "minimum": 0 - }, - "month": { - "type": "integer", - "maximum": 12, - "minimum": 1 + "no_vcs_ignore": { + "default": false, + "description": "do not load VCS ignore files such as `.gitignore` when watching this task", + "type": "boolean" } - }, - "minProperties": 1, - "additionalProperties": false + } }, - "bootstrap_file_phase": { - "type": "string", - "enum": ["pre-packages", "post-packages"], - "default": "post-packages", - "description": "When to apply the managed path during bootstrap; pre-packages runs before the pre-packages hook" - }, - "bootstrap_hook": { - "description": "command or commands to run for a bootstrap phase", + "tool": { "oneOf": [ { + "description": "version of the tool to install", "type": "string" }, { - "type": "array", - "items": { + "allOf": [ + { + "$ref": "#/$defs/tool_options" + }, + { + "properties": { + "auto_update": { + "description": "update this tool before it runs when its check is due; true, or a check interval such as \"12h\". Only honored in global config", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ] + }, + "install_env": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/env_value" + }, + "description": "environment variables during tool installation" + }, + "depends": { + "oneOf": [ + { + "description": "tools that must be installed before this tool", + "type": "string" + }, + { + "description": "tools that must be installed before this tool", + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "lazy": { + "description": "defer installation until one of the tool's bootstrap shims is invoked", + "type": "boolean", + "default": false + }, + "lazy_bins": { + "description": "bootstrap shim names for lazy tools without registry bin metadata", + "oneOf": [ + { + "type": "string", + "minLength": 1, + "pattern": "^[^/\\\\]+$" + }, + { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "pattern": "^[^/\\\\]+$" + } + } + ] + }, + "postinstall": { + "description": "command to run after tool installation, or on every install when requested", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "run": { + "type": "string" + }, + "when": { + "type": "string", + "enum": ["install", "always"], + "default": "install" + } + }, + "required": ["run"], + "additionalProperties": false + } + ] + } + } + } + ], + "type": "object", + "unevaluatedProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "object", + "additionalProperties": true + } + ] + } + } + ] + }, + "tool_alias_entry": { + "oneOf": [ + { + "description": "where the alias goes", + "type": "string" + }, + { + "description": "tool to set aliases for", + "type": "object", + "properties": { + "backend": { + "description": "where the alias goes", + "type": "string" + }, + "versions": { + "$ref": "#/$defs/tool_alias_versions" + } + }, + "additionalProperties": { + "description": "version alias points to", "type": "string" } - }, - { - "type": "object", - "properties": { - "run": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } - ] - } - }, - "required": ["run"], - "additionalProperties": true } ] }, - "bootstrap_shell_activation": { + "tool_alias_versions": { + "description": "tool version aliases", + "type": "object", + "additionalProperties": { + "description": "version alias points to", + "type": "string" + } + }, + "tool_aqua": { + "description": "a tool installed with the aqua backend", "oneOf": [ { - "type": "boolean", - "description": "enable the target with its default mode, or disable it" + "description": "version of the tool to install", + "type": "string" }, { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_aqua_options" + } + ], + "type": "object" + } + ] + }, + "tool_aqua_options": { + "type": "object", + "properties": { + "symlink_bins": { + "description": "expose only the registry-declared binaries through a filtered bin directory", + "$ref": "#/$defs/tool_bool" + }, + "libc": { + "description": "force strict glibc or musl asset selection on linux", "type": "string", - "enum": ["activate", "shims", "auto"], - "description": "enable the target with the selected activation mode" + "enum": ["glibc", "gnu", "musl"] + }, + "slsa_signer_identity": { + "description": "expected fulcio certificate subject for slsa provenance; requires slsa_signer_issuer", + "type": "string" + }, + "slsa_signer_issuer": { + "description": "expected oidc issuer for slsa provenance; requires slsa_signer_identity", + "type": "string" }, + "vars": { + "description": "values for aqua registry template variables", + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "tool_bool": { + "description": "a boolean, also accepted as the strings true/false or the integers 1/0", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string", + "pattern": "^\\s*([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|1|0)\\s*$" + }, { - "type": "object", - "description": "shell activation settings for the target", - "properties": { - "enabled": { - "type": "boolean", - "description": "whether to manage the activation block" - }, - "mode": { - "type": "string", - "enum": ["activate", "shims", "auto"], - "description": "activation mode; defaults according to the target" - } - }, - "required": ["enabled"], - "additionalProperties": false + "type": "integer", + "enum": [0, 1] } ] }, - "bootstrap_remote_install_mise": { + "tool_bool_text": { + "description": "a boolean, also accepted as the string true or false", "oneOf": [ { - "type": "boolean", - "description": "install the provisioned mise to ~/.local/bin/mise, or do not install it" + "type": "boolean" }, { "type": "string", - "description": "absolute or ~/-relative remote path to install the provisioned mise to" + "pattern": "^([Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee])$" } ] + }, + "tool_cargo": { + "description": "a tool installed with the cargo backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_cargo_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_cargo_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_cargo_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_cargo_options": { + "type": "object", + "properties": { + "features": { + "description": "crate features to enable (cargo install --features); skips cargo-binstall", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "default-features": { + "description": "set to false to pass --no-default-features; skips cargo-binstall", + "$ref": "#/$defs/tool_bool" + }, + "bin": { + "description": "binary to install when a crate has several (cargo install --bin)", + "type": "string" + }, + "rename_exe": { + "description": "rename the installed executable: a name, or a table of glob = new name", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "crate": { + "description": "crate to install when a git repository contains several", + "type": "string" + }, + "locked": { + "description": "pass --locked to cargo install (default true)", + "$ref": "#/$defs/tool_bool" + } + } }, - "bootstrap_apt_sources": { + "tool_cargo_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", "type": "object", - "description": "apt repositories added before apt packages are installed, keyed by a name used in the files mise writes", "propertyNames": { - "pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$" + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" }, "additionalProperties": { "type": "object", - "additionalProperties": false, "properties": { - "ppa": { - "type": "string", - "description": "Launchpad PPA as owner/archive, such as git-core/ppa; added on Ubuntu and Ubuntu-based releases only" + "bin": { + "$ref": "#/$defs/tool_cargo_options/properties/bin" + } + } + } + }, + "tool_conda": { + "description": "a tool installed with the conda backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_conda_options" + } + ], + "type": "object" + } + ] + }, + "tool_conda_options": { + "type": "object", + "properties": { + "channel": { + "description": "conda channel to resolve the package from (default conda-forge)", + "type": "string" + } + } + }, + "tool_config": { + "type": "object", + "description": "policy for tools declared by configs sharing the containing config root", + "additionalProperties": false, + "properties": { + "locked": { + "type": "boolean", + "description": "require tools declared by this config root to resolve and install from their lockfiles", + "default": false + } + } + }, + "tool_core_dotnet": { + "description": "the core dotnet tool", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_core_dotnet_options" + } + ], + "type": "object" + } + ] + }, + "tool_core_dotnet_options": { + "type": "object", + "properties": { + "runtime": { + "description": "install a shared runtime instead of the sdk; the version is the runtime version", + "type": "string", + "enum": ["dotnet", "aspnetcore", "windowsdesktop"] + } + } + }, + "tool_core_java": { + "description": "the core java tool", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_core_java_options" + } + ], + "type": "object" + } + ] + }, + "tool_core_java_options": { + "type": "object", + "properties": { + "release_type": { + "description": "jdk metadata channel: ga (default) or ea for early access builds", + "type": "string", + "enum": ["ga", "ea"] + } + } + }, + "tool_core_python": { + "description": "the core python tool", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_core_python_options" + } + ], + "type": "object" + } + ] + }, + "tool_core_python_options": { + "type": "object", + "properties": { + "patch_sysconfig": { + "description": "patch sysconfig paths of precompiled python after install (default true, unix only)", + "$ref": "#/$defs/tool_bool" + }, + "virtualenv": { + "description": "virtualenv path to activate; use env._.python.venv instead", + "type": "string", + "deprecated": true + } + } + }, + "tool_core_rust": { + "description": "the core rust tool", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_core_rust_options" + } + ], + "type": "object" + } + ] + }, + "tool_core_rust_options": { + "type": "object", + "properties": { + "profile": { + "description": "rustup profile to install, such as minimal, default or complete", + "type": "string" + }, + "components": { + "description": "rustup components to install", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "targets": { + "description": "rustup targets to install, for cross-compilation", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "mr_boxington": { + "description": "wrap cargo with mr boxington (mbx); needs mr-boxington in tools (default false)", + "$ref": "#/$defs/tool_bool" + } + } + }, + "tool_forge_options": { + "type": "object", + "properties": { + "version_prefix": { + "description": "release tag prefix stripped from versions and re-added when resolving tags", + "type": "string" + }, + "asset_pattern": { + "description": "glob (* and ?) matched against release asset names, tera templated; replaces autodetection", + "type": "string" + }, + "additional_asset_patterns": { + "description": "extra archives from the same release extracted over the primary asset, in order", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "matching": { + "description": "substring an asset name must contain, applied before platform autodetection", + "type": "string" + }, + "matching_regex": { + "description": "regex an asset name must match, applied before platform autodetection", + "type": "string" + }, + "no_app": { + "description": "penalize macos .app bundle assets during autodetection", + "$ref": "#/$defs/tool_bool" + }, + "filter_bins": { + "description": "only expose these binaries on PATH", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "format": { + "description": "force the archive format instead of detecting it from the file name", + "type": "string", + "enum": [ + "tar.gz", + "tgz", + "gz", + "tar.xz", + "txz", + "xz", + "tar.bz2", + "tbz2", + "tbz", + "bz2", + "tar.zst", + "tzst", + "zst", + "tar", + "zip", + "vsix", + "7z", + "tar.br", + "tbr", + "br", + "tar.lz4", + "tlz4", + "lz4", + "tar.sz", + "tsz", + "sz", + "rar", + "raw" + ] + }, + "checksum": { + "description": "expected artifact digest as algo:hash (sha256, sha512, sha1, md5, blake3)", + "type": "string" + }, + "size": { + "description": "expected artifact size in bytes", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "strip_components": { + "description": "leading directory components to strip on extract (auto 1 for single-root archives)", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "bin": { + "description": "executable name (or safe relative path) to install the downloaded binary as", + "type": "string" + }, + "rename_exe": { + "description": "rename the extracted executable: a name, or a table of glob = new name", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "bin_path": { + "description": "directory inside the install that contains binaries (tera templated)", + "type": "string" + } + } + }, + "tool_forge_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "asset_pattern": { + "$ref": "#/$defs/tool_forge_options/properties/asset_pattern" }, - "uris": { - "type": "string", - "description": "repository URL(s), space-separated" + "additional_asset_patterns": { + "$ref": "#/$defs/tool_forge_options/properties/additional_asset_patterns" }, - "suites": { - "type": "string", - "description": "suite(s), space-separated; defaults to the release codename" + "url": { + "description": "direct download url for this platform, tera templated; skips release asset selection", + "type": "string" }, - "components": { - "type": "string", - "description": "component(s), space-separated; defaults to main, or none when suites is a path ending in /" + "no_app": { + "$ref": "#/$defs/tool_forge_options/properties/no_app" }, - "architectures": { - "type": "string", - "description": "architecture(s) to fetch, space-separated" + "filter_bins": { + "$ref": "#/$defs/tool_forge_options/properties/filter_bins" }, - "key": { - "type": "string", - "description": "http(s) URL of the repository's signing key, armored or binary" + "checksum": { + "$ref": "#/$defs/tool_forge_options/properties/checksum" }, - "fingerprint": { - "description": "full primary key fingerprint(s) the signing key must have; required with key", + "size": { + "$ref": "#/$defs/tool_forge_options/properties/size" + }, + "strip_components": { + "$ref": "#/$defs/tool_forge_options/properties/strip_components" + }, + "bin": { + "$ref": "#/$defs/tool_forge_options/properties/bin" + }, + "rename_exe": { + "$ref": "#/$defs/tool_forge_options/properties/rename_exe" + }, + "bin_path": { + "$ref": "#/$defs/tool_forge_options/properties/bin_path" + }, + "format": { + "$ref": "#/$defs/tool_forge_options/properties/format" + } + } + } + }, + "tool_forgejo": { + "description": "a tool installed with the forgejo backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_forge_options" + }, + { + "$ref": "#/$defs/tool_forgejo_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_forge_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_forge_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_forgejo_options": { + "type": "object", + "properties": { + "api_url": { + "description": "api base url, for self-hosted forgejo or gitea instances", + "type": "string" + } + } + }, + "tool_gem": { + "description": "a tool installed with the gem backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_gem_options" + } + ], + "type": "object" + } + ] + }, + "tool_gem_options": { + "type": "object", + "properties": { + "source": { + "description": "gem registry http(s) url used to resolve and install this gem", + "type": "string" + } + } + }, + "tool_github": { + "description": "a tool installed with the github backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_forge_options" + }, + { + "$ref": "#/$defs/tool_github_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_forge_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_forge_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_github_options": { + "type": "object", + "properties": { + "api_url": { + "description": "api base url, for github enterprise or self-hosted instances", + "type": "string" + }, + "github_attestations": { + "description": "verify github artifact attestations when available", + "$ref": "#/$defs/tool_bool" + }, + "slsa_signer_identity": { + "description": "expected fulcio certificate identity for slsa provenance, tera templated", + "type": "string" + }, + "slsa_signer_issuer": { + "description": "expected oidc issuer for slsa provenance; requires slsa_signer_identity", + "type": "string" + } + } + }, + "tool_gitlab": { + "description": "a tool installed with the gitlab backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_forge_options" + }, + { + "$ref": "#/$defs/tool_gitlab_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_forge_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_forge_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_gitlab_options": { + "type": "object", + "properties": { + "api_url": { + "description": "api base url, for self-hosted gitlab instances", + "type": "string" + } + } + }, + "tool_go": { + "description": "a tool installed with the go backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_go_options" + } + ], + "type": "object" + } + ] + }, + "tool_go_options": { + "type": "object", + "properties": { + "tags": { + "description": "build tags passed to go install -tags", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + } + }, + "tool_http": { + "description": "a tool installed with the http backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_http_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_http_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_http_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_http_options": { + "type": "object", + "properties": { + "url": { + "description": "download url, tera templated, http(s) or file://; required for each platform", + "type": "string" + }, + "checksum": { + "description": "expected artifact digest as algo:hash", + "type": "string" + }, + "checksum_url": { + "description": "checksum file or manifest url, used by mise lock without downloading", + "type": "string" + }, + "checksum_expr": { + "description": "expr-lang expression extracting algo:hash from the checksum_url manifest", + "type": "string" + }, + "headers": { + "description": "request headers sent with the download, version_list_url and checksum_url requests; values are tera templated (e.g. Bearer {{ env.TOKEN }})", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "headers_forward": { + "description": "hosts each header in headers may follow a redirect to; exact hosts or *.suffix, and a header with no entry is never forwarded", + "type": "object", + "additionalProperties": { "oneOf": [ { "type": "string" @@ -6107,427 +5322,1212 @@ ] } }, - "oneOf": [ - { - "required": ["ppa"], - "properties": { - "fingerprint": { + "size": { + "description": "expected artifact size in bytes", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "format": { + "description": "force the archive format instead of detecting it from the url", + "type": "string", + "enum": [ + "tar.gz", + "tgz", + "gz", + "tar.xz", + "txz", + "xz", + "tar.bz2", + "tbz2", + "tbz", + "bz2", + "tar.zst", + "tzst", + "zst", + "tar", + "zip", + "vsix", + "7z", + "tar.br", + "tbr", + "br", + "tar.lz4", + "tlz4", + "lz4", + "tar.sz", + "tsz", + "sz", + "rar", + "raw" + ] + }, + "strip_components": { + "description": "leading directory components to strip on extract (auto 1 for single-root archives)", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "bin": { + "description": "name for a downloaded raw or compressed single binary (ignored for archives)", + "type": "string" + }, + "rename_exe": { + "description": "rename the extracted executable: a name, or a table of glob = new name", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { "type": "string" } + } + ] + }, + "bin_path": { + "description": "directory inside the install that contains binaries (tera templated)", + "type": "string" + }, + "shared_extraction": { + "description": "share extracted content between installs with identical artifacts", + "$ref": "#/$defs/tool_bool" + }, + "windows_script_interpreter": { + "description": "windows only: write a .cmd launcher running the raw script with this interpreter", + "type": "string" + }, + "version_list_url": { + "description": "url returning the version list (text, json or html)", + "type": "string" + }, + "version_regex": { + "description": "regex extracting versions from the list (first capture group or whole match)", + "type": "string" + }, + "version_json_path": { + "description": "jq-like path extracting versions from json, such as .[].version", + "type": "string" + }, + "version_expr": { + "description": "expr-lang expression post-processing versions (variables: body, versions)", + "type": "string" + } + } + }, + "tool_http_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "url": { + "$ref": "#/$defs/tool_http_options/properties/url" + }, + "headers": { + "$ref": "#/$defs/tool_http_options/properties/headers" + }, + "headers_forward": { + "$ref": "#/$defs/tool_http_options/properties/headers_forward" + }, + "checksum": { + "$ref": "#/$defs/tool_http_options/properties/checksum" + }, + "checksum_url": { + "$ref": "#/$defs/tool_http_options/properties/checksum_url" + }, + "size": { + "$ref": "#/$defs/tool_http_options/properties/size" + }, + "format": { + "$ref": "#/$defs/tool_http_options/properties/format" + }, + "strip_components": { + "$ref": "#/$defs/tool_http_options/properties/strip_components" + }, + "bin": { + "$ref": "#/$defs/tool_http_options/properties/bin" + }, + "rename_exe": { + "$ref": "#/$defs/tool_http_options/properties/rename_exe" + }, + "bin_path": { + "$ref": "#/$defs/tool_http_options/properties/bin_path" + }, + "windows_script_interpreter": { + "$ref": "#/$defs/tool_http_options/properties/windows_script_interpreter" + } + } + } + }, + "tool_npm": { + "description": "a tool installed with the npm backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" }, - "not": { - "anyOf": [ + { + "$ref": "#/$defs/tool_npm_options" + } + ], + "type": "object" + } + ] + }, + "tool_npm_options": { + "type": "object", + "properties": { + "allow_builds": { + "description": "dependency packages whose build scripts are approved; true approves all", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "trust_policy_excludes": { + "description": "packages or version ranges exempt from aube's trustPolicy no-downgrade check", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "allow_low_downloads": { + "description": "approve the requested package past aube's reputation checks", + "$ref": "#/$defs/tool_bool_text" + }, + "allow_exotic_deps": { + "description": "dependencies allowed from non-registry sources (git, file, tarball url); true allows all", + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "aube_args": { + "description": "extra arguments for aube add --global (only with npm.package_manager = aube_cli)", + "type": "string" + }, + "pnpm_args": { + "description": "extra arguments for pnpm installs (shell-split)", + "type": "string" + }, + "bun_args": { + "description": "extra arguments for bun installs (shell-split)", + "type": "string" + }, + "npm_args": { + "description": "extra arguments for npm installs (shell-split)", + "type": "string" + } + } + }, + "tool_options": { + "properties": { + "version": { + "description": "version of the tool to install", + "type": "string" + }, + "path": { + "description": "path to a custom compiled version", + "type": "string" + }, + "prefix": { + "description": "version prefix to resolve", + "type": "string" + }, + "ref": { + "description": "version control reference to compile", + "type": "string" + }, + "os": { + "$ref": "#/$defs/os_filter" + }, + "minimum_release_age": { + "description": "minimum time since a version's release before mise selects it, as a duration such as \"7d\" or an absolute date", + "type": "string" + }, + "install_before": { + "description": "deprecated alias for minimum_release_age", + "deprecated": true, + "type": "string" + }, + "version_order": { + "description": "ordering applied to remote versions: \"source\" keeps the backend's order, \"semver\" sorts semantic versions", + "enum": ["source", "semver"] + }, + "prerelease": { + "description": "include pre-release versions when listing versions and resolving latest", + "$ref": "#/$defs/tool_bool" + }, + "platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "$ref": "#/$defs/tool_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_platforms" + } + }, + "oneOf": [ + { + "required": ["version"] + }, + { + "required": ["path"] + }, + { + "required": ["prefix"] + }, + { + "required": ["ref"] + } + ], + "type": "object" + }, + "tool_packslip": { + "description": "a tool installed with the packslip backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_packslip_options" + } + ], + "type": "object" + } + ] + }, + "tool_packslip_options": { + "type": "object", + "properties": { + "variant": { + "description": "publisher-declared alternative build to select, such as fips", + "type": "string" + }, + "pubkey": { + "description": "minisign public key line or path to a .pub file pinning the signer", + "type": "string" + }, + "identity": { + "description": "exact keyless signer certificate identity", + "type": "string" + }, + "identity_prefix": { + "description": "keyless signer certificate identity prefix", + "type": "string" + }, + "workflow": { + "description": "repository workflow file, or a list of them, that signs releases on a tag (or on the ref a name ends with, as in release.yml@refs/heads/main); github.com projects only, not valid with pubkey, identity, identity_prefix, or issuer", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + ] + }, + "list_identity_prefix": { + "description": "separate identity prefix for the vendor release list; needs an issuer, not valid with pubkey", + "type": "string" + }, + "issuer": { + "description": "oidc issuer for the keyless signer", + "type": "string" + }, + "allow_unlogged": { + "description": "accept key-signed bundles without transparency-log evidence", + "$ref": "#/$defs/tool_bool" + }, + "ignore_requirements": { + "description": "install despite confirmed host requirement failures", + "$ref": "#/$defs/tool_bool" + }, + "trust": { + "description": "vendor exempts this tool from configured packslip stampers", + "type": "string", + "enum": ["vendor"] + } + } + }, + "tool_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object" + } + }, + "tool_pypi": { + "description": "a tool installed with the pypi backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_pypi_options" + } + ], + "type": "object" + } + ] + }, + "tool_pypi_options": { + "type": "object", + "properties": { + "extras": { + "description": "python package extras to install", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "with": { + "description": "additional python requirements installed into the tool environment (requires uv)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "expose": { + "description": "extra requirements whose executables are exposed (requires uv 0.8.5+)", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "dependency_prereleases": { + "description": "uv prerelease policy for dependency resolution (requires uv)", + "type": "string", + "enum": ["disallow", "allow", "if-necessary", "explicit"] + }, + "python": { + "description": "mise-managed CPython version for this tool (minor or exact patch)", + "type": "string" + }, + "package_name": { + "description": "distribution name for git sources, used to attach extras", + "type": "string" + }, + "pipx_args": { + "description": "extra arguments for pipx install (shell-split)", + "type": "string" + }, + "uvx": { + "description": "set to false to install with pipx instead of uv", + "$ref": "#/$defs/tool_bool" + }, + "uvx_args": { + "description": "extra arguments for uv tool install (shell-split)", + "type": "string" + }, + "registry_url": { + "description": "version-listing url containing a {} placeholder for the package name", + "type": "string" + } + } + }, + "tool_s3": { + "description": "a tool installed with the s3 backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_s3_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_s3_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_s3_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_s3_options": { + "type": "object", + "properties": { + "url": { + "description": "s3://bucket/key object url, tera templated; required for each platform", + "type": "string" + }, + "checksum": { + "description": "expected artifact digest as algo:hash", + "type": "string" + }, + "size": { + "description": "expected artifact size in bytes", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "format": { + "description": "force the archive format when the object key has no usable extension", + "type": "string", + "enum": [ + "tar.gz", + "tgz", + "gz", + "tar.xz", + "txz", + "xz", + "tar.bz2", + "tbz2", + "tbz", + "bz2", + "tar.zst", + "tzst", + "zst", + "tar", + "zip", + "vsix", + "7z", + "tar.br", + "tbr", + "br", + "tar.lz4", + "tlz4", + "lz4", + "tar.sz", + "tsz", + "sz", + "rar", + "raw" + ] + }, + "strip_components": { + "description": "leading directory components to strip on extract (auto 1 for single-root archives)", + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ] + }, + "bin": { + "description": "executable name (or safe relative path) to install the downloaded binary as", + "type": "string" + }, + "rename_exe": { + "description": "rename the extracted executable: a name, or a table of glob = new name", + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + ] + }, + "bin_path": { + "description": "directory inside the install that contains binaries (tera templated)", + "type": "string" + }, + "region": { + "description": "aws region used for the s3 client", + "type": "string" + }, + "endpoint": { + "description": "custom s3-compatible endpoint url (minio and similar); enables path-style addressing", + "type": "string" + }, + "version_list_url": { + "description": "s3:// url of a manifest file listing versions", + "type": "string" + }, + "version_prefix": { + "description": "s3 key prefix to list objects under for version discovery", + "type": "string" + }, + "version_regex": { + "description": "regex extracting versions from the manifest or object keys", + "type": "string" + }, + "version_json_path": { + "description": "jq-like path extracting versions from json, such as .[].version", + "type": "string" + }, + "version_expr": { + "description": "expr-lang expression post-processing versions (variables: body, versions)", + "type": "string" + } + } + }, + "tool_s3_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "url": { + "$ref": "#/$defs/tool_s3_options/properties/url" + }, + "checksum": { + "$ref": "#/$defs/tool_s3_options/properties/checksum" + }, + "size": { + "$ref": "#/$defs/tool_s3_options/properties/size" + }, + "format": { + "$ref": "#/$defs/tool_s3_options/properties/format" + }, + "strip_components": { + "$ref": "#/$defs/tool_s3_options/properties/strip_components" + }, + "bin": { + "$ref": "#/$defs/tool_s3_options/properties/bin" + }, + "rename_exe": { + "$ref": "#/$defs/tool_s3_options/properties/rename_exe" + }, + "bin_path": { + "$ref": "#/$defs/tool_s3_options/properties/bin_path" + }, + "region": { + "$ref": "#/$defs/tool_s3_options/properties/region" + }, + "endpoint": { + "$ref": "#/$defs/tool_s3_options/properties/endpoint" + }, + "version_list_url": { + "$ref": "#/$defs/tool_s3_options/properties/version_list_url" + }, + "version_prefix": { + "$ref": "#/$defs/tool_s3_options/properties/version_prefix" + }, + "version_regex": { + "$ref": "#/$defs/tool_s3_options/properties/version_regex" + }, + "version_json_path": { + "$ref": "#/$defs/tool_s3_options/properties/version_json_path" + }, + "version_expr": { + "$ref": "#/$defs/tool_s3_options/properties/version_expr" + } + } + } + }, + "tool_spinel": { + "description": "a tool installed with the spinel backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_spinel_options" + } + ], + "type": "object" + } + ] + }, + "tool_spinel_options": { + "type": "object", + "properties": { + "entrypoint": { + "description": "ruby file to compile, relative to the repository root (default main.rb)", + "type": "string" + }, + "bin": { + "description": "name of the executable written to bin/ (default: the repository name)", + "type": "string" + }, + "tag_prefix": { + "description": "text before the version in each git tag, such as v", + "type": "string" + }, + "source_ref": { + "description": "full 40-character commit SHA to build instead of the tag", + "type": "string", + "pattern": "^[0-9a-fA-F]{40}$" + }, + "spinel": { + "description": "spinel compiler to run (default: spinel on PATH)", + "type": "string" + } + } + }, + "tool_spm": { + "description": "a tool installed with the spm backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_spm_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_spm_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_spm_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_spm_options": { + "type": "object", + "properties": { + "provider": { + "description": "git host used for release info and assets", + "type": "string", + "enum": ["github", "gitlab"] + }, + "api_url": { + "description": "provider api url, for self-hosted instances", + "type": "string" + }, + "artifactbundle": { + "description": "true requires an artifactbundle, false forces a source build, unset tries the bundle first", + "$ref": "#/$defs/tool_bool" + }, + "artifactbundle_asset": { + "description": "specific *.artifactbundle.zip release asset to use", + "type": "string" + }, + "filter_bins": { + "description": "executable products to install", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "install_command": { + "description": "command run in the checkout instead of swift build; incompatible with filter_bins", + "type": "string" + } + } + }, + "tool_spm_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "provider": { + "$ref": "#/$defs/tool_spm_options/properties/provider" + }, + "api_url": { + "$ref": "#/$defs/tool_spm_options/properties/api_url" + }, + "artifactbundle": { + "$ref": "#/$defs/tool_spm_options/properties/artifactbundle" + }, + "artifactbundle_asset": { + "$ref": "#/$defs/tool_spm_options/properties/artifactbundle_asset" + } + } + } + }, + "tool_ubi": { + "description": "a tool installed with the ubi backend", + "oneOf": [ + { + "description": "version of the tool to install", + "type": "string" + }, + { + "allOf": [ + { + "$ref": "#/$defs/tool" + }, + { + "$ref": "#/$defs/tool_ubi_options" + }, + { + "properties": { + "platforms": { + "description": "per-platform option overrides", + "$ref": "#/$defs/tool_ubi_platforms" + }, + "platform": { + "description": "per-platform option overrides (alias of platforms)", + "$ref": "#/$defs/tool_ubi_platforms" + } + } + } + ], + "type": "object" + } + ] + }, + "tool_ubi_options": { + "type": "object", + "properties": { + "exe": { + "description": "name of the executable inside the archive to extract", + "type": "string" + }, + "rename_exe": { + "description": "name to give the extracted executable", + "type": "string" + }, + "matching": { + "description": "substring to match release filenames when several match the os and arch", + "type": "string" + }, + "matching_regex": { + "description": "regex release filenames must match before os and arch matching", + "type": "string" + }, + "provider": { + "description": "forge used to fetch releases and assets", + "type": "string", + "enum": ["github", "gitlab"] + }, + "api_url": { + "description": "provider api base url, for self-hosted instances", + "type": "string" + }, + "tag_regex": { + "description": "regex release tags must match to be listed as versions", + "type": "string" + }, + "extract_all": { + "description": "extract every file in the archive instead of only the binary", + "$ref": "#/$defs/tool_bool" + }, + "bin_path": { + "description": "directory in the install containing binaries, used with extract_all", + "type": "string" + } + } + }, + "tool_ubi_platforms": { + "description": "per-platform option overrides keyed by -, such as linux-x64 or macos-arm64", + "type": "object", + "propertyNames": { + "pattern": "^[A-Za-z]+-[A-Za-z0-9_]+$" + }, + "additionalProperties": { + "type": "object", + "properties": { + "bin_path": { + "$ref": "#/$defs/tool_ubi_options/properties/bin_path" + } + } + } + }, + "vars": { + "description": "variables to set", + "type": "object", + "properties": { + "_": { + "description": "vars modules", + "additionalProperties": true, + "properties": { + "file": { + "oneOf": [ { - "required": ["uris"] + "$ref": "#/$defs/env_directive" }, { - "required": ["suites"] + "description": "environment file to load (dotenv, json, yaml, or toml)", + "type": "string" }, { - "required": ["components"] + "description": "environment files to load (dotenv, json, yaml, or toml)", + "items": { + "oneOf": [ + { + "description": "environment file to load (dotenv, json, yaml, or toml)", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + }, + "type": "array" + } + ] + }, + "source": { + "oneOf": [ + { + "$ref": "#/$defs/env_directive" }, { - "required": ["architectures"] + "description": "bash script to load", + "type": "string" }, { - "required": ["key"] + "description": "bash scripts to load", + "items": { + "oneOf": [ + { + "description": "bash script to load", + "type": "string" + }, + { + "$ref": "#/$defs/env_directive" + } + ] + }, + "type": "array" } ] } }, + "type": "object" + } + }, + "additionalProperties": { + "oneOf": [ { - "required": ["uris", "key", "fingerprint"], - "not": { - "required": ["ppa"] - } + "description": "value of variable", + "type": "string" + }, + { + "description": "value of variable", + "type": "integer" + }, + { + "description": "value of variable", + "const": true + }, + { + "type": "object", + "properties": { + "value": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "const": true + } + ] + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "redact": { + "$ref": "#/$defs/env_redact" + } + }, + "required": ["value"], + "unevaluatedProperties": false + }, + { + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/env_default" + }, + "prompt": { + "description": "question to ask once, on a machine with no saved answer, when a command is run with a prompt flag such as mise bootstrap --prompt-vars", + "type": "string" + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "redact": { + "$ref": "#/$defs/env_redact" + } + }, + "required": ["default"], + "unevaluatedProperties": false + }, + { + "type": "object", + "properties": { + "prompt": { + "description": "question to ask once, on a machine with no saved answer, when a command is run with a prompt flag such as mise bootstrap --prompt-vars", + "type": "string" + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "redact": { + "$ref": "#/$defs/env_redact" + }, + "required": { + "oneOf": [ + { + "const": true + }, + { + "type": "string" + } + ] + } + }, + "required": ["required"], + "unevaluatedProperties": false + }, + { + "type": "object", + "properties": { + "age": { + "$ref": "#/$defs/env_age" + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "redact": { + "$ref": "#/$defs/env_redact" + } + }, + "required": ["age"], + "unevaluatedProperties": false, + "description": "[experimental] age-encrypted variable" + }, + { + "type": "object", + "properties": { + "age": { + "type": "object", + "properties": { + "value": { + "$ref": "#/$defs/env_age_value" + }, + "format": { + "$ref": "#/$defs/env_age_format" + }, + "tools": { + "$ref": "#/$defs/env_tools" + }, + "redact": { + "$ref": "#/$defs/env_redact" + } + }, + "required": ["value"], + "unevaluatedProperties": false, + "description": "[experimental] age-encrypted value (complex format)" + } + }, + "required": ["age"], + "unevaluatedProperties": false } ] } }, - "bootstrap_dnf_repos": { - "type": "object", - "description": "dnf repositories enabled before dnf packages are installed, keyed by a name for the entry", - "propertyNames": { - "pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]*$" - }, - "additionalProperties": { + "watch_files": { + "type": "array", + "items": { "type": "object", - "additionalProperties": false, - "required": ["copr"], - "properties": { - "copr": { - "type": "string", - "description": "COPR project as owner/project or @group/project, such as scottames/ghostty; enabled as dnf copr enable would" - } - } - } - }, - "bootstrap_hooks": { - "type": "object", - "description": "commands to run before and after bootstrap phases", - "properties": { - "pre-packages": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_packages": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-packages": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_packages": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre-repos": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_repos": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-repos": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_repos": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre-dotfiles": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_dotfiles": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-dotfiles": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_dotfiles": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre-defaults": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_defaults": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-defaults": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_defaults": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre-user": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_user": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-user": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_user": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre-tools": { - "$ref": "#/$defs/bootstrap_hook" - }, - "pre_tools": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post-tools": { - "$ref": "#/$defs/bootstrap_hook" - }, - "post_tools": { - "$ref": "#/$defs/bootstrap_hook" - }, - "final": { - "$ref": "#/$defs/bootstrap_hook" - } - }, - "additionalProperties": false - }, - "bootstrap_macos_defaults": { - "type": "object", - "description": "macOS user defaults to apply with `mise bootstrap macos defaults apply`, keyed by preferences domain (e.g. \"com.apple.dock\", \"NSGlobalDomain\")", - "additionalProperties": { - "type": "object", - "description": "defaults keys and their desired values for this domain", - "additionalProperties": { - "$ref": "#/$defs/macos_defaults_value", - "description": "desired value, written with the matching `defaults write` type; arrays and tables become property list arrays and dictionaries" - } - } - }, - "bootstrap_macos_dock": { - "type": "object", - "description": "curated Dock preferences that compile into macOS defaults", - "properties": { - "apps": { - "type": "array", - "items": { - "type": "string", - "pattern": "^(?:/|~/(?!/)).+\\.[aA][pP][pP]/*$" - }, - "uniqueItems": true, - "description": "Pinned application paths in order; absolute or ~/ paths ending in .app. An empty array removes application tiles." - }, - "autohide_delay": { - "type": "number", - "description": "delay before showing the Dock" - }, - "autohide_time_modifier": { - "type": "number", - "description": "Dock autohide animation time modifier" - }, - "autohide": { - "type": "boolean", - "description": "hide and show the Dock automatically" - }, - "orientation": { - "type": "string", - "enum": ["bottom", "left", "right"], - "description": "Dock screen edge" - }, - "tilesize": { - "type": "integer", - "description": "Dock icon size" - }, - "magnification": { - "type": "boolean", - "description": "enable Dock magnification" - }, - "largesize": { - "type": "integer", - "description": "Dock magnified icon size" - }, - "show_recents": { - "type": "boolean", - "description": "show recent applications in the Dock" - }, - "mru_spaces": { - "type": "boolean", - "description": "automatically rearrange Spaces based on most recent use" - } - }, - "additionalProperties": false - }, - "bootstrap_macos_finder": { - "type": "object", - "description": "curated Finder preferences that compile into macOS defaults", - "properties": { - "sort_folders_first": { - "type": "boolean", - "description": "keep folders first when sorting by name" - }, - "save_new_documents_to_cloud": { - "type": "boolean", - "description": "save new documents to iCloud by default (applies globally to application save dialogs)" - }, - "show_all_files": { - "type": "boolean", - "description": "show hidden files in Finder" - }, - "show_pathbar": { - "type": "boolean", - "description": "show the Finder path bar" - }, - "show_status_bar": { - "type": "boolean", - "description": "show the Finder status bar" - }, - "show_extensions_warning": { - "type": "boolean", - "description": "show the warning when changing file extensions" - }, - "preferred_view_style": { - "type": "string", - "enum": ["icon", "list", "column", "gallery"], - "description": "Finder preferred view style" - } - }, - "additionalProperties": false - }, - "bootstrap_macos_keyboard": { - "type": "object", - "description": "curated keyboard preferences that compile into macOS defaults", - "properties": { - "automatic_capitalization": { - "type": "boolean", - "description": "automatically capitalize words" - }, - "automatic_spelling_correction": { - "type": "boolean", - "description": "automatically correct spelling" - }, - "key_repeat": { - "type": "integer", - "description": "keyboard repeat interval" - }, - "initial_key_repeat": { - "type": "integer", - "description": "delay before key repeat starts" - }, - "press_and_hold": { - "type": "boolean", - "description": "enable press-and-hold accent picker" - }, - "fn_state": { - "type": "boolean", - "description": "use F1, F2, etc. as standard function keys" - } - }, - "additionalProperties": false - }, - "bootstrap_macos_trackpad": { - "type": "object", - "description": "curated trackpad preferences that compile into macOS defaults", - "properties": { - "tap_to_click": { - "type": "boolean", - "description": "enable tap to click for built-in and Bluetooth trackpads" - }, - "three_finger_drag": { - "type": "boolean", - "description": "enable three finger drag for built-in and Bluetooth trackpads" - } - }, - "additionalProperties": false - }, - "bootstrap_mise_shell_activate": { - "type": "object", - "description": "declarative mise shell activation snippets, applied with `mise bootstrap mise-shell-activate apply`", - "properties": { - "bash": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "zsh": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "fish": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "bash_profile": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "bashrc": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "zshenv": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "zprofile": { - "$ref": "#/$defs/bootstrap_shell_activation" - }, - "zshrc": { - "$ref": "#/$defs/bootstrap_shell_activation" - } - }, - "additionalProperties": false - }, - "macos_defaults_value": { - "description": "a property list value: boolean, integer, real, string, or an array or dictionary of these", - "anyOf": [ - { - "type": "boolean" - }, - { - "type": "integer" - }, - { - "type": "number" - }, - { - "type": "string" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/macos_defaults_value" - } - }, - { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/macos_defaults_value" - } - } - ] - }, - "doctor": { - "type": "object", - "description": "Project diagnostic checks, run explicitly with mise doctor project.", - "properties": { - "checks": { - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": false, + "description": "file to watch for changes", + "oneOf": [ + { "properties": { "run": { "type": "string", - "description": "Diagnostic command. Exit zero to pass; other exit codes fail.", - "minLength": 1 - }, - "description": { - "type": "string", - "description": "Human-readable requirement checked by the command." - }, - "hint": { - "type": "string", - "description": "Remediation guidance displayed on failure; never executed." - }, - "timeout": { - "type": "string", - "description": "Positive command timeout, such as 5s. Defaults to 10s." - }, - "dir": { - "type": "string", - "description": "Working directory. Relative paths resolve from the declaring config's root (the invocation directory for global and system config); a leading ~/ and absolute paths are used as given." + "description": "script to run when file changes" }, "shell": { "type": "string", - "description": "Shell command, including the command flag, as in tasks.", - "examples": ["bash -c", "pwsh -Command"] + "description": "inline shell command used to run script when file changes" }, - "os": { - "description": "OS or OS/arch selectors; omit to run everywhere.", - "$ref": "#/$defs/os_filter" + "patterns": { + "type": "array", + "description": "patterns to watch for", + "items": { + "type": "string" + } } }, - "required": ["run"] + "unevaluatedProperties": false, + "required": ["run", "patterns"] + }, + { + "properties": { + "task": { + "type": "string", + "description": "mise task to run when file changes" + }, + "patterns": { + "type": "array", + "description": "patterns to watch for", + "items": { + "type": "string" + } + } + }, + "unevaluatedProperties": false, + "required": ["task", "patterns"] } - } + ] }, - "additionalProperties": true + "description": "files to watch for changes" } }, "unevaluatedProperties": false, "description": "config file for mise version manager (mise.toml)", "properties": { "alias": { - "deprecated": true, - "description": "custom shorthands", "type": "object", + "description": "custom shorthands", "additionalProperties": { "$ref": "#/$defs/tool_alias_entry" - } + }, + "deprecated": true }, "tool_alias": { - "description": "Tool version aliases", "type": "object", + "description": "Tool version aliases", "additionalProperties": { "$ref": "#/$defs/tool_alias_entry" } }, "shell_alias": { - "description": "shell aliases", "type": "object", + "description": "shell aliases", "additionalProperties": { - "description": "command to run", - "type": "string" + "type": "string", + "description": "command to run" } }, "wrappers": { - "description": "commands that intercept a binary name before delegating to the active toolset", "type": "object", + "description": "commands that intercept a binary name before delegating to the active toolset", "additionalProperties": { "oneOf": [ { @@ -6584,12 +6584,12 @@ "description": "config files merged into this one, ranking just below it: a local path (relative to this file), git:://.toml?ref= or oci::/[:tag|@sha256:] (the artifact holds a mise.toml). Paranoid mode requires a commit sha or digest for remote includes and rejects local ones; otherwise a branch or tag is refreshed after fetch_remote_versions_cache", "oneOf": [ { - "description": "a config fragment", - "type": "string" + "type": "string", + "description": "a config fragment" }, { - "description": "config fragments", "type": "array", + "description": "config fragments", "items": { "type": "string" } @@ -6598,62 +6598,62 @@ }, "env_file": { "description": "dotenv file(s) to load. Deprecated; use env._.file instead. This will be removed in mise 2027.4.0.", - "deprecated": true, "oneOf": [ { - "description": "dotenv file to load", - "type": "string" + "type": "string", + "description": "dotenv file to load" }, { - "description": "dotenv files to load", "type": "array", + "description": "dotenv files to load", "items": { "type": "string" } } - ] + ], + "deprecated": true }, "dotenv": { "description": "dotenv file(s) to load. Deprecated; use env._.file instead. This will be removed in mise 2027.4.0.", - "deprecated": true, "oneOf": [ { - "description": "dotenv file to load", - "type": "string" + "type": "string", + "description": "dotenv file to load" }, { - "description": "dotenv files to load", "type": "array", + "description": "dotenv files to load", "items": { "type": "string" } } - ] + ], + "deprecated": true }, "env_path": { "description": "PATH entries to add. Deprecated; use env._.path instead. This will be removed in mise 2027.4.0.", - "deprecated": true, "oneOf": [ { - "description": "PATH entry to add", - "type": "string" + "type": "string", + "description": "PATH entry to add" }, { - "description": "PATH entries to add", "type": "array", + "description": "PATH entries to add", "items": { "type": "string" } } - ] + ], + "deprecated": true }, "monorepo_root": { "description": "marks this config as a monorepo root for task path syntax", "$ref": "#/$defs/monorepo_root" }, "experimental_monorepo_root": { - "description": "deprecated alias of monorepo_root", "type": "boolean", + "description": "deprecated alias of monorepo_root", "deprecated": true }, "monorepo": { @@ -6686,7 +6686,6 @@ ] }, "secrets": { - "description": "[experimental] Sources for mise secrets: values resolved only when mise starts a task or `mise x` command that was granted them. Project config only.", "type": "object", "properties": { "fnox": { @@ -6700,22 +6699,23 @@ } } } - } + }, + "description": "[experimental] Sources for mise secrets: values resolved only when mise starts a task or `mise x` command that was granted them. Project config only." }, "redactions": { - "description": "env or vars keys to redact from logs", "type": "array", + "description": "env or vars keys to redact from logs", "items": { "type": "string" } }, "plugins": { - "additionalProperties": { - "description": "url to plugin repository", - "type": "string" - }, + "type": "object", "description": "plugins to use", - "type": "object" + "additionalProperties": { + "type": "string", + "description": "url to plugin repository" + } }, "deps": { "type": "object", @@ -6870,13 +6870,12 @@ } }, "settings": { - "$ref": "#/$defs/settings", + "type": "object", "description": "mise settings", - "type": "object" + "$ref": "#/$defs/settings" }, "dotfiles": { "type": "object", - "description": "dotfiles applied with `mise dotfiles apply` or `mise bootstrap`, keyed by target path or a logical name when every variant overrides the target; an omitted source for the latter resolves the relative key under dotfiles.root", "additionalProperties": { "oneOf": [ { @@ -6884,30 +6883,30 @@ "description": "source path for a whole-file dotfile entry" }, { - "type": "object", "description": "whole-file dotfile entry or file edit entry", + "type": "object", "properties": { "encrypt": { + "description": "encrypt tracked file contents before storing them in Git; the live file remains plaintext", "type": "boolean", - "default": false, - "description": "encrypt tracked file contents before storing them in Git; the live file remains plaintext" + "default": false }, "allow_plaintext": { + "description": "allow this directly tracked credential-named file to be saved in plaintext history and any connected origin", "type": "boolean", - "default": false, - "description": "allow this directly tracked credential-named file to be saved in plaintext history and any connected origin" + "default": false }, "source": { - "type": "string", - "description": "source file or directory" + "description": "source file or directory", + "type": "string" }, "content": { - "type": "string", - "description": "literal inline file content; alternative to source" + "description": "literal inline file content; alternative to source", + "type": "string" }, "mode": { - "type": "string", "description": "how to apply a whole-file source to the target; omitted uses dotfiles.default_mode. \"absent\" removes a file or symlink at the target and takes no source. \"track\" saves the file's history where it is, and \"track-local\" saves it in this machine's local-only history, which is never shared", + "type": "string", "anyOf": [ { "enum": [ @@ -6926,50 +6925,50 @@ ] }, "exclude": { - "type": "array", "description": "glob patterns to skip when the mode walks a directory (symlink-each, copy with a directory source, or a tracked directory, where they are relative to the tracked path); a pattern without \"/\" matches any path component, one with \"/\" is anchored to the root", + "type": "array", "items": { "type": "string" } }, "include": { - "type": "array", "description": "history only (mode = \"track\"): the only paths of the tracked directory that are captured, relative to it and matched like \"exclude\"; without it the whole tree is captured with the builtin credential filtering, and an explicit \"exclude\" still wins. A selected file is captured even when it looks like a credential store, in plaintext unless the entry sets encrypt", + "type": "array", "items": { "type": "string" } }, "manifest": { - "type": "string", "description": "source manifest used to select managed files from a directory", + "type": "string", "enum": ["git"] }, "remove_empty": { + "description": "template mode only: remove the target when the template renders empty or whitespace-only; a target that changed since mise last wrote it needs --force", "type": "boolean", - "default": false, - "description": "template mode only: remove the target when the template renders empty or whitespace-only; a target that changed since mise last wrote it needs --force" + "default": false }, "dot_prefix": { + "description": "symlink-each and directory copy only: deploy each source path component named \"dot-\" as \".\", like GNU Stow's --dotfiles; exclude still matches source names", "type": "boolean", - "default": false, - "description": "symlink-each and directory copy only: deploy each source path component named \"dot-\" as \".\", like GNU Stow's --dotfiles; exclude still matches source names" + "default": false }, "relative": { - "type": "boolean", - "description": "symlink and symlink-each only: link by a path relative to the link's directory instead of an absolute one; overrides the dotfiles.relative_symlinks setting (ignored on Windows)" + "description": "symlink and symlink-each only: link by a path relative to the link's directory instead of an absolute one; overrides the dotfiles.relative_symlinks setting (ignored on Windows)", + "type": "boolean" }, "permissions": { - "type": "string", "description": "octal permissions for the target, such as \"0600\"; overrides what copy, template, and inline content entries set, and on its own manages only the permissions of an existing target (Unix only)", + "type": "string", "pattern": "^(0o)?[0-7]{1,4}$" }, "block": { - "type": "string", - "description": "block content, maintained between marker comments" + "description": "block content, maintained between marker comments", + "type": "string" }, "template": { - "type": "string", "description": "template engine to render block content with; currently only \"tera\"", + "type": "string", "anyOf": [ { "enum": ["tera"] @@ -6980,13 +6979,13 @@ ] }, "line": { - "type": "string", - "description": "exact line to ensure exists" + "description": "exact line to ensure exists", + "type": "string" }, "position": { + "description": "where to insert a missing line", "type": "string", "default": "append", - "description": "where to insert a missing line", "anyOf": [ { "enum": ["append", "prepend"] @@ -6997,10 +6996,11 @@ ] }, "comment": { - "type": "string", - "description": "comment prefix for edit marker lines; inferred from the file extension when omitted" + "description": "comment prefix for edit marker lines; inferred from the file extension when omitted", + "type": "string" }, "merge": { + "description": "edit entry: true sets the keys of source (default: the target's path under dotfiles.root) on a JSON, TOML, or YAML target and leaves the target's other keys to the application that also writes it; \"missing\" sets only the keys the target has no value for; the format comes from the target's extension", "oneOf": [ { "type": "boolean" @@ -7008,15 +7008,13 @@ { "const": "missing" } - ], - "description": "edit entry: true sets the keys of source (default: the target's path under dotfiles.root) on a JSON, TOML, or YAML target and leaves the target's other keys to the application that also writes it; \"missing\" sets only the keys the target has no value for; the format comes from the target's extension" + ] }, "variants": { - "type": "array", "description": "OS/profile variants: history streams for track mode, or optional destinations for deployment modes", + "type": "array", "items": { "type": "object", - "additionalProperties": false, "properties": { "os": { "oneOf": [ @@ -7032,33 +7030,34 @@ ] }, "profile": { - "type": "string", - "description": "mise environment selected with -E or MISE_ENV" + "description": "mise environment selected with -E or MISE_ENV", + "type": "string" }, "default": { "type": "boolean" }, "machine": { - "type": "boolean", - "description": "track mode only: a separate stream on every machine, never applied on another machine; must be the only variant and cannot be combined with encrypt" + "description": "track mode only: a separate stream on every machine, never applied on another machine; must be the only variant and cannot be combined with encrypt", + "type": "boolean" }, "target": { - "type": "string", - "description": "deployment destination override; requires either an explicit source or a safe relative entry key when every variant has a target, and is not supported in track mode" + "description": "deployment destination override; requires either an explicit source or a safe relative entry key when every variant has a target, and is not supported in track mode", + "type": "string" } - } + }, + "additionalProperties": false } } }, "allOf": [ { "if": { - "required": ["merge"], "properties": { "merge": { "enum": [true, "missing"] } - } + }, + "required": ["merge"] }, "then": { "properties": { @@ -7094,12 +7093,12 @@ } }, { - "required": ["mode"], "properties": { "mode": { "enum": ["symlink", "symlink-each"] } - } + }, + "required": ["mode"] }, { "properties": { @@ -7146,12 +7145,12 @@ } ], "if": { - "required": ["mode"], "properties": { "mode": { "const": "absent" } - } + }, + "required": ["mode"] }, "then": { "properties": { @@ -7178,7 +7177,6 @@ { "oneOf": [ { - "required": ["manifest"], "properties": { "source": {}, "content": false, @@ -7192,7 +7190,8 @@ "line": false, "template": false, "comment": false - } + }, + "required": ["manifest"] }, { "properties": { @@ -7203,12 +7202,12 @@ }, { "if": { - "required": ["dot_prefix"], "properties": { "dot_prefix": { "const": true } - } + }, + "required": ["dot_prefix"] }, "then": { "properties": { @@ -7239,7 +7238,6 @@ { "oneOf": [ { - "required": ["permissions"], "properties": { "mode": { "enum": ["copy", "template"] @@ -7248,6 +7246,7 @@ "template": false, "comment": false }, + "required": ["permissions"], "anyOf": [ { "required": ["source"] @@ -7277,7 +7276,6 @@ { "oneOf": [ { - "required": ["content"], "properties": { "source": false, "manifest": false, @@ -7288,10 +7286,10 @@ "position": false, "template": false, "comment": false - } + }, + "required": ["content"] }, { - "required": ["line"], "properties": { "content": false, "permissions": false, @@ -7301,7 +7299,8 @@ "block": false, "template": false, "comment": false - } + }, + "required": ["line"] }, { "properties": { @@ -7315,12 +7314,12 @@ { "oneOf": [ { - "required": ["include", "mode"], "properties": { "mode": { "enum": ["track", "track-local"] } - } + }, + "required": ["include", "mode"] }, { "properties": { @@ -7332,12 +7331,12 @@ { "oneOf": [ { - "required": ["allow_plaintext", "mode"], "properties": { "mode": { "enum": ["track", "track-local"] } - } + }, + "required": ["allow_plaintext", "mode"] }, { "properties": { @@ -7366,7 +7365,8 @@ ] } ] - } + }, + "description": "dotfiles applied with `mise dotfiles apply` or `mise bootstrap`, keyed by target path or a logical name when every variant overrides the target; an omitted source for the latter resolves the relative key under dotfiles.root" }, "dotfile_groups": { "type": "object", @@ -9201,18 +9201,18 @@ "$ref": "#/$defs/tool_config" }, "tasks": { + "type": "object", + "description": "task runner tasks", "additionalProperties": { "$ref": "#/$defs/task" - }, - "description": "task runner tasks", - "type": "object" + } }, "task_templates": { + "type": "object", "description": "task templates that can be extended by tasks via extends", "additionalProperties": { "$ref": "#/$defs/task_template" - }, - "type": "object" + } }, "tools": { "patternProperties": { @@ -9490,8 +9490,8 @@ } ] }, - "description": "dev tools to use", - "type": "object" + "type": "object", + "description": "dev tools to use" }, "hooks": { "description": "hooks to run on events like cd, enter, leave", @@ -9513,7 +9513,6 @@ "$ref": "#/$defs/doctor" }, "daemons": { - "description": "Experimental project daemons: custom pitchfork commands or managed presets.", "type": "object", "additionalProperties": { "oneOf": [ @@ -9732,10 +9731,10 @@ } } ] - } + }, + "description": "Experimental project daemons: custom pitchfork commands or managed presets." }, "daemon_providers": { - "description": "Experimental shared servers owned by the user. Only allowed in global configuration.", "type": "object", "propertyNames": { "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" @@ -9773,7 +9772,8 @@ "type": "string" } } - } + }, + "description": "Experimental shared servers owned by the user. Only allowed in global configuration." }, "daemons_settings": { "type": "object", @@ -9792,8 +9792,8 @@ } }, "daemon_groups": { - "description": "Experimental named groups of project daemons. Members are daemon names or other group names declared in the same project.", "type": "object", + "description": "Experimental named groups of project daemons. Members are daemon names or other group names declared in the same project.", "additionalProperties": { "oneOf": [ { diff --git a/src/config/config_file/mise_toml.rs b/src/config/config_file/mise_toml.rs index baeb3ff5a7..a60cf8331a 100644 --- a/src/config/config_file/mise_toml.rs +++ b/src/config/config_file/mise_toml.rs @@ -1973,7 +1973,7 @@ impl ConfigFile for MiseToml { .hooks .iter() .map(|(hook_type, def)| { - let mut hooks = def.clone().into_hooks(*hook_type); + let mut hooks = crate::hooks::hooks_from_def(def.clone(), *hook_type); for hook in hooks.iter_mut() { hook.render_templates(|s| self.parse_template(s))?; } diff --git a/src/config/config_file/mise_toml/schema_conformance.rs b/src/config/config_file/mise_toml/schema_conformance.rs index 281c43519a..ad0da564d0 100644 --- a/src/config/config_file/mise_toml/schema_conformance.rs +++ b/src/config/config_file/mise_toml/schema_conformance.rs @@ -566,3 +566,60 @@ fn redirect_targets() { assert_eq!(redirect_target(line), target, "{line}"); } } + +/// `schema/mise.json` lists its top-level keys from `mise_config::mise_toml::MiseTomlDecl`, and +/// [`MiseToml`] must read exactly those keys: one it reads that the schema lacks is flagged in +/// every editor, and one the schema lists that it skips is ignored with a warning. +#[test] +fn schema_keys_match_parser() { + let root = Path::new(env!("CARGO_MANIFEST_DIR")); + let schema = std::fs::read_to_string(root.join("schema/mise.json")).unwrap(); + let schema: serde_json::Value = serde_json::from_str(&schema).unwrap(); + let declared: BTreeSet<&str> = schema["properties"] + .as_object() + .unwrap() + .keys() + .map(String::as_str) + .collect(); + let parsed: BTreeSet<&str> = serde_fields::().iter().copied().collect(); + assert_eq!( + declared, parsed, + "MiseTomlDecl in crates/mise-config and MiseToml read different top-level keys" + ); +} + +/// The fields a derived `Deserialize` reads, as serde hands them to `deserialize_struct`. +fn serde_fields() -> &'static [&'static str] { + use serde::de::{self, Visitor}; + + struct Fields<'a>(&'a mut &'static [&'static str]); + + impl<'de> de::Deserializer<'de> for Fields<'_> { + type Error = de::value::Error; + + fn deserialize_any>(self, _: V) -> Result { + Err(de::Error::custom("not a struct")) + } + + fn deserialize_struct>( + self, + _: &'static str, + fields: &'static [&'static str], + _: V, + ) -> Result { + *self.0 = fields; + Err(de::Error::custom("only the field names were wanted")) + } + + serde::forward_to_deserialize_any! { + bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes byte_buf + option unit unit_struct newtype_struct seq tuple tuple_struct map enum identifier + ignored_any + } + } + + let mut fields: &'static [&'static str] = &[]; + let _ = T::deserialize(Fields(&mut fields)); + assert!(!fields.is_empty(), "not a derived struct"); + fields +} diff --git a/src/hooks.rs b/src/hooks.rs index 4bf0a8cf00..a76baac9ec 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -60,174 +60,56 @@ fn hook_backend_identifier(full: &str) -> String { format!("{backend}:{url}") } -#[derive( - Debug, - Clone, - Copy, - serde::Serialize, - serde::Deserialize, - strum::Display, - Ord, - PartialOrd, - Eq, - PartialEq, - Hash, -)] -#[serde(rename_all = "lowercase")] -pub enum Hooks { - Enter, - Leave, - Cd, - Preinstall, - Postinstall, +pub use mise_config::hooks::Hooks; +pub(crate) use mise_config::hooks::{HookDef, HookDefItem}; + +/// The hooks a `[hooks]` entry declares for `hook_type`. +pub(crate) fn hooks_from_def(def: HookDef, hook_type: Hooks) -> Vec { + def.into_items() + .into_iter() + .map(|item| hook_from_item(item, hook_type)) + .collect() } -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(untagged)] -pub(crate) enum HookScripts { - One(String), - Many(Vec), -} - -impl HookScripts { - fn into_script(self) -> String { - match self { - Self::One(script) => script, - Self::Many(scripts) => scripts.join("\n"), - } - } -} - -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(untagged)] -pub(crate) enum HookDef { - /// Array of hook definitions: `enter = ["echo hello", { task = "setup" }]` - Array(Vec), - One(HookDefItem), -} - -impl HookDef { - /// This definition followed by the hooks of `later`, so both run. - pub(crate) fn then(self, later: HookDef) -> HookDef { - let items = |def: HookDef| match def { - HookDef::Array(items) => items, - HookDef::One(item) => vec![item], - }; - let mut all = items(self); - all.extend(items(later)); - HookDef::Array(all) - } -} - -#[derive(Debug, Clone, serde::Deserialize)] -#[serde(untagged, deny_unknown_fields)] -pub(crate) enum HookDefItem { - /// Simple run string: `enter = "echo hello"` - RunString(String), - /// Table with run: `enter = { run = "echo hello" }` - Run(HookRunTable), - /// Table with script and optional shell: `enter = { script = "echo hello", shell = "bash" }` - ScriptTable { - script: HookScripts, - shell: Option, - }, - /// Table with scripts and optional shell: `enter = { scripts = ["echo hello"], shell = "bash" }` - ScriptsTable { - scripts: Vec, - shell: Option, - }, - /// Task reference: `enter = { task = "setup" }` - TaskRef { task: String }, -} - -#[derive(Debug, Clone)] -pub(crate) struct HookRunTable { - run: Option, - run_windows: Option, - shell: Option, -} - -impl<'de> serde::Deserialize<'de> for HookRunTable { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[derive(serde::Deserialize)] - #[serde(deny_unknown_fields)] - struct Helper { - run: Option, - run_windows: Option, - shell: Option, - } - - let helper = ::deserialize(deserializer)?; - if helper.run.is_none() && helper.run_windows.is_none() { - return Err(serde::de::Error::custom( - "hook run table must define `run` or `run_windows`", - )); - } - Ok(Self { - run: helper.run, - run_windows: helper.run_windows, - shell: helper.shell, - }) - } -} - -impl HookDef { - /// Convert to a list of Hook structs with the given hook type - pub(crate) fn into_hooks(self, hook_type: Hooks) -> Vec { - match self { - HookDef::One(def) => vec![def.into_hook(hook_type)], - HookDef::Array(arr) => arr - .into_iter() - .map(|def| def.into_hook(hook_type)) - .collect(), - } - } -} - -impl HookDefItem { - fn into_hook(self, hook_type: Hooks) -> Hook { - match self { - HookDefItem::RunString(script) => Hook { - hook: hook_type, - action: HookAction::Run { - run: Some(script), - run_windows: None, - shell: None, - legacy_script: false, - ignored_shell: None, - }, - global: false, +fn hook_from_item(item: HookDefItem, hook_type: Hooks) -> Hook { + match item { + HookDefItem::RunString(script) => Hook { + hook: hook_type, + action: HookAction::Run { + run: Some(script), + run_windows: None, + shell: None, + legacy_script: false, + ignored_shell: None, }, - HookDefItem::Run(table) => Hook { - hook: hook_type, - action: HookAction::Run { - run: table.run, - run_windows: table.run_windows, - shell: table.shell, - legacy_script: false, - ignored_shell: None, - }, - global: false, + global: false, + }, + HookDefItem::Run(table) => Hook { + hook: hook_type, + action: HookAction::Run { + run: table.run, + run_windows: table.run_windows, + shell: table.shell, + legacy_script: false, + ignored_shell: None, }, - HookDefItem::ScriptTable { script, shell } => Hook { - hook: hook_type, - action: script_hook_action(hook_type, script.into_script(), shell, true), - global: false, - }, - HookDefItem::ScriptsTable { scripts, shell } => Hook { - hook: hook_type, - action: script_hook_action(hook_type, scripts.join("\n"), shell, true), - global: false, - }, - HookDefItem::TaskRef { task } => Hook { - hook: hook_type, - action: HookAction::Task { task_name: task }, - global: false, - }, - } + global: false, + }, + HookDefItem::ScriptTable { script, shell } => Hook { + hook: hook_type, + action: script_hook_action(hook_type, script.into_script(), shell, true), + global: false, + }, + HookDefItem::ScriptsTable { scripts, shell } => Hook { + hook: hook_type, + action: script_hook_action(hook_type, scripts.join("\n"), shell, true), + global: false, + }, + HookDefItem::TaskRef { task } => Hook { + hook: hook_type, + action: HookAction::Task { task_name: task }, + global: false, + }, } } @@ -1066,7 +948,7 @@ mod tests { "#, ) .unwrap(); - let hooks = parsed.hook.into_hooks(Hooks::Postinstall); + let hooks = hooks_from_def(parsed.hook, Hooks::Postinstall); assert_eq!(hooks.len(), 1); match &hooks[0].action { @@ -1092,7 +974,7 @@ mod tests { "#, ) .unwrap(); - let hooks = parsed.hook.into_hooks(Hooks::Postinstall); + let hooks = hooks_from_def(parsed.hook, Hooks::Postinstall); assert_eq!(hooks.len(), 3); let runs = hooks @@ -1165,11 +1047,13 @@ mod tests { #[test] fn scripts_tables_are_legacy_only_when_spawned() { - let spawned = HookDef::One(HookDefItem::ScriptsTable { - scripts: vec!["echo one".into(), "echo two".into()], - shell: None, - }) - .into_hooks(Hooks::Postinstall); + let spawned = hooks_from_def( + HookDef::One(HookDefItem::ScriptsTable { + scripts: vec!["echo one".into(), "echo two".into()], + shell: None, + }), + Hooks::Postinstall, + ); assert!(matches!( &spawned[0].action, HookAction::Run { @@ -1178,11 +1062,13 @@ mod tests { } )); - let current_shell = HookDef::One(HookDefItem::ScriptsTable { - scripts: vec!["echo one".into(), "echo two".into()], - shell: Some("bash".into()), - }) - .into_hooks(Hooks::Enter); + let current_shell = hooks_from_def( + HookDef::One(HookDefItem::ScriptsTable { + scripts: vec!["echo one".into(), "echo two".into()], + shell: Some("bash".into()), + }), + Hooks::Enter, + ); assert!(matches!( ¤t_shell[0].action, HookAction::CurrentShell { .. } diff --git a/tasks.md b/tasks.md index 8a823f909e..0894c9b0f5 100644 --- a/tasks.md +++ b/tasks.md @@ -196,8 +196,6 @@ Generate man pages ## `render:schema` -- Depends: docs:setup - - **Usage:** `render:schema` Render JSON schema diff --git a/xtasks/render/schema.sh b/xtasks/render/schema.sh new file mode 100755 index 0000000000..9e46e9d222 --- /dev/null +++ b/xtasks/render/schema.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +#MISE description="Render JSON schema" +set -euo pipefail + +# mise-config generates schema/mise.json, schema/mise-task.json and schema/miserc.json from its +# types and settings.toml. Like CI's other cargo commands, this skips the MSRV check: the lint +# runner's rustc can be older than the MSRV. +cargo run --quiet --ignore-rust-version --package mise-config --example render-schema +oxfmt --write schema/mise.json schema/mise-task.json schema/miserc.json diff --git a/xtasks/render/schema.ts b/xtasks/render/schema.ts deleted file mode 100755 index f06b096f42..0000000000 --- a/xtasks/render/schema.ts +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env node - -//MISE description="Render JSON schema" -//MISE depends=["docs:setup"] - -import { spawnSync } from "node:child_process"; -import * as fs from "node:fs"; -import * as toml from "toml"; - -type EnumValue = string | boolean | number; -type EnumItem = EnumValue | { value: EnumValue; description?: string }; - -type Props = { - type: string; - description: string; - default?: unknown; - rust_type?: string; - deprecated?: string; - enum?: EnumItem[]; - rc?: boolean; - deserialize_with?: string; -}; - -type SettingsNode = Props | { [key: string]: SettingsNode }; -type SettingsToml = Record; -type JsonObject = Record; - -type Element = { - type: string | string[]; - default: unknown; - description: string; - deprecated?: true; - minimum?: number; - enum?: EnumValue[]; - items?: { - type: string | string[]; - enum?: EnumValue[]; - }; - additionalProperties?: { - type: string; - }; -}; - -type NestedElement = { - type: "object"; - unevaluatedProperties: false; - deprecated?: true; - properties: Record; -}; - -const writtenPaths: string[] = []; - -function writeFormattedJson(path: string, value: unknown) { - fs.writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`); - writtenPaths.push(path); -} - -function formatWithOxfmt(paths: string[]) { - if (paths.length === 0) return; - const result = spawnSync("oxfmt", ["--write", ...paths], { - stdio: "inherit", - }); - if (result.error) { - throw new Error(`oxfmt failed to spawn: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error(`oxfmt exited with status ${result.status}`); - } -} - -function crawlReferencedDefs(schema: JsonObject, root: unknown) { - const defs = schema["$defs"] as JsonObject | undefined; - if (!defs) { - throw new Error("schema/mise.json is missing $defs"); - } - - const queued = [root]; - const seenDefs = new Set(); - const picked: JsonObject = {}; - - for (let i = 0; i < queued.length; i++) { - const value = queued[i]; - if (Array.isArray(value)) { - queued.push(...value); - continue; - } - if (!value || typeof value !== "object") { - continue; - } - - const obj = value as JsonObject; - const ref = obj["$ref"]; - if (typeof ref === "string" && ref.startsWith("#/$defs/")) { - const key = ref.slice("#/$defs/".length).split("/")[0]; - if (defs[key] === undefined) { - throw new Error(`schema/mise.json is missing $defs.${key}`); - } - if (!seenDefs.has(key)) { - seenDefs.add(key); - picked[key] = defs[key]; - queued.push(defs[key]); - } - } else if (typeof ref === "string" && ref.startsWith("#/")) { - throw new Error(`unsupported local JSON schema ref: ${ref}`); - } - - queued.push(...Object.values(obj)); - } - - return picked; -} - -function buildTaskSchema(schema: JsonObject) { - const taskSchema: JsonObject = { - $id: "https://mise.jdx.dev/schema/mise-task.json", - $schema: schema["$schema"], - title: "mise-task-schema", - type: "object", - description: - "Config file for included mise tasks (https://mise.jdx.dev/tasks/task-configuration.html)", - additionalProperties: { - $ref: "#/$defs/task", - }, - }; - taskSchema["$defs"] = crawlReferencedDefs(schema, taskSchema); - return taskSchema; -} - -function buildElement(key: string, props: Props): Element { - const typeMap: Record = { - String: "string", - Path: "string", - Url: "string", - Duration: "string", - Bool: "boolean", - Integer: "integer", - ListString: "string[]", - ListPath: "string[]", - SetString: "string[]", - "IndexMap": "object", - BoolOrString: "__bool_or_string__", - BoolOrList: "__bool_or_list__", - }; - const type = props.type ? typeMap[props.type] : undefined; - if (!type) { - throw new Error(`Unknown type: ${props.type}`); - } - - if (!props.description) { - throw new Error(`Missing description for ${key}`); - } - - const element: Element = { - default: props.default, - description: props.description, - type, - }; - - if (props.deprecated) { - element.deprecated = true; - } - if (type === "integer" && props.rust_type?.startsWith("u")) { - element.minimum = 0; - } - const enumValues = props.enum?.map((e) => - typeof e === "object" && e !== null && "value" in e ? e.value : e, - ); - - if (type === "string[]") { - element.type = "array"; - element.items = { - type: "string", - ...(enumValues ? { enum: enumValues } : {}), - }; - } else if (enumValues) { - element.enum = enumValues; - } - - if (type === "object") { - element.additionalProperties = { - type: "string", - }; - } - - // A list setting that also takes a single value: use oneOf for AJV strictTypes - if (type === "string[]" && props.deserialize_with === "path_or_paths") { - const items = element.items; - delete (element as Record).type; - delete (element as Record).items; - (element as Record).oneOf = [ - { type: "string" }, - { type: "array", items }, - ]; - } - - // BoolOrString: use oneOf instead of union type array for AJV strictTypes - if (type === "__bool_or_string__") { - delete (element as Record).type; - const oneOfTypes: Array<{ type: string }> = [ - { type: "boolean" }, - { type: "string" }, - ]; - (element as Record).oneOf = oneOfTypes; - } - - // BoolOrList: a boolean, or a list of the strings the setting's enum allows. - if (type === "__bool_or_list__") { - delete (element as Record).type; - delete element.enum; - (element as Record).oneOf = [ - { type: "boolean" }, - { - type: "array", - items: { type: "string", ...(enumValues ? { enum: enumValues } : {}) }, - }, - ]; - } - - return element; -} - -const doc = toml.parse( - fs.readFileSync("settings.toml", "utf-8"), -) as SettingsToml; -const isLeaf = (node: SettingsNode): node is Props => { - return "type" in node; -}; - -function buildSettings( - table: Record, - prefix = "", -): Record { - const settings: Record = {}; - for (const [key, node] of Object.entries(table)) { - const name = prefix ? `${prefix}.${key}` : key; - if (isLeaf(node)) { - settings[key] = buildElement(name, node); - } else { - settings[key] = { - type: "object", - unevaluatedProperties: false, - properties: buildSettings(node, name), - }; - } - } - return settings; -} - -const settings = buildSettings(doc); - -// The sections whose types live in crates/mise-config, generated from those types. -function generatedFragments(): Record { - const result = spawnSync( - "cargo", - [ - "run", - "--quiet", - // Like CI's other cargo commands: the lint runner's rustc can be older than the MSRV. - "--ignore-rust-version", - "--package", - "mise-config", - "--example", - "render-schema", - ], - { - encoding: "utf-8", - stdio: ["ignore", "pipe", "inherit"], - maxBuffer: 64 * 1024 * 1024, - }, - ); - if (result.error) { - throw new Error(`cargo failed to spawn: ${result.error.message}`); - } - if (result.status !== 0) { - throw new Error( - `mise-config's render-schema example exited with status ${result.status}`, - ); - } - return JSON.parse(result.stdout); -} - -// Replaces the value at a JSON pointer. The key must already exist, so a mistyped pointer -// fails instead of adding a section nothing references. -function replaceAtPointer(root: JsonObject, pointer: string, value: unknown) { - const keys = pointer - .split("/") - .slice(1) - .map((key) => key.replaceAll("~1", "/").replaceAll("~0", "~")); - const last = keys.pop(); - let node: unknown = root; - for (const key of keys) { - node = (node as JsonObject | undefined)?.[key]; - } - if ( - last === undefined || - typeof node !== "object" || - node === null || - !(last in node) - ) { - throw new Error(`schema/mise.json has no ${pointer} to replace`); - } - (node as JsonObject)[last] = value; -} - -const schema = JSON.parse(fs.readFileSync("schema/mise.json", "utf-8")); -schema["$defs"].settings.properties = settings; -for (const [pointer, fragment] of Object.entries(generatedFragments())) { - replaceAtPointer(schema, pointer, fragment); -} - -writeFormattedJson("schema/mise.json", schema); -writeFormattedJson("schema/mise-task.json", buildTaskSchema(schema)); - -// Generate .miserc.toml schema with only rc=true settings -const misercSettings: Record = {}; - -for (const key in doc) { - const props = doc[key]; - if (isLeaf(props) && props.rc === true) { - misercSettings[key] = buildElement(key, props); - } -} - -const misercSchema = { - $schema: "https://json-schema.org/draft/2020-12/schema", - title: "mise rc config", - description: - "Early initialization settings for mise. These settings are loaded before the main config files.", - type: "object", - unevaluatedProperties: false, - properties: misercSettings, -}; - -writeFormattedJson("schema/miserc.json", misercSchema); - -formatWithOxfmt(writtenPaths);