> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirurobotics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a release

export const Framed = ({image, background, link, alt = "Framed content", borderWidth = "24px", outerRadius = "10px", innerRadius = "6px"}) => {
  const parseShorthand = value => {
    const values = String(value).trim().split(/\s+/);
    switch (values.length) {
      case 1:
        return [values[0], values[0], values[0], values[0]];
      case 2:
        return [values[0], values[1], values[0], values[1]];
      case 3:
        return [values[0], values[1], values[2], values[1]];
      default:
        return values.slice(0, 4);
    }
  };
  const isZero = v => parseFloat(v) === 0;
  const padding = parseShorthand(borderWidth);
  const outer = parseShorthand(outerRadius);
  const cornerMasks = [];
  if (isZero(padding[0]) && isZero(padding[3])) {
    cornerMasks.push(`radial-gradient(circle at 0% 0%, transparent ${outer[0]}, black ${outer[0]})`);
  }
  if (isZero(padding[0]) && isZero(padding[1])) {
    cornerMasks.push(`radial-gradient(circle at 100% 0%, transparent ${outer[1]}, black ${outer[1]})`);
  }
  if (isZero(padding[2]) && isZero(padding[1])) {
    cornerMasks.push(`radial-gradient(circle at 100% 100%, transparent ${outer[2]}, black ${outer[2]})`);
  }
  if (isZero(padding[2]) && isZero(padding[3])) {
    cornerMasks.push(`radial-gradient(circle at 0% 100%, transparent ${outer[3]}, black ${outer[3]})`);
  }
  const backgroundMask = cornerMasks.length > 0 ? cornerMasks.join(", ") : undefined;
  const innerImage = <img src={image} alt={alt} noZoom={link ? true : false} style={{
    display: "block",
    width: "100%",
    margin: 0,
    borderRadius: 0
  }} />;
  return <div style={{
    display: "inline-block",
    position: "relative",
    borderRadius: outerRadius,
    overflow: "hidden"
  }}>
            {}
            {background && <div style={{
    position: "absolute",
    inset: 0,
    backgroundImage: `url(${background})`,
    backgroundSize: "cover",
    maskImage: backgroundMask,
    WebkitMaskImage: backgroundMask,
    maskComposite: "intersect",
    WebkitMaskComposite: "source-in"
  }} />}
            {}
            <div style={{
    position: "relative",
    padding: borderWidth
  }}>
                <div style={{
    borderRadius: innerRadius,
    overflow: "hidden",
    lineHeight: 0
  }}>
                    {link ? <a href={link}>{innerImage}</a> : innerImage}
                </div>
            </div>
        </div>;
};

<Framed background="https://assets.mirurobotics.com/docs/v04/images/releases/background.jpeg" image="https://assets.mirurobotics.com/docs/v04/images/releases/header:release-create.png" borderWidth="28px" innerRadius="10px" outerRadius="12px" />

