From d9a4ce2ecd394b152c15d4785204a4902b2c4e45 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 15 Mar 2023 11:32:59 -0500 Subject: [PATCH] feat(shorebird_cli): `shorebird init` (#72) --- packages/shorebird_cli/README.md | 56 ++++- .../shorebird_cli/lib/src/command_runner.dart | 1 + .../lib/src/commands/commands.dart | 1 + .../lib/src/commands/init_command.dart | 127 ++++++++++ .../lib/src/commands/publish_command.dart | 104 ++------- .../shorebird_cli/lib/src/config/config.dart | 1 + .../lib/src/shorebird_config_mixin.dart | 42 ++++ .../shorebird_cli/test/ensure_build_test.dart | 1 + .../test/src/commands/init_command_test.dart | 206 +++++++++++++++++ .../src/commands/publish_command_test.dart | 216 ++++-------------- 10 files changed, 489 insertions(+), 266 deletions(-) create mode 100644 packages/shorebird_cli/lib/src/commands/init_command.dart create mode 100644 packages/shorebird_cli/lib/src/config/config.dart create mode 100644 packages/shorebird_cli/lib/src/shorebird_config_mixin.dart create mode 100644 packages/shorebird_cli/test/src/commands/init_command_test.dart diff --git a/packages/shorebird_cli/README.md b/packages/shorebird_cli/README.md index 16b65411..c0b75d3c 100644 --- a/packages/shorebird_cli/README.md +++ b/packages/shorebird_cli/README.md @@ -14,9 +14,37 @@ dart pub global activate --source git https://github.com/shorebirdtech/shorebird ## Commands +### Init + +Get started by initializing shorebird in your current project. + +```bash +shorebird init +``` + +**Sample** + +``` +shorebird init +βœ“ Initialized Shorebird (27ms) + +🐦 Shorebird initialized successfully! + +βœ… A "shorebird.yaml" has been created. +βœ… The "pubspec.yaml" has been updated to include "shorebird.yaml" as an asset. + +Reference the following commands to get started: + +πŸš™ To run your project use: "shorebird run". +πŸ“¦ To build your project use: "shorebird build". +πŸš€ To publish a new update use: "shorebird publish". + +For more information, visit https://shorebird.dev +``` + ### Login -Get started by requesting an API key and using `shorebird login` to authenticate: +Request an API key and use `shorebird login` to authenticate: ```bash shorebird login @@ -24,7 +52,7 @@ shorebird login **Sample** -```bash +``` shorebird login ? Please enter your API Key: βœ“ Logging into shorebird.dev (7ms) @@ -41,11 +69,27 @@ shorebird logout **Sample** -```bash +``` shorebird logout βœ“ Logging out of shorebird.dev (1ms) ``` +### List Apps + +List all existing apps in Shorebird using the `shorebird apps list` command: + +```bash +shorebird apps list +``` + +**Sample** + +``` +shorebird apps list +my-counter: v1.0.0 (patch #1) +my-example: v2.1.0 (patch #2) +``` + ### Run Run an existing application using the Shorebird Engine via the `shorebird run` command: @@ -54,7 +98,7 @@ Run an existing application using the Shorebird Engine via the `shorebird run` c shorebird run ``` -**❗️Note**: If it's the first time, `shorebird run` will download and build the shorebird engine which may take some time. The shorebird engine will be cached for subsequent runs. +**❗️Note**: If it's the first time using shorebird, `shorebird run` will download and build the shorebird engine which may take some time. The shorebird engine will be cached for subsequent runs. ### Build @@ -64,6 +108,8 @@ Build a new release of your application using the `shorebird build` command: shorebird build ``` +**❗️Note**: If it's the first time using shorebird, `shorebird build` will download and build the shorebird engine which may take some time. The shorebird engine will be cached for subsequent runs. + ### Publish The publish command allows developers to publish new releases of their Flutter application to the Shorebird CodePush API. These updates are then pushed directly to users' devices. @@ -89,7 +135,9 @@ Global options: --[no-]verbose Noisy logging, including all shell commands executed. Available commands: + apps Manage your Shorebird apps. build Build a new release of your application. + init Initialize Shorebird. login Login as a new Shorebird user. logout Logout of the current Shorebird user publish Publish an update. diff --git a/packages/shorebird_cli/lib/src/command_runner.dart b/packages/shorebird_cli/lib/src/command_runner.dart index 62b4f678..a6cbdf00 100644 --- a/packages/shorebird_cli/lib/src/command_runner.dart +++ b/packages/shorebird_cli/lib/src/command_runner.dart @@ -37,6 +37,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner { addCommand(AppsCommand(logger: _logger)); addCommand(BuildCommand(logger: _logger)); + addCommand(InitCommand(logger: _logger)); addCommand(LoginCommand(logger: _logger)); addCommand(LogoutCommand(logger: _logger)); addCommand(PublishCommand(logger: _logger)); diff --git a/packages/shorebird_cli/lib/src/commands/commands.dart b/packages/shorebird_cli/lib/src/commands/commands.dart index dbdd0a35..8a2da5fd 100644 --- a/packages/shorebird_cli/lib/src/commands/commands.dart +++ b/packages/shorebird_cli/lib/src/commands/commands.dart @@ -1,5 +1,6 @@ export 'apps/apps.dart'; export 'build_command.dart'; +export 'init_command.dart'; export 'login_command.dart'; export 'logout_command.dart'; export 'publish_command.dart'; diff --git a/packages/shorebird_cli/lib/src/commands/init_command.dart b/packages/shorebird_cli/lib/src/commands/init_command.dart new file mode 100644 index 00000000..21ab4a9f --- /dev/null +++ b/packages/shorebird_cli/lib/src/commands/init_command.dart @@ -0,0 +1,127 @@ +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +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:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + +/// {@template init_command} +/// +/// `shorebird init` +/// Initialize Shorebird. +/// {@endtemplate} +class InitCommand extends ShorebirdCommand with ShorebirdConfigMixin { + /// {@macro init_command} + InitCommand({required super.logger, super.buildUuid}); + + @override + String get description => 'Initialize Shorebird.'; + + @override + String get name => 'init'; + + @override + Future run() async { + final progress = logger.progress('Initializing Shorebird'); + + try { + if (!hasPubspecYaml) { + logger.err('Could not find a "pubspec.yaml".'); + return ExitCode.noInput.code; + } + } catch (error) { + logger.err('Error parsing "pubspec.yaml": $error'); + return ExitCode.software.code; + } + + progress.update('Creating "shorebird.yaml"'); + + try { + if (hasShorebirdYaml) { + progress.update('"shorebird.yaml" already exists.'); + } else { + _addShorebirdYamlToProject(); + progress.update('Generated a "shorebird.yaml".'); + } + } catch (error) { + progress.fail(); + logger.err('Error creating "shorebird.yaml".\n$error'); + return ExitCode.software.code; + } + + progress.update('Adding "shorebird.yaml" to "pubspec.yaml" assets'); + + if (pubspecContainsShorebirdYaml) { + progress.update('"shorebird.yaml" already in "pubspec.yaml" assets.'); + } else { + _addShorebirdYamlToPubspecAssets(); + } + + progress.complete('Initialized Shorebird'); + + logger.info( + ''' + +${lightGreen.wrap('🐦 Shorebird initialized successfully!')} + +βœ… A "shorebird.yaml" has been created. +βœ… The "pubspec.yaml" has been updated to include "shorebird.yaml" as an asset. + +Reference the following commands to get started: + +πŸš™ To run your project use: "${lightCyan.wrap('shorebird run')}". +πŸ“¦ To build your project use: "${lightCyan.wrap('shorebird build')}". +πŸš€ To publish a new update use: "${lightCyan.wrap('shorebird publish')}". + +For more information about Shorebird, visit ${link(uri: Uri.parse('https://shorebird.dev'))}''', + ); + return ExitCode.success.code; + } + + ShorebirdYaml _addShorebirdYamlToProject() { + final productId = buildUuid(); + File( + p.join(Directory.current.path, 'shorebird.yaml'), + ).writeAsStringSync(''' +# This file is used to configure the Shorebird CLI. +# Learn more at https://shorebird.dev + +# This is the unique identifier for your app. +product_id: $productId +'''); + + return ShorebirdYaml(productId: productId); + } + + void _addShorebirdYamlToPubspecAssets() { + final pubspecFile = File(p.join(Directory.current.path, 'pubspec.yaml')); + final pubspecContents = pubspecFile.readAsStringSync(); + final yaml = loadYaml(pubspecContents, sourceUrl: pubspecFile.uri) as Map; + final editor = YamlEditor(pubspecContents); + + if (!yaml.containsKey('flutter')) { + editor.update( + ['flutter'], + { + 'assets': ['shorebird.yaml'] + }, + ); + } else { + if (!(yaml['flutter'] as Map).containsKey('assets')) { + editor.update(['flutter', 'assets'], ['shorebird.yaml']); + } else { + final assets = (yaml['flutter'] as Map)['assets'] as List; + if (!assets.contains('shorebird.yaml')) { + editor.update(['flutter', 'assets'], [...assets, 'shorebird.yaml']); + } + } + } + + if (editor.edits.isEmpty) return; + + pubspecFile.writeAsStringSync(editor.toString()); + } +} diff --git a/packages/shorebird_cli/lib/src/commands/publish_command.dart b/packages/shorebird_cli/lib/src/commands/publish_command.dart index 77cf592f..379e3d68 100644 --- a/packages/shorebird_cli/lib/src/commands/publish_command.dart +++ b/packages/shorebird_cli/lib/src/commands/publish_command.dart @@ -1,20 +1,16 @@ import 'dart:io'; -import 'package:checked_yaml/checked_yaml.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; -import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:shorebird_cli/src/command.dart'; -import 'package:shorebird_cli/src/config/shorebird_yaml.dart'; -import 'package:yaml/yaml.dart'; -import 'package:yaml_edit/yaml_edit.dart'; +import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; /// {@template publish_command} /// /// `shorebird publish ` /// Publish new releases to the Shorebird CodePush server. /// {@endtemplate} -class PublishCommand extends ShorebirdCommand { +class PublishCommand extends ShorebirdCommand with ShorebirdConfigMixin { /// {@macro publish_command} PublishCommand({ required super.logger, @@ -31,6 +27,13 @@ class PublishCommand extends ShorebirdCommand { @override Future run() async { + if (!isShorebirdInitialized) { + logger.err( + 'Shorebird is not initialized. Did you run "shorebird init"?', + ); + return ExitCode.config.code; + } + final session = auth.currentSession; if (session == null) { logger.err('You must be logged in to publish.'); @@ -42,45 +45,6 @@ class PublishCommand extends ShorebirdCommand { usageException('A single file path must be specified.'); } - late final Pubspec? pubspecYaml; - try { - pubspecYaml = _readPubspecYaml(); - if (pubspecYaml == null) { - logger.err('Could not find a "pubspec.yaml".'); - return ExitCode.noInput.code; - } - } catch (error) { - logger.err('Error parsing "pubspec.yaml": $error'); - return ExitCode.software.code; - } - - late final String productId; - late final ShorebirdYaml? shorebirdYaml; - try { - shorebirdYaml = _readShorebirdYaml(); - } catch (error) { - logger.err('Error parsing "shorebird.yaml": $error'); - return ExitCode.software.code; - } - - if (shorebirdYaml == null) { - productId = buildUuid(); - File( - p.join(Directory.current.path, 'shorebird.yaml'), - ).writeAsStringSync(''' -# This file is used to configure the Shorebird CLI. -# Learn more at https://shorebird.dev - -# This is the unique identifier for your app. -product_id: $productId -'''); - logger.info('Generated a "shorebird.yaml".'); - } else { - productId = shorebirdYaml.productId; - } - - _addShorebirdYamlToAssets(); - final artifactPath = args.isEmpty ? p.join( Directory.current.path, @@ -103,15 +67,17 @@ product_id: $productId } try { + final pubspecYaml = getPubspecYaml()!; + final shorebirdYaml = getShorebirdYaml()!; final codePushClient = buildCodePushClient(apiKey: session.apiKey); logger.detail( - 'Deploying ${artifact.path} to $productId (${pubspecYaml.version})', + '''Deploying ${artifact.path} to ${shorebirdYaml.productId} (${pubspecYaml.version})''', ); final version = pubspecYaml.version!; await codePushClient.createPatch( artifactPath: artifact.path, baseVersion: '${version.major}.${version.minor}.${version.patch}', - productId: productId, + productId: shorebirdYaml.productId, channel: 'stable', ); } catch (error) { @@ -122,48 +88,4 @@ product_id: $productId logger.success('Successfully deployed.'); return ExitCode.success.code; } - - ShorebirdYaml? _readShorebirdYaml() { - final file = File(p.join(Directory.current.path, 'shorebird.yaml')); - if (!file.existsSync()) return null; - final yaml = file.readAsStringSync(); - return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!)); - } - - Pubspec? _readPubspecYaml() { - final file = File(p.join(Directory.current.path, 'pubspec.yaml')); - if (!file.existsSync()) return null; - final yaml = file.readAsStringSync(); - return Pubspec.parse(yaml); - } - - void _addShorebirdYamlToAssets() { - final pubspecFile = File(p.join(Directory.current.path, 'pubspec.yaml')); - final pubspecContents = pubspecFile.readAsStringSync(); - final yaml = loadYaml(pubspecContents, sourceUrl: pubspecFile.uri) as Map; - final editor = YamlEditor(pubspecContents); - - if (!yaml.containsKey('flutter')) { - editor.update( - ['flutter'], - { - 'assets': ['shorebird.yaml'] - }, - ); - } else { - if (!(yaml['flutter'] as Map).containsKey('assets')) { - editor.update(['flutter', 'assets'], ['shorebird.yaml']); - } else { - final assets = (yaml['flutter'] as Map)['assets'] as List; - if (!assets.contains('shorebird.yaml')) { - editor.update(['flutter', 'assets'], [...assets, 'shorebird.yaml']); - } - } - } - - if (editor.edits.isNotEmpty) { - pubspecFile.writeAsStringSync(editor.toString()); - logger.info('Added "shorebird.yaml" to "pubspec.yaml".'); - } - } } diff --git a/packages/shorebird_cli/lib/src/config/config.dart b/packages/shorebird_cli/lib/src/config/config.dart new file mode 100644 index 00000000..fb8391af --- /dev/null +++ b/packages/shorebird_cli/lib/src/config/config.dart @@ -0,0 +1 @@ +export 'shorebird_yaml.dart'; diff --git a/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart new file mode 100644 index 00000000..18278c89 --- /dev/null +++ b/packages/shorebird_cli/lib/src/shorebird_config_mixin.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import 'package:checked_yaml/checked_yaml.dart'; +import 'package:path/path.dart' as p; +import 'package:pubspec_parse/pubspec_parse.dart'; +import 'package:shorebird_cli/src/command.dart'; +import 'package:shorebird_cli/src/config/config.dart'; +import 'package:yaml/yaml.dart'; + +mixin ShorebirdConfigMixin on ShorebirdCommand { + bool get hasShorebirdYaml => getShorebirdYaml() != null; + + bool get hasPubspecYaml => getPubspecYaml() != null; + + bool get isShorebirdInitialized { + return hasShorebirdYaml && pubspecContainsShorebirdYaml; + } + + bool get pubspecContainsShorebirdYaml { + final file = File(p.join(Directory.current.path, 'pubspec.yaml')); + final pubspecContents = file.readAsStringSync(); + final yaml = loadYaml(pubspecContents, sourceUrl: file.uri) as Map; + if (!yaml.containsKey('flutter')) return false; + if (!(yaml['flutter'] as Map).containsKey('assets')) return false; + final assets = (yaml['flutter'] as Map)['assets'] as List; + return assets.contains('shorebird.yaml'); + } + + ShorebirdYaml? getShorebirdYaml() { + final file = File(p.join(Directory.current.path, 'shorebird.yaml')); + if (!file.existsSync()) return null; + final yaml = file.readAsStringSync(); + return checkedYamlDecode(yaml, (m) => ShorebirdYaml.fromJson(m!)); + } + + Pubspec? getPubspecYaml() { + final file = File(p.join(Directory.current.path, 'pubspec.yaml')); + if (!file.existsSync()) return null; + final yaml = file.readAsStringSync(); + return Pubspec.parse(yaml); + } +} diff --git a/packages/shorebird_cli/test/ensure_build_test.dart b/packages/shorebird_cli/test/ensure_build_test.dart index 71a69814..bd5e2bb1 100644 --- a/packages/shorebird_cli/test/ensure_build_test.dart +++ b/packages/shorebird_cli/test/ensure_build_test.dart @@ -8,5 +8,6 @@ void main() { test( 'ensure_build', () => expectBuildClean(packageRelativeDirectory: 'packages/shorebird_cli'), + timeout: const Timeout.factor(2), ); } diff --git a/packages/shorebird_cli/test/src/commands/init_command_test.dart b/packages/shorebird_cli/test/src/commands/init_command_test.dart new file mode 100644 index 00000000..828b27ed --- /dev/null +++ b/packages/shorebird_cli/test/src/commands/init_command_test.dart @@ -0,0 +1,206 @@ +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/commands/init_command.dart'; +import 'package:test/test.dart'; + +class _MockLogger extends Mock implements Logger {} + +class _MockProgress extends Mock implements Progress {} + +void main() { + group('init', () { + const version = '1.2.3'; + const pubspecYamlContent = ''' +name: example +version: $version +environment: + sdk: ">=2.19.0 <3.0.0"'''; + const productId = 'test-product-id'; + + late Logger logger; + late Progress progress; + late InitCommand command; + + setUp(() { + logger = _MockLogger(); + progress = _MockProgress(); + command = InitCommand( + logger: logger, + buildUuid: () => productId, + ); + + when(() => logger.progress(any())).thenReturn(progress); + }); + + test('throws no input error when pubspec.yaml is not found.', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => logger.err('Could not find a "pubspec.yaml".')).called(1); + expect(exitCode, ExitCode.noInput.code); + }); + + test('throws software error when pubspec.yaml is malformed.', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File(p.join(tempDir.path, 'pubspec.yaml')).createSync(); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify( + () => logger.err(any(that: contains('Error parsing "pubspec.yaml":'))), + ).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('throws software error when shorebird.yaml is malformed.', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(pubspecYamlContent); + File(p.join(tempDir.path, 'shorebird.yaml')).createSync(); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify( + () => logger.err( + any(that: contains('Error creating "shorebird.yaml".')), + ), + ).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('detects existing shorebird.yaml', () async { + const existingProductId = 'existing-product-id'; + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(pubspecYamlContent); + File( + p.join(tempDir.path, 'shorebird.yaml'), + ).writeAsStringSync('product_id: $existingProductId'); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(), + contains('product_id: $existingProductId'), + ); + verify(() => progress.update('"shorebird.yaml" already exists.')); + }); + + test('creates shorebird.yaml', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(pubspecYamlContent); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(), + contains('product_id: $productId'), + ); + }); + + test('detects existing shorebird.yaml in pubspec.yaml assets', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(''' +$pubspecYamlContent +flutter: + assets: + - shorebird.yaml +'''); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'shorebird.yaml')).readAsStringSync(), + contains('product_id: $productId'), + ); + verify( + () => progress.update( + '"shorebird.yaml" already in "pubspec.yaml" assets.', + ), + ); + }); + + test('creates flutter.assets and adds shorebird.yaml', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(pubspecYamlContent); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), + equals(''' +$pubspecYamlContent +flutter: + assets: + - shorebird.yaml +'''), + ); + }); + + test('creates assets and adds shorebird.yaml', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync(''' +$pubspecYamlContent +flutter: + uses-material-design: true +'''); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), + equals(''' +$pubspecYamlContent +flutter: + assets: + - shorebird.yaml + uses-material-design: true +'''), + ); + }); + + test('adds shorebird.yaml to assets', () async { + final tempDir = Directory.systemTemp.createTempSync(); + File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync(''' +$pubspecYamlContent +flutter: + assets: + - some/asset.txt +'''); + await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + expect( + File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), + equals(''' +$pubspecYamlContent +flutter: + assets: + - some/asset.txt + - shorebird.yaml +'''), + ); + }); + }); +} diff --git a/packages/shorebird_cli/test/src/commands/publish_command_test.dart b/packages/shorebird_cli/test/src/commands/publish_command_test.dart index 8d868fe2..785d14a5 100644 --- a/packages/shorebird_cli/test/src/commands/publish_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/publish_command_test.dart @@ -38,7 +38,11 @@ void main() { name: example version: $version environment: - sdk: ">=2.19.0 <3.0.0"'''; + sdk: ">=2.19.0 <3.0.0" + +flutter: + assets: + - shorebird.yaml'''; late ArgResults argResults; late Auth auth; @@ -46,6 +50,17 @@ environment: late CodePushClient codePushClient; late PublishCommand command; + Directory setUpTempDir() { + final tempDir = Directory.systemTemp.createTempSync(); + File( + p.join(tempDir.path, 'pubspec.yaml'), + ).writeAsStringSync(pubspecYamlContent); + File( + p.join(tempDir.path, 'shorebird.yaml'), + ).writeAsStringSync('product_id: $productId'); + return tempDir; + } + setUp(() { argResults = _MockArgResults(); auth = _MockAuth(); @@ -72,67 +87,45 @@ environment: ).thenAnswer((_) async {}); }); - test('throws no user error when session does not exist', () async { - when(() => auth.currentSession).thenReturn(null); - final exitCode = await command.run(); - expect(exitCode, equals(ExitCode.noUser.code)); - }); - - test('throws usage error when multiple args are passed.', () async { - when(() => argResults.rest).thenReturn(['arg1', 'arg2']); - await expectLater(command.run, throwsA(isA())); - }); - - test('throws no input error when pubspec.yaml is not found.', () async { + test('throws config error when shorebird is not initialized', () async { final tempDir = Directory.systemTemp.createTempSync(); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, ); - verify(() => logger.err('Could not find a "pubspec.yaml".')).called(1); - expect(exitCode, ExitCode.noInput.code); - }); - - test('throws software error when pubspec.yaml is malformed.', () async { - final tempDir = Directory.systemTemp.createTempSync(); - File(p.join(tempDir.path, 'pubspec.yaml')).createSync(); - final exitCode = await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err(any(that: contains('Error parsing "pubspec.yaml":'))), - ).called(1); - expect(exitCode, ExitCode.software.code); - }); - - test('throws software error when shorebird.yaml is malformed.', () async { - final tempDir = Directory.systemTemp.createTempSync(); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - File(p.join(tempDir.path, 'shorebird.yaml')).createSync(); - final exitCode = await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); verify( () => logger.err( - any(that: contains('Error parsing "shorebird.yaml":')), + 'Shorebird is not initialized. Did you run "shorebird init"?', ), ).called(1); - expect(exitCode, ExitCode.software.code); + expect(exitCode, ExitCode.config.code); + }); + + test('throws no user error when session does not exist', () async { + when(() => auth.currentSession).thenReturn(null); + final tempDir = setUpTempDir(); + final exitCode = await IOOverrides.runZoned( + () => command.run(), + getCurrentDirectory: () => tempDir, + ); + expect(exitCode, equals(ExitCode.noUser.code)); + }); + + test('throws usage error when multiple args are passed.', () async { + when(() => argResults.rest).thenReturn(['arg1', 'arg2']); + final tempDir = setUpTempDir(); + await expectLater( + IOOverrides.runZoned( + () => command.run(), + getCurrentDirectory: () => tempDir, + ), + throwsA(isA()), + ); }); test('throws no input error when artifact is not found (default).', () async { - final tempDir = Directory.systemTemp.createTempSync(); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - File( - p.join(tempDir.path, 'shorebird.yaml'), - ).writeAsStringSync('product_id: $productId'); + final tempDir = setUpTempDir(); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, @@ -145,15 +138,9 @@ environment: test('throws no input error when artifact is not found (custom).', () async { - final tempDir = Directory.systemTemp.createTempSync(); + final tempDir = setUpTempDir(); final artifact = File(p.join(tempDir.path, 'patch.txt')); when(() => argResults.rest).thenReturn([artifact.path]); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - File( - p.join(tempDir.path, 'shorebird.yaml'), - ).writeAsStringSync('product_id: $productId'); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, @@ -178,15 +165,9 @@ environment: productId: any(named: 'productId'), ), ).thenThrow(error); - final tempDir = Directory.systemTemp.createTempSync(); + final tempDir = setUpTempDir(); final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); when(() => argResults.rest).thenReturn([artifact.path]); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - File( - p.join(tempDir.path, 'shorebird.yaml'), - ).writeAsStringSync('product_id: $productId'); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, @@ -197,15 +178,9 @@ environment: test('succeeds when publish is successful using existing product id', () async { - final tempDir = Directory.systemTemp.createTempSync(); + final tempDir = setUpTempDir(); final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); when(() => argResults.rest).thenReturn([artifact.path]); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - File( - p.join(tempDir.path, 'shorebird.yaml'), - ).writeAsStringSync('product_id: $productId'); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, @@ -221,106 +196,5 @@ environment: ).called(1); expect(exitCode, ExitCode.success.code); }); - - test('succeeds when publish is successful using newly generated product id', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - final exitCode = await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - verify(() => logger.success('Successfully deployed.')).called(1); - verify( - () => codePushClient.createPatch( - baseVersion: version, - productId: any(named: 'productId', that: isNotEmpty), - artifactPath: artifact.path, - channel: 'stable', - ), - ).called(1); - expect( - File(p.join(tempDir.path, 'shorebird.yaml')).existsSync(), - isTrue, - ); - expect(exitCode, ExitCode.success.code); - }); - - test('creates flutter.assets and adds shorebird.yaml', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); - File( - p.join(tempDir.path, 'pubspec.yaml'), - ).writeAsStringSync(pubspecYamlContent); - await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - expect( - File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), - equals(''' -$pubspecYamlContent -flutter: - assets: - - shorebird.yaml -'''), - ); - }); - - test('creates assets and adds shorebird.yaml', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); - File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync(''' -$pubspecYamlContent -flutter: - uses-material-design: true -'''); - await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - expect( - File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), - equals(''' -$pubspecYamlContent -flutter: - assets: - - shorebird.yaml - uses-material-design: true -'''), - ); - }); - - test('adds shorebird.yaml to assets', () async { - final tempDir = Directory.systemTemp.createTempSync(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); - File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync(''' -$pubspecYamlContent -flutter: - assets: - - some/asset.txt -'''); - await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - expect( - File(p.join(tempDir.path, 'pubspec.yaml')).readAsStringSync(), - equals(''' -$pubspecYamlContent -flutter: - assets: - - some/asset.txt - - shorebird.yaml -'''), - ); - }); }); }