# Modifications

Source: https://docs.interop.io/desktop/developers/seed-project/modifications/index.html

## Overview

Use modifications to change component configuration, replace assets, and add app definitions without editing downloaded files. Store these changes in `/modifications` so that you can apply them again after a component update.

## Folders & Processing Order

The following example demonstrates where to place modifications for the `iocd` component:

```text
modifications/
├── base/
│   └── iocd/
│       ├── config/
│       │   └── apps/
│       └── assets/
├── dev/
│   └── iocd/
│       └── config/
└── build/
    └── iocd/
        └── config/
```

The CLI applies `/modifications/base` first, followed by `/modifications/dev` or `/modifications/build` for the selected mode. A file in the mode folder can therefore override a base modification. The directory structure beneath a component name matches the target structure inside that component.

Use `/modifications/base/iocd/config/apps` for app definitions and `/modifications/base/iocd/assets` for assets. Use the development and build folders when an app needs different URLs or settings in each mode.

The CLI resets tracked modifications before running `dev` or `build`, then applies the selected changes. Removing a modification source file also removes its tracked output when the modifications are reset and reapplied.

> ℹ️ *For additional examples, see [Multi-Environment Builds in the io.Connect Seed Project](https://community.interop.io/t/multi-environment-builds-in-io-connect-seed-project/577) and [How to Avoid Overwriting Component App Definitions](https://community.interop.io/t/component-config-files-in-modifications-base-get-overwritten-during-seed-project-build/578) on the Developer Community site.*

## Replacing & Deleting Files

An ordinary file replaces the corresponding component file. Special filenames provide additional operations:

| File | Description |
|------|-------------|
| `system.json.merge` | Merges the contents into `system.json`, preserving properties that aren't specified. |
| `filename.delete` | Removes the corresponding `filename` from the component. |
| `.replace` | Replaces the target directory with the contents of the modification directory containing this marker. |
| `.delete` | Removes the target directory corresponding to the modification directory containing this marker. |

## JSON Merging

A `.json.merge` file recursively merges object properties into the corresponding JSON file. New properties are added, supplied values override existing ones, and omitted properties remain unchanged.

The following example demonstrates a merge into an app configuration file named `settings.json`. These settings belong to the example app and aren't platform configuration properties:

```json
{
    "display": {
        "theme": "light",
        "showToolbar": true
    },
    "features": {
        "search": true
    }
}
```

To change the theme and enable notifications, create `settings.json.merge`:

```json
{
    "display": {
        "theme": "dark"
    },
    "features": {
        "notifications": true
    }
}
```

The resulting `settings.json` will contain:

```json
{
    "display": {
        "theme": "dark",
        "showToolbar": true
    },
    "features": {
        "search": true,
        "notifications": true
    }
}
```

### Named Merge Files

Use a suffix after `.json.merge-` to separate changes targeting the same file:

```text
system.json.merge
system.json.merge-autoUpdate
system.json.merge-launchpad
system.json.merge-myFeature
```

All these files target `system.json`. Base modifications are applied before modifications for the selected mode. Within each directory, files are processed in the order returned by the file system. Don't rely on the names to establish an ordering between conflicting merge files in the same directory.

### Platform Selection

If the suffix contains `darwin` or `win32`, the merge file will apply only on that platform:

| File | Platform |
|------|----------|
| `system.json.merge-darwin` | macOS. |
| `system.json.merge-win32` | Windows. |
| `system.json.merge-myFeature-win32` | Windows. |

Merge files without a platform identifier apply on both platforms.

### Array Merging

Available since @interopio/iocd-cli 0.0.71

As of `@interopio/iocd-cli` 0.0.71, arrays are concatenated by default - e.g., merging `{"apps": ["app3", "app4"]}` into `{"apps": ["app1", "app2"]}` will produce `{"apps": ["app1", "app2", "app3", "app4"]}`. Earlier versions replaced the target array.

To select another operation, replace the array value in the merge file with a directive object:

| Directive | Description | Example |
|-----------|-------------|---------|
| `"$append"` | Adds items to the end, matching the default behavior. | `{"apps": {"$append": ["app3"]}}` |
| `"$prepend"` | Adds items to the beginning. | `{"apps": {"$prepend": ["dashboard"]}}` |
| `"$remove"` | Removes matching items. | `{"apps": {"$remove": ["legacy-app"]}}` |
| `"$replace"` | Replaces the entire array. | `{"apps": {"$replace": ["only-this-app"]}}` |

> ⚠️ *Note that if the target property is missing or isn't an array, the CLI sets it to the array in the directive, regardless of the directive type. A `"$remove"` directive for a missing property therefore adds the listed items.*

For object values, `"$remove"` compares the result of `JSON.stringify()`. The complete serialized object must match, including property order. The following merge removes an object containing only the specified `"name"` property. It won't match an object with additional properties:

```json
{
    "apps": {
        "$remove": [{ "name": "unwanted-app" }]
    }
}
```

Object and array modifications can be combined in one file. The following example demonstrates how to change an app setting, extend one array, remove an item from another, and prepend a startup app:

```json
{
    "display": {
        "theme": "dark"
    },
    "features": {
        "enabledPlugins": ["my-plugin"],
        "disabledPlugins": { "$remove": ["old-plugin"] }
    },
    "startupApps": { "$prepend": ["dashboard"] }
}
```

## System Configuration

To change platform settings, add a `system.json.merge` file in `/modifications/base/iocd/config`. Use the actual system configuration structure for the feature you are changing.

The following example demonstrates how to change the default logging category to `"debug"` while preserving its configured appenders and other categories:

```json
{
    "logging": {
        "configuration": {
            "categories": {
                "default": {
                    "level": "debug"
                }
            }
        }
    }
}
```

For development only, place this file in `/modifications/dev/iocd/config`. To use `"error"` for production builds, add the same structure with `"level": "error"` in `/modifications/build/iocd/config/system.json.merge`. These files will be applied after the base modifications for their respective modes.

Use `.json.merge` when preserving unspecified defaults. A file named `system.json` will replace the entire configuration.

The CLI replaces `${<VARIABLE>}` placeholders in copied `.json` files and in the files produced by `.json.merge` files. Files of other types are copied unchanged. The following variables are available:

| Variable | Description |
|----------|-------------|
| `${COMPONENTS_PATH}` | Folder containing the additional components. Resolves to `%IO_CD_ROOT_DIR%/components` in builds and to the seed `/components` folder in development mode. The platform expands `%IO_CD_ROOT_DIR%` at runtime. |
| `${PRODUCT_NAME}` | Product name from the [basic settings](https://docs.interop.io/desktop/developers/seed-project/configuration/index.md#basic_settings). |
| `${PRODUCT_SLUG}` | Product slug from the basic settings. |
| `${PRODUCT_VERSION}` | Product version from the basic settings. |

Any other name is replaced with the value of the environment variable with that name. Names that don't match a variable are replaced with an empty string, so check the spelling of each placeholder. Backslashes in the values are converted to forward slashes.

> ℹ️ *For details on platform properties, see the [Developers > Configuration > System](https://docs.interop.io/desktop/developers/configuration/system/index.md) section. For a complete auto update example, see the [Publishing & Auto Updates](https://docs.interop.io/desktop/developers/seed-project/publishing-auto-updates/index.md) section.*

## Icons & Images

The native app icon, tray icon, and platform branding images are controlled via the `/modifications/base/iocd/assets/images` folder. Create the `/images` folder if it doesn't exist, and add the image files you want to override.

Use the exact filenames in the following tables because the CLI, Electron Forge configuration, and platform runtime look for these paths during development and packaging.

### Native App Icons

| File | Description | Requirements |
|------|-------------|--------------|
| `logo.ico` | Windows executable icon. The CLI embeds it into the main executable during `npm run dev` and `npm run build`. The Electron Forge Squirrel maker also uses it as the setup executable icon. | Use a Windows `.ico` file with multiple sizes, such as 16x16, 32x32, 48x48, and 256x256. |
| `logo.icns` | App icon for macOS. The CLI copies it into the `<installation_location>/Resources` folder and sets `CFBundleIconFile` to `logo.icns`. The Electron Forge DMG maker also uses it for the generated `.dmg`. | Use a macOS `.icns` file with multiple sizes. You can create it with Icon Composer or `iconutil`. |
| `logo.png` | Image asset used by the platform UI and branding. | Use a PNG file, recommended size 512x512. This doesn't replace the native Windows `.exe` or macOS `.app` icon by itself. |

On Windows, close any running instance of the app before running the command again, because the CLI has to patch the executable icon and metadata. If the Windows executable icon is stored somewhere else, update the `"win.exe.exeIconPath"` setting in `config/iocd.cli.config.json` or set the `WIN_EXE_ICON_PATH` environment variable. The default path is recommended because it also keeps the Squirrel installer icon aligned with the app icon.

On macOS, keep the file name `logo.icns`. The CLI expects this name when it updates the app bundle and signs the result.

### Tray Icons

```text
modifications/base/iocd/assets/
└── images/
    ├── tray-light.ico
    ├── tray-dark.ico
    ├── tray.ico
    ├── tray-alert-light.ico
    ├── tray-alert-dark.ico
    ├── tray-alert.ico
    ├── logo-alert.ico
    ├── trayTemplate.png
    ├── trayTemplate@2X.png
    ├── tray-alertTemplate.png
    └── tray-alertTemplate@2x.png
```

On Windows, the tray icon changes with the current platform theme:

| State | Lookup Order |
|-------|--------------|
| Light theme | `tray-light.ico`, then `tray.ico`, then `logo.ico` |
| Dark theme | `tray-dark.ico`, then `tray.ico`, then `logo.ico` |
| Light theme with active notifications | `tray-alert-light.ico`, then `tray-alert.ico`, then `logo-alert.ico`, then the current tray icon |
| Dark theme with active notifications | `tray-alert-dark.ico`, then `tray-alert.ico`, then `logo-alert.ico`, then the current tray icon |

On macOS, the platform uses template PNG files (monochrome icons) instead of `.ico` files:

| State | Lookup Order | High-DPI Companion |
|-------|--------------|--------------------|
| Default | `trayTemplate.png` | `trayTemplate@2X.png` |
| Active notifications | `tray-alertTemplate.png`, then the current tray icon | `tray-alertTemplate@2x.png` |

The alert tray icon is used only when the platform has active notifications, the notification badge is enabled, and the tray notifications item is enabled. The `"tray"` object in `system.json` controls whether the tray menu and its items are shown. It doesn't provide custom icon file names.

After replacing any of these files, run `npm run dev` to apply them in development mode, or `npm run build` to create a new package.
