{"id":32147206,"url":"https://github.com/egeniq/app-remote-config","last_synced_at":"2025-10-21T08:54:26.269Z","repository":{"id":217284053,"uuid":"743445915","full_name":"egeniq/app-remote-config","owner":"egeniq","description":"Configure apps remotely: A simple but effective way to manage apps remotely.","archived":false,"fork":false,"pushed_at":"2025-06-12T07:43:28.000Z","size":209,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-10-21T08:53:47.343Z","etag":null,"topics":["android","app","care","cli","config","ios","json","kotlin","macos","remote","remote-config","remote-configuration","tvos","visionos","watchos","yaml"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/egeniq.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-15T08:56:54.000Z","updated_at":"2025-06-12T17:42:38.000Z","dependencies_parsed_at":"2024-01-15T14:01:19.942Z","dependency_job_id":"245e720e-0d95-4cd5-86f5-b6a39fe5d924","html_url":"https://github.com/egeniq/app-remote-config","commit_stats":null,"previous_names":["egeniq/app-remote-config"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/egeniq/app-remote-config","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egeniq%2Fapp-remote-config","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egeniq%2Fapp-remote-config/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egeniq%2Fapp-remote-config/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egeniq%2Fapp-remote-config/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/egeniq","download_url":"https://codeload.github.com/egeniq/app-remote-config/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/egeniq%2Fapp-remote-config/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280232858,"owners_count":26295149,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","app","care","cli","config","ios","json","kotlin","macos","remote","remote-config","remote-configuration","tvos","visionos","watchos","yaml"],"created_at":"2025-10-21T08:54:25.189Z","updated_at":"2025-10-21T08:54:26.263Z","avatar_url":"https://github.com/egeniq.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AppRemoteConfig\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fegeniq%2Fapp-remote-config%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/egeniq/app-remote-config) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fegeniq%2Fapp-remote-config%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/egeniq/app-remote-config)\n\nConfigure apps remotely: A simple but effective way to manage apps remotely.\n\nCreate a simple configuration file that is easy to maintain and host, yet provides important flexibility to specify settings based on your needs.\n\n## Schema\n\nThe JSON/YAML schema is defined [here](https://raw.githubusercontent.com/egeniq/app-remote-config/main/Schema/appremoteconfig.schema.json).\n\n## AppRemoteConfigClient\n\nImport the package in your `Package.swift` file:\n\n    .package(url: \"https://github.com/egeniq/app-remote-config\", from: \"0.7.0\"),\n\nThen a good approach is to create your own `AppRemoteConfigClient`.\n\n    // App Remote Config\n    .target(\n        name: \"AppRemoteConfigClient\",\n        dependencies: [\n            .product(name: \"AppRemoteConfigService\", package: \"app-remote-config\"),\n            .product(name: \"AppRemoteConfigServiceMacros\", package: \"app-remote-config\"),\n            .product(name: \"Dependencies\", package: \"swift-dependencies\"),\n            .product(name: \"DependenciesMacros\", package: \"swift-dependencies\"),\n            .product(name: \"Perception\", package: \"swift-perception\")\n        ]\n    )\n        \nUsing these dependencies:\n\n    .package(url: \"https://github.com/pointfreeco/swift-dependencies\", from: \"1.0.0\"),\n    .package(url: \"https://github.com/pointfreeco/swift-perception\", from: \"1.0.0\"),\n    .package(url: \"https://github.com/tgrapperon/swift-dependencies-additions\", from: \"1.0.0\")\n     \nThen your `AppRemoteConfigClient.swift` is something like this:\n        \n    import AppRemoteConfigService\n    import AppRemoteConfigServiceMacros\n    import Dependencies\n    import DependenciesMacros\n    import Foundation\n    import Perception\n\n    @AppRemoteConfigValues @Perceptible @MainActor\n    public class Values {\n        public private(set) var updateRecommended: Bool = false\n        public private(set) var updateRequired: Bool = false\n    }\n\n    @DependencyClient\n    public struct AppRemoteConfigClient: Sendable {\n        public var values: @Sendable @MainActor () -\u003e Values = { Values() }\n    }\n\n    extension DependencyValues {\n        public var configClient: AppRemoteConfigClient {\n            get { self[AppRemoteConfigClient.self] }\n            set { self[AppRemoteConfigClient.self] = newValue }\n        }\n    }\n\n    extension AppRemoteConfigClient: TestDependencyKey {\n        public static let testValue = Self()\n    }\n    \n    extension AppRemoteConfigClient: DependencyKey {\n        public static let liveValue = {\n            let live = LockIsolated\u003cLiveMainActorAppRemoteConfigClient?\u003e(nil)\n            return AppRemoteConfigClient(\n                values: {\n                    if live.value == nil {\n                        let dependency = LiveMainActorAppRemoteConfigClient()\n                        live.setValue(dependency)\n                    }\n                    return live.value!.values\n                }\n            )\n        }()\n    }\n\n    // This is used to workaround the error:\n    // Main actor-isolated static property 'liveValue' cannot be used to satisfy nonisolated protocol requirement.\n    @MainActor\n    private class LiveMainActorAppRemoteConfigClient {\n        fileprivate let values: Values\n        private let service: AppRemoteConfigService\n        \n        init() {\n            let url = URL(string: \"https://www.example.com/config.json\")!\n            let bundledConfigURL = Bundle.main.url(forResource: \"appconfig\", withExtension: \"json\")\n            values = Values()\n            service = AppRemoteConfigService(url: url, publicKey: nil, bundledConfigURL: bundledConfigURL, bundleIdentifier: Bundle.main.bundleIdentifier ?? \"Sample\", apply: values.apply(settings:))\n        }\n    }\n\n## CLI Utility\n\nUse the `care` CLI utility to initialize, verify, resolve and prepare configuration files.\n\nTo install use:\n\n    brew install egeniq/app-utilities/care\n\n### Usage\n\nThe \"care\" command line utility has built-in help available.\n\n    care --help\n   \nThere are five subcommands to use: init, create-key-pair, verify, resolve and prepare.\n\n#### Init\n\nCreate a new configuration file:\n\n    care init appconfig.yaml\n   \nThis will create a new file:\n\n```yaml\n# yaml-language-server: $schema=https://raw.githubusercontent.com/egeniq/app-remote-config/main/Schema/appremoteconfig.schema.json\n$schema: https://raw.githubusercontent.com/egeniq/app-remote-config/main/Schema/appremoteconfig.schema.json\n\n# Settings for the current app.\nsettings:\n  foo: 42\n  coolFeature: false\n\n# Keep track of keys that are no longer in use.\ndeprecatedKeys:\n- bar\n\n# Override settings\noverrides:\n- matching:\n  # If any of the following combinations match\n  - appVersion: \u003c=0.9.0\n    platform: Android\n  - appVersion: \u003c1.0.0\n    platform: iOS\n  - platformVersion: \u003c15.0.0\n    platform: iOS.iPad\n  # These settings get overriden.\n  settings:\n    bar: low\n \n# Or release a new feature at a specific time\n- schedule:\n    from: '2024-12-31T00:00:00Z'\n  settings:\n    coolFeature: true\n    \n# Store metadata here\nmeta:\n  author: Your Name\n```\n\nUnder the `settings` key you would put the current settings you want in your app. If any keys are deprecated, add them to the `deprecatedKeys` array so they can still be overriden.\n\nIn `overrides` you can define settings that vary on a number of factors such as appVersion, platform, platformVersion. See help for the full list of options. If one of the combinations match, the settings are override the settings from the root `settings` key. Settings are applied from top to bottom.\n\nYou can also schedule changes in settings. If you add a `schedule` with `from` and/or `until` ISO8601 dates settings will only be applied if the current time lies with the range. Omitting `from` means using the distant past, and omitting `to` means using the distant future.\n\nThe keys under `meta` are not used, but only exist to keep track of metadata.\n\n#### Create Key Pair\n\nYour app can use a public key to ensure that the configuration was signed with your private key. Signing the configuration is optional.\n\n    care create-key-pair\n\n#### Verify\n\nIf your editor supports it, the JSON schema helps you to properly format the config file. You can further verify correctness of a config file by running:\n\n    care verify appconfig.yaml\n    \n#### Resolve\n\nTo check that settings and overrides you have setup are correct, you can ask `care` to show how the settings would resolve.\n\n    care resolve appconfig.yaml -p iOS --platform-version 16  -v 1.0.1\n    \nThat would show something like this:\n    \n```    \nResolving for:\n  platform            : iOS\n  platform version    : 16.0.0\n  app version         : 1.0.1\n  build variant       : release\n\nSettings on 2024-02-22 11:40:00 +0000:\n  coolFeature         : false\n  foo                 : 42\n\nSettings on 2024-12-31 00:00:00 +0000:\n  coolFeature         : false -\u003e true\n  foo                 : 42\n\nNo further overrides scheduled.\n```\n\n#### Prepare\n\nThe last step is convert the config file to compact json.\n\n    care prepare appconfig.yaml appconfig.json\n\nThe file `appconfig.json` is now ready to made available to your app via a webserver.\n\n## Android\n\nThis package compiles on Android, but there is also a port to Kotlin available [here](https://github.com/egeniq/app-remote-config-android).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegeniq%2Fapp-remote-config","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fegeniq%2Fapp-remote-config","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fegeniq%2Fapp-remote-config/lists"}