diff --git a/lib/src/generated/fai/v1/hub.pb.dart b/lib/src/generated/fai/v1/hub.pb.dart index 935b637..c1f6164 100644 --- a/lib/src/generated/fai/v1/hub.pb.dart +++ b/lib/src/generated/fai/v1/hub.pb.dart @@ -2457,6 +2457,177 @@ class StoreEntry extends $pb.GeneratedMessage { void clearInstalled() => $_clearField(14); } +class CheckUpdateResponse extends $pb.GeneratedMessage { + factory CheckUpdateResponse({ + $core.String? channel, + $core.String? localVersion, + $core.String? latestVersion, + $core.bool? updateAvailable, + $core.String? releasedAt, + $core.String? releaseNotesUrl, + $core.bool? manifestReachable, + $core.String? reason, + $core.String? manifestUrl, + }) { + final $result = create(); + if (channel != null) { + $result.channel = channel; + } + if (localVersion != null) { + $result.localVersion = localVersion; + } + if (latestVersion != null) { + $result.latestVersion = latestVersion; + } + if (updateAvailable != null) { + $result.updateAvailable = updateAvailable; + } + if (releasedAt != null) { + $result.releasedAt = releasedAt; + } + if (releaseNotesUrl != null) { + $result.releaseNotesUrl = releaseNotesUrl; + } + if (manifestReachable != null) { + $result.manifestReachable = manifestReachable; + } + if (reason != null) { + $result.reason = reason; + } + if (manifestUrl != null) { + $result.manifestUrl = manifestUrl; + } + return $result; + } + CheckUpdateResponse._() : super(); + factory CheckUpdateResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r); + factory CheckUpdateResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CheckUpdateResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'channel') + ..aOS(2, _omitFieldNames ? '' : 'localVersion') + ..aOS(3, _omitFieldNames ? '' : 'latestVersion') + ..aOB(4, _omitFieldNames ? '' : 'updateAvailable') + ..aOS(5, _omitFieldNames ? '' : 'releasedAt') + ..aOS(6, _omitFieldNames ? '' : 'releaseNotesUrl') + ..aOB(7, _omitFieldNames ? '' : 'manifestReachable') + ..aOS(8, _omitFieldNames ? '' : 'reason') + ..aOS(9, _omitFieldNames ? '' : 'manifestUrl') + ..hasRequiredFields = false + ; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckUpdateResponse clone() => CheckUpdateResponse()..mergeFromMessage(this); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CheckUpdateResponse copyWith(void Function(CheckUpdateResponse) updates) => super.copyWith((message) => updates(message as CheckUpdateResponse)) as CheckUpdateResponse; + + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CheckUpdateResponse create() => CheckUpdateResponse._(); + CheckUpdateResponse createEmptyInstance() => create(); + static $pb.PbList createRepeated() => $pb.PbList(); + @$core.pragma('dart2js:noInline') + static CheckUpdateResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static CheckUpdateResponse? _defaultInstance; + + /// The active channel ("dev", "beta", "production"). + @$pb.TagNumber(1) + $core.String get channel => $_getSZ(0); + @$pb.TagNumber(1) + set channel($core.String v) { $_setString(0, v); } + @$pb.TagNumber(1) + $core.bool hasChannel() => $_has(0); + @$pb.TagNumber(1) + void clearChannel() => $_clearField(1); + + /// The version the running hub was built with. + @$pb.TagNumber(2) + $core.String get localVersion => $_getSZ(1); + @$pb.TagNumber(2) + set localVersion($core.String v) { $_setString(1, v); } + @$pb.TagNumber(2) + $core.bool hasLocalVersion() => $_has(1); + @$pb.TagNumber(2) + void clearLocalVersion() => $_clearField(2); + + /// The version the manifest advertises as latest. Empty if + /// `manifest_reachable` is false. + @$pb.TagNumber(3) + $core.String get latestVersion => $_getSZ(2); + @$pb.TagNumber(3) + set latestVersion($core.String v) { $_setString(2, v); } + @$pb.TagNumber(3) + $core.bool hasLatestVersion() => $_has(2); + @$pb.TagNumber(3) + void clearLatestVersion() => $_clearField(3); + + /// True iff `latest_version > local_version` (semver-aware). + @$pb.TagNumber(4) + $core.bool get updateAvailable => $_getBF(3); + @$pb.TagNumber(4) + set updateAvailable($core.bool v) { $_setBool(3, v); } + @$pb.TagNumber(4) + $core.bool hasUpdateAvailable() => $_has(3); + @$pb.TagNumber(4) + void clearUpdateAvailable() => $_clearField(4); + + /// RFC 3339 timestamp of the release. Empty if not present. + @$pb.TagNumber(5) + $core.String get releasedAt => $_getSZ(4); + @$pb.TagNumber(5) + set releasedAt($core.String v) { $_setString(4, v); } + @$pb.TagNumber(5) + $core.bool hasReleasedAt() => $_has(4); + @$pb.TagNumber(5) + void clearReleasedAt() => $_clearField(5); + + /// URL of the release notes for `latest_version`. Empty if not + /// present in the manifest. + @$pb.TagNumber(6) + $core.String get releaseNotesUrl => $_getSZ(5); + @$pb.TagNumber(6) + set releaseNotesUrl($core.String v) { $_setString(5, v); } + @$pb.TagNumber(6) + $core.bool hasReleaseNotesUrl() => $_has(5); + @$pb.TagNumber(6) + void clearReleaseNotesUrl() => $_clearField(6); + + /// True iff the release host responded with a parseable manifest. + /// False on network errors / 404 / malformed JSON. The remaining + /// string fields are empty in that case; `reason` carries a + /// human-readable diagnostic. + @$pb.TagNumber(7) + $core.bool get manifestReachable => $_getBF(6); + @$pb.TagNumber(7) + set manifestReachable($core.bool v) { $_setBool(6, v); } + @$pb.TagNumber(7) + $core.bool hasManifestReachable() => $_has(6); + @$pb.TagNumber(7) + void clearManifestReachable() => $_clearField(7); + + /// Empty on success; carries an error description when + /// `manifest_reachable=false`. + @$pb.TagNumber(8) + $core.String get reason => $_getSZ(7); + @$pb.TagNumber(8) + set reason($core.String v) { $_setString(7, v); } + @$pb.TagNumber(8) + $core.bool hasReason() => $_has(7); + @$pb.TagNumber(8) + void clearReason() => $_clearField(8); + + /// Manifest URL that was queried (configurable per operator). + @$pb.TagNumber(9) + $core.String get manifestUrl => $_getSZ(8); + @$pb.TagNumber(9) + set manifestUrl($core.String v) { $_setString(8, v); } + @$pb.TagNumber(9) + $core.bool hasManifestUrl() => $_has(8); + @$pb.TagNumber(9) + void clearManifestUrl() => $_clearField(9); +} + const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/src/generated/fai/v1/hub.pbgrpc.dart b/lib/src/generated/fai/v1/hub.pbgrpc.dart index 16765b9..6ef0375 100644 --- a/lib/src/generated/fai/v1/hub.pbgrpc.dart +++ b/lib/src/generated/fai/v1/hub.pbgrpc.dart @@ -179,6 +179,10 @@ class HubAdminClient extends $grpc.Client { '/fai.v1.HubAdmin/ListServices', ($1.Empty value) => value.writeToBuffer(), ($core.List<$core.int> value) => $0.ServiceList.fromBuffer(value)); + static final _$checkUpdate = $grpc.ClientMethod<$1.Empty, $0.CheckUpdateResponse>( + '/fai.v1.HubAdmin/CheckUpdate', + ($1.Empty value) => value.writeToBuffer(), + ($core.List<$core.int> value) => $0.CheckUpdateResponse.fromBuffer(value)); HubAdminClient(super.channel, {super.options, super.interceptors}); @@ -258,6 +262,16 @@ class HubAdminClient extends $grpc.Client { $grpc.ResponseFuture<$0.ServiceList> listServices($1.Empty request, {$grpc.CallOptions? options}) { return $createUnaryCall(_$listServices, request, options: options); } + + /// Compare the running hub's version against the channel release + /// manifest. Surfaced by the CLI as `fai update check` and by + /// Studio's Doctor page as a "v0.x.y available" pill. Network + /// failures are returned as `manifest_reachable=false` rather + /// than gRPC errors so Studio can render gracefully when the + /// release host is unreachable (air-gapped, offline laptop). + $grpc.ResponseFuture<$0.CheckUpdateResponse> checkUpdate($1.Empty request, {$grpc.CallOptions? options}) { + return $createUnaryCall(_$checkUpdate, request, options: options); + } } @$pb.GrpcServiceName('fai.v1.HubAdmin') @@ -363,6 +377,13 @@ abstract class HubAdminServiceBase extends $grpc.Service { false, ($core.List<$core.int> value) => $1.Empty.fromBuffer(value), ($0.ServiceList value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$1.Empty, $0.CheckUpdateResponse>( + 'CheckUpdate', + checkUpdate_Pre, + false, + false, + ($core.List<$core.int> value) => $1.Empty.fromBuffer(value), + ($0.CheckUpdateResponse value) => value.writeToBuffer())); } $async.Future<$0.CapabilityList> listCapabilities_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { @@ -421,6 +442,10 @@ abstract class HubAdminServiceBase extends $grpc.Service { return listServices($call, await $request); } + $async.Future<$0.CheckUpdateResponse> checkUpdate_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async { + return checkUpdate($call, await $request); + } + $async.Future<$0.CapabilityList> listCapabilities($grpc.ServiceCall call, $1.Empty request); $async.Future<$0.ModuleInfoResponse> moduleInfo($grpc.ServiceCall call, $0.ModuleInfoRequest request); $async.Future<$0.CheckPermissionResponse> checkPermission($grpc.ServiceCall call, $0.CheckPermissionRequest request); @@ -435,4 +460,5 @@ abstract class HubAdminServiceBase extends $grpc.Service { $async.Future<$0.DecideApprovalResponse> decideApproval($grpc.ServiceCall call, $0.DecideApprovalRequest request); $async.Future<$0.VerifyEventChainResponse> verifyEventChain($grpc.ServiceCall call, $1.Empty request); $async.Future<$0.ServiceList> listServices($grpc.ServiceCall call, $1.Empty request); + $async.Future<$0.CheckUpdateResponse> checkUpdate($grpc.ServiceCall call, $1.Empty request); } diff --git a/lib/src/generated/fai/v1/hub.pbjson.dart b/lib/src/generated/fai/v1/hub.pbjson.dart index 419fb92..a822670 100644 --- a/lib/src/generated/fai/v1/hub.pbjson.dart +++ b/lib/src/generated/fai/v1/hub.pbjson.dart @@ -612,3 +612,29 @@ final $typed_data.Uint8List storeEntryDescriptor = $convert.base64Decode( 'Npb24YDCABKAlSC2Jlc3RWZXJzaW9uEhYKBnN0YXR1cxgNIAEoCVIGc3RhdHVzEhwKCWluc3Rh' 'bGxlZBgOIAEoCFIJaW5zdGFsbGVk'); +@$core.Deprecated('Use checkUpdateResponseDescriptor instead') +const CheckUpdateResponse$json = { + '1': 'CheckUpdateResponse', + '2': [ + {'1': 'channel', '3': 1, '4': 1, '5': 9, '10': 'channel'}, + {'1': 'local_version', '3': 2, '4': 1, '5': 9, '10': 'localVersion'}, + {'1': 'latest_version', '3': 3, '4': 1, '5': 9, '10': 'latestVersion'}, + {'1': 'update_available', '3': 4, '4': 1, '5': 8, '10': 'updateAvailable'}, + {'1': 'released_at', '3': 5, '4': 1, '5': 9, '10': 'releasedAt'}, + {'1': 'release_notes_url', '3': 6, '4': 1, '5': 9, '10': 'releaseNotesUrl'}, + {'1': 'manifest_reachable', '3': 7, '4': 1, '5': 8, '10': 'manifestReachable'}, + {'1': 'reason', '3': 8, '4': 1, '5': 9, '10': 'reason'}, + {'1': 'manifest_url', '3': 9, '4': 1, '5': 9, '10': 'manifestUrl'}, + ], +}; + +/// Descriptor for `CheckUpdateResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List checkUpdateResponseDescriptor = $convert.base64Decode( + 'ChNDaGVja1VwZGF0ZVJlc3BvbnNlEhgKB2NoYW5uZWwYASABKAlSB2NoYW5uZWwSIwoNbG9jYW' + 'xfdmVyc2lvbhgCIAEoCVIMbG9jYWxWZXJzaW9uEiUKDmxhdGVzdF92ZXJzaW9uGAMgASgJUg1s' + 'YXRlc3RWZXJzaW9uEikKEHVwZGF0ZV9hdmFpbGFibGUYBCABKAhSD3VwZGF0ZUF2YWlsYWJsZR' + 'IfCgtyZWxlYXNlZF9hdBgFIAEoCVIKcmVsZWFzZWRBdBIqChFyZWxlYXNlX25vdGVzX3VybBgG' + 'IAEoCVIPcmVsZWFzZU5vdGVzVXJsEi0KEm1hbmlmZXN0X3JlYWNoYWJsZRgHIAEoCFIRbWFuaW' + 'Zlc3RSZWFjaGFibGUSFgoGcmVhc29uGAggASgJUgZyZWFzb24SIQoMbWFuaWZlc3RfdXJsGAkg' + 'ASgJUgttYW5pZmVzdFVybA=='); + diff --git a/lib/src/hub_client.dart b/lib/src/hub_client.dart index 2c3a795..aeaaeff 100644 --- a/lib/src/hub_client.dart +++ b/lib/src/hub_client.dart @@ -41,6 +41,7 @@ typedef LoggedEvent = pb.LoggedEvent; typedef PendingApprovalEntry = pb.PendingApprovalEntry; typedef DeclaredService = pb.DeclaredService; typedef VerifyEventChainResponse = pb.VerifyEventChainResponse; +typedef CheckUpdateResponse = pb.CheckUpdateResponse; typedef ModuleInfoResponse = pb.ModuleInfoResponse; typedef FlowSummary = pb.FlowSummary; typedef Payload = pb_common.Payload; @@ -163,6 +164,14 @@ class HubClient { return r.services; } + /// Compare local hub version vs. release-channel manifest. + /// Network failures land in `manifestReachable=false` rather + /// than as exceptions — Studio renders that case as a quiet + /// "release host unreachable" pill instead of an error. + Future checkUpdate() { + return _admin.checkUpdate(Empty()); + } + /// All saved flows known to the hub. Each entry carries the /// flow name, on-disk path and byte size. Future> listFlows() async { diff --git a/pubspec.yaml b/pubspec.yaml index 2d1baf5..0dac438 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fai_dart_sdk description: gRPC client SDK for the F∆I Platform hub. Used by F∆I Studio and Tier-3 domain apps. -version: 0.1.0 +version: 0.2.0 publish_to: 'none' repository: https://git.flemming.ws/fai/dart-sdk