diff --git a/.github/actions/dart_package/action.yaml b/.github/actions/dart_package/action.yaml index 96102122..bb5e5439 100644 --- a/.github/actions/dart_package/action.yaml +++ b/.github/actions/dart_package/action.yaml @@ -32,7 +32,7 @@ inputs: platform: required: false type: string - default: "vm" + default: "vm" runs: using: "composite" @@ -41,23 +41,28 @@ runs: with: sdk: ${{inputs.dart_sdk}} - - working-directory: ${{ inputs.working_directory }} + - name: Install Dependencies + working-directory: ${{ inputs.working_directory }} shell: ${{ inputs.shell }} run: dart pub get - - working-directory: ${{ inputs.working_directory }} + - name: Format + working-directory: ${{ inputs.working_directory }} shell: ${{ inputs.shell }} run: dart format --set-exit-if-changed . - - working-directory: ${{ inputs.working_directory }} + - name: Analyze + working-directory: ${{ inputs.working_directory }} shell: ${{ inputs.shell }} run: dart analyze --fatal-warnings ${{inputs.analyze_directories}} - - working-directory: ${{ inputs.working_directory }} + - name: Test + working-directory: ${{ inputs.working_directory }} shell: ${{ inputs.shell }} run: | dart pub global activate coverage dart test -j ${{inputs.concurrency}} --coverage=coverage --platform=${{inputs.platform}} && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=${{inputs.report_on}} --check-ignore + - uses: VeryGoodOpenSource/very_good_coverage@v2 with: path: ${{inputs.working_directory}}/coverage/lcov.info diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 5f7ef08a..edb1b5c7 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -38,6 +38,9 @@ jobs: shorebird_code_push_api_client: - ./.github/actions/dart_package - packages/shorebird_code_push_api_client/** + shorebird_code_push_protocol: + - ./.github/actions/dart_package + - packages/shorebird_code_push_protocol/** - uses: dorny/paths-filter@v2 name: Verify Detection diff --git a/README.md b/README.md index 9c7c8862..e9006b10 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ This repository is a monorepo containing the following packages: | ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | [shorebird_cli](packages/shorebird_cli/README.md) | Command-line which allows developers to interact with various Shorebird services | | [shorebird_code_push_api_client](packages/shorebird_code_push_api_client/README.md) | Dart library which allows Dart applications to interact with the ShoreBird CodePush API | +| [shorebird_code_push_protocol](packages/shorebird_code_push_protocol/README.md) | Dart library which contains common interfaces used by Shorebird CodePush | | [shorebird_code_push_updater](updater/README.md) | Rust library which handles the CodePush logic and does the real update work | For more information, please refer to the documentation for each package. diff --git a/packages/shorebird_code_push_protocol/.gitignore b/packages/shorebird_code_push_protocol/.gitignore new file mode 100644 index 00000000..57f01160 --- /dev/null +++ b/packages/shorebird_code_push_protocol/.gitignore @@ -0,0 +1,8 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +build/ +pubspec.lock +coverage/ \ No newline at end of file diff --git a/packages/shorebird_code_push_protocol/README.md b/packages/shorebird_code_push_protocol/README.md new file mode 100644 index 00000000..95d9decb --- /dev/null +++ b/packages/shorebird_code_push_protocol/README.md @@ -0,0 +1,3 @@ +## Shorebird CodePush Protocol + +The Shorebird CodePush Protocol is a Dart library which contains common interfaces used by Shorebird CodePush. diff --git a/packages/shorebird_code_push_protocol/analysis_options.yaml b/packages/shorebird_code_push_protocol/analysis_options.yaml new file mode 100644 index 00000000..84e34fba --- /dev/null +++ b/packages/shorebird_code_push_protocol/analysis_options.yaml @@ -0,0 +1 @@ +include: package:very_good_analysis/analysis_options.4.0.0.yaml diff --git a/packages/shorebird_code_push_protocol/build.yaml b/packages/shorebird_code_push_protocol/build.yaml new file mode 100644 index 00000000..6f2fc8a2 --- /dev/null +++ b/packages/shorebird_code_push_protocol/build.yaml @@ -0,0 +1,15 @@ +targets: + $default: + builders: + source_gen|combining_builder: + options: + ignore_for_file: + - implicit_dynamic_parameter + - require_trailing_commas + - cast_nullable_to_non_nullable + - lines_longer_than_80_chars + json_serializable: + options: + field_rename: snake + checked: true + explicit_to_json: true diff --git a/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart b/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart new file mode 100644 index 00000000..0bd4e418 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/shorebird_code_push_protocol.dart @@ -0,0 +1,4 @@ +/// The Shorebird CodePush Protocol +library shorebird_code_push_protocol; + +export 'src/models/models.dart'; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/account.dart b/packages/shorebird_code_push_protocol/lib/src/models/account.dart new file mode 100644 index 00000000..96e83266 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/account.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'account.g.dart'; + +/// {@template account} +/// A user account which contains zero or more apps. +/// {@endtemplate} +@JsonSerializable() +class Account { + /// {@macro account} + Account({required this.apiKey, List? apps}) : apps = apps ?? []; + + /// Converts a Map to a [Account] + factory Account.fromJson(Map json) => + _$AccountFromJson(json); + + /// Converts a [Account] to a Map + Map toJson() => _$AccountToJson(this); + + /// List of apps associated with this account. + final List apps; + + /// The api key associated with this account. + final String apiKey; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/account.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/account.g.dart new file mode 100644 index 00000000..17a0fccb --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/account.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'account.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Account _$AccountFromJson(Map json) => $checkedCreate( + 'Account', + json, + ($checkedConvert) { + final val = Account( + apiKey: $checkedConvert('api_key', (v) => v as String), + apps: $checkedConvert( + 'apps', + (v) => (v as List?) + ?.map((e) => App.fromJson(e as Map)) + .toList()), + ); + return val; + }, + fieldKeyMap: const {'apiKey': 'api_key'}, + ); + +Map _$AccountToJson(Account instance) => { + 'apps': instance.apps.map((e) => e.toJson()).toList(), + 'api_key': instance.apiKey, + }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/app.dart b/packages/shorebird_code_push_protocol/lib/src/models/app.dart new file mode 100644 index 00000000..d7104975 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/app.dart @@ -0,0 +1,26 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'app.g.dart'; + +/// {@template app} +/// A single app which contains zero or more releases. +/// {@endtemplate} +@JsonSerializable() +class App { + /// {@macro app} + App({required this.productId, List? releases}) + : releases = releases ?? []; + + /// Converts a Map to an [App] + factory App.fromJson(Map json) => _$AppFromJson(json); + + /// Converts a [App] to a Map + Map toJson() => _$AppToJson(this); + + /// The product ID of the app. + final String productId; + + /// List of releases associated with this app. + final List releases; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/app.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/app.g.dart new file mode 100644 index 00000000..5263d7c9 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/app.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'app.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +App _$AppFromJson(Map json) => $checkedCreate( + 'App', + json, + ($checkedConvert) { + final val = App( + productId: $checkedConvert('product_id', (v) => v as String), + releases: $checkedConvert( + 'releases', + (v) => (v as List?) + ?.map((e) => Release.fromJson(e as Map)) + .toList()), + ); + return val; + }, + fieldKeyMap: const {'productId': 'product_id'}, + ); + +Map _$AppToJson(App instance) => { + 'product_id': instance.productId, + 'releases': instance.releases.map((e) => e.toJson()).toList(), + }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/artifact.dart b/packages/shorebird_code_push_protocol/lib/src/models/artifact.dart new file mode 100644 index 00000000..99f53924 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/artifact.dart @@ -0,0 +1,39 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'artifact.g.dart'; + +/// {@template artifact} +/// An artifact represents the contents of an update (patch) for a specific +/// platform and architecture. +/// {@endtemplate} +@JsonSerializable() +class Artifact { + /// {@macro artifact} + const Artifact({ + required this.arch, + required this.platform, + required this.url, + required this.hash, + }); + + /// Converts a Map to an [Artifact] + factory Artifact.fromJson(Map json) => + _$ArtifactFromJson(json); + + /// Converts an [Artifact] to a Map + Map toJson() => _$ArtifactToJson(this); + + /// The architecture of the artifact. + /// e.g. arm64, x86_64 + final String arch; + + /// The platform of the artifact. + /// e.g. android, ios + final String platform; + + /// The URL of the artifact. + final String url; + + /// The hash of the artifact. + final String hash; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/artifact.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/artifact.g.dart new file mode 100644 index 00000000..70235a4f --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/artifact.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'artifact.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Artifact _$ArtifactFromJson(Map json) => $checkedCreate( + 'Artifact', + json, + ($checkedConvert) { + final val = Artifact( + arch: $checkedConvert('arch', (v) => v as String), + platform: $checkedConvert('platform', (v) => v as String), + url: $checkedConvert('url', (v) => v as String), + hash: $checkedConvert('hash', (v) => v as String), + ); + return val; + }, + ); + +Map _$ArtifactToJson(Artifact instance) => { + 'arch': instance.arch, + 'platform': instance.platform, + 'url': instance.url, + 'hash': instance.hash, + }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/models.dart b/packages/shorebird_code_push_protocol/lib/src/models/models.dart new file mode 100644 index 00000000..161678c3 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/models.dart @@ -0,0 +1,6 @@ +export 'account.dart'; +export 'app.dart'; +export 'artifact.dart'; +export 'patch.dart'; +export 'registry.dart'; +export 'release.dart'; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/patch.dart b/packages/shorebird_code_push_protocol/lib/src/models/patch.dart new file mode 100644 index 00000000..d5ebf35b --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/patch.dart @@ -0,0 +1,32 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'patch.g.dart'; + +/// {@template patch} +/// A single patch which contains zero or more artifacts. +/// {@endtemplate} +@JsonSerializable() +class Patch { + /// {@macro patch} + const Patch({ + required this.number, + this.artifacts = const [], + this.channels = const [], + }); + + /// Converts a Map to a [Patch] + factory Patch.fromJson(Map json) => _$PatchFromJson(json); + + /// Converts a [Patch] to a Map + Map toJson() => _$PatchToJson(this); + + /// The patch number (newer patches are larger). + final int number; + + /// The list of channels associated with this patch. + final List channels; + + /// List of artifacts associated with this patch. + final List artifacts; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/patch.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/patch.g.dart new file mode 100644 index 00000000..7b942887 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/patch.g.dart @@ -0,0 +1,38 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'patch.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Patch _$PatchFromJson(Map json) => $checkedCreate( + 'Patch', + json, + ($checkedConvert) { + final val = Patch( + number: $checkedConvert('number', (v) => v as int), + artifacts: $checkedConvert( + 'artifacts', + (v) => + (v as List?) + ?.map((e) => Artifact.fromJson(e as Map)) + .toList() ?? + const []), + channels: $checkedConvert( + 'channels', + (v) => + (v as List?)?.map((e) => e as String).toList() ?? + const []), + ); + return val; + }, + ); + +Map _$PatchToJson(Patch instance) => { + 'number': instance.number, + 'channels': instance.channels, + 'artifacts': instance.artifacts.map((e) => e.toJson()).toList(), + }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/registry.dart b/packages/shorebird_code_push_protocol/lib/src/models/registry.dart new file mode 100644 index 00000000..c6568ac3 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/registry.dart @@ -0,0 +1,23 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'registry.g.dart'; + +/// {@template registry} +/// A collection of accounts and their respective apps +/// {@endtemplate} +@JsonSerializable() +class Registry { + /// {@macro registry} + const Registry({this.accounts = const []}); + + /// Converts a Map to a [Registry] + factory Registry.fromJson(Map json) => + _$RegistryFromJson(json); + + /// Converts a [Registry] to a Map + Map toJson() => _$RegistryToJson(this); + + /// List of accounts associated with this registry. + final List accounts; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/registry.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/registry.g.dart new file mode 100644 index 00000000..70b6fa89 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/registry.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'registry.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Registry _$RegistryFromJson(Map json) => $checkedCreate( + 'Registry', + json, + ($checkedConvert) { + final val = Registry( + accounts: $checkedConvert( + 'accounts', + (v) => + (v as List?) + ?.map((e) => Account.fromJson(e as Map)) + .toList() ?? + const []), + ); + return val; + }, + ); + +Map _$RegistryToJson(Registry instance) => { + 'accounts': instance.accounts.map((e) => e.toJson()).toList(), + }; diff --git a/packages/shorebird_code_push_protocol/lib/src/models/release.dart b/packages/shorebird_code_push_protocol/lib/src/models/release.dart new file mode 100644 index 00000000..6f32d02a --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/release.dart @@ -0,0 +1,27 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; + +part 'release.g.dart'; + +/// {@template release} +/// A single release which contains zero or more patches. +/// {@endtemplate} +@JsonSerializable() +class Release { + /// {@macro release} + Release({required this.version, List? patches}) + : patches = patches ?? []; + + /// Converts a Map to a [Release] + factory Release.fromJson(Map json) => + _$ReleaseFromJson(json); + + /// Converts a [Release] to a Map + Map toJson() => _$ReleaseToJson(this); + + /// The version of the release. + final String version; + + /// List of patches associated with this release. + final List patches; +} diff --git a/packages/shorebird_code_push_protocol/lib/src/models/release.g.dart b/packages/shorebird_code_push_protocol/lib/src/models/release.g.dart new file mode 100644 index 00000000..4a24ffc8 --- /dev/null +++ b/packages/shorebird_code_push_protocol/lib/src/models/release.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars + +part of 'release.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Release _$ReleaseFromJson(Map json) => $checkedCreate( + 'Release', + json, + ($checkedConvert) { + final val = Release( + version: $checkedConvert('version', (v) => v as String), + patches: $checkedConvert( + 'patches', + (v) => (v as List?) + ?.map((e) => Patch.fromJson(e as Map)) + .toList()), + ); + return val; + }, + ); + +Map _$ReleaseToJson(Release instance) => { + 'version': instance.version, + 'patches': instance.patches.map((e) => e.toJson()).toList(), + }; diff --git a/packages/shorebird_code_push_protocol/pubspec.yaml b/packages/shorebird_code_push_protocol/pubspec.yaml new file mode 100644 index 00000000..9a386a14 --- /dev/null +++ b/packages/shorebird_code_push_protocol/pubspec.yaml @@ -0,0 +1,20 @@ +name: shorebird_code_push_protocol +description: Library which contains common interfaces used by Shorebird CodePush +version: 0.1.0+1 +repository: https://github.com/shorebirdtech/shorebird + +publish_to: none + +environment: + sdk: ">=2.19.0 <3.0.0" + +dependencies: + json_annotation: ^4.8.0 + +dev_dependencies: + build_runner: ^2.0.0 + json_serializable: ^6.6.1 + mocktail: ^0.3.0 + path: ^1.8.3 + test: ^1.19.2 + very_good_analysis: ^4.0.0 diff --git a/packages/shorebird_code_push_protocol/test/src/models/registry_test.dart b/packages/shorebird_code_push_protocol/test/src/models/registry_test.dart new file mode 100644 index 00000000..14421718 --- /dev/null +++ b/packages/shorebird_code_push_protocol/test/src/models/registry_test.dart @@ -0,0 +1,99 @@ +import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart'; +import 'package:test/test.dart'; + +void main() { + group('Registry', () { + test('can be (de)serialized', () { + final registry = Registry( + accounts: [ + Account( + apiKey: 'api_key1', + apps: [ + App( + productId: 'app1', + releases: [ + Release( + version: '1.0.0', + patches: [ + const Patch( + number: 1, + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'localhost', + hash: '#', + ) + ], + channels: ['stable'], + ), + const Patch( + number: 2, + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'localhost', + hash: '#', + ) + ], + channels: ['stable'], + ) + ], + ), + Release(version: '1.0.1'), + ], + ), + App(productId: 'app2'), + ], + ), + Account( + apiKey: 'api_key2', + apps: [ + App( + productId: 'app2', + releases: [ + Release( + version: '1.0.0', + patches: [ + const Patch( + number: 1, + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'localhost', + hash: '#', + ) + ], + channels: ['stable'], + ), + const Patch( + number: 2, + artifacts: [ + Artifact( + arch: 'aarm64', + platform: 'android', + url: 'localhost', + hash: '#', + ) + ], + channels: ['stable'], + ) + ], + ), + Release(version: '2.0.0'), + ], + ), + ], + ), + ], + ); + + expect( + Registry.fromJson(registry.toJson()).toJson(), + equals(registry.toJson()), + ); + }); + }); +}