From fc774d855f21f74e8d8aae884f5a5bda3afad7c6 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Sat, 7 Jan 2023 02:22:02 +0100 Subject: [PATCH 01/10] Export version checker --- sidekick_core/lib/sidekick_core.dart | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sidekick_core/lib/sidekick_core.dart b/sidekick_core/lib/sidekick_core.dart index 135f311f..25954a8b 100644 --- a/sidekick_core/lib/sidekick_core.dart +++ b/sidekick_core/lib/sidekick_core.dart @@ -38,6 +38,7 @@ export 'package:sidekick_core/src/git.dart'; export 'package:sidekick_core/src/repository.dart'; export 'package:sidekick_core/src/sidekick_package.dart'; export 'package:sidekick_core/src/template/sidekick_package.template.dart'; +export 'package:sidekick_core/src/version_checker.dart'; /// The version of package:sidekick_core /// @@ -75,8 +76,7 @@ SidekickCommandRunner initializeSidekick({ final repo = findRepository(); if (mainProjectPath != null) { - mainProject = - DartPackage.fromDirectory(repo.root.directory(mainProjectPath)); + mainProject = DartPackage.fromDirectory(repo.root.directory(mainProjectPath)); } if (flutterSdkPath != null && dartSdkPath != null) { @@ -87,8 +87,7 @@ SidekickCommandRunner initializeSidekick({ final runner = SidekickCommandRunner._( cliName: name, - description: description ?? - 'A sidekick CLI to equip Dart/Flutter projects with custom tasks', + description: description ?? 'A sidekick CLI to equip Dart/Flutter projects with custom tasks', repository: repo, mainProject: mainProject, workingDirectory: Directory.current, @@ -259,8 +258,7 @@ void deinitializeSidekick() {} SidekickCommandRunner? _activeRunner; /// The working directory (cwd) from which the cli was started -Directory get entryWorkingDirectory => - _entryWorkingDirectory ?? Directory.current; +Directory get entryWorkingDirectory => _entryWorkingDirectory ?? Directory.current; Directory? _entryWorkingDirectory; /// Name of the cli program @@ -327,8 +325,7 @@ class SdkNotFoundException implements Exception { final String sdkPath; final Directory repoRoot; - late final String message = - "Dart or Flutter SDK set to '$sdkPath', but that directory doesn't exist. " + late final String message = "Dart or Flutter SDK set to '$sdkPath', but that directory doesn't exist. " "Please fix the path in `initializeSidekick` (dartSdkPath/flutterSdkPath). " "Note that relative sdk paths are resolved relative to the project root, " "which in this case is '${repoRoot.path}'."; From 8e1f9906a9649d01bb0f9fb89e219420879a00b1 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Sat, 7 Jan 2023 02:22:37 +0100 Subject: [PATCH 02/10] Handle potential downgrade --- sidekick/lib/src/commands/init_command.dart | 78 +++++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index 525f7626..a6968acd 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -23,8 +23,7 @@ class InitCommand extends Command { argParser.addOption( 'entrypointDirectory', abbr: 'e', - help: - 'The directory in which the CLI entrypoint script should be created.', + help: 'The directory in which the CLI entrypoint script should be created.', ); argParser.addOption( 'cliPackageDirectory', @@ -37,8 +36,7 @@ class InitCommand extends Command { argParser.addOption( 'mainProjectPath', abbr: 'm', - help: - 'Optionally sets the mainProject, the package that ultimately builds your app. \n' + help: 'Optionally sets the mainProject, the package that ultimately builds your app. \n' 'This directory must be within the entrypointDirectory, ' 'or if the entrypointDirectory is inside a git repository, ' 'the mainProjectPath must be within the same git repository.', @@ -51,6 +49,48 @@ class InitCommand extends Command { "Welcome to sidekick. You're about to initialize a sidekick project\n", ); + // Initial versions + Version versionToInstall = core.version; + Version newestVersion = Version.none; + + final currentSidekickCliVersion = VersionChecker.getMinimumVersionConstraint( + Repository.requiredSidekickPackage, + ['sidekick', 'cli_version'], + ) ?? + Version.none; + + // Check for existing project + if (currentSidekickCliVersion != Version.none) { + final confirmNewProject = + dcli.confirm('You already have a sidekick project initialized. Do you wish to proceed?'); + if (!confirmNewProject) { + throw 'Initialization aborted by user.'; + } + } + + final lookForUpdate = dcli.confirm('Do you want to check for the latest version of sidekick before proceeding?'); + + if (lookForUpdate) { + newestVersion = await VersionChecker.getLatestDependencyVersion('sidekick'); + if (currentSidekickCliVersion < newestVersion) { + final confirmUpdate = + dcli.confirm('A newer version ($newestVersion) of sidekick is available. Use latest version for project?'); + if (confirmUpdate) { + versionToInstall = newestVersion; + } + } + } + + if (versionToInstall == core.version && currentSidekickCliVersion > core.version) { + final keepCurrentVersion = dcli.confirm( + 'Your current sidekick version would be downgraded from ${currentSidekickCliVersion.toString()} \n' + 'to ${core.version.toString()}. Keep current version?', + ); + if (keepCurrentVersion) { + versionToInstall = currentSidekickCliVersion; + } + } + final entrypointDir = Directory( argResults!['entrypointDirectory'] as String? ?? argResults!.rest.firstOrNull ?? @@ -88,8 +128,7 @@ class InitCommand extends Command { ? DartPackage.fromDirectory( Directory(mainProjectPath), ) - : (DartPackage.fromDirectory(entrypointDir) ?? - DartPackage.fromDirectory(repoRoot)); + : (DartPackage.fromDirectory(entrypointDir) ?? DartPackage.fromDirectory(repoRoot)); if (mainProjectPath != null && mainProject == null) { throw 'mainProjectPath was given, but no DartPackage could be found at the given path $mainProjectPath'; } @@ -145,6 +184,7 @@ class InitCommand extends Command { entrypointDir: entrypointDir, mainProject: mainProject, packages: packages, + version: versionToInstall, ); } @@ -164,6 +204,7 @@ class InitCommand extends Command { required Directory packageDir, required Directory entrypointDir, DartPackage? mainProject, + Version? version, List packages = const [], }) async { // init git, required for flutterw @@ -174,26 +215,20 @@ class InitCommand extends Command { final entrypoint = entrypointDir.file(cliName.snakeCase); final props = SidekickTemplateProperties( name: cliName, - mainProjectPath: mainProject != null - ? relative(mainProject.root.path, from: repoRoot.absolute.path) - : null, - isMainProjectRoot: - mainProject?.root.absolute.path == repoRoot.absolute.path, - hasNestedPackagesPath: mainProject != null && - !relative(mainProject.root.path, from: repoRoot.absolute.path) - .startsWith('packages'), - shouldSetFlutterSdkPath: Repository(root: repoRoot) - .findAllPackages() - .any((package) => package.isFlutterPackage), + mainProjectPath: mainProject != null ? relative(mainProject.root.path, from: repoRoot.absolute.path) : null, + isMainProjectRoot: mainProject?.root.absolute.path == repoRoot.absolute.path, + hasNestedPackagesPath: + mainProject != null && !relative(mainProject.root.path, from: repoRoot.absolute.path).startsWith('packages'), + shouldSetFlutterSdkPath: Repository(root: repoRoot).findAllPackages().any((package) => package.isFlutterPackage), entrypointLocation: entrypoint, packageLocation: cliPackage, - sidekickCliVersion: core.version, + sidekickCliVersion: version ?? core.version, ); SidekickTemplate().generate(props); // Install flutterw when a Flutter project is detected - final flutterPackages = [if (mainProject != null) mainProject, ...packages] - .filter((package) => package.isFlutterPackage); + final flutterPackages = + [if (mainProject != null) mainProject, ...packages].filter((package) => package.isFlutterPackage); if (flutterPackages.isNotEmpty) { print('We detected Flutter packages in your project:'); @@ -244,8 +279,7 @@ Future gitInit(Directory directory) async { // no need to initialize return; } - final Process process = - await Process.start('git', ['init'], workingDirectory: directory.path); + final Process process = await Process.start('git', ['init'], workingDirectory: directory.path); stdout.addStream(process.stdout); stderr.addStream(process.stderr); await process.exitCode; From b29cc2b1499aade6b41ec9d0074d4b6617109f10 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 01:10:03 +0100 Subject: [PATCH 03/10] Handle potential downgrade with existsSync --- sidekick/lib/sidekick.dart | 3 +- sidekick/lib/src/commands/init_command.dart | 71 ++++++++------------- sidekick_core/lib/sidekick_core.dart | 2 +- sidekick_core/lib/src/version_checker.dart | 32 +++++----- 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/sidekick/lib/sidekick.dart b/sidekick/lib/sidekick.dart index 6a03c190..133442be 100644 --- a/sidekick/lib/sidekick.dart +++ b/sidekick/lib/sidekick.dart @@ -56,6 +56,5 @@ class GlobalSidekickCommandRunner extends CommandRunner { return super.run(args); } - static const _desc = - 'Generator for a sidekick command line application (cli)'; + static const _desc = 'Generator for a sidekick command line application (cli)'; } diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index a6968acd..3c5674fb 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -49,48 +49,6 @@ class InitCommand extends Command { "Welcome to sidekick. You're about to initialize a sidekick project\n", ); - // Initial versions - Version versionToInstall = core.version; - Version newestVersion = Version.none; - - final currentSidekickCliVersion = VersionChecker.getMinimumVersionConstraint( - Repository.requiredSidekickPackage, - ['sidekick', 'cli_version'], - ) ?? - Version.none; - - // Check for existing project - if (currentSidekickCliVersion != Version.none) { - final confirmNewProject = - dcli.confirm('You already have a sidekick project initialized. Do you wish to proceed?'); - if (!confirmNewProject) { - throw 'Initialization aborted by user.'; - } - } - - final lookForUpdate = dcli.confirm('Do you want to check for the latest version of sidekick before proceeding?'); - - if (lookForUpdate) { - newestVersion = await VersionChecker.getLatestDependencyVersion('sidekick'); - if (currentSidekickCliVersion < newestVersion) { - final confirmUpdate = - dcli.confirm('A newer version ($newestVersion) of sidekick is available. Use latest version for project?'); - if (confirmUpdate) { - versionToInstall = newestVersion; - } - } - } - - if (versionToInstall == core.version && currentSidekickCliVersion > core.version) { - final keepCurrentVersion = dcli.confirm( - 'Your current sidekick version would be downgraded from ${currentSidekickCliVersion.toString()} \n' - 'to ${core.version.toString()}. Keep current version?', - ); - if (keepCurrentVersion) { - versionToInstall = currentSidekickCliVersion; - } - } - final entrypointDir = Directory( argResults!['entrypointDirectory'] as String? ?? argResults!.rest.firstOrNull ?? @@ -123,6 +81,31 @@ class InitCommand extends Command { throw 'CLI package directory ${cliPackageDir.path} is not within or equal to ${repoRoot.path}'; } + if (cliPackageDir.existsSync()) { + print('\nYou already have an existing sidekick project initialized.\n' + 'In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.'); + final override = dcli.confirm( + 'Do you want to override your existing CLI?', + defaultValue: false, + ); + if (!override) { + return; + } + final packageVersion = sidekickPackageCliVersion(); + if (packageVersion != null && packageVersion > core.version) { + print( + '\n' + 'Your current sidekick version would be downgraded ' + 'from ${sidekickPackageCliVersion.toString()} to ${core.version.toString()}', + ); + final downgrade = dcli.confirm('Do you want to downgrade?', defaultValue: false); + if (!downgrade) { + print('In order to update sidekick run ${dcli.cyan('sidekick update')}.'); + return; + } + } + } + final mainProjectPath = argResults!['mainProjectPath'] as String?; DartPackage? mainProject = mainProjectPath != null ? DartPackage.fromDirectory( @@ -184,7 +167,6 @@ class InitCommand extends Command { entrypointDir: entrypointDir, mainProject: mainProject, packages: packages, - version: versionToInstall, ); } @@ -204,7 +186,6 @@ class InitCommand extends Command { required Directory packageDir, required Directory entrypointDir, DartPackage? mainProject, - Version? version, List packages = const [], }) async { // init git, required for flutterw @@ -222,7 +203,7 @@ class InitCommand extends Command { shouldSetFlutterSdkPath: Repository(root: repoRoot).findAllPackages().any((package) => package.isFlutterPackage), entrypointLocation: entrypoint, packageLocation: cliPackage, - sidekickCliVersion: version ?? core.version, + sidekickCliVersion: core.version, ); SidekickTemplate().generate(props); diff --git a/sidekick_core/lib/sidekick_core.dart b/sidekick_core/lib/sidekick_core.dart index 25954a8b..fc2602e4 100644 --- a/sidekick_core/lib/sidekick_core.dart +++ b/sidekick_core/lib/sidekick_core.dart @@ -38,7 +38,7 @@ export 'package:sidekick_core/src/git.dart'; export 'package:sidekick_core/src/repository.dart'; export 'package:sidekick_core/src/sidekick_package.dart'; export 'package:sidekick_core/src/template/sidekick_package.template.dart'; -export 'package:sidekick_core/src/version_checker.dart'; +export 'package:sidekick_core/src/version_checker.dart' show sidekickPackageCliVersion; /// The version of package:sidekick_core /// diff --git a/sidekick_core/lib/src/version_checker.dart b/sidekick_core/lib/src/version_checker.dart index bf2ad708..0bc2db40 100644 --- a/sidekick_core/lib/src/version_checker.dart +++ b/sidekick_core/lib/src/version_checker.dart @@ -97,8 +97,7 @@ abstract class VersionChecker { startTag: largestMatch!, endTag: '\n', content: '${missingKeys.isNotEmpty ? '\n' : ''}${missingKeys.mapIndexed( - (index, key) => - '${' ' * (index + pubspecKeys.length - missingKeys.length)}$key:', + (index, key) => '${' ' * (index + pubspecKeys.length - missingKeys.length)}$key:', ).join('\n')} $newVersionConstraint', ); } @@ -131,8 +130,7 @@ abstract class VersionChecker { /// even if a local dependency doesn't explicitly specify a version in their /// pubspec.yaml, there always is an implicit version of 0.0.0 static Version? getResolvedVersion(DartPackage package, String dependency) { - final resolvedVersion = - _readFromYaml(package.lockfile, ['packages', dependency, 'version']); + final resolvedVersion = _readFromYaml(package.lockfile, ['packages', dependency, 'version']); return resolvedVersion.match( () => null, (t) => t != null ? Version.parse(t) : null, @@ -145,16 +143,14 @@ abstract class VersionChecker { return testFakeGetLatestDependencyVersion!(dependency); } - final response = - await get(Uri.parse('https://pub.dev/api/packages/$dependency')); + final response = await get(Uri.parse('https://pub.dev/api/packages/$dependency')); if (response.statusCode != HttpStatus.ok) { throw "Package '$dependency' not found on pub.dev"; } final latestVersion = - ((jsonDecode(response.body) as Map)['latest'] - as Map)['version'] as String; + ((jsonDecode(response.body) as Map)['latest'] as Map)['version'] as String; return Version.parse(latestVersion); } @@ -166,13 +162,10 @@ abstract class VersionChecker { ); // e.g. {"date": "2022-12-13", "version": "2.18.6", "revision": "f16b62ea92cc0f04cfd9166992f93419e425c809"} final response = await get(endpoint); - final latestVersion = (jsonDecode(response.body) - as Map)['version'] as String; + final latestVersion = (jsonDecode(response.body) as Map)['version'] as String; // e.g. Dart SDK version: 2.18.4 (stable) (Tue Nov 1 15:15:07 2022 +0000) on "macos_arm64" - final dartVersionResult = '$dartExecutablePath --version' - .start(progress: Progress.capture()) - .firstLine; + final dartVersionResult = '$dartExecutablePath --version'.start(progress: Progress.capture()).firstLine; if (dartVersionResult == null) { throw "Couldn't determine version of Dart executable $dartExecutablePath"; } @@ -187,8 +180,17 @@ abstract class VersionChecker { /// Set to override behavior of [getLatestDependencyVersion] in tests @visibleForTesting - static Future Function(String dependency)? - testFakeGetLatestDependencyVersion; + static Future Function(String dependency)? testFakeGetLatestDependencyVersion; +} + +/// To remember which sidekick_core version the sidekick CLI was generated +/// with, that sidekick_core version is written into the CLI's pubspec.yaml +/// at the path ['sidekick', 'cli_version'] +Version? sidekickPackageCliVersion() { + return VersionChecker.getMinimumVersionConstraint( + Repository.requiredSidekickPackage, + ['sidekick', 'cli_version'], + ); } /// Returns the string specified by [path] in [yamlFile] From 5bf91ce67a592f0144be91e7adbdda6f89e662b2 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 01:12:33 +0100 Subject: [PATCH 04/10] Format code --- sidekick/lib/sidekick.dart | 3 +- sidekick/lib/src/commands/init_command.dart | 38 ++++++++++++++------- sidekick_core/lib/sidekick_core.dart | 15 +++++--- sidekick_core/lib/src/version_checker.dart | 22 ++++++++---- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/sidekick/lib/sidekick.dart b/sidekick/lib/sidekick.dart index 133442be..6a03c190 100644 --- a/sidekick/lib/sidekick.dart +++ b/sidekick/lib/sidekick.dart @@ -56,5 +56,6 @@ class GlobalSidekickCommandRunner extends CommandRunner { return super.run(args); } - static const _desc = 'Generator for a sidekick command line application (cli)'; + static const _desc = + 'Generator for a sidekick command line application (cli)'; } diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index 3c5674fb..903f4645 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -23,7 +23,8 @@ class InitCommand extends Command { argParser.addOption( 'entrypointDirectory', abbr: 'e', - help: 'The directory in which the CLI entrypoint script should be created.', + help: + 'The directory in which the CLI entrypoint script should be created.', ); argParser.addOption( 'cliPackageDirectory', @@ -36,7 +37,8 @@ class InitCommand extends Command { argParser.addOption( 'mainProjectPath', abbr: 'm', - help: 'Optionally sets the mainProject, the package that ultimately builds your app. \n' + help: + 'Optionally sets the mainProject, the package that ultimately builds your app. \n' 'This directory must be within the entrypointDirectory, ' 'or if the entrypointDirectory is inside a git repository, ' 'the mainProjectPath must be within the same git repository.', @@ -98,9 +100,11 @@ class InitCommand extends Command { 'Your current sidekick version would be downgraded ' 'from ${sidekickPackageCliVersion.toString()} to ${core.version.toString()}', ); - final downgrade = dcli.confirm('Do you want to downgrade?', defaultValue: false); + final downgrade = + dcli.confirm('Do you want to downgrade?', defaultValue: false); if (!downgrade) { - print('In order to update sidekick run ${dcli.cyan('sidekick update')}.'); + print( + 'In order to update sidekick run ${dcli.cyan('sidekick update')}.'); return; } } @@ -111,7 +115,8 @@ class InitCommand extends Command { ? DartPackage.fromDirectory( Directory(mainProjectPath), ) - : (DartPackage.fromDirectory(entrypointDir) ?? DartPackage.fromDirectory(repoRoot)); + : (DartPackage.fromDirectory(entrypointDir) ?? + DartPackage.fromDirectory(repoRoot)); if (mainProjectPath != null && mainProject == null) { throw 'mainProjectPath was given, but no DartPackage could be found at the given path $mainProjectPath'; } @@ -196,11 +201,17 @@ class InitCommand extends Command { final entrypoint = entrypointDir.file(cliName.snakeCase); final props = SidekickTemplateProperties( name: cliName, - mainProjectPath: mainProject != null ? relative(mainProject.root.path, from: repoRoot.absolute.path) : null, - isMainProjectRoot: mainProject?.root.absolute.path == repoRoot.absolute.path, - hasNestedPackagesPath: - mainProject != null && !relative(mainProject.root.path, from: repoRoot.absolute.path).startsWith('packages'), - shouldSetFlutterSdkPath: Repository(root: repoRoot).findAllPackages().any((package) => package.isFlutterPackage), + mainProjectPath: mainProject != null + ? relative(mainProject.root.path, from: repoRoot.absolute.path) + : null, + isMainProjectRoot: + mainProject?.root.absolute.path == repoRoot.absolute.path, + hasNestedPackagesPath: mainProject != null && + !relative(mainProject.root.path, from: repoRoot.absolute.path) + .startsWith('packages'), + shouldSetFlutterSdkPath: Repository(root: repoRoot) + .findAllPackages() + .any((package) => package.isFlutterPackage), entrypointLocation: entrypoint, packageLocation: cliPackage, sidekickCliVersion: core.version, @@ -208,8 +219,8 @@ class InitCommand extends Command { SidekickTemplate().generate(props); // Install flutterw when a Flutter project is detected - final flutterPackages = - [if (mainProject != null) mainProject, ...packages].filter((package) => package.isFlutterPackage); + final flutterPackages = [if (mainProject != null) mainProject, ...packages] + .filter((package) => package.isFlutterPackage); if (flutterPackages.isNotEmpty) { print('We detected Flutter packages in your project:'); @@ -260,7 +271,8 @@ Future gitInit(Directory directory) async { // no need to initialize return; } - final Process process = await Process.start('git', ['init'], workingDirectory: directory.path); + final Process process = + await Process.start('git', ['init'], workingDirectory: directory.path); stdout.addStream(process.stdout); stderr.addStream(process.stderr); await process.exitCode; diff --git a/sidekick_core/lib/sidekick_core.dart b/sidekick_core/lib/sidekick_core.dart index fc2602e4..59027f24 100644 --- a/sidekick_core/lib/sidekick_core.dart +++ b/sidekick_core/lib/sidekick_core.dart @@ -38,7 +38,8 @@ export 'package:sidekick_core/src/git.dart'; export 'package:sidekick_core/src/repository.dart'; export 'package:sidekick_core/src/sidekick_package.dart'; export 'package:sidekick_core/src/template/sidekick_package.template.dart'; -export 'package:sidekick_core/src/version_checker.dart' show sidekickPackageCliVersion; +export 'package:sidekick_core/src/version_checker.dart' + show sidekickPackageCliVersion; /// The version of package:sidekick_core /// @@ -76,7 +77,8 @@ SidekickCommandRunner initializeSidekick({ final repo = findRepository(); if (mainProjectPath != null) { - mainProject = DartPackage.fromDirectory(repo.root.directory(mainProjectPath)); + mainProject = + DartPackage.fromDirectory(repo.root.directory(mainProjectPath)); } if (flutterSdkPath != null && dartSdkPath != null) { @@ -87,7 +89,8 @@ SidekickCommandRunner initializeSidekick({ final runner = SidekickCommandRunner._( cliName: name, - description: description ?? 'A sidekick CLI to equip Dart/Flutter projects with custom tasks', + description: description ?? + 'A sidekick CLI to equip Dart/Flutter projects with custom tasks', repository: repo, mainProject: mainProject, workingDirectory: Directory.current, @@ -258,7 +261,8 @@ void deinitializeSidekick() {} SidekickCommandRunner? _activeRunner; /// The working directory (cwd) from which the cli was started -Directory get entryWorkingDirectory => _entryWorkingDirectory ?? Directory.current; +Directory get entryWorkingDirectory => + _entryWorkingDirectory ?? Directory.current; Directory? _entryWorkingDirectory; /// Name of the cli program @@ -325,7 +329,8 @@ class SdkNotFoundException implements Exception { final String sdkPath; final Directory repoRoot; - late final String message = "Dart or Flutter SDK set to '$sdkPath', but that directory doesn't exist. " + late final String message = + "Dart or Flutter SDK set to '$sdkPath', but that directory doesn't exist. " "Please fix the path in `initializeSidekick` (dartSdkPath/flutterSdkPath). " "Note that relative sdk paths are resolved relative to the project root, " "which in this case is '${repoRoot.path}'."; diff --git a/sidekick_core/lib/src/version_checker.dart b/sidekick_core/lib/src/version_checker.dart index 0bc2db40..2b490aeb 100644 --- a/sidekick_core/lib/src/version_checker.dart +++ b/sidekick_core/lib/src/version_checker.dart @@ -97,7 +97,8 @@ abstract class VersionChecker { startTag: largestMatch!, endTag: '\n', content: '${missingKeys.isNotEmpty ? '\n' : ''}${missingKeys.mapIndexed( - (index, key) => '${' ' * (index + pubspecKeys.length - missingKeys.length)}$key:', + (index, key) => + '${' ' * (index + pubspecKeys.length - missingKeys.length)}$key:', ).join('\n')} $newVersionConstraint', ); } @@ -130,7 +131,8 @@ abstract class VersionChecker { /// even if a local dependency doesn't explicitly specify a version in their /// pubspec.yaml, there always is an implicit version of 0.0.0 static Version? getResolvedVersion(DartPackage package, String dependency) { - final resolvedVersion = _readFromYaml(package.lockfile, ['packages', dependency, 'version']); + final resolvedVersion = + _readFromYaml(package.lockfile, ['packages', dependency, 'version']); return resolvedVersion.match( () => null, (t) => t != null ? Version.parse(t) : null, @@ -143,14 +145,16 @@ abstract class VersionChecker { return testFakeGetLatestDependencyVersion!(dependency); } - final response = await get(Uri.parse('https://pub.dev/api/packages/$dependency')); + final response = + await get(Uri.parse('https://pub.dev/api/packages/$dependency')); if (response.statusCode != HttpStatus.ok) { throw "Package '$dependency' not found on pub.dev"; } final latestVersion = - ((jsonDecode(response.body) as Map)['latest'] as Map)['version'] as String; + ((jsonDecode(response.body) as Map)['latest'] + as Map)['version'] as String; return Version.parse(latestVersion); } @@ -162,10 +166,13 @@ abstract class VersionChecker { ); // e.g. {"date": "2022-12-13", "version": "2.18.6", "revision": "f16b62ea92cc0f04cfd9166992f93419e425c809"} final response = await get(endpoint); - final latestVersion = (jsonDecode(response.body) as Map)['version'] as String; + final latestVersion = (jsonDecode(response.body) + as Map)['version'] as String; // e.g. Dart SDK version: 2.18.4 (stable) (Tue Nov 1 15:15:07 2022 +0000) on "macos_arm64" - final dartVersionResult = '$dartExecutablePath --version'.start(progress: Progress.capture()).firstLine; + final dartVersionResult = '$dartExecutablePath --version' + .start(progress: Progress.capture()) + .firstLine; if (dartVersionResult == null) { throw "Couldn't determine version of Dart executable $dartExecutablePath"; } @@ -180,7 +187,8 @@ abstract class VersionChecker { /// Set to override behavior of [getLatestDependencyVersion] in tests @visibleForTesting - static Future Function(String dependency)? testFakeGetLatestDependencyVersion; + static Future Function(String dependency)? + testFakeGetLatestDependencyVersion; } /// To remember which sidekick_core version the sidekick CLI was generated From 6f3fea2055cc68d4dc200dee23ba2299a2c4111e Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 01:26:57 +0100 Subject: [PATCH 05/10] Remove redundant toString() --- sidekick/lib/src/commands/init_command.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index 903f4645..a0ef65cf 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -98,7 +98,7 @@ class InitCommand extends Command { print( '\n' 'Your current sidekick version would be downgraded ' - 'from ${sidekickPackageCliVersion.toString()} to ${core.version.toString()}', + 'from $packageVersion to ${core.version}', ); final downgrade = dcli.confirm('Do you want to downgrade?', defaultValue: false); From 35299b00a8b1cc437f85a3f916cf0cdf6c92abe3 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 01:43:46 +0100 Subject: [PATCH 06/10] Use local path for core temporarily --- sidekick/lib/src/commands/init_command.dart | 2 +- sidekick/pubspec.lock | 8 ++++---- sidekick/pubspec.yaml | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index a0ef65cf..a19bb292 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -4,7 +4,7 @@ import 'package:sidekick/src/util/dcli_ask_validators.dart'; import 'package:sidekick/src/util/directory_extension.dart'; import 'package:sidekick/src/util/name_suggester.dart'; import 'package:sidekick_core/sidekick_core.dart'; -import 'package:sidekick_core/sidekick_core.dart' as core; +import 'package:sidekick_core/sidekick_core.dart' as core show version; class InitCommand extends Command { @override diff --git a/sidekick/pubspec.lock b/sidekick/pubspec.lock index a07d6da6..6c3e37ad 100644 --- a/sidekick/pubspec.lock +++ b/sidekick/pubspec.lock @@ -452,10 +452,10 @@ packages: sidekick_core: dependency: "direct main" description: - name: sidekick_core - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.1" + path: "../sidekick_core" + relative: true + source: path + version: "0.14.0" sidekick_test: dependency: "direct dev" description: diff --git a/sidekick/pubspec.yaml b/sidekick/pubspec.yaml index 19cc1171..6cc2d277 100644 --- a/sidekick/pubspec.yaml +++ b/sidekick/pubspec.yaml @@ -17,7 +17,8 @@ dependencies: path: ^1.8.0 process: ^4.2.4 recase: ^4.0.0 - sidekick_core: '>=0.13.0 <1.0.0' + sidekick_core: + path: ../sidekick_core dev_dependencies: lint: ^1.5.0 From e464e8a2fe77c14fd2c7166ea4f7d26c3e9d5bd7 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 01:55:40 +0100 Subject: [PATCH 07/10] Check correct folder for previous installation --- sidekick/lib/src/commands/init_command.dart | 56 +++++++++++---------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/sidekick/lib/src/commands/init_command.dart b/sidekick/lib/src/commands/init_command.dart index a19bb292..f004fd3d 100644 --- a/sidekick/lib/src/commands/init_command.dart +++ b/sidekick/lib/src/commands/init_command.dart @@ -83,33 +83,6 @@ class InitCommand extends Command { throw 'CLI package directory ${cliPackageDir.path} is not within or equal to ${repoRoot.path}'; } - if (cliPackageDir.existsSync()) { - print('\nYou already have an existing sidekick project initialized.\n' - 'In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.'); - final override = dcli.confirm( - 'Do you want to override your existing CLI?', - defaultValue: false, - ); - if (!override) { - return; - } - final packageVersion = sidekickPackageCliVersion(); - if (packageVersion != null && packageVersion > core.version) { - print( - '\n' - 'Your current sidekick version would be downgraded ' - 'from $packageVersion to ${core.version}', - ); - final downgrade = - dcli.confirm('Do you want to downgrade?', defaultValue: false); - if (!downgrade) { - print( - 'In order to update sidekick run ${dcli.cyan('sidekick update')}.'); - return; - } - } - } - final mainProjectPath = argResults!['mainProjectPath'] as String?; DartPackage? mainProject = mainProjectPath != null ? DartPackage.fromDirectory( @@ -148,6 +121,35 @@ class InitCommand extends Command { throw 'The CLI name $cliName is already taken by an executable on your system see $cliNameCollisions'; } + final packageDirectory = cliPackageDir.directory("${cliName}_sidekick"); + if (packageDirectory.existsSync()) { + print( + '\nYou already have an existing sidekick project initialized in ${relative(packageDirectory.path)}.\n' + 'In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.'); + final override = dcli.confirm( + 'Do you want to override your existing CLI?', + defaultValue: false, + ); + if (!override) { + return; + } + final packageVersion = sidekickPackageCliVersion(); + if (packageVersion != null && packageVersion > core.version) { + print( + '\n' + 'Your current sidekick version would be downgraded ' + 'from $packageVersion to ${core.version}', + ); + final downgrade = + dcli.confirm('Do you want to downgrade?', defaultValue: false); + if (!downgrade) { + print( + 'In order to update sidekick run ${dcli.cyan('sidekick update')}.'); + return; + } + } + } + print("\nGenerating ${cliName}_sidekick"); final packages = Repository(root: repoRoot).findAllPackages(); From 2cf6de6c0e44852ba2b429614b212bc0847f08aa Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Wed, 11 Jan 2023 18:50:24 +0100 Subject: [PATCH 08/10] Add first test --- sidekick/test/init_test.dart | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/sidekick/test/init_test.dart b/sidekick/test/init_test.dart index dd4efecd..5db72b18 100644 --- a/sidekick/test/init_test.dart +++ b/sidekick/test/init_test.dart @@ -1,3 +1,4 @@ +import 'package:dcli/dcli.dart' as dcli; import 'package:sidekick/sidekick.dart'; import 'package:sidekick/src/util/dcli_ask_validators.dart'; import 'package:sidekick_core/sidekick_core.dart' hide version; @@ -202,6 +203,53 @@ void main() { ); }, ); + + test( + 'Warns about an already existing project in the current path', + () async { + final projectRoot = + setupTemplateProject('test/templates/minimal_flutter_package'); + + // Create initial sidekick + final firstProcess = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi'], + workingDirectory: projectRoot, + ); + await firstProcess.shouldExit(0); + final entrypoint = File("${projectRoot.path}/dashi"); + expect(entrypoint.existsSync(), isTrue); + + // Create sidekick again in the same directory + final TestProcess secondProcess = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi'], + workingDirectory: projectRoot, + ); + + await expectLater( + secondProcess.stdout, + emitsThrough( + "Welcome to sidekick. You're about to initialize a sidekick project", + ), + ); + printOnFailure(await secondProcess.stdoutStream().join('\n')); + printOnFailure(await secondProcess.stdoutStream().join('\n')); + + await expectLater( + secondProcess.stdout, + emitsThrough( + "You already have an existing sidekick project initialized in packages/dashi_sidekick.", + ), + ); + printOnFailure(await secondProcess.stdoutStream().join('\n')); + + await expectLater( + secondProcess.stdout, + emitsThrough( + "In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.", + ), + ); + }, + ); }); // TODO do we really need groups for all of these layouts? we had them because in the past we had some ProjectStructureDetector From ce4cc008416a33d2bdacdba2d529e9f22efe07b7 Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Thu, 12 Jan 2023 16:31:02 +0100 Subject: [PATCH 09/10] Create directory instead of running first project --- sidekick/test/init_test.dart | 443 +++++++++++++++++++---------------- 1 file changed, 238 insertions(+), 205 deletions(-) diff --git a/sidekick/test/init_test.dart b/sidekick/test/init_test.dart index 5db72b18..be7513f0 100644 --- a/sidekick/test/init_test.dart +++ b/sidekick/test/init_test.dart @@ -41,216 +41,249 @@ void main() { skip: !shouldUseLocalDeps, ); - group('sidekick init - argument validation', () { - test( - 'throws when entrypointDirectory does not exist', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - addTearDown(() => tempDir.deleteSync()); - - final projectRoot = - setupTemplateProject('test/templates/minimal_dart_package'); - final process = await cachedGlobalSidekickCli.run( - [ - 'init', - '-n', - 'dashi', - '--entrypointDirectory', - tempDir.directory('foo').path, - ], - workingDirectory: projectRoot, - ); - process.stderrStream().listen(printOnFailure); - await process.shouldExit(255); - expect( - await process.stderrStream().contains( - 'Entrypoint directory ${tempDir.directory('foo').path} does not exist', + group( + 'sidekick init - argument validation', + () { + test( + 'throws when entrypointDirectory does not exist', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync()); + + final projectRoot = + setupTemplateProject('test/templates/minimal_dart_package'); + final process = await cachedGlobalSidekickCli.run( + [ + 'init', + '-n', + 'dashi', + '--entrypointDirectory', + tempDir.directory('foo').path, + ], + workingDirectory: projectRoot, + ); + process.stderrStream().listen(printOnFailure); + await process.shouldExit(255); + expect( + await process.stderrStream().contains( + 'Entrypoint directory ${tempDir.directory('foo').path} does not exist', + ), + isTrue, + ); + }, + timeout: const Timeout(Duration(minutes: 5)), + ); + + test( + 'throws when cliPackageDirectory is not inside entrypointDirectory', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync()); + + final projectRoot = + setupTemplateProject('test/templates/minimal_dart_package'); + final process = await cachedGlobalSidekickCli.run( + [ + 'init', + '-n', + 'dashi', + '--entrypointDirectory', + projectRoot.path, + '--cliPackageDirectory', + tempDir.path + ], + workingDirectory: projectRoot, + ); + process.stderrStream().listen(printOnFailure); + await process.shouldExit(255); + expect( + await process.stderrStream().contains( + 'CLI package directory ${tempDir.path} is not within or equal to ${projectRoot.path}', + ), + isTrue, + ); + }, + timeout: const Timeout(Duration(minutes: 5)), + ); + + test( + 'throws when mainProjectPath is not inside entrypointDirectory', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + tempDir.file('pubspec.yaml').writeAsStringSync('name: fake'); + + final projectRoot = + setupTemplateProject('test/templates/minimal_dart_package'); + final process = await cachedGlobalSidekickCli.run( + [ + 'init', + '-n', + 'dashi', + '--entrypointDirectory', + projectRoot.path, + '--mainProjectPath', + tempDir.path + ], + workingDirectory: projectRoot, + ); + process.stderrStream().listen(printOnFailure); + await process.shouldExit(255); + expect( + await process.stderrStream().contains( + 'Main project ${tempDir.path} is not within or equal to ${projectRoot.path}', + ), + isTrue, + ); + }, + timeout: const Timeout(Duration(minutes: 5)), + ); + + test( + 'throws when mainProjectPath is given but it does not contain a DartPackage', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync()); + + final projectRoot = + setupTemplateProject('test/templates/minimal_dart_package'); + final process = await cachedGlobalSidekickCli.run( + [ + 'init', + '-n', + 'dashi', + '--entrypointDirectory', + projectRoot.path, + '--mainProjectPath', + tempDir.path + ], + workingDirectory: projectRoot, + ); + process.stderrStream().listen(printOnFailure); + await process.shouldExit(255); + expect( + await process.stderrStream().contains( + 'mainProjectPath was given, but no DartPackage could be found at the given path ${tempDir.path}', + ), + isTrue, + ); + }, + timeout: const Timeout(Duration(minutes: 5)), + ); + + test( + 'throws error when cli name is invalid', + () async { + final process = await cachedGlobalSidekickCli.run( + ['init', '-n', '-42invalidName'], + workingDirectory: Directory.systemTemp.createTempSync(), + ); + + await process.shouldExit(255); + expect( + await process.stderr.rest.contains(invalidCliNameErrorMessage), + isTrue, + ); + }, + ); + + test( + 'throws error when cli name collides with an system executable', + () async { + final process = await cachedGlobalSidekickCli.run( + ['init', '-n', 'rm'], + workingDirectory: Directory.systemTemp.createTempSync(), + ); + + await process.shouldExit(255); + final stderrText = await process.stderr.rest.toList(); + expect( + stderrText, + contains( + 'The CLI name rm is already taken by an executable on your system see [/bin/rm]', + ), + ); + }, + ); + group('Case of pre-existing sidekick in directory', () { + test( + 'Warning', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final package = tempDir.directory('dashi_sidekick')..createSync(); + package.file('pubspec.yaml').writeAsStringSync(""" +name: dashi_sidekick +environment: + sdk: '>=2.14.0 <3.0.0' +sidekick: + cli_version: 0.13.1 + """); + + // Create sidekick again in the same directory + final TestProcess process = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi', '-c', '.'], + workingDirectory: package.parent, + ); + await process.shouldExit(0); + + await expectLater( + process.stdout, + emitsThrough( + "Welcome to sidekick. You're about to initialize a sidekick project", ), - isTrue, - ); - }, - timeout: const Timeout(Duration(minutes: 5)), - ); - - test( - 'throws when cliPackageDirectory is not inside entrypointDirectory', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - addTearDown(() => tempDir.deleteSync()); + ); - final projectRoot = - setupTemplateProject('test/templates/minimal_dart_package'); - final process = await cachedGlobalSidekickCli.run( - [ - 'init', - '-n', - 'dashi', - '--entrypointDirectory', - projectRoot.path, - '--cliPackageDirectory', - tempDir.path - ], - workingDirectory: projectRoot, - ); - process.stderrStream().listen(printOnFailure); - await process.shouldExit(255); - expect( - await process.stderrStream().contains( - 'CLI package directory ${tempDir.path} is not within or equal to ${projectRoot.path}', + await expectLater( + process.stdout, + emitsThrough( + "You already have an existing sidekick project initialized in dashi_sidekick.", ), - isTrue, - ); - }, - timeout: const Timeout(Duration(minutes: 5)), - ); - - test( - 'throws when mainProjectPath is not inside entrypointDirectory', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - addTearDown(() => tempDir.deleteSync(recursive: true)); - tempDir.file('pubspec.yaml').writeAsStringSync('name: fake'); - - final projectRoot = - setupTemplateProject('test/templates/minimal_dart_package'); - final process = await cachedGlobalSidekickCli.run( - [ - 'init', - '-n', - 'dashi', - '--entrypointDirectory', - projectRoot.path, - '--mainProjectPath', - tempDir.path - ], - workingDirectory: projectRoot, - ); - process.stderrStream().listen(printOnFailure); - await process.shouldExit(255); - expect( - await process.stderrStream().contains( - 'Main project ${tempDir.path} is not within or equal to ${projectRoot.path}', - ), - isTrue, - ); - }, - timeout: const Timeout(Duration(minutes: 5)), - ); - - test( - 'throws when mainProjectPath is given but it does not contain a DartPackage', - () async { - final tempDir = Directory.systemTemp.createTempSync(); - addTearDown(() => tempDir.deleteSync()); - - final projectRoot = - setupTemplateProject('test/templates/minimal_dart_package'); - final process = await cachedGlobalSidekickCli.run( - [ - 'init', - '-n', - 'dashi', - '--entrypointDirectory', - projectRoot.path, - '--mainProjectPath', - tempDir.path - ], - workingDirectory: projectRoot, - ); - process.stderrStream().listen(printOnFailure); - await process.shouldExit(255); - expect( - await process.stderrStream().contains( - 'mainProjectPath was given, but no DartPackage could be found at the given path ${tempDir.path}', + ); + await expectLater( + process.stdout, + emitsThrough( + "In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.", ), - isTrue, - ); - }, - timeout: const Timeout(Duration(minutes: 5)), - ); - - test( - 'throws error when cli name is invalid', - () async { - final process = await cachedGlobalSidekickCli.run( - ['init', '-n', '-42invalidName'], - workingDirectory: Directory.systemTemp.createTempSync(), + ); + }, ); - - await process.shouldExit(255); - expect( - await process.stderr.rest.contains(invalidCliNameErrorMessage), - isTrue, - ); - }, - ); - - test( - 'throws error when cli name collides with an system executable', - () async { - final process = await cachedGlobalSidekickCli.run( - ['init', '-n', 'rm'], - workingDirectory: Directory.systemTemp.createTempSync(), - ); - - await process.shouldExit(255); - final stderrText = await process.stderr.rest.toList(); - expect( - stderrText, - contains( - 'The CLI name rm is already taken by an executable on your system see [/bin/rm]', - ), - ); - }, - ); - - test( - 'Warns about an already existing project in the current path', - () async { - final projectRoot = - setupTemplateProject('test/templates/minimal_flutter_package'); - - // Create initial sidekick - final firstProcess = await cachedGlobalSidekickCli.run( - ['init', '-n', 'dashi'], - workingDirectory: projectRoot, - ); - await firstProcess.shouldExit(0); - final entrypoint = File("${projectRoot.path}/dashi"); - expect(entrypoint.existsSync(), isTrue); - - // Create sidekick again in the same directory - final TestProcess secondProcess = await cachedGlobalSidekickCli.run( - ['init', '-n', 'dashi'], - workingDirectory: projectRoot, - ); - - await expectLater( - secondProcess.stdout, - emitsThrough( - "Welcome to sidekick. You're about to initialize a sidekick project", - ), - ); - printOnFailure(await secondProcess.stdoutStream().join('\n')); - printOnFailure(await secondProcess.stdoutStream().join('\n')); - - await expectLater( - secondProcess.stdout, - emitsThrough( - "You already have an existing sidekick project initialized in packages/dashi_sidekick.", - ), - ); - printOnFailure(await secondProcess.stdoutStream().join('\n')); - - await expectLater( - secondProcess.stdout, - emitsThrough( - "In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.", - ), - ); - }, - ); - }); + }); + + test( + 'Override', + () async { + final projectRoot = + setupTemplateProject('test/templates/minimal_flutter_package'); + + // Create initial sidekick + final firstProcess = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi'], + workingDirectory: projectRoot, + ); + await firstProcess.shouldExit(0); + final entrypoint = File("${projectRoot.path}/dashi"); + expect(entrypoint.existsSync(), isTrue); + + // Create sidekick again in the same directory + final TestProcess secondProcess = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi'], + workingDirectory: projectRoot, + runInShell: true, + ); + + secondProcess.stdoutStream().listen((event) { + if (event == "Do you want to override your existing CLI?") { + secondProcess.stdin.writeln('y'); + } + print(event); + }); + + await secondProcess.shouldExit(0); + }, + ); + }, + ); // TODO do we really need groups for all of these layouts? we had them because in the past we had some ProjectStructureDetector group('sidekick init - simple layout', () { From 5160e0f326345f9e8e0511db890a556d119277bd Mon Sep 17 00:00:00 2001 From: danielmolnar Date: Fri, 13 Jan 2023 02:34:18 +0100 Subject: [PATCH 10/10] Recreate test process tests in temporary file --- sidekick/pubspec.lock | 7 + sidekick/pubspec.yaml | 1 + sidekick/test/downgrade_on_init_test.dart | 160 ++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 sidekick/test/downgrade_on_init_test.dart diff --git a/sidekick/pubspec.lock b/sidekick/pubspec.lock index 6c3e37ad..388cb824 100644 --- a/sidekick/pubspec.lock +++ b/sidekick/pubspec.lock @@ -554,6 +554,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.21" + test_descriptor: + dependency: "direct dev" + description: + name: test_descriptor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" test_process: dependency: "direct dev" description: diff --git a/sidekick/pubspec.yaml b/sidekick/pubspec.yaml index 6cc2d277..0078ea53 100644 --- a/sidekick/pubspec.yaml +++ b/sidekick/pubspec.yaml @@ -24,6 +24,7 @@ dev_dependencies: lint: ^1.5.0 sidekick_test: path: ../sidekick_test + test_descriptor: ^2.0.1 test: ^1.17.0 test_process: ^2.0.2 diff --git a/sidekick/test/downgrade_on_init_test.dart b/sidekick/test/downgrade_on_init_test.dart new file mode 100644 index 00000000..124670c7 --- /dev/null +++ b/sidekick/test/downgrade_on_init_test.dart @@ -0,0 +1,160 @@ +import 'package:dcli/dcli.dart' as dcli; +import 'package:path/path.dart' as path; +import 'package:sidekick/sidekick.dart' as sidekick show main; +import 'package:sidekick_core/sidekick_core.dart' hide version; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as descriptor; +import 'package:test_process/test_process.dart'; +import 'util/cli_runner.dart'; + +//TODO(danielmolnar) Remove text_descriptor dependency once this file gets deleted +/// Starts a Dart process running [script] in a main method. +Future _startDartProcess(String script) { + final dartPath = path.join(descriptor.sandbox, 'test.dart'); + File(dartPath).writeAsStringSync(''' + import 'dart:async'; + import 'dart:convert'; + import 'dart:io'; + var stdinLines = stdin + .transform(utf8.decoder) + .transform(new LineSplitter()); + void main() { + $script + } + '''); + return TestProcess.start(Platform.executable, ['--enable-asserts', dartPath]); +} + +void main() { + test('stdin writes to the process', () async { + final process = await _startDartProcess(r''' + stdinLines.listen((line) => print("> $line")); + '''); + + process.stdin.writeln('hello'); + await expectLater(process.stdout, emits('> hello')); + process.stdin.writeln('world'); + await expectLater(process.stdout, emits('> world')); + await process.kill(); + }); + + group('Case of pre-existing sidekick in directory', () { + test( + 'Warning', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final package = tempDir.directory('dashi_sidekick')..createSync(); + package.file('pubspec.yaml').writeAsStringSync(""" +name: dashi_sidekick +environment: + sdk: '>=2.14.0 <3.0.0' +sidekick: + cli_version: 0.13.1 + """); + + // Create sidekick again in the same directory + final TestProcess process = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi', '-c', '.'], + workingDirectory: package.parent, + ); + await process.shouldExit(0); + + await expectLater( + process.stdout, + emitsThrough( + "Welcome to sidekick. You're about to initialize a sidekick project", + ), + ); + + await expectLater( + process.stdout, + emitsThrough( + "You already have an existing sidekick project initialized in dashi_sidekick.", + ), + ); + await expectLater( + process.stdout, + emitsThrough( + "In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.", + ), + ); + }, + ); + test( + 'Override', + () async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final package = tempDir.directory('dashi_sidekick')..createSync(); + package.file('pubspec.yaml').writeAsStringSync(""" +name: dashi_sidekick +environment: + sdk: '>=2.14.0 <3.0.0' +sidekick: + cli_version: 0.13.1 + """); + + // Create sidekick again in the same directory + final TestProcess process = await cachedGlobalSidekickCli.run( + ['init', '-n', 'dashi', '-c', '.'], + workingDirectory: package.parent, + forwardStdio: true, + ); + + process.stdoutStream().listen((event) { + if (event == "Do you want to override your existing CLI?") { + process.stdin.writeln('y'); + } + print(event); + }); + + process.stdin.writeln('y'); + + await process.shouldExit(0); + + await expectLater( + process.stdout, + emitsThrough( + "Welcome to sidekick. You're about to initialize a sidekick project", + ), + ); + + await expectLater( + process.stdout, + emitsThrough( + "You already have an existing sidekick project initialized in dashi_sidekick.", + ), + ); + await expectLater( + process.stdout, + emitsThrough( + "In order to update your existing project run ${dcli.cyan(' sidekick update')} instead.", + ), + ); + await expectLater( + process.stdout, + emitsThrough( + "Do you want to override your existing CLI?", + ), + ); + + await expectLater( + process.stdout, + emitsThrough( + "y", + ), + ); + }, + ); + }); + +/* secondProcess.stdoutStream().listen((event) { + if (event == "Do you want to override your existing CLI?") { + secondProcess.stdin.writeln('y'); + } + print(event); + }); + + await secondProcess.shouldExit(0);*/ +}