The only way to create releases is via the command line. The dashboard only supports [duplicating releases](/primitives/releases#duplicate-a-release).

Creating releases from the CLI is a deliberate design choice—we believe a release's
definition should be versioned in a Git repository. The CLI promotes this practice,
simplifying the release process and encouraging better software development habits.

### Continuous integration

The CLI can be used to programmatically create releases in a [CI pipeline](/developers/ci/overview), ensuring releases are automatically created when code is pushed to a Git repository. Visit the [CI/CD](/developers/ci/overview) page for more information.

## Prerequisites

To create a release, the following prerequisites must be met:

1. CLI installed - [install](/developers/cli/install) the Miru CLI on your development machine and authenticate using the [login](/developers/cli/login) command
2. Local Git repository - the release's definitions must be committed to a local Git repository

## Config schemas

[Config schemas](/cfg-mgmt/primitives/schemas/overview) aren't created separately from releases. Instead, the CLI creates the schemas in the same command as the release itself. Releases may contain any number of schemas, but they must contain at least one.

Schemas may belong to multiple releases and are identified by hashing their content. Schemas with equivalent content are considered identical (even if comments, spacing, or other formatting is different).

### Define the schemas

Schemas must be defined in a supported schema [language](/cfg-mgmt/primitives/schemas/languages/overview). You can find example schemas in our [getting-started repository](https://github.com/mirurobotics/getting-started).

If you're not sure which language to use, we recommend starting with JSON Schema — it's the most widely used and easiest to get started with.

If you don't currently use a schema, we recommend starting with an empty schema, one that regards all config instances as valid. Over time, you can gradually add constraints to make it more strict.

<CodeGroup>
  ```yaml JSON Schema theme={null}
  $schema: "https://json-schema.org/draft/2020-12/schema"
  x-miru-config-type: "{your-config-type-slug}"
  ```

  ```cue CUE theme={null}
  @miru(config_type="{your-config-type-slug}")
  ```
</CodeGroup>

<Tip>
  Miru supports CUE packages as a way to split a schema's definition into multiple files. Read more in the [CUE documentation](/cfg-mgmt/primitives/schemas/languages/cue#cue-packages).
</Tip>

### Schema annotations

Schemas support annotations—Miru-specific extensions that define metadata about a schema.

The `config type` annotation is the only required annotation—it's how Miru knows which config type the schema belongs to. However, there are other useful annotations, such as the `instance file path` annotation, which defines where config instances for this schema are written to disk on your devices.

<ParamField path="config type" required>
  The config type is a required annotation that identifies the config type to which a schema belongs. Below is the syntax for annotating a schema with a [config type slug](/cfg-mgmt/primitives/config-types#param-slug).

  <CodeGroup>
    ```yaml JSON Schema theme={null}
    x-miru-config-type: "{config-type-slug}"
    ```

    ```cue CUE theme={null}
    @miru(config_type="{config-type-slug}")
    ```
  </CodeGroup>

  If the provided config type slug does not yet exist, it is automatically created. To edit a config type after creation, visit the [config types documentation](/cfg-mgmt/primitives/config-types#edit-a-config-type).

  Examples: `mobility`, `communication`, `perception`
</ParamField>

<ParamField path="instance file path">
  The instance file path is the absolute file system path where config instances for this schema are written on the device.

  <Warning>
    The Miru Agent ships with out-of-the-box access to `/srv/miru`. To deploy configs to a custom directory, please visit the [File system access](/developers/agent/filesys-access#configs) page.
  </Warning>

  Instance file paths control the type of file that config instances are deployed as. Currently, JSON (`.json`) and YAML (`.yaml`, `.yml`) are supported.

  This annotation is optional and defaults to `/srv/miru/configs/{config-type-slug}.json`.

  <CodeGroup>
    ```yaml JSON Schema theme={null}
    x-miru-instance-filepath: "/srv/miru/configs/{config-type-slug}.json"
    ```

    ```cue CUE theme={null}
    @miru(instance_filepath="/srv/miru/configs/{config-type-slug}.json")
    ```
  </CodeGroup>

  Examples:

  * `/srv/miru/configs/mobility.json`
  * `/home/myapp/configs/communication.yaml`
  * `/var/lib/myapp/configs/safety.yaml`
</ParamField>

## Git commit

When creating a release, the CLI captures the following Git metadata from the *local* Git repository where the CLI is run:

| Metadata       | Description                                                   |
| -------------- | ------------------------------------------------------------- |
| **Commit SHA** | The SHA of the current commit                                 |
| **Origin URL** | The remote URL of the Git repository                          |
| **File paths** | The file paths of the schemas relative to the repository root |

This metadata is then made available in the dashboard for the release, allowing you to easily trace the release's origin.

### Supported Git providers

The following Git providers are currently supported:

* GitHub
* GitLab
* Bitbucket

If your Git repository is hosted on a different provider, please reach out to [support@mirurobotics.com](mailto:support@mirurobotics.com).

### Requirements

To be able to capture Git metadata, the provided config schemas and upload rules must meet the following requirements when creating a release via the CLI:

* Must be defined in a local Git repository
* The Git repository must have a remote URL (e.g., GitHub, GitLab, Bitbucket).
* The schemas and upload rules' latest changes must be committed to the Git repository before pushing to Miru (cannot be dirty or unstaged)

Git metadata is not optional. If a schema or upload rule does not meet these requirements, the CLI will return an error and fail to create the release.

## CLI command

Once you've defined the schemas and committed them to Git, you're ready to create a release.

### Usage

You must specify two flags when creating a release: the version and the schemas. Schemas can be specified as a directory or as individual files.

<Tabs>
  <Tab title="Schema Directory">
    Use the `--schemas` flag to specify a directory containing the schemas to include in the release.

    ```bash theme={null}
    miru release create \
      --version {version} \
      --schemas {/path/to/schemas/directory/}
    ```
  </Tab>

  <Tab title="Schema Files">
    Use the `--schema` flag to specify schema files to include in the release.

    ```bash theme={null}
    miru release create \
      --version {version} \
      --schema {path/to/schema1.yaml} \
      --schema {path/to/schema2.yaml}
    ```
  </Tab>

  <Tab title="Upload Rules">
    Use the `--upload-rule` flag to include [upload rule](/data-uploads/define-upload-rules) files in the release, or `--upload-rules` for a directory of rule files. Upload rules ride along with the release's config schemas.

    ```bash theme={null}
    miru release create \
      --version {version} \
      --schemas {/path/to/schemas/directory/} \
      --upload-rule {path/to/rule1.yaml} \
      --upload-rules {/path/to/upload/rules/directory/}
    ```
  </Tab>
</Tabs>

### Flags

Below are the CLI flags which can be specified when creating a release.

<ParamField path="--version, -v" type="string" required>
  A [semantic version](https://semver.org/), unique for a given release.

  Versions must be dot-separated integers. You may optionally use a `v` prefix, a prerelease suffix (e.g. `-alpha.X`, `-beta.X`, `-rc.X`), or a build suffix (e.g. `+build-metadata`).

  Examples: `v1`, `1`, `v2.1`, `2.1`, `v3.2.1`, `3.2.1`, `v4.3.2-beta.1`, `4.3.2-rc.1`, `v5.4.3+metadata`, `6.5.4-beta.2+metadata`
</ParamField>

<ParamField path="--schemas" type="string">
  A directory containing the config schemas to include in the release. May be specified multiple times for multiple directories.

  Must be specified if no schema files are provided.

  Examples: `./schemas`
</ParamField>

<ParamField path="--schema" type="string">
  The path to a config schema to include in the release. May be specified multiple times to include multiple schemas.

  Must be specified if no schema directory is provided.

  Examples: `./schemas/schema1.yaml`, `./schemas/schema2.yaml`
</ParamField>

<ParamField path="--upload-rules" type="string">
  A directory containing [upload rule](data-uploads/primitives/upload-rules#file-format) YAML files to include in the release. May be specified multiple times for multiple directories. Only YAML files in the directory are picked up.

  Examples: `./upload-rules/`
</ParamField>

<ParamField path="--upload-rule" type="string">
  The path to an [upload rule](data-uploads/primitives/upload-rules#file-format) YAML file to include in the release. May be specified multiple times to include multiple rules.

  Examples: `./upload-rules/robot-logs.yaml`
</ParamField>

### Examples

Below are some examples of what the CLI command might look like when creating a release.

<Tabs>
  <Tab title="New schemas">
    If the provided git commit and schemas do not exist in Miru, they are created and attached to the new release. This is indicated by the `(new)` suffix in the output.

    ```bash command theme={null}
    $ miru release create \
      --version v1.0.0 \
      --schemas ./cue/strict-schemas/

    ◐ Creating release v1.0.0

      ✓ Pushed git commit (new)
        1c7f7a8 · mirurobotics/getting-started

      ◐ Pushing schemas
        ✓ Mobility · SCH-D5nFP (new)
        ✓ Planning · SCH-37QAr (new)
        ✓ Communication · SCH-9WfCx (new)

    ✓ Created release v1.0.0
    ```
  </Tab>

  <Tab title="Existing schemas">
    If the provided git commit and schemas already exist in Miru, they are attached to the new release. This is indicated by the `(existed)` suffix in the output.

    ```bash command theme={null}
    $ miru release create \
      --version v1.0.0 \
      --schemas ./cue/strict-schemas/

    ◐ Creating release v1.0.0

      ✓ Pushed git commit (existed)
        1c7f7a8 · mirurobotics/getting-started

      ◐ Pushing schemas
        ✓ Mobility · SCH-D5nFP (existed)
        ✓ Planning · SCH-37QAr (existed)
        ✓ Communication · SCH-9WfCx (existed)

    ✓ Created release v1.0.0
    ```
  </Tab>
</Tabs>
