import "dart:io"; import "package:args/command_runner.dart"; import "package:console/console.dart"; import 'package:random_string/random_string.dart' as rs; import 'package:path/path.dart' as p; import 'key.dart'; import 'rename.dart'; final RegExp _leadingSlashes = new RegExp(r'^/+'); class InitCommand extends Command { final KeyCommand _key = new KeyCommand(); final TextPen _pen = new TextPen(); @override String get name => "init"; @override String get description => "Initializes a new Angel project in the current directory."; InitCommand() { argParser.addFlag('pub-get', defaultsTo: true); } @override run() async { Directory projectDir = new Directory( argResults.arguments.isEmpty ? "." : argResults.arguments[0]); print("Creating new Angel project in ${projectDir.absolute.path}..."); await _cloneRepo(projectDir); // await preBuild(projectDir); var secret = rs.randomAlphaNumeric(32); print('Generated new development JWT secret: $secret'); await _key.changeSecret( new File.fromUri(projectDir.uri.resolve('config/default.yaml')), secret); secret = rs.randomAlphaNumeric(32); print('Generated new production JWT secret: $secret'); await _key.changeSecret( new File.fromUri(projectDir.uri.resolve('config/production.yaml')), secret); var name = p.basenameWithoutExtension( projectDir.absolute.uri.normalizePath().toFilePath()); print('Renaming project from "angel" to "$name"...'); await renamePubspec(projectDir, 'angel', name); await renameDartFiles(projectDir, 'angel', name); if (argResults['pub-get'] != false) { print('Now running pub get...'); await _pubGet(projectDir); } _pen.green(); _pen("${Icon.CHECKMARK} Successfully initialized Angel project."); _pen(); _pen ..reset() ..text('\nCongratulations! You are ready to start developing with Angel!') ..text('\nTo start the server (with file watching), run ') ..magenta() ..text('`angel start`') ..normal() ..text(' in your terminal.') ..text('\n\nFind more documentation about Angel:') ..text('\n * https://angel-dart.github.io') ..text('\n * https://github.com/angel-dart/angel/wiki') ..text( '\n * https://www.youtube.com/playlist?list=PLl3P3tmiT-frEV50VdH_cIrA2YqIyHkkY') ..text('\n * https://medium.com/the-angel-framework') ..text('\n * https://dart.academy/tag/angel') ..text('\n\nHappy coding!') ..call(); } _deleteRecursive(FileSystemEntity entity, [bool self = true]) async { if (entity is Directory) { await for (var entity in entity.list(recursive: true)) { try { await _deleteRecursive(entity); } catch (e) {} } try { if (self != false) await entity.delete(recursive: true); } catch (e) {} } else if (entity is File) { try { await entity.delete(recursive: true); } catch (e) {} } else if (entity is Link) { var path = await entity.resolveSymbolicLinks(); var stat = await FileStat.stat(path); switch (stat.type) { case FileSystemEntityType.DIRECTORY: return await _deleteRecursive(new Directory(path)); case FileSystemEntityType.FILE: return await _deleteRecursive(new File(path)); default: break; } } } _cloneRepo(Directory projectDir) async { try { if (await projectDir.exists()) { var chooser = new Chooser(["Yes", "No"], message: "Directory '${projectDir.absolute.path}' already exists. Overwrite it? (Yes/No)"); if (await chooser.choose() != "Yes") throw new Exception("Chose not to overwrite existing directory."); else if (projectDir.absolute.uri.normalizePath().toFilePath() != Directory.current.absolute.uri.normalizePath().toFilePath()) await projectDir.delete(recursive: true); else { await _deleteRecursive(projectDir, false); } } print('Choose a project type before continuing:'); var boilerplateChooser = new Chooser(ALL_BOILERPLATES); var boilerplate = await boilerplateChooser.choose(); print( 'Cloning "${boilerplate.name}" boilerplate from "${boilerplate.url}"...'); var git = await Process.start("git", ["clone", "--depth", "1", boilerplate.url, projectDir.absolute.path]); stdout.addStream(git.stdout); stderr.addStream(git.stderr); if (await git.exitCode != 0) { throw new Exception("Could not clone repo."); } var gitDir = new Directory.fromUri(projectDir.uri.resolve(".git")); if (await gitDir.exists()) await gitDir.delete(recursive: true); } catch (e) { print(e); _pen.red(); _pen("${Icon.BALLOT_X} Could not initialize Angel project."); _pen(); rethrow; } } static String resolvePub() { var exec = new File(Platform.resolvedExecutable); var pubPath = exec.parent.uri.resolve('pub').path; if (Platform.isWindows) pubPath = pubPath.replaceAll(_leadingSlashes, '') + '.bat'; pubPath = Uri.decodeFull(pubPath); return pubPath; } _pubGet(Directory projectDir) async { var pubPath = resolvePub(); print('Running pub at "$pubPath"...'); var pub = await Process.start(pubPath, ["get"], workingDirectory: projectDir.absolute.path); stdout.addStream(pub.stdout); stderr.addStream(pub.stderr); var code = await pub.exitCode; print("Pub process exited with code $code"); } } preBuild(Directory projectDir) async { // Run build print('Pre-building resources...'); var build = await Process.start(Platform.executable, ['tool/build.dart'], workingDirectory: projectDir.absolute.path); stdout.addStream(build.stdout); stderr.addStream(build.stderr); var buildCode = await build.exitCode; if (buildCode != 0) throw new Exception('Failed to pre-build resources.'); } const BoilerplateInfo FULL_APPLICATION_BOILERPLATE = const BoilerplateInfo( 'Full Application', 'A complete project including authentication, multi-threading, and more.', 'https://github.com/angel-dart/angel.git'); const BoilerplateInfo LIGHT_BOILERPLATE = const BoilerplateInfo( 'Light', 'Minimal starting point for new users', 'https://github.com/angel-dart/boilerplate_light.git'); const List ALL_BOILERPLATES = const [ FULL_APPLICATION_BOILERPLATE, LIGHT_BOILERPLATE ]; class BoilerplateInfo { final String name, description, url; const BoilerplateInfo(this.name, this.description, this.url); @override String toString() => '$name ($description)'; }