Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
6 changes: 1 addition & 5 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
include: package:flame_lint/analysis_options.yaml

linter:
rules:
avoid_print: false # since it is a CLI application

analyzer:
exclude:
- bricks/
- bricks/**/*
17 changes: 14 additions & 3 deletions bin/ignite.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import 'package:ignite_cli/main.dart';
import 'dart:io';

void main(List<String> args) {
mainCommand(args);
import 'package:ignite_cli/flame_version_manager.dart';
import 'package:ignite_cli/ignite_command_runner.dart';
import 'package:ignite_cli/ignite_context.dart';
import 'package:mason_logger/mason_logger.dart';

Future<void> main(List<String> args) async {
final manager = await FlameVersionManager.fetch();
final context = IgniteContext(logger: Logger(), flameVersionManager: manager);
final runner = IgniteCommandRunner(context);
final code = await runner.run(args);

// ensure all buffered output is written before exit
await Future.wait([stdout.close(), stderr.close()]).then((_) => exit(code));
}
260 changes: 185 additions & 75 deletions lib/commands/create_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,97 @@ import 'dart:io';

import 'package:args/args.dart';
import 'package:dartlin/dartlin.dart';
import 'package:ignite_cli/commands/ignite_command.dart';
import 'package:ignite_cli/flame_version_manager.dart';
import 'package:ignite_cli/ignite_context.dart';
import 'package:ignite_cli/templates/template.dart';
import 'package:ignite_cli/utils.dart';
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 {
class CreateCommand extends IgniteCommand {
CreateCommand(super.context) {
final packages = context.flameVersionManager.versions;
final flameVersions = packages[Package.flame]!;

argParser.addFlag(
'interactive',
abbr: 'i',
help: 'Whether to run in interactive mode or not.',
defaultsTo: true,
);
argParser.addOption(
'name',
help: 'The name of your game (valid dart identifier).',
);
argParser.addOption(
'org',
help: 'The org name, in reverse domain notation '
'(package name/bundle identifier).',
);
argParser.addFlag(
'create-folder',
abbr: 'f',
help: 'If you want to create a new folder on the current location with '
"the project name or if you are already on the new project's folder.",
);
argParser.addOption(
'template',
help: 'What Flame template you would like to use for your new project',
allowed: ['simple', 'example'],
);
argParser.addOption(
'flame-version',
help: 'What Flame version you would like to use.',
allowed: [
...flameVersions.versions.take(5),
'...',
flameVersions.versions.last,
],
);
argParser.addMultiOption(
'extra-packages',
help: 'Which packages to use',
allowed: packages.keys.map((e) => e.name).toList(),
);
}

@override
String get description => 'Create a new Flame project';

@override
String get name => 'create';

@override
Future<int> run() async {
final argResults = this.argResults;
if (argResults == null) {
return ExitCode.usage.code;
}

final code = await createCommand(context, argResults);

return code;
}
}

Future<int> createCommand(
IgniteContext context,
ArgResults command,
) async {
final interactive = command['interactive'] != 'false';

if (interactive) {
stdout.write('\nWelcome to ${ansi.red.wrap('Ignite CLI')}! 🔥\n');
stdout.write("Let's create a new project!\n\n");
context.logger
..info('\nWelcome to ${red.wrap('Ignite CLI')}! 🔥')
..info("Let's create a new project!\n");
}

final name = getString(
isInteractive: interactive,
logger: context.logger,
command,
'name',
'Choose a name for your project: ',
'Choose a name for your project',
desc: 'Note: this must be a valid dart identifier (no dashes). '
'For example: my_game',
validate: (it) => switch (it) {
Expand All @@ -34,10 +105,11 @@ Future<void> createCommand(ArgResults command) async {
);

final org = getString(
logger: context.logger,
isInteractive: interactive,
command,
'org',
'Choose an org for your project: ',
'Choose an org for your project:',
desc: 'Note: this is a dot separated list of "packages", '
'normally in reverse domain notation. '
'For example: org.flame_engine.games',
Expand All @@ -48,9 +120,10 @@ Future<void> createCommand(ArgResults command) async {
},
);

final versions = FlameVersionManager.singleton.versions;
final versions = context.flameVersionManager.versions;
final flameVersions = versions[Package.flame]!;
final flameVersion = getOption(
logger: context.logger,
isInteractive: interactive,
command,
'flame-version',
Expand All @@ -60,11 +133,12 @@ Future<void> createCommand(ArgResults command) async {
fullOptions: flameVersions.versions.associateWith((e) => e),
);

final extraPackageOptions = FlameVersionManager.singleton.versions.keys
final extraPackageOptions = context.flameVersionManager.versions.keys
.where((key) => !Package.includedByDefault.contains(key))
.map((key) => key.name)
.toList();
final extraPackages = getMultiOption(
logger: context.logger,
isInteractive: interactive,
isRequired: false,
command,
Expand All @@ -83,23 +157,21 @@ Future<void> createCommand(ArgResults command) async {
final devDependencies = packages.where((e) => e.isDevDependency);

final currentDir = Directory.current.path;
print('\nYour current directory is: $currentDir');

final createFolder = getOption(
isInteractive: interactive,
command,
'create-folder',
'Do you want to put your project files directly on the current dir, '
'or do you want to create a folder called $name?',
{
'Create a folder called $name': 'true',
'Put the files directly on $currentDir': 'false',
},
) ==
'true';

print('\n');

context.logger.info('Your current directory is: $currentDir');

bool createFolder;
if (!interactive) {
createFolder = command['create-folder'] == true;
} else {
createFolder = context.logger.confirm(
'Create project a folder called $name?',
defaultValue: command['create-folder'] == true,
);
}

final template = getOption(
logger: context.logger,
isInteractive: interactive,
command,
'template',
Expand All @@ -109,60 +181,98 @@ Future<void> createCommand(ArgResults command) async {
);

final actualDir = '$currentDir${createFolder ? '/$name' : ''}';
if (createFolder) {
await runExecutableArguments('mkdir', [actualDir]);
}
print('\nRunning flutter create on $actualDir ...');

await runExecutableArguments(
'flutter',
'create --org $org --project-name $name .'.split(' '),
workingDirectory: actualDir,
verbose: true,
);
await runExecutableArguments(
'rm',
'-rf lib test'.split(' '),
workingDirectory: actualDir,
verbose: true,
);
final progress = context.logger.progress('Generating project');
ProcessResult? processResult;
var code = ExitCode.success.code;

final bundle = Template.byKey(template).bundle;
final generator = await MasonGenerator.fromBundle(bundle);
final target = DirectoryGeneratorTarget(
Directory(actualDir),
);
final variables = <String, dynamic>{
'name': name,
'description': 'A simple Flame game.',
'version': '0.1.0',
'extra-dependencies': dependencies
.sortedBy((e) => e.name)
.map((package) => package.toMustache(versions, flameVersion))
.toList(),
'extra-dev-dependencies': devDependencies
.sortedBy((e) => e.name)
.map((package) => package.toMustache(versions, flameVersion))
.toList(),
};
final files = await generator.generate(target, vars: variables);

final canHaveTests = devDependencies.contains(Package.flameTest);
if (!canHaveTests) {
await runExecutableArguments(
try {
if (createFolder) {
progress.update('Running [mkdir] on $actualDir');
processResult = await context.run('mkdir', [actualDir]);
if (processResult.exitCode > ExitCode.success.code) {
return code = processResult.exitCode;
}
}

progress.update('Running [flutter create] on $actualDir');
processResult = await context.run(
'flutter',
'create --org $org --project-name $name .'.split(' '),
workingDirectory: actualDir,
);
if (processResult.exitCode > ExitCode.success.code) {
return code = processResult.exitCode;
}

progress.update('Running [rm -rf lib test] on $actualDir');
processResult = await context.run(
'rm',
'-rf test'.split(' '),
'-rf lib test'.split(' '),
workingDirectory: actualDir,
verbose: true,
);
}
await runExecutableArguments(
'flutter',
'pub get'.split(' '),
workingDirectory: actualDir,
verbose: true,
);
if (processResult.exitCode > ExitCode.success.code) {
return code = processResult.exitCode;
}
progress.update('Bundling game template');

final bundle = Template.byKey(template).bundle;
final generator = await context.generatorFromBundle(bundle);
final target = context.createTarget(Directory(actualDir));

print('Updated ${files.length} files on top of flutter create.\n');
print('Your new Flame project was successfully created!');
final variables = <String, dynamic>{
'name': name,
'description': 'A simple Flame game.',
'version': '0.1.0',
'extra-dependencies': dependencies
.sortedBy((e) => e.name)
.map((package) => package.toMustache(versions, flameVersion))
.toList(),
'extra-dev-dependencies': devDependencies
.sortedBy((e) => e.name)
.map((package) => package.toMustache(versions, flameVersion))
.toList(),
};

final files = await generator.generate(target, vars: variables);
final canHaveTests = devDependencies.contains(Package.flameTest);

if (!canHaveTests) {
progress.update('Removing tests');
processResult = await context.run(
'rm',
'-rf test'.split(' '),
workingDirectory: actualDir,
);
if (processResult.exitCode > ExitCode.success.code) {
return code = processResult.exitCode;
}
}

progress.update('Removing tests');
processResult = await context.run(
'flutter',
'pub get'.split(' '),
workingDirectory: actualDir,
);

if (processResult.exitCode > ExitCode.success.code) {
return code = processResult.exitCode;
}

progress
..complete('Updated ${files.length} files on top of flutter create.')
..complete('Your new Flame project was successfully created!');

return code;
} catch (_) {
if (processResult != null) {
progress.fail(processResult.stderr.toString());
code = processResult.exitCode;
} else {
progress.fail();
}

rethrow;
}
}
13 changes: 13 additions & 0 deletions lib/commands/ignite_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'dart:async';

import 'package:args/command_runner.dart';
import 'package:ignite_cli/ignite_context.dart';

abstract class IgniteCommand extends Command<int> {
final IgniteContext context;

IgniteCommand(this.context);

@override
Future<int> run();
}
Loading
Loading