{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cockpit-project.org/manifest-1/schema",
  "title": "Cockpit manifest file validation for Cockpit pages.",
  "description": "Validates Cockpit manifest files according to the standard that is parsed and set by Cockpit itself. This is used for Cockpit pages only and does not validate any other AppStream manifest types.",
  "examples": [
    {
      "url": "https://github.com/cockpit-project/cockpit/blob/0e3b78e0a7c841e7608c87e87821247e308d3d14/pkg/packagekit/manifest.json"
    }
  ],
  "type": "object",
  "properties": {
    "content-security-policy": {
      "description": "By default Cockpit serves packages using a strict Content Security Policy, which among other things does not allow inline styles or scripts. This can be overridden on a per-package basis, with this setting. \n\nIf the overridden content security policy does not contain a `default-src`, `connect-src`, `base-uri`, `form-action`, `object-src`, or `block-all-mixed-content` then these will be added to the policy from the manifest.",
      "type": "string"
    },
    "name": {
      "description": "An optional string that changes the name of the package. Normally packages derive their name from the directory that they are located in. This field overrides that name.",
      "type": "string",
      "pattern": "^[A-Za-z0-9_]+$"
    },
    "priority": {
      "description": "An optional integer that specifies which package is preferred in cases where there are conflicts. For example given two packages with the same `name` a package is chosen based on its priority. A higher integer means higher priority.",
      "type": "integer"
    },
    "conditions": {
      "description": "An optional list of `{\"predicate\": \"value\"}` objects. Cockpit will only consider the package if all conditions are met. Currently supported predicates are `path-exists` and `path-not-exists`. Unknown predicates are ignored. This is preferable to using `priority`, but only available since Cockpit 286.",
      "$ref": "#/$defs/condition-types"
    },
    "requires": {
      "description": "An optional JSON object that contains a \"cockpit\" string version number. The package will only be usable if the Cockpit bridge and javascript base are equal or newer than the given version number.",
      "type": "object",
      "required": [ "cockpit" ],
      "properties": {
        "cockpit": {
          "type": "string",
          "pattern": "^\\d[\\d|\\.]*$"
        }
      },
      "additionalProperties": {
        "type": "string"
      }
    },
    "version": {
      "description": "An informational version number for the package.",
      "type": [
        "string",
        "number"
      ]
    },
    "preload": {
      "description": "A list of identifiers of the components that should be preloaded. Normally, the files of a component are loaded when the user navigates to it for the first time. The files of a preloaded component are loaded immediately after the user logs in, and the initialization code of the component is invoked. \n\nThe value of this field is an array of strings, where each string is one of the keys used in the `dashboard`, `menu`, or `tools` fields",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "parent": {
      "description": "Used when module does not have its own menu item but is a part of a different module.",
      "type": "object",
      "properties": {
        "component": {
          "description": "The name of the superordinate component.",
          "type": "string"
        },
        "docs": {
          "$ref": "#/$defs/docs"
        }
      }
    },
    "dashboard": {
      "description": "Dashboard items appear in the menu under the section Apps.",
      "$ref": "#/$defs/menu-type"
    },
    "menu": {
      "description": "These items appear in the menu under the section System. This section is roughly ordered into these categories (with their order in parentheses):\n- System Information (10) \n- Logs (20) \n- Configuring major subsystems (30-40) \n- Things running on the machine (VMs, Containers - 50-60) \n- Implementation Details (Accounts, Services - 70-100).",
      "$ref": "#/$defs/menu-type"
    },
    "tools": {
      "description": "These items appear in the menu under the section Tools.",
      "$ref": "#/$defs/menu-type"
    },
    "bridges": {
      "description": "Details for additional server-side bridges. Each item specifies how to match requests and which executable to use.",
      "type": "array",
      "items": {
        "$comment": "A privileged bridge can not have a 'match' property.",
        "type": "object",
        "if": {
          "required": [ "privileged" ],
          "properties": {
            "privileged": {
              "const": true
            }
          }
        },
        "then": {
          "properties": {
            "match": false
          }
        },
        "required": [ "spawn" ],
        "properties": {
          "environ": {
            "description": "Optional, an array of environment variables to pass to the bridge command. Dynamic values can be used with ${} syntax.",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "match": {
            "type": "object",
            "required": [ "payload" ],
            "properties": {
              "payload": {
                "description": "The payload pattern to match incoming requests.",
                "type": "string"
              }
            }
          },
          "privileged": {
            "description": "If set to true, marks the bridge as a superuser bridge, to be started with escalated privileges. A privileged bridge can not have a \"match\" property.",
            "default": false,
            "type": "boolean"
          },
          "label": {
            "description": "Optional, sets a label on the bridge for selection in the UI, useful for privileged bridges.",
            "type": "string"
          },
          "problem": {
            "description": "If specified, when bridge fails to start it will close the associated channel with this problem code.",
            "type": "string"
          },
          "spawn": {
            "description": "The command and arguments to invoke the bridge. Dynamic values can be used with ${} syntax.",
            "type": "array"
          }
        }
      }
    }
  },
  "$defs": {
    "docs": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "description": "List of documentation URLs for the given page, describing where the component is located within Cockpit.",
      "type": "array",
      "items": {
        "type": "object",
        "required": [ "label", "url" ],
        "properties": {
          "label": {
            "description": "The label for the documentation page.",
            "type": "string"
          },
          "url": {
            "description": "The URL for the documentation page.",
            "type": "string"
          }
        }
      }
    },
    "menu-type": {
      "type": "object",
      "properties": {
        "index": {
          "description": "An index of menu items with labels and their order.",
          "type": "object",
          "properties": {
            "label": {
              "description": "The displayed text of the menu item.",
              "type": "string"
            },
            "order": {
              "description": "An optional order integer to place this menu item or tool. Lower integers are listed first.",
              "type": "integer"
            },
            "path": {
              "description": "The relative path to the HTML file within the package that implements the menu item or tool.",
              "type": "string"
            },
            "docs": {
              "$ref": "#/$defs/docs"
            },
            "keywords": {
              "description": "Keywords that describe the page and which are used for searching. These keywords should be lowercase. Page label is prepended as first keyword in the first keyword item.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "matches": {
                    "description": "An array of strings matching keywords in lowercase.",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[^A-Z]+$"
                    }
                  },
                  "goto": {
                    "description": "Optional path that is used for all keywords in this item. When this argument starts with slash, then it is used as pathname, otherwise it is used as hash. Defining `goto:\"page_hash\"` in page with `path:\"/page_path\"` would redirect to `/page_path#page_hash`, while `goto:\"/page_path\"` would redirect to `/page_path` ignoring default page path.",
                    "type": "string"
                  },
                  "weight": {
                    "description": "How much keywords are prioritized over others.",
                    "default": 3,
                    "type": "integer"
                  },
                  "translate": {
                    "description": "If keywords should be localized or not",
                    "default": true,
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      }
    },
    "predicates": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "path-exists": {
          "description": "A condition where a specific file path must exist.",
          "type": "string"
        },
        "path-not-exists": {
          "description": "A condition where a specific file path must not exist.",
          "type": "string"
        },
        "any": {
          "description": "A list of conditions that evaluate true if at least one of the predicates is met.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/predicates"
          }
        }
      }
    },
    "condition-types": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/predicates"
      }
    }
  }
}
