{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://koru.js.org/schema/koru.schema.json",
  "title": "koru project configuration",
  "description": "koru.json: app identity, web files, Android SDK range, permissions, plugins and signing.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "appId",
    "name",
    "version",
    "versionCode",
    "webDir",
    "minSdk",
    "targetSdk",
    "permissions",
    "plugins",
    "signingKey"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "description": "Editor hint; koru ignores it."
    },
    "appId": {
      "type": "string",
      "maxLength": 127,
      "pattern": "^[a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)+$",
      "description": "Android package name in reverse-domain form, for example com.example.myapp. Never change it after publishing.",
      "examples": ["com.example.myapp"]
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 256,
      "description": "Name shown under the launcher icon."
    },
    "version": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Version shown to people, for example 1.2.0."
    },
    "versionCode": {
      "type": "integer",
      "minimum": 1,
      "maximum": 2100000000,
      "description": "Whole number Android uses to order updates. Increase it for every release."
    },
    "webDir": {
      "type": "string",
      "minLength": 1,
      "description": "Folder with index.html and the static files to package, for example www or dist."
    },
    "minSdk": {
      "type": "integer",
      "minimum": 26,
      "maximum": 36,
      "description": "Oldest supported Android API level (26 = Android 8.0)."
    },
    "targetSdk": {
      "type": "integer",
      "minimum": 26,
      "maximum": 36,
      "description": "Android API level the app targets. Must be at least minSdk."
    },
    "permissions": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "pattern": "^android\\.permission\\.[A-Z][A-Z0-9_]*$"
      },
      "description": "Extra Android permissions, for example android.permission.INTERNET for HTTPS APIs."
    },
    "plugins": {
      "type": "array",
      "uniqueItems": true,
      "items": {
        "type": "string",
        "pattern": "^[a-z][a-z0-9_]{0,31}$",
        "anyOf": [
          {
            "enum": [
              "alarms",
              "app",
              "browser",
              "clipboard",
              "device",
              "notifications",
              "screen",
              "share",
              "storage",
              "vibration"
            ]
          },
          { "description": "Custom plugin id declared in pluginManifests." }
        ]
      },
      "description": "Enabled JavaScript APIs. Built-in: alarms, app, browser, clipboard, device, notifications, screen, share, storage, vibration."
    },
    "signingKey": {
      "type": "string",
      "minLength": 1,
      "description": "Path to the PEM signing identity. Back it up: updates need the same key."
    },
    "icon": {
      "type": "string",
      "description": "Path to a PNG launcher icon."
    },
    "spa": {
      "type": "boolean",
      "description": "Serve index.html for unknown paths, for client-side routers."
    },
    "pluginManifests": {
      "type": "array",
      "items": { "type": "string" },
      "description": "Paths to custom plugin manifests (ABI 1)."
    },
    "orientation": {
      "enum": ["portrait", "landscape", "sensor"],
      "description": "Lock the screen orientation (android:screenOrientation). Omit to keep Android's default."
    },
    "allowBackup": {
      "type": "boolean",
      "default": false,
      "description": "Allow Android auto backup of app data (android:allowBackup). Off by default."
    },
    "keyboard": {
      "enum": ["resize", "pan"],
      "default": "resize",
      "description": "Soft keyboard behaviour (android:windowSoftInputMode): resize shrinks the page, pan slides it up."
    },
    "deepLinks": {
      "type": "array",
      "uniqueItems": true,
      "maxItems": 32,
      "description": "Links that open the app: https App Links (verified with koru deeplinks) or a custom scheme. The link opens the app on its initial page; the URL is not delivered to JavaScript yet.",
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["scheme"],
        "properties": {
          "scheme": {
            "type": "string",
            "pattern": "^[a-z][a-z0-9+.-]{0,31}$",
            "not": { "enum": ["http", "javascript", "file", "content"] },
            "description": "\"https\" or a custom lowercase scheme such as myapp. http, javascript, file and content are rejected."
          },
          "host": {
            "type": "string",
            "maxLength": 253,
            "pattern": "^(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$",
            "description": "Lowercase host such as example.com or *.example.com. Required for https."
          },
          "pathPrefix": {
            "type": "string",
            "pattern": "^/[^\\s?#]*$",
            "maxLength": 256,
            "description": "Path prefix starting with /, for example /app. Needs host."
          }
        }
      }
    },
    "server": {
      "type": "object",
      "additionalProperties": false,
      "minProperties": 1,
      "description": "Remote content: open a website as the app, or mix remote pages with webDir. Adds the INTERNET permission.",
      "properties": {
        "url": {
          "type": "string",
          "pattern": "^https://",
          "maxLength": 2048,
          "description": "Start page instead of webDir/index.html, for example https://example.com/app."
        },
        "allowNavigation": {
          "type": "array",
          "uniqueItems": true,
          "maxItems": 32,
          "description": "Hosts that open inside the app, such as example.com or *.example.com (subdomains only). The url host is included. Any other link opens in the system browser or app.",
          "items": { "type": "string", "maxLength": 253, "pattern": "^(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$" }
        },
        "bridge": {
          "type": "array",
          "uniqueItems": true,
          "maxItems": 32,
          "description": "Exact https origins whose pages get the koru JavaScript API, such as https://example.com. Each must load in the app (url or allowNavigation). Those pages load core.js themselves (koru js).",
          "items": { "type": "string", "pattern": "^https://[^/?#*]+/?$" }
        }
      }
    },
    "webFeatures": {
      "type": "array",
      "uniqueItems": true,
      "items": { "enum": ["files", "geolocation", "camera", "microphone"] },
      "description": "Web APIs the app lets pages use: files (<input type=file>), geolocation (navigator.geolocation), camera and microphone (getUserMedia). Adds the Android permissions each needs; geolocation, camera and microphone ask the user and only work on the app origin and server.bridge origins."
    },
    "nativeLibraries": {
      "type": "object",
      "description": "Prebuilt JNI libraries per ABI, for example {\"arm64-v8a\": [\"libs/libfoo.so\"]}.",
      "additionalProperties": {
        "type": "array",
        "items": { "type": "string" }
      }
    }
  }
}
