internal async Task <(bool upgrade, string newversion)> IsCliUpgradable()
        {
            string tag           = "latest";
            var    gitHubVersion = GitHubVersionResponse.TryReadFromCache();

            if (gitHubVersion != null)
            {
                logger.WriteVerbose("located a cached GitHub version query response");
            }
            if (gitHubVersion == null || !gitHubVersion.CacheIsInDate() || tag != gitHubVersion.Tag)
            {
                logger.WriteVerbose($"Checking runtime package versions in GitHub");
                gitHubVersion = await FindVersionInGitHubAsync(tag);

                if (gitHubVersion != null && gitHubVersion.SaveCache())
                {
                    logger.WriteVerbose($"Saved GitHub response to disk");
                }
            }
            else
            {
                logger.WriteVerbose($"Cached versions are recent enough to not require checking GitHub");
            }

            if (gitHubVersion == null || string.IsNullOrEmpty(gitHubVersion.Name))
            {
                logger.WriteError($"Requested CLI version does not exist in GitHub.");
                return(upgrade : false, newversion : "");
            }
            logger.WriteVerbose($"Found {gitHubVersion.Name} on {gitHubVersion.When} in GitHub.");

            if (!SemVersion.TryParse(gitHubVersion.Name[1..], out var latest))
        async Task <(SemVersion requiredRuntimeVer, GitHubVersionResponse gitHubVersion)> GetGitHubReleaseMatchingUserVersionAsync(string tag, string requiredVersion)
        {
            var gitHubVersion = GitHubVersionResponse.TryReadFromCache();

            if (gitHubVersion != null)
            {
                logger.WriteVerbose("located a cached GitHub version query response");
            }
            if (gitHubVersion == null || !gitHubVersion.CacheIsInDate() || tag != gitHubVersion.Tag)
            {
                logger.WriteVerbose($"Checking runtime package versions in GitHub");
                gitHubVersion = await FindVersionInGitHubAsync(tag);

                if (gitHubVersion != null && gitHubVersion.SaveCache())
                {
                    logger.WriteVerbose($"Saved GitHub response to disk");
                }
            }
            else
            {
                logger.WriteVerbose($"Cached versions are recent enough to not require checking GitHub");
            }

            if (gitHubVersion == null || string.IsNullOrEmpty(gitHubVersion.Name))
            {
                logger.WriteError($"Requested runtime {requiredVersion} version does not exist in GitHub.");
                return(null, null);
            }
            logger.WriteVerbose($"Found {gitHubVersion.Name} on {gitHubVersion.When} in GitHub .");

            if (gitHubVersion.Name[0] == 'v')
            {
                gitHubVersion.Name = gitHubVersion.Name.Substring(1);
            }
            var requiredRuntimeVer = SemVersion.Parse(gitHubVersion.Name);

            logger.WriteVerbose($"Latest Runtime package version is {requiredRuntimeVer} (released on {gitHubVersion.When}).");

            return(requiredRuntimeVer, gitHubVersion);
        }
        internal async Task <bool> UpdateVersionFromGitHubAsync(string requiredVersion, InstanceName instance, IAzure azure, CancellationToken cancellationToken)
        {
            string tag = string.IsNullOrWhiteSpace(requiredVersion)
                ? "latest"
                : (requiredVersion != "latest"
                    ? (requiredVersion[0] != 'v'
                        ? "v" + requiredVersion
                        : requiredVersion)
                    : requiredVersion);
            var gitHubVersion = GitHubVersionResponse.TryReadFromCache();

            if (gitHubVersion != null)
            {
                logger.WriteVerbose("located a cached GitHub vesion query response");
            }
            if (gitHubVersion == null || !gitHubVersion.CacheIsInDate() || tag != gitHubVersion.Tag)
            {
                logger.WriteVerbose($"Checking runtime package versions in GitHub");
                gitHubVersion = await FindVersionInGitHubAsync(tag);

                if (gitHubVersion != null && gitHubVersion.SaveCache())
                {
                    logger.WriteVerbose($"Saved GitHub response to disk");
                }
            }
            else
            {
                logger.WriteVerbose($"Cached version is recent enough to not require checking GitHub");
            }

            if (string.IsNullOrEmpty(gitHubVersion.Name))
            {
                logger.WriteError($"Requested runtime {requiredVersion} version does not exist.");
                return(false);
            }
            logger.WriteVerbose($"Found {gitHubVersion.Name} on {gitHubVersion.When} in GitHub .");

            if (gitHubVersion.Name[0] == 'v')
            {
                gitHubVersion.Name = gitHubVersion.Name.Substring(1);
            }
            var requiredRuntimeVer = SemVersion.Parse(gitHubVersion.Name);

            logger.WriteVerbose($"Latest Runtime package version is {requiredRuntimeVer} (released on {gitHubVersion.When}).");

            var localRuntimeVer = await GetLocalPackageVersionAsync(RuntimePackageFile);

            logger.WriteVerbose($"Locally cached Runtime package version is {localRuntimeVer}.");

            // TODO check the uploaded version before overwriting?
            SemVersion uploadedRuntimeVer = await GetDeployedRuntimeVersion(instance, azure, cancellationToken);

            if (requiredRuntimeVer > uploadedRuntimeVer || localRuntimeVer > uploadedRuntimeVer)
            {
                if (requiredRuntimeVer > localRuntimeVer)
                {
                    logger.WriteVerbose($"Downloading runtime package {gitHubVersion.Name}");
                    await DownloadAsync(gitHubVersion.Url, cancellationToken);

                    logger.WriteInfo($"Runtime package downloaded.");
                }
                else
                {
                    logger.WriteInfo($"Using local cached runtime package {localRuntimeVer}");
                }

                logger.WriteVerbose($"Uploading runtime package to {instance.DnsHostName}");
                bool ok = await UploadRuntimeZip(instance, azure, cancellationToken);

                if (ok)
                {
                    logger.WriteInfo($"Runtime package uploaded to {instance.PlainName}.");
                }
                else
                {
                    logger.WriteError($"Failed uploading Runtime to {instance.DnsHostName}.");
                }
                return(ok);
            }
            else
            {
                logger.WriteInfo($"Runtime package is up to date.");
                return(true);
            }
        }