Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/commands/create_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:io/ansi.dart' as ansi;
import 'package:mason/mason.dart';
import 'package:process_run/process_run.dart';

Future<void> createCommand(ArgResults command) async {
Future<void> createCommand(ArgResults command, Logger logger) async {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for a followup, I would envision we creating some class that wraps the ArgResults and Logger to pass around as a unit, and then the "utils" methods would live on this class as well
something like "CLI context", not sure what is a good name
but as I said - this is def a followup!

final interactive = command['interactive'] != 'false';

if (interactive) {
Expand All @@ -19,6 +19,7 @@ Future<void> createCommand(ArgResults command) async {

final name = getString(
isInteractive: interactive,
logger: logger,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we standardize the logger to be the first parameter?

command,
'name',
'Choose a name for your project: ',
Expand All @@ -34,6 +35,7 @@ Future<void> createCommand(ArgResults command) async {
);

final org = getString(
logger: logger,
isInteractive: interactive,
command,
'org',
Expand All @@ -51,6 +53,7 @@ Future<void> createCommand(ArgResults command) async {
final versions = FlameVersionManager.singleton.versions;
final flameVersions = versions[Package.flame]!;
final flameVersion = getOption(
logger: logger,
isInteractive: interactive,
command,
'flame-version',
Expand All @@ -65,6 +68,7 @@ Future<void> createCommand(ArgResults command) async {
.map((key) => key.name)
.toList();
final extraPackages = getMultiOption(
logger: logger,
isInteractive: interactive,
isRequired: false,
command,
Expand All @@ -86,6 +90,7 @@ Future<void> createCommand(ArgResults command) async {
print('\nYour current directory is: $currentDir');

final createFolder = getOption(
logger: logger,
isInteractive: interactive,
command,
'create-folder',
Expand All @@ -100,6 +105,7 @@ Future<void> createCommand(ArgResults command) async {

print('\n');
final template = getOption(
logger: logger,
isInteractive: interactive,
command,
'template',
Expand Down
11 changes: 6 additions & 5 deletions lib/commands/version_command.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import 'package:ignite_cli/version.g.dart';
import 'package:mason/mason.dart';
import 'package:process_run/process_run.dart';

Future<void> versionCommand() async {
print(r'$ ignite --version:');
print(igniteVersion);
print('');
Future<void> versionCommand(Logger logger) async {
logger.detail(r'$ ignite --version:');
logger.detail(igniteVersion);
logger.detail('');
await runExecutableArguments('dart', ['--version'], verbose: true);
print('');
logger.detail('');
await runExecutableArguments('flutter', ['--version'], verbose: true);
}
23 changes: 14 additions & 9 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import 'package:completion/completion.dart' as completion;
import 'package:ignite_cli/commands/create_command.dart';
import 'package:ignite_cli/commands/version_command.dart';
import 'package:ignite_cli/flame_version_manager.dart';
import 'package:mason_logger/mason_logger.dart';

Future<void> mainCommand(List<String> args) async {
await FlameVersionManager.init();

final parser = ArgParser();
final logger = Logger();

parser.addFlag('help', abbr: 'h', help: 'Displays this message.');
parser.addFlag('version', abbr: 'v', help: 'Shows relevant version info.');

Expand Down Expand Up @@ -56,22 +59,24 @@ Future<void> mainCommand(List<String> args) async {

final results = completion.tryArgsCompletion(args, parser);
if (results['help'] as bool) {
print(parser.usage);
print('');
print('List of available commands:');
print('');
print('create:');
print(' ${create.usage}');
logger.info(parser.usage);
logger.info('');
logger.info('List of available commands:');
logger.info('');
logger.info('create:');
logger.info(' ${create.usage}');
return;
} else if (results['version'] as bool) {
await versionCommand();
await versionCommand(logger);
return;
}

final command = results.command;
if (command?.name == 'create') {
await createCommand(command!);
await createCommand(command!, logger);
} else {
print('Invalid command. Please select an option, use --help for help.');
logger.info(
'Invalid command. Please select an option, use --help for help.',
);
}
}
150 changes: 36 additions & 114 deletions lib/utils.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'dart:io';

import 'package:args/args.dart';
import 'package:charcode/ascii.dart' as ascii;
import 'package:io/ansi.dart' as ansi;
import 'package:io/io.dart';
import 'package:mason_logger/mason_logger.dart' as logger;
import 'package:path/path.dart' as p;
import 'package:prompts/prompts.dart' as prompts;

String getBundledFile(String name) {
final binFolder = p.dirname(p.fromUri(Platform.script));
Expand All @@ -16,42 +16,33 @@ String getString(
String name,
String message, {
required bool isInteractive,
required logger.Logger logger,
String? desc,
String? Function(String)? validate,
}) {
var value = results[name] as String?;
if (!isInteractive) {
if (value == null || value.isEmpty) {
print('Missing parameter $name is required.');
exit(1);
logger.info('Missing parameter $name is required.');
exit(ExitCode.usage.code);
}
final error = validate?.call(value);
if (error != null) {
print('Invalid value $value provided: $error');
exit(1);
logger.info('Invalid value $value provided: $error');
exit(ExitCode.usage.code);
}
}
while (value == null || value.isEmpty) {
if (desc != null) {
stdout.write(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
logger.info(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
}
value = prompts.get(
message,
validate: (e) {
final error = validate?.call(e);
if (error != null) {
// clear the line
stdout.write('\n\r\u{1B}[K');
stdout.write(ansi.red.wrap('$error\u{1B}[1A\r'));
return false;
} else {
stdout.write('\n\r\u{1B}[K');
return true;
}
},
);

value = logger.prompt(message);

// TODO(elijah): validation callback

if (desc != null) {
stdout.write('\r\u{1B}[K');
logger.info('\r\u{1B}[K');
}
}
return value;
Expand All @@ -63,33 +54,39 @@ String getOption(
String message,
Map<String, String> options, {
required bool isInteractive,
required logger.Logger logger,
String? desc,
String? defaultsTo,
Map<String, String> fullOptions = const {},
}) {
var value = results[name] as String?;

if (!isInteractive) {
if (value == null) {
if (defaultsTo != null) {
return defaultsTo;
} else {
print('Missing parameter $name is required.');
exit(1);
logger.info('Missing parameter $name is required.');
exit(ExitCode.usage.code);
}
}
}
final fullValues = {...options, ...fullOptions}.values;
if (value != null && !fullValues.contains(value)) {
print('Invalid value $value provided. Must be in: ${options.values}');
logger.info('Invalid value $value provided. Must be in: ${options.values}');
value = null;
}

while (value == null) {
if (desc != null) {
stdout.write(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
logger.info(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
}
value = options[prompts.choose(message, options.keys)];

final option = logger.chooseOne(message, choices: options.keys.toList());
value = options[option];

if (desc != null) {
stdout.write('\r\u{1B}[K');
logger.info('\r\u{1B}[K');
}
}
return value;
Expand All @@ -114,6 +111,7 @@ List<String> getMultiOption(
List<String> options, {
required bool isInteractive,
required bool isRequired,
required logger.Logger logger,
List<String> startingOptions = const [],
String? desc,
}) {
Expand All @@ -123,104 +121,28 @@ List<String> getMultiOption(
if (startingOptions.isNotEmpty) {
return startingOptions;
} else {
print('Missing parameter $name is required.');
exit(1);
logger.info('Missing parameter $name is required.');
exit(ExitCode.usage.code);
}
} else {
return value;
}
}

if (value.any((e) => !options.contains(e))) {
print('Invalid value $value provided. Must be in: $options');
logger.info('Invalid value $value provided. Must be in: $options');
value = [];
}
if (desc != null) {
stdout.write(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
logger.info(ansi.darkGray.wrap('\n$desc\u{1B}[1A\r'));
}
final selectedOptions = value.isEmpty ? startingOptions : value;
value = cbx(message, options, selectedOptions);
if (desc != null) {
stdout.write('\r\u{1B}[K');
}
return value;
}

List<String> cbx(
String message,
List<String> keys,
List<String> startingKeys,
) {
final selected = startingKeys;
var hereIdx = 0;

var needsClear = false;
void writeIt() {
if (needsClear) {
for (var i = 0; i <= keys.length; i++) {
prompts.goUpOneLine();
prompts.clearLine();
}
} else {
needsClear = true;
}
print(message);
keys.asMap().forEach((index, option) {
final isSelected = selected.contains(option);
final isHere = index == hereIdx;
final text = ' ${isSelected ? '♦' : '♢'} $option';
final color = isHere ? ansi.cyan : ansi.darkGray;
print(color.wrap(text));
});
}
value = logger.chooseAny(message, choices: options);

final oldEchoMode = stdin.echoMode;
final oldLineMode = stdin.lineMode;
while (true) {
int ch;
writeIt();

try {
stdin.lineMode = stdin.echoMode = false;
ch = stdin.readByteSync();

if (ch == ascii.$esc) {
ch = stdin.readByteSync();
if (ch == ascii.$lbracket) {
ch = stdin.readByteSync();
if (ch == ascii.$A) {
// Up key
hereIdx--;
if (hereIdx < 0) {
hereIdx = keys.length - 1;
}
writeIt();
} else if (ch == ascii.$B) {
// Down key
hereIdx++;
if (hereIdx >= keys.length) {
hereIdx = 0;
}
writeIt();
}
}
} else if (ch == ascii.$lf) {
// Enter key pressed - submit
return selected;
} else if (ch == ascii.$space) {
// Space key pressed - selected/unselect
final key = keys[hereIdx];
if (selected.contains(key)) {
selected.remove(key);
} else {
selected.add(key);
}
writeIt();
}
} finally {
stdin.lineMode = oldLineMode;
stdin.echoMode = oldEchoMode;
}
if (desc != null) {
logger.info('\r\u{1B}[K');
}
return value;
}

extension SortedBy<T> on Iterable<T> {
Expand Down
10 changes: 1 addition & 9 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ packages:
source: hosted
version: "0.1.2"
mason_logger:
dependency: transitive
dependency: "direct main"
description:
name: mason_logger
sha256: "6d5a989ff41157915cb5162ed6e41196d5e31b070d2f86e1c2edf216996a158c"
Expand Down Expand Up @@ -409,14 +409,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.4"
prompts:
dependency: "direct main"
description:
name: prompts
sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
pub_semver:
dependency: transitive
description:
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ dependencies:
http: ^1.3.0
io: ^1.0.5
mason: ^0.1.1
mason_logger: ^0.3.3
path: ^1.9.1
process_run: ^1.2.4
prompts: ^2.0.0
yaml: ^3.1.3

dev_dependencies:
Expand Down