feat(shorebird_cli): app creation enhancements (#134)
This commit is contained in:
@@ -3,13 +3,16 @@ import 'dart:async';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// {@template create_app_command}
|
||||
///
|
||||
/// `shorebird apps create`
|
||||
/// Create a new app on Shorebird.
|
||||
/// {@endtemplate}
|
||||
class CreateAppCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
class CreateAppCommand extends ShorebirdCommand
|
||||
with ShorebirdConfigMixin, ShorebirdCreateAppMixin {
|
||||
/// {@macro create_app_command}
|
||||
CreateAppCommand({
|
||||
required super.logger,
|
||||
@@ -17,10 +20,10 @@ class CreateAppCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
super.auth,
|
||||
}) {
|
||||
argParser.addOption(
|
||||
'app-id',
|
||||
'app-name',
|
||||
help: '''
|
||||
The unique application identifier.
|
||||
Defaults to the app_id in "shorebird.yaml".''',
|
||||
The display name of your application.
|
||||
Defaults to the name in "pubspec.yaml".''',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,37 +41,17 @@ Defaults to the app_id in "shorebird.yaml".''',
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
final appIdArg = results['app-id'] as String?;
|
||||
late final String appId;
|
||||
|
||||
if (appIdArg == null) {
|
||||
String? defaultAppId;
|
||||
try {
|
||||
defaultAppId = getShorebirdYaml()?.appId;
|
||||
} catch (_) {}
|
||||
|
||||
appId = logger.prompt(
|
||||
'${lightGreen.wrap('?')} Enter the App ID',
|
||||
defaultValue: defaultAppId,
|
||||
);
|
||||
} else {
|
||||
appId = appIdArg;
|
||||
}
|
||||
|
||||
final client = buildCodePushClient(
|
||||
apiKey: session.apiKey,
|
||||
hostedUri: hostedUri,
|
||||
);
|
||||
|
||||
final appName = results['app-name'] as String?;
|
||||
late final App app;
|
||||
try {
|
||||
await client.createApp(appId: appId);
|
||||
app = await createApp(appName: appName);
|
||||
} catch (error) {
|
||||
logger.err('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'${lightGreen.wrap('Created new app: ${cyan.wrap(appId)}')}',
|
||||
'''${lightGreen.wrap('Created ${cyan.wrap(app.displayName)} ${styleDim.wrap(cyan.wrap('(${app.id})'))}')}''',
|
||||
);
|
||||
|
||||
return ExitCode.success.code;
|
||||
|
||||
@@ -40,7 +40,7 @@ class ListAppsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
hostedUri: hostedUri,
|
||||
);
|
||||
|
||||
late final List<App> apps;
|
||||
late final List<AppMetadata> apps;
|
||||
try {
|
||||
apps = await client.getApps();
|
||||
} catch (error) {
|
||||
@@ -61,12 +61,12 @@ class ListAppsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
}
|
||||
}
|
||||
|
||||
extension on App {
|
||||
extension on AppMetadata {
|
||||
String prettyPrint() {
|
||||
final latestReleasePart =
|
||||
latestReleaseVersion != null ? 'v$latestReleaseVersion' : '(empty)';
|
||||
final latestPatchPart =
|
||||
latestPatchNumber != null ? '(patch #$latestPatchNumber)' : '';
|
||||
return '$appId: $latestReleasePart $latestPatchPart';
|
||||
latestPatchNumber != null ? ' (patch #$latestPatchNumber)' : '';
|
||||
return '$displayName: $latestReleasePart$latestPatchPart ($appId)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_create_app_mixin.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
import 'package:yaml_edit/yaml_edit.dart';
|
||||
|
||||
@@ -13,9 +15,10 @@ import 'package:yaml_edit/yaml_edit.dart';
|
||||
/// `shorebird init`
|
||||
/// Initialize Shorebird.
|
||||
/// {@endtemplate}
|
||||
class InitCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
class InitCommand extends ShorebirdCommand
|
||||
with ShorebirdConfigMixin, ShorebirdCreateAppMixin {
|
||||
/// {@macro init_command}
|
||||
InitCommand({required super.logger});
|
||||
InitCommand({required super.logger, super.auth, super.buildCodePushClient});
|
||||
|
||||
@override
|
||||
String get description => 'Initialize Shorebird.';
|
||||
@@ -25,6 +28,12 @@ class InitCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
|
||||
@override
|
||||
Future<int> run() async {
|
||||
final session = auth.currentSession;
|
||||
if (session == null) {
|
||||
logger.err('You must be logged in.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
final progress = logger.progress('Initializing Shorebird');
|
||||
try {
|
||||
if (!hasPubspecYaml) {
|
||||
@@ -36,14 +45,22 @@ class InitCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
late final App app;
|
||||
try {
|
||||
final pubspecYaml = getPubspecYaml()!;
|
||||
app = await createApp(appName: pubspecYaml.name);
|
||||
} catch (error) {
|
||||
logger.err('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
progress.update('Creating "shorebird.yaml"');
|
||||
|
||||
try {
|
||||
if (hasShorebirdYaml) {
|
||||
progress.update('"shorebird.yaml" already exists.');
|
||||
} else {
|
||||
final pubspecYaml = getPubspecYaml()!;
|
||||
_addShorebirdYamlToProject(pubspecYaml.name);
|
||||
_addShorebirdYamlToProject(app.id);
|
||||
progress.update('Generated a "shorebird.yaml".');
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -72,7 +89,6 @@ ${lightGreen.wrap('🐦 Shorebird initialized successfully!')}
|
||||
|
||||
Reference the following commands to get started:
|
||||
|
||||
✨ To create a new app use: "${lightCyan.wrap('shorebird apps create')}".
|
||||
🚙 To run your project use: "${lightCyan.wrap('shorebird run')}".
|
||||
📦 To build your project use: "${lightCyan.wrap('shorebird build')}".
|
||||
🚀 To publish an update use: "${lightCyan.wrap('shorebird publish')}".
|
||||
@@ -89,7 +105,8 @@ For more information about Shorebird, visit ${link(uri: Uri.parse('https://shore
|
||||
# This file is used to configure the Shorebird CLI.
|
||||
# Learn more at https://shorebird.dev
|
||||
|
||||
# This is the unique identifier for your app.
|
||||
# This is the unique identifier assigned to your app.
|
||||
# It is used by your app to request the correct patches from the Shorebird servers.
|
||||
app_id: $appId
|
||||
''');
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
mixin ShorebirdCreateAppMixin on ShorebirdConfigMixin {
|
||||
Future<App> createApp({String? appName}) async {
|
||||
late final String displayName;
|
||||
if (appName == null) {
|
||||
String? defaultAppName;
|
||||
try {
|
||||
defaultAppName = getPubspecYaml()?.name;
|
||||
} catch (_) {}
|
||||
|
||||
displayName = logger.prompt(
|
||||
'${lightGreen.wrap('?')} How should we refer to this app?',
|
||||
defaultValue: defaultAppName,
|
||||
);
|
||||
} else {
|
||||
displayName = appName;
|
||||
}
|
||||
|
||||
final client = buildCodePushClient(
|
||||
apiKey: auth.currentSession!.apiKey,
|
||||
hostedUri: hostedUri,
|
||||
);
|
||||
|
||||
return client.createApp(displayName: displayName);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,8 @@ class _MockLogger extends Mock implements Logger {}
|
||||
void main() {
|
||||
group('create', () {
|
||||
const apiKey = 'test-api-key';
|
||||
const appId = 'example';
|
||||
const appId = 'app-id';
|
||||
const displayName = 'Example App';
|
||||
const session = Session(apiKey: apiKey);
|
||||
|
||||
late ArgResults argResults;
|
||||
@@ -53,33 +54,43 @@ void main() {
|
||||
expect(result, ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
test('prompts for app-id when not provided', () async {
|
||||
when(() => logger.prompt(any())).thenReturn(appId);
|
||||
test('prompts for app name when not provided', () async {
|
||||
when(
|
||||
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
|
||||
).thenReturn(displayName);
|
||||
await command.run();
|
||||
verify(() => logger.prompt(any())).called(1);
|
||||
verify(() => codePushClient.createApp(appId: appId)).called(1);
|
||||
verify(
|
||||
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
|
||||
).called(1);
|
||||
verify(
|
||||
() => codePushClient.createApp(displayName: displayName),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('uses provided app-id when provided', () async {
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
test('uses provided app name when provided', () async {
|
||||
when(() => argResults['app-name']).thenReturn(displayName);
|
||||
await command.run();
|
||||
verifyNever(() => logger.prompt(any()));
|
||||
verify(() => codePushClient.createApp(appId: appId)).called(1);
|
||||
verify(
|
||||
() => codePushClient.createApp(displayName: displayName),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('returns success when app is created', () async {
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(() => argResults['app-name']).thenReturn(displayName);
|
||||
when(
|
||||
() => codePushClient.createApp(appId: appId),
|
||||
).thenAnswer((_) async {});
|
||||
() => codePushClient.createApp(displayName: displayName),
|
||||
).thenAnswer((_) async => const App(id: appId, displayName: displayName));
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.success.code);
|
||||
});
|
||||
|
||||
test('returns software error when app creation fails', () async {
|
||||
final error = Exception('oops');
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(() => codePushClient.createApp(appId: appId)).thenThrow(error);
|
||||
when(() => argResults['app-name']).thenReturn(displayName);
|
||||
when(
|
||||
() => codePushClient.createApp(displayName: displayName),
|
||||
).thenThrow(error);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.software.code);
|
||||
verify(() => logger.err('$error')).called(1);
|
||||
|
||||
@@ -59,8 +59,9 @@ void main() {
|
||||
|
||||
test('returns ExitCode.success when apps are not empty', () async {
|
||||
final apps = [
|
||||
const App(
|
||||
appId: 'shorebird-counter',
|
||||
const AppMetadata(
|
||||
appId: '30370f27-dbf1-4673-8b20-fb096e38dffa',
|
||||
displayName: 'Shorebird Counter',
|
||||
latestReleaseVersion: '1.0.0',
|
||||
latestPatchNumber: 1,
|
||||
),
|
||||
@@ -68,7 +69,9 @@ void main() {
|
||||
when(() => codePushClient.getApps()).thenAnswer((_) async => apps);
|
||||
expect(await command.run(), ExitCode.success.code);
|
||||
verify(
|
||||
() => logger.info('shorebird-counter: v1.0.0 (patch #1)'),
|
||||
() => logger.info(
|
||||
'''Shorebird Counter: v1.0.0 (patch #1) (30370f27-dbf1-4673-8b20-fb096e38dffa)''',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,33 +3,66 @@ import 'dart:io';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/auth/session.dart';
|
||||
import 'package:shorebird_cli/src/commands/init_command.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
|
||||
void main() {
|
||||
group('init', () {
|
||||
const apiKey = 'test-api-key';
|
||||
const version = '1.2.3';
|
||||
const appId = 'test_app_id';
|
||||
const appName = 'test_app_name';
|
||||
const app = App(id: appId, displayName: appName);
|
||||
const pubspecYamlContent = '''
|
||||
name: $appId
|
||||
name: $appName
|
||||
version: $version
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"''';
|
||||
const session = Session(apiKey: apiKey);
|
||||
|
||||
late Auth auth;
|
||||
late CodePushClient codePushClient;
|
||||
late Logger logger;
|
||||
late Progress progress;
|
||||
late InitCommand command;
|
||||
|
||||
setUp(() {
|
||||
auth = _MockAuth();
|
||||
codePushClient = _MockCodePushClient();
|
||||
logger = _MockLogger();
|
||||
progress = _MockProgress();
|
||||
command = InitCommand(logger: logger);
|
||||
command = InitCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({required String apiKey, Uri? hostedUri}) {
|
||||
return codePushClient;
|
||||
},
|
||||
logger: logger);
|
||||
|
||||
when(() => auth.currentSession).thenReturn(session);
|
||||
when(
|
||||
() => codePushClient.createApp(displayName: any(named: 'displayName')),
|
||||
).thenAnswer((_) async => app);
|
||||
when(() => logger.progress(any())).thenReturn(progress);
|
||||
when(
|
||||
() => logger.prompt(any(), defaultValue: any(named: 'defaultValue')),
|
||||
).thenReturn(appName);
|
||||
});
|
||||
|
||||
test('returns no user error when not logged in', () async {
|
||||
when(() => auth.currentSession).thenReturn(null);
|
||||
final result = await command.run();
|
||||
expect(result, ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
test('throws no input error when pubspec.yaml is not found.', () async {
|
||||
@@ -55,6 +88,24 @@ environment:
|
||||
expect(exitCode, ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('throws software error when error occurs creating app.', () async {
|
||||
final error = Exception('oops');
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(
|
||||
p.join(tempDir.path, 'pubspec.yaml'),
|
||||
).writeAsStringSync(pubspecYamlContent);
|
||||
File(p.join(tempDir.path, 'shorebird.yaml')).createSync();
|
||||
when(
|
||||
() => codePushClient.createApp(displayName: any(named: 'displayName')),
|
||||
).thenThrow(error);
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
command.run,
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
verify(() => logger.err('$error')).called(1);
|
||||
expect(exitCode, ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('throws software error when shorebird.yaml is malformed.', () async {
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
File(
|
||||
|
||||
@@ -9,7 +9,9 @@ Future<void> main() async {
|
||||
final engine = await client.downloadEngine('1837b5be5f');
|
||||
|
||||
// Create a new Shorebird application.
|
||||
await client.createApp(appId: '<APP ID>');
|
||||
final app = await client.createApp(
|
||||
displayName: '<DISPLAY NAME>', // e.g. 'Shorebird Example'
|
||||
);
|
||||
|
||||
// List all apps.
|
||||
final apps = await client.getApps();
|
||||
@@ -18,7 +20,7 @@ Future<void> main() async {
|
||||
await client.createPatch(
|
||||
artifactPath: '<PATH TO ARTIFACT>', // e.g. 'libapp.so'
|
||||
releaseVersion: '<RELEASE VERSION>', // e.g. '1.0.0'
|
||||
appId: '<APP ID>', // e.g. 'shorebird-example'
|
||||
appId: app.id, // e.g. '30370f27-dbf1-4673-8b20-fb096e38dffa'
|
||||
channel: '<CHANNEL>', // e.g. 'stable'
|
||||
);
|
||||
|
||||
|
||||
@@ -46,17 +46,20 @@ class CodePushClient {
|
||||
|
||||
Map<String, String> get _apiKeyHeader => {'x-api-key': _apiKey};
|
||||
|
||||
/// Create a new app with the provided [appId].
|
||||
Future<void> createApp({required String appId}) async {
|
||||
/// Create a new app with the provided [displayName].
|
||||
/// Returns the newly created app.
|
||||
Future<App> createApp({required String displayName}) async {
|
||||
final response = await _httpClient.post(
|
||||
Uri.parse('$hostedUri/api/v1/apps'),
|
||||
headers: _apiKeyHeader,
|
||||
body: json.encode({'app_id': appId}),
|
||||
body: json.encode({'display_name': displayName}),
|
||||
);
|
||||
|
||||
if (response.statusCode != HttpStatus.created) {
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
throw _parseErrorResponse(response.body);
|
||||
}
|
||||
final body = json.decode(response.body) as Map<String, dynamic>;
|
||||
return App.fromJson(body);
|
||||
}
|
||||
|
||||
/// Create a new patch.
|
||||
@@ -119,7 +122,7 @@ class CodePushClient {
|
||||
}
|
||||
|
||||
/// List all apps for the current account.
|
||||
Future<List<App>> getApps() async {
|
||||
Future<List<AppMetadata>> getApps() async {
|
||||
final response = await _httpClient.get(
|
||||
Uri.parse('$hostedUri/api/v1/apps'),
|
||||
headers: _apiKeyHeader,
|
||||
@@ -131,7 +134,7 @@ class CodePushClient {
|
||||
|
||||
final apps = json.decode(response.body) as List;
|
||||
return apps
|
||||
.map((app) => App.fromJson(app as Map<String, dynamic>))
|
||||
.map((app) => AppMetadata.fromJson(app as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ class _FakeBaseRequest extends Fake implements http.BaseRequest {}
|
||||
void main() {
|
||||
group('CodePushClient', () {
|
||||
const apiKey = 'api-key';
|
||||
const appId = 'shorebird-example';
|
||||
const appId = 'app-id';
|
||||
const displayName = 'shorebird-example';
|
||||
const errorResponse = ErrorResponse(
|
||||
code: 'test_code',
|
||||
message: 'test message',
|
||||
@@ -66,7 +67,7 @@ void main() {
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.badRequest));
|
||||
|
||||
expect(
|
||||
codePushClient.createApp(appId: appId),
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
throwsA(
|
||||
isA<CodePushException>().having(
|
||||
(e) => e.message,
|
||||
@@ -92,7 +93,7 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
codePushClient.createApp(appId: appId),
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
throwsA(
|
||||
isA<CodePushException>().having(
|
||||
(e) => e.message,
|
||||
@@ -110,9 +111,23 @@ void main() {
|
||||
headers: any(named: 'headers'),
|
||||
body: any(named: 'body'),
|
||||
),
|
||||
).thenAnswer((_) async => http.Response('', HttpStatus.created));
|
||||
).thenAnswer(
|
||||
(_) async => http.Response(
|
||||
json.encode(App(id: appId, displayName: displayName)),
|
||||
HttpStatus.ok,
|
||||
),
|
||||
);
|
||||
|
||||
await codePushClient.createApp(appId: appId);
|
||||
await expectLater(
|
||||
codePushClient.createApp(displayName: displayName),
|
||||
completion(
|
||||
equals(
|
||||
isA<App>()
|
||||
.having((a) => a.id, 'id', appId)
|
||||
.having((a) => a.displayName, 'displayName', displayName),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final uri = verify(
|
||||
() => httpClient.post(
|
||||
@@ -393,8 +408,8 @@ void main() {
|
||||
|
||||
test('completes when request succeeds (populated)', () async {
|
||||
final expected = [
|
||||
App(appId: 'shorebird-example'),
|
||||
App(appId: 'shorebird-counter'),
|
||||
AppMetadata(appId: '1', displayName: 'Shorebird Example'),
|
||||
AppMetadata(appId: '2', displayName: 'Shorebird Clock'),
|
||||
];
|
||||
|
||||
when(
|
||||
|
||||
@@ -9,9 +9,8 @@ part 'app.g.dart';
|
||||
class App {
|
||||
/// {@macro app}
|
||||
const App({
|
||||
required this.appId,
|
||||
this.latestReleaseVersion,
|
||||
this.latestPatchNumber,
|
||||
required this.id,
|
||||
required this.displayName,
|
||||
});
|
||||
|
||||
/// Converts a Map<String, dynamic> to an [App]
|
||||
@@ -21,11 +20,8 @@ class App {
|
||||
Map<String, dynamic> toJson() => _$AppToJson(this);
|
||||
|
||||
/// The ID of the app.
|
||||
final String appId;
|
||||
final String id;
|
||||
|
||||
/// The latest release version of the app.
|
||||
final String? latestReleaseVersion;
|
||||
|
||||
/// The latest patch number of the app.
|
||||
final int? latestPatchNumber;
|
||||
/// The display name of the app.
|
||||
final String displayName;
|
||||
}
|
||||
|
||||
@@ -13,23 +13,15 @@ App _$AppFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = App(
|
||||
appId: $checkedConvert('app_id', (v) => v as String),
|
||||
latestReleaseVersion:
|
||||
$checkedConvert('latest_release_version', (v) => v as String?),
|
||||
latestPatchNumber:
|
||||
$checkedConvert('latest_patch_number', (v) => v as int?),
|
||||
id: $checkedConvert('id', (v) => v as String),
|
||||
displayName: $checkedConvert('display_name', (v) => v as String),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {
|
||||
'appId': 'app_id',
|
||||
'latestReleaseVersion': 'latest_release_version',
|
||||
'latestPatchNumber': 'latest_patch_number'
|
||||
},
|
||||
fieldKeyMap: const {'displayName': 'display_name'},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppToJson(App instance) => <String, dynamic>{
|
||||
'app_id': instance.appId,
|
||||
'latest_release_version': instance.latestReleaseVersion,
|
||||
'latest_patch_number': instance.latestPatchNumber,
|
||||
'id': instance.id,
|
||||
'display_name': instance.displayName,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'app_metadata.g.dart';
|
||||
|
||||
/// {@template app_metadata}
|
||||
/// A single app which contains zero or more releases.
|
||||
/// {@endtemplate}
|
||||
@JsonSerializable()
|
||||
class AppMetadata {
|
||||
/// {@macro app_metadata}
|
||||
const AppMetadata({
|
||||
required this.appId,
|
||||
required this.displayName,
|
||||
this.latestReleaseVersion,
|
||||
this.latestPatchNumber,
|
||||
});
|
||||
|
||||
/// Converts a Map<String, dynamic> to an [AppMetadata]
|
||||
factory AppMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$AppMetadataFromJson(json);
|
||||
|
||||
/// Converts a [AppMetadata] to a Map<String, dynamic>
|
||||
Map<String, dynamic> toJson() => _$AppMetadataToJson(this);
|
||||
|
||||
/// The ID of the app.
|
||||
final String appId;
|
||||
|
||||
/// The display name of the app.
|
||||
final String displayName;
|
||||
|
||||
/// The latest release version of the app.
|
||||
final String? latestReleaseVersion;
|
||||
|
||||
/// The latest patch number of the app.
|
||||
final int? latestPatchNumber;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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_metadata.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AppMetadata _$AppMetadataFromJson(Map<String, dynamic> json) => $checkedCreate(
|
||||
'AppMetadata',
|
||||
json,
|
||||
($checkedConvert) {
|
||||
final val = AppMetadata(
|
||||
appId: $checkedConvert('app_id', (v) => v as String),
|
||||
displayName: $checkedConvert('display_name', (v) => v as String),
|
||||
latestReleaseVersion:
|
||||
$checkedConvert('latest_release_version', (v) => v as String?),
|
||||
latestPatchNumber:
|
||||
$checkedConvert('latest_patch_number', (v) => v as int?),
|
||||
);
|
||||
return val;
|
||||
},
|
||||
fieldKeyMap: const {
|
||||
'appId': 'app_id',
|
||||
'displayName': 'display_name',
|
||||
'latestReleaseVersion': 'latest_release_version',
|
||||
'latestPatchNumber': 'latest_patch_number'
|
||||
},
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AppMetadataToJson(AppMetadata instance) =>
|
||||
<String, dynamic>{
|
||||
'app_id': instance.appId,
|
||||
'display_name': instance.displayName,
|
||||
'latest_release_version': instance.latestReleaseVersion,
|
||||
'latest_patch_number': instance.latestPatchNumber,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
export 'app.dart';
|
||||
export 'app_metadata.dart';
|
||||
export 'error_response.dart';
|
||||
export 'patch.dart';
|
||||
export 'user.dart';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:shorebird_code_push_protocol/shorebird_code_push_protocol.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('AppMetadata', () {
|
||||
test('can be (de)serialized', () {
|
||||
const appMetadata = AppMetadata(
|
||||
appId: '30370f27-dbf1-4673-8b20-fb096e38dffa',
|
||||
displayName: 'My App',
|
||||
latestReleaseVersion: '1.0.0',
|
||||
latestPatchNumber: 1,
|
||||
);
|
||||
expect(
|
||||
AppMetadata.fromJson(appMetadata.toJson()).toJson(),
|
||||
equals(appMetadata.toJson()),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -5,9 +5,8 @@ void main() {
|
||||
group('App', () {
|
||||
test('can be (de)serialized', () {
|
||||
const app = App(
|
||||
appId: 'my_app',
|
||||
latestReleaseVersion: '1.0.0',
|
||||
latestPatchNumber: 1,
|
||||
id: '30370f27-dbf1-4673-8b20-fb096e38dffa',
|
||||
displayName: 'My App',
|
||||
);
|
||||
expect(App.fromJson(app.toJson()).toJson(), equals(app.toJson()));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user