mirror of
https://github.com/chenasraf/sofmani.git
synced 2026-05-17 17:28:04 +00:00
feat: support json schema
This commit is contained in:
@@ -40,6 +40,8 @@ reproducible.
|
||||
- Group software installations into logical "steps" with sophisticated orchestration.
|
||||
- **Category headers** to visually organize your installers list.
|
||||
- **Template variables** for dynamic values (architecture, OS, device ID) in commands and filenames.
|
||||
- **JSON Schema** for editor autocompletion and validation of your config files. See
|
||||
[JSON Schema docs](./docs/json-schema.md).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -8,4 +8,5 @@ For a general overview, see the [README](/README.md).
|
||||
- [Command Line Interface (CLI)](./command-line-interface.md)
|
||||
- [Configuration Reference](./configuration-reference.md)
|
||||
- [Installer Configuration](./installer-configuration.md)
|
||||
- [JSON Schema](./json-schema.md) - Editor autocompletion and validation for your config files
|
||||
- [Recipes](./recipes) - Installer groups you can use immediately as remote manifests
|
||||
|
||||
139
docs/json-schema.md
Normal file
139
docs/json-schema.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# JSON Schema
|
||||
|
||||
`sofmani` ships a [JSON Schema](https://json-schema.org/) describing the full configuration format.
|
||||
Pointing your editor at it gives you **autocompletion**, **inline documentation**, and
|
||||
**validation** while editing your `sofmani.yaml` or `sofmani.json` files.
|
||||
|
||||
The schema lives in the repo at [`schema/sofmani.schema.json`](../schema/sofmani.schema.json) and is
|
||||
published on the `master` branch at:
|
||||
|
||||
```
|
||||
https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json
|
||||
```
|
||||
|
||||
## Using the schema with YAML
|
||||
|
||||
Most editors use the
|
||||
[YAML Language Server](https://github.com/redhat-developer/yaml-language-server) (bundled with VS
|
||||
Code's Red Hat YAML extension, Neovim's `yamlls`, Zed, and others). You can enable the schema in
|
||||
either of two ways.
|
||||
|
||||
### 1. Inline comment (per file)
|
||||
|
||||
Add a modeline as the **first line** of the YAML file:
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json
|
||||
|
||||
debug: true
|
||||
install:
|
||||
- name: neovim
|
||||
type: brew
|
||||
```
|
||||
|
||||
### 2. Editor-wide association
|
||||
|
||||
In VS Code, add this to your `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json": [
|
||||
"sofmani.yaml",
|
||||
"sofmani.yml",
|
||||
"**/sofmani/*.yml",
|
||||
"**/recipes/*.yml"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Adjust the glob patterns to match where you keep your manifests.
|
||||
|
||||
### Using a local copy
|
||||
|
||||
If you have `sofmani` checked out locally, or you vendor the schema, point at the file on disk:
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=./schema/sofmani.schema.json
|
||||
```
|
||||
|
||||
## Using the schema with JSON
|
||||
|
||||
In a JSON config, set the `$schema` key at the top of the document:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json",
|
||||
"debug": true,
|
||||
"install": [
|
||||
{
|
||||
"name": "neovim",
|
||||
"type": "brew"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
VS Code picks this up automatically. Most other JSON-aware editors do too.
|
||||
|
||||
Alternatively, you can configure `json.schemas` in VS Code's `settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["sofmani.json", "**/sofmani/*.json"],
|
||||
"url": "https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Validating from the command line
|
||||
|
||||
You can validate a config file against the schema using any JSON Schema validator. Two convenient
|
||||
options:
|
||||
|
||||
### `check-jsonschema` (Python)
|
||||
|
||||
```bash
|
||||
pipx install check-jsonschema
|
||||
check-jsonschema \
|
||||
--schemafile schema/sofmani.schema.json \
|
||||
sofmani.yaml
|
||||
```
|
||||
|
||||
`check-jsonschema` supports both JSON and YAML input files out of the box.
|
||||
|
||||
### `ajv` (Node)
|
||||
|
||||
For JSON files:
|
||||
|
||||
```bash
|
||||
npx ajv-cli validate \
|
||||
-s schema/sofmani.schema.json \
|
||||
-d sofmani.json
|
||||
```
|
||||
|
||||
For YAML files, convert on the fly with `yq`:
|
||||
|
||||
```bash
|
||||
yq -o=json sofmani.yaml | npx ajv-cli validate -s schema/sofmani.schema.json -d /dev/stdin
|
||||
```
|
||||
|
||||
## What the schema covers
|
||||
|
||||
- All top-level options (`debug`, `check_updates`, `summary`, `category_display`, `repo_update`,
|
||||
`defaults`, `env`, `platform_env`, `machine_aliases`, `install`).
|
||||
- All supported installer types and their type-specific `opts`.
|
||||
- Enums for `category_display`, `repo_update` modes, installer `type`, and platform names.
|
||||
- The `frequency` duration pattern (`1d`, `12h`, `1w2d`, ...).
|
||||
- Dual shapes for fields like `skip_summary` (bool or object), `enabled` (bool or shell string), and
|
||||
`github-release` `download_filename` (string or per-platform map).
|
||||
- Per-type narrowing of `opts`: typos like `tap: foo` vs. `tapp: foo` are flagged, and `group`
|
||||
installers cannot accidentally set `opts`.
|
||||
- The shell-script fields (`check_has_update`, `check_installed`, `pre_install`, `post_install`,
|
||||
`pre_update`, `post_update`) accept either a string or a boolean. Booleans are a shorthand that
|
||||
YAML coerces to the literal `"true"`/`"false"` — handy for forcing `check_has_update: true` to
|
||||
mean "always treat as having an update".
|
||||
193
schema/schema_test.go
Normal file
193
schema/schema_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package schema_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/chenasraf/sofmani/appconfig"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// schemaPath returns the absolute path to the schema file regardless of where
|
||||
// the tests are run from.
|
||||
func schemaPath(t *testing.T) string {
|
||||
t.Helper()
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
return filepath.Join(wd, "sofmani.schema.json")
|
||||
}
|
||||
|
||||
func loadSchema(t *testing.T) map[string]any {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(schemaPath(t))
|
||||
require.NoError(t, err)
|
||||
var m map[string]any
|
||||
require.NoError(t, json.Unmarshal(data, &m), "schema must be valid JSON")
|
||||
return m
|
||||
}
|
||||
|
||||
func TestSchemaIsValidJSON(t *testing.T) {
|
||||
m := loadSchema(t)
|
||||
assert.Equal(t, "http://json-schema.org/draft-07/schema#", m["$schema"])
|
||||
assert.NotEmpty(t, m["$id"])
|
||||
assert.NotEmpty(t, m["title"])
|
||||
assert.Equal(t, "object", m["type"])
|
||||
}
|
||||
|
||||
func TestSchemaTopLevelProperties(t *testing.T) {
|
||||
m := loadSchema(t)
|
||||
props, ok := m["properties"].(map[string]any)
|
||||
require.True(t, ok, "top-level properties must exist")
|
||||
|
||||
// These must be declared at the top level of the schema.
|
||||
expected := []string{
|
||||
"$schema",
|
||||
"debug",
|
||||
"check_updates",
|
||||
"summary",
|
||||
"category_display",
|
||||
"repo_update",
|
||||
"defaults",
|
||||
"env",
|
||||
"platform_env",
|
||||
"machine_aliases",
|
||||
"install",
|
||||
}
|
||||
for _, key := range expected {
|
||||
_, exists := props[key]
|
||||
assert.Truef(t, exists, "top-level property %q missing from schema", key)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInstallerTypesMatchGoConstants ensures the schema's list of installer
|
||||
// types stays in lock-step with the Go InstallerType constants. If a new
|
||||
// installer type is added in code but not in the schema (or vice-versa), this
|
||||
// test fails and prompts the developer to update both sides.
|
||||
func TestInstallerTypesMatchGoConstants(t *testing.T) {
|
||||
m := loadSchema(t)
|
||||
defs, ok := m["definitions"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
installerType, ok := defs["installerType"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
enum, ok := installerType["enum"].([]any)
|
||||
require.True(t, ok)
|
||||
|
||||
schemaTypes := make([]string, 0, len(enum))
|
||||
for _, v := range enum {
|
||||
s, ok := v.(string)
|
||||
require.True(t, ok)
|
||||
schemaTypes = append(schemaTypes, s)
|
||||
}
|
||||
sort.Strings(schemaTypes)
|
||||
|
||||
goTypes := []string{
|
||||
string(appconfig.InstallerTypeGroup),
|
||||
string(appconfig.InstallerTypeShell),
|
||||
string(appconfig.InstallerTypeDocker),
|
||||
string(appconfig.InstallerTypeBrew),
|
||||
string(appconfig.InstallerTypeApt),
|
||||
string(appconfig.InstallerTypeApk),
|
||||
string(appconfig.InstallerTypeGit),
|
||||
string(appconfig.InstallerTypeGitHubRelease),
|
||||
string(appconfig.InstallerTypeRsync),
|
||||
string(appconfig.InstallerTypeNpm),
|
||||
string(appconfig.InstallerTypePnpm),
|
||||
string(appconfig.InstallerTypeYarn),
|
||||
string(appconfig.InstallerTypePipx),
|
||||
string(appconfig.InstallerTypeManifest),
|
||||
string(appconfig.InstallerTypePacman),
|
||||
string(appconfig.InstallerTypeYay),
|
||||
string(appconfig.InstallerTypeCargo),
|
||||
}
|
||||
sort.Strings(goTypes)
|
||||
|
||||
assert.Equal(t, goTypes, schemaTypes, "installerType enum in schema is out of sync with Go constants")
|
||||
}
|
||||
|
||||
func TestCategoryDisplayEnumMatchesGoConstants(t *testing.T) {
|
||||
m := loadSchema(t)
|
||||
props := m["properties"].(map[string]any)
|
||||
cat := props["category_display"].(map[string]any)
|
||||
enum, ok := cat["enum"].([]any)
|
||||
require.True(t, ok)
|
||||
|
||||
schemaVals := make([]string, 0, len(enum))
|
||||
for _, v := range enum {
|
||||
schemaVals = append(schemaVals, v.(string))
|
||||
}
|
||||
sort.Strings(schemaVals)
|
||||
|
||||
goVals := []string{
|
||||
string(appconfig.CategoryDisplayBorder),
|
||||
string(appconfig.CategoryDisplayBorderCompact),
|
||||
string(appconfig.CategoryDisplayMinimal),
|
||||
}
|
||||
sort.Strings(goVals)
|
||||
|
||||
assert.Equal(t, goVals, schemaVals)
|
||||
}
|
||||
|
||||
func TestRepoUpdateEnumMatchesGoConstants(t *testing.T) {
|
||||
m := loadSchema(t)
|
||||
defs := m["definitions"].(map[string]any)
|
||||
mode := defs["repoUpdateMode"].(map[string]any)
|
||||
enum, ok := mode["enum"].([]any)
|
||||
require.True(t, ok)
|
||||
|
||||
schemaVals := make([]string, 0, len(enum))
|
||||
for _, v := range enum {
|
||||
schemaVals = append(schemaVals, v.(string))
|
||||
}
|
||||
sort.Strings(schemaVals)
|
||||
|
||||
goVals := []string{
|
||||
string(appconfig.RepoUpdateOnce),
|
||||
string(appconfig.RepoUpdateAlways),
|
||||
string(appconfig.RepoUpdateNever),
|
||||
}
|
||||
sort.Strings(goVals)
|
||||
|
||||
assert.Equal(t, goVals, schemaVals)
|
||||
}
|
||||
|
||||
// TestRecipesParseAgainstSchemaShape is a structural smoke test: every recipe
|
||||
// shipped in docs/recipes must only use top-level keys that the schema
|
||||
// declares. This catches typos and schema drift without pulling in a full
|
||||
// JSON-schema validator as a dependency.
|
||||
func TestRecipesParseAgainstSchemaShape(t *testing.T) {
|
||||
recipesDir := filepath.Join("..", "docs", "recipes")
|
||||
entries, err := os.ReadDir(recipesDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if filepath.Ext(name) != ".yml" && filepath.Ext(name) != ".yaml" {
|
||||
continue
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
path := filepath.Join(recipesDir, name)
|
||||
data, err := os.ReadFile(path)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg, err := appconfig.ParseConfigFromContent(data)
|
||||
require.NoError(t, err, "recipe must parse as AppConfig")
|
||||
|
||||
// Basic sanity: every installer in the recipe has a recognized
|
||||
// type (or is a category header).
|
||||
for _, inst := range cfg.Install {
|
||||
if inst.IsCategory() {
|
||||
continue
|
||||
}
|
||||
assert.NotEmptyf(t, string(inst.Type), "installer in %s missing type", name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
471
schema/sofmani.schema.json
Normal file
471
schema/sofmani.schema.json
Normal file
@@ -0,0 +1,471 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://raw.githubusercontent.com/chenasraf/sofmani/master/schema/sofmani.schema.json",
|
||||
"title": "sofmani configuration",
|
||||
"description": "Schema for sofmani (Software Manifest) configuration files. See https://github.com/chenasraf/sofmani for documentation.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Reference to the JSON schema for this configuration file."
|
||||
},
|
||||
"debug": {
|
||||
"type": "boolean",
|
||||
"description": "Enable or disable debug mode.",
|
||||
"default": false
|
||||
},
|
||||
"check_updates": {
|
||||
"type": "boolean",
|
||||
"description": "Enable or disable checking for updates before running operations.",
|
||||
"default": false
|
||||
},
|
||||
"summary": {
|
||||
"type": "boolean",
|
||||
"description": "Enable or disable the installation summary at the end.",
|
||||
"default": true
|
||||
},
|
||||
"category_display": {
|
||||
"description": "Controls how category headers are rendered.",
|
||||
"type": "string",
|
||||
"enum": ["border", "border-compact", "minimal"],
|
||||
"default": "border"
|
||||
},
|
||||
"repo_update": {
|
||||
"type": "object",
|
||||
"description": "Controls repository index updates per installer type.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"brew": { "$ref": "#/definitions/repoUpdateMode" },
|
||||
"apt": { "$ref": "#/definitions/repoUpdateMode" },
|
||||
"apk": { "$ref": "#/definitions/repoUpdateMode" }
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"type": "object",
|
||||
"description": "Default configurations to apply to all installers of a given type.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "object",
|
||||
"description": "Map of installer type to default installer data.",
|
||||
"additionalProperties": { "$ref": "#/definitions/installer" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"$ref": "#/definitions/envMap",
|
||||
"description": "Environment variables set for all installers."
|
||||
},
|
||||
"platform_env": {
|
||||
"$ref": "#/definitions/platformEnvMap",
|
||||
"description": "Platform-specific environment variables set for all installers."
|
||||
},
|
||||
"machine_aliases": {
|
||||
"type": "object",
|
||||
"description": "Map of friendly names to machine IDs. Use 'sofmani --machine-id' to get your machine's ID.",
|
||||
"additionalProperties": { "type": "string" }
|
||||
},
|
||||
"install": {
|
||||
"type": "array",
|
||||
"description": "List of installers / steps to run, in order.",
|
||||
"items": { "$ref": "#/definitions/installStep" }
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"repoUpdateMode": {
|
||||
"type": "string",
|
||||
"enum": ["once", "always", "never"],
|
||||
"description": "How often the repository index should be updated during a sofmani run."
|
||||
},
|
||||
"platform": {
|
||||
"type": "string",
|
||||
"enum": ["macos", "linux", "windows"]
|
||||
},
|
||||
"envMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": { "type": "string" }
|
||||
},
|
||||
"platformEnvMap": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"macos": { "$ref": "#/definitions/envMap" },
|
||||
"linux": { "$ref": "#/definitions/envMap" },
|
||||
"windows": { "$ref": "#/definitions/envMap" }
|
||||
}
|
||||
},
|
||||
"platforms": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Platform-specific execution controls.",
|
||||
"properties": {
|
||||
"only": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/platform" },
|
||||
"description": "Platforms where the step should execute. Supersedes 'except'."
|
||||
},
|
||||
"except": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/platform" },
|
||||
"description": "Platforms where the step should NOT execute."
|
||||
}
|
||||
}
|
||||
},
|
||||
"machines": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Machine-specific execution controls.",
|
||||
"properties": {
|
||||
"only": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Machine IDs or aliases where the step should execute. Supersedes 'except'."
|
||||
},
|
||||
"except": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Machine IDs or aliases where the step should NOT execute."
|
||||
}
|
||||
}
|
||||
},
|
||||
"envShell": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Shell to use for command executions per platform. Windows always uses cmd.",
|
||||
"properties": {
|
||||
"macos": { "type": "string" },
|
||||
"linux": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"skipSummary": {
|
||||
"description": "Exclude this installer from the summary. Set to true to skip both install and update summaries, or use an object for granular control.",
|
||||
"oneOf": [
|
||||
{ "type": "boolean" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"install": { "type": "boolean" },
|
||||
"update": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"enabled": {
|
||||
"description": "Enable or disable the step. Accepts a boolean, a boolean string ('true'/'false'), or a shell command whose exit code decides the result.",
|
||||
"oneOf": [{ "type": "boolean" }, { "type": "string" }]
|
||||
},
|
||||
"shellScript": {
|
||||
"description": "A shell command or script. Accepts a string, or a boolean shorthand where 'true' runs the unix 'true' builtin (always succeeds) and 'false' runs 'false' (always fails).",
|
||||
"oneOf": [{ "type": "string" }, { "type": "boolean" }]
|
||||
},
|
||||
"frequency": {
|
||||
"type": "string",
|
||||
"description": "Duration string limiting how often the installer runs (e.g. '60s', '30m', '12h', '1d', '1w', '1d12h'). Supported units: s, m, h, d, w.",
|
||||
"pattern": "^(\\d+[smhdw])+$"
|
||||
},
|
||||
"installerType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"group",
|
||||
"shell",
|
||||
"docker",
|
||||
"brew",
|
||||
"apt",
|
||||
"apk",
|
||||
"git",
|
||||
"github-release",
|
||||
"rsync",
|
||||
"npm",
|
||||
"pnpm",
|
||||
"yarn",
|
||||
"pipx",
|
||||
"manifest",
|
||||
"pacman",
|
||||
"yay",
|
||||
"cargo"
|
||||
]
|
||||
},
|
||||
"installStep": {
|
||||
"description": "An entry in the top-level 'install' list. Must be either a category header (has 'category') or an installer (has 'name' and 'type').",
|
||||
"allOf": [
|
||||
{ "$ref": "#/definitions/installer" },
|
||||
{
|
||||
"if": { "not": { "required": ["category"] } },
|
||||
"then": { "required": ["name", "type"] }
|
||||
}
|
||||
]
|
||||
},
|
||||
"installer": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Installer data. Used both for steps in 'install' and for 'defaults.type' overrides (where 'name' and 'type' may be omitted).",
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Category header text. When set, this entry is a category header only (no installation)."
|
||||
},
|
||||
"desc": {
|
||||
"type": "string",
|
||||
"description": "Optional description shown below a category header."
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Identifier for the step. Typically also used to check for the app's existence unless overridden by bin_name."
|
||||
},
|
||||
"type": { "$ref": "#/definitions/installerType" },
|
||||
"enabled": { "$ref": "#/definitions/enabled" },
|
||||
"tags": {
|
||||
"type": "string",
|
||||
"description": "Space-separated list of tags used for filtering."
|
||||
},
|
||||
"platforms": { "$ref": "#/definitions/platforms" },
|
||||
"machines": { "$ref": "#/definitions/machines" },
|
||||
"env": { "$ref": "#/definitions/envMap" },
|
||||
"platform_env": { "$ref": "#/definitions/platformEnvMap" },
|
||||
"steps": {
|
||||
"type": "array",
|
||||
"description": "Sub-installers for 'group' type.",
|
||||
"items": { "$ref": "#/definitions/installStep" }
|
||||
},
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"description": "Step-specific options. Content depends on the installer 'type'."
|
||||
},
|
||||
"bin_name": {
|
||||
"type": "string",
|
||||
"description": "Binary name for the installed software, used instead of 'name' when checking for existence."
|
||||
},
|
||||
"check_has_update": {
|
||||
"$ref": "#/definitions/shellScript",
|
||||
"description": "Shell command to check if an update is available. Exit 0 means an update is available. Use `true` as a shortcut for 'always has update'."
|
||||
},
|
||||
"check_installed": {
|
||||
"$ref": "#/definitions/shellScript",
|
||||
"description": "Shell command to check if the software is already installed. Exit 0 means installed. Use `true` as a shortcut for 'always installed'."
|
||||
},
|
||||
"pre_install": { "$ref": "#/definitions/shellScript", "description": "Shell script to run before install." },
|
||||
"post_install": { "$ref": "#/definitions/shellScript", "description": "Shell script to run after install." },
|
||||
"pre_update": { "$ref": "#/definitions/shellScript", "description": "Shell script to run before update." },
|
||||
"post_update": { "$ref": "#/definitions/shellScript", "description": "Shell script to run after update." },
|
||||
"env_shell": { "$ref": "#/definitions/envShell" },
|
||||
"skip_summary": { "$ref": "#/definitions/skipSummary" },
|
||||
"verbose": {
|
||||
"type": "boolean",
|
||||
"description": "Pass verbose flags to the underlying installer tool.",
|
||||
"default": false
|
||||
},
|
||||
"frequency": { "$ref": "#/definitions/frequency" }
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "shell" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"command": { "type": "string", "description": "Shell command to run for install." },
|
||||
"update_command": { "type": "string", "description": "Shell command to run for update." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "git" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"destination": { "type": "string" },
|
||||
"ref": { "type": "string" },
|
||||
"flags": { "type": "string" },
|
||||
"install_flags": { "type": "string" },
|
||||
"update_flags": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "github-release" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"repository": { "type": "string", "description": "user/repository-name" },
|
||||
"destination": { "type": "string" },
|
||||
"strategy": {
|
||||
"type": "string",
|
||||
"enum": ["tar", "zip", "none"],
|
||||
"default": "none"
|
||||
},
|
||||
"download_filename": {
|
||||
"description": "Asset filename, or a per-platform map. Supports Go template variables.",
|
||||
"oneOf": [
|
||||
{ "type": "string" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"macos": { "type": "string" },
|
||||
"linux": { "type": "string" },
|
||||
"windows": { "type": "string" }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"archive_bin_name": { "type": "string" },
|
||||
"extract_to": { "type": "string", "description": "Enables tree mode: extract entire archive to this directory." },
|
||||
"strip_components": { "type": "integer", "minimum": 0 },
|
||||
"bin_links": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["target"],
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"target": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"github_token": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "manifest" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"path": { "type": "string" },
|
||||
"ref": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "rsync" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"source": { "type": "string" },
|
||||
"destination": { "type": "string" },
|
||||
"flags": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "brew" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"tap": { "type": "string" },
|
||||
"cask": { "type": "boolean" },
|
||||
"flags": { "type": "string" },
|
||||
"install_flags": { "type": "string" },
|
||||
"update_flags": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": { "enum": ["npm", "pnpm", "yarn", "apt", "apk", "pipx", "cargo"] }
|
||||
},
|
||||
"required": ["type"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"flags": { "type": "string" },
|
||||
"install_flags": { "type": "string" },
|
||||
"update_flags": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": { "type": { "enum": ["pacman", "yay"] } },
|
||||
"required": ["type"]
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"needed": { "type": "boolean" },
|
||||
"flags": { "type": "string" },
|
||||
"install_flags": { "type": "string" },
|
||||
"update_flags": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "docker" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"properties": {
|
||||
"opts": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"flags": { "type": "string" },
|
||||
"platform": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"macos": { "type": "string" },
|
||||
"linux": { "type": "string" },
|
||||
"windows": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"skip_if_unavailable": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": { "properties": { "type": { "const": "group" } }, "required": ["type"] },
|
||||
"then": {
|
||||
"not": { "required": ["opts"] },
|
||||
"description": "The 'group' type uses 'steps', not 'opts'."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user