diff --git a/.idea/angel_cli.iml b/.idea/angel_cli.iml
index 0f3fa76..78848ff 100644
--- a/.idea/angel_cli.iml
+++ b/.idea/angel_cli.iml
@@ -4,21 +4,11 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/bin/angel.dart b/bin/angel.dart
index 90aa8cd..ccb0b14 100644
--- a/bin/angel.dart
+++ b/bin/angel.dart
@@ -17,6 +17,7 @@ main(List args) async {
..addCommand(new KeyCommand())
..addCommand(new ServiceCommand())
..addCommand(new InitCommand())
+ ..addCommand(new InstallCommand())
..addCommand(new TestCommand())
..addCommand(new PluginCommand())
..addCommand(new StartCommand())
diff --git a/lib/src/commands/commands.dart b/lib/src/commands/commands.dart
index d536055..2853422 100644
--- a/lib/src/commands/commands.dart
+++ b/lib/src/commands/commands.dart
@@ -4,6 +4,7 @@ export 'controller.dart';
export "doctor.dart";
export "key.dart";
export "init.dart";
+export "install.dart";
export "make.dart";
export "plugin.dart";
export "rename.dart";
diff --git a/lib/src/commands/init.dart b/lib/src/commands/init.dart
index c854f24..cb5480a 100644
--- a/lib/src/commands/init.dart
+++ b/lib/src/commands/init.dart
@@ -19,7 +19,14 @@ class InitCommand extends Command {
"Initializes a new Angel project in the current directory.";
InitCommand() {
- argParser.addFlag('pub-get', defaultsTo: true);
+ argParser
+ ..addFlag('pub-get', defaultsTo: true)
+ ..addFlag(
+ 'legacy',
+ help:
+ 'Generate a project using Angel 1.0.x boilerplates, rather than 1.1.x+.',
+ negatable: false,
+ );
}
@override
@@ -60,7 +67,7 @@ class InitCommand extends Command {
..text('\nCongratulations! You are ready to start developing with Angel!')
..text('\nTo start the server (with file watching), run ')
..magenta()
- ..text('`dart bin/server.dart`')
+ ..text(argResults['legacy'] ? '`dart bin/server.dart`' : '`dart bin/dev.dart`')
..normal()
..text(' in your terminal.')
..text('\n\nFind more documentation about Angel:')
@@ -122,7 +129,9 @@ class InitCommand extends Command {
}
print('Choose a project type before continuing:');
- var boilerplateChooser = new Chooser(allBoilerplates);
+ var boilerplateChooser = new Chooser(
+ argResults['legacy'] ? legacyBoilerplates : boilerplates,
+ );
var boilerplate = await boilerplateChooser.choose();
print(
@@ -137,6 +146,17 @@ class InitCommand extends Command {
throw new Exception("Could not clone repo.");
}
+ if (boilerplate.ref != null) {
+ git = await Process.start("git", ["checkout", boilerplate.ref]);
+
+ stdout.addStream(git.stdout);
+ stderr.addStream(git.stderr);
+
+ if (await git.exitCode != 0) {
+ throw new Exception("Could not checkout branch ${boilerplate.ref}.");
+ }
+ }
+
var gitDir = new Directory.fromUri(projectDir.uri.resolve(".git"));
if (await gitDir.exists()) await gitDir.delete(recursive: true);
} catch (e) {
@@ -176,30 +196,45 @@ preBuild(Directory projectDir) async {
}
const BoilerplateInfo fullApplicationBoilerplate = const BoilerplateInfo(
- 'Full Application',
- 'A complete project including authentication, multi-threading, and more.',
- 'https://github.com/angel-dart/angel.git');
+ 'Full Application',
+ 'A complete project including authentication, multi-threading, and more.',
+ 'https://github.com/angel-dart/angel.git',
+ ref: '1.0.x',
+);
const BoilerplateInfo lightBoilerplate = const BoilerplateInfo(
- 'Light',
- 'Minimal starting point for new users.',
- 'https://github.com/angel-dart/boilerplate_light.git');
+ 'Light',
+ 'Minimal starting point for new users.',
+ 'https://github.com/angel-dart/boilerplate_light.git',
+);
const BoilerplateInfo ormBoilerplate = const BoilerplateInfo(
- 'ORM',
- "A starting point for applications that use Angel's ORM.",
- 'https://github.com/angel-dart/boilerplate_orm.git');
+ 'ORM',
+ "A starting point for applications that use Angel's ORM.",
+ 'https://github.com/angel-dart/boilerplate_orm.git',
+);
-const List allBoilerplates = const [
+const List legacyBoilerplates = const [
fullApplicationBoilerplate,
lightBoilerplate,
ormBoilerplate
];
-class BoilerplateInfo {
- final String name, description, url;
+const BoilerplateInfo basicBoilerplate = const BoilerplateInfo(
+ 'Basic',
+ 'Minimal starting point - A simple server with only a few additional packages.',
+ 'https://github.com/angel-dart/angel.git',
+);
- const BoilerplateInfo(this.name, this.description, this.url);
+const List boilerplates = const [
+ basicBoilerplate,
+ ormBoilerplate,
+];
+
+class BoilerplateInfo {
+ final String name, description, url, ref;
+
+ const BoilerplateInfo(this.name, this.description, this.url, {this.ref});
@override
String toString() => '$name ($description)';
diff --git a/lib/src/commands/install.dart b/lib/src/commands/install.dart
new file mode 100644
index 0000000..3f2721c
--- /dev/null
+++ b/lib/src/commands/install.dart
@@ -0,0 +1,245 @@
+import 'dart:async';
+import 'dart:io';
+import 'package:args/command_runner.dart';
+import 'package:console/console.dart';
+import 'package:glob/glob.dart';
+import 'package:homedir/homedir.dart';
+import 'package:mustache4dart/mustache4dart.dart' as mustache;
+import 'package:path/path.dart' as p;
+import 'package:pubspec/pubspec.dart';
+import 'package:yaml/yaml.dart' as yaml;
+import 'make/maker.dart';
+
+class InstallCommand extends Command {
+ static const String repo = 'https://github.com/angel-dart/install.git';
+ static final Directory installRepo =
+ new Directory.fromUri(homeDir.uri.resolve('./.angel/addons'));
+
+ @override
+ String get name => 'install';
+
+ @override
+ String get description =>
+ 'Installs additional add-ons to minimize boilerplate.';
+
+ InstallCommand() {
+ argParser
+ ..addFlag(
+ 'list',
+ help: 'List all currently-installed add-ons.',
+ negatable: false,
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'update',
+ help: 'Update the local add-on repository.',
+ negatable: false,
+ defaultsTo: false,
+ )
+ ..addFlag(
+ 'wipe',
+ help: 'Wipe the local add-on repository.',
+ negatable: false,
+ defaultsTo: false,
+ );
+ }
+
+ @override
+ run() async {
+ if (argResults['wipe']) {
+ if (await installRepo.exists()) await installRepo.delete();
+ } else if (argResults['list']) {
+ var addons = await list();
+ print('${addons.length} add-on(s) installed:');
+
+ for (var addon in addons) {
+ print(' * ${addon.name}@${addon.version}: ${addon.description}');
+ }
+ } else if (argResults['update']) {
+ await update();
+ } else if (argResults.rest.isNotEmpty) {
+ if (!await installRepo.exists())
+ throw 'No local add-on database exists. Run `angel install --update` first.';
+
+ var pubspec = await PubSpec.load(Directory.current);
+
+ for (var packageName in argResults.rest) {
+ var packageDir =
+ new Directory.fromUri(installRepo.uri.resolve(packageName));
+
+ if (!await packageDir.exists())
+ throw 'No add-on named "$packageName" is installed. You might need to run `angel install --update`.';
+
+ Map values = {
+ 'project_name': pubspec.name,
+ 'pubspec': pubspec,
+ };
+
+ List globs = [];
+
+ var promptFile =
+ new File.fromUri(packageDir.uri.resolve('angel_cli.yaml'));
+ var projectPubspec = await PubSpec.load(packageDir);
+
+ if (await promptFile.exists()) {
+ var contents = await promptFile.readAsString();
+ var y = yaml.loadYamlDocument(contents);
+ var cfg = y.contents.value as Map;
+
+ // Loads globs
+ if (cfg['templates'] is List) {
+ globs.addAll(cfg['templates'].map((p) => new Glob(p)));
+ }
+
+ if (cfg['values'] is Map) {
+ var val = cfg['values'] as Map;
+
+ for (var key in val.keys) {
+ var desc = val[key]['description'] ?? key;
+
+ if (val[key]['type'] == 'prompt') {
+ Prompter prompt;
+
+ if (val[key]['default'] != null) {
+ prompt = new Prompter('$desc (${val[key]['default']}): ');
+ } else {
+ prompt = new Prompter('$desc: ');
+ }
+
+ if (val[key]['default'] != null) {
+ var v = await prompt.prompt();
+ v = v.isNotEmpty ? v : val[key]['default'];
+ values[key] = v;
+ } else
+ values[key] =
+ await prompt.prompt(checker: (s) => s.isNotEmpty);
+ } else if (val[key]['type'] == 'choice') {
+ var chooser =
+ new Chooser(val[key]['choices'], message: '$desc: ');
+ values[key] = await chooser.choose();
+ }
+ }
+ }
+
+ var deps = projectPubspec.dependencies.keys.map((k) {
+ var dep = projectPubspec.dependencies[k];
+ if (dep is HostedReference)
+ return new MakerDependency(k, dep.versionConstraint.toString());
+ return null;
+ }).where((d) => d != null);
+ await depend(deps);
+ }
+
+ Future merge(Directory src, Directory dst, String prefix) async {
+ if (!await src.exists()) return;
+ print('Copying ${src.absolute.path} into ${dst.absolute.path}...');
+ if (!await dst.exists()) await dst.create(recursive: true);
+
+ await for (var entity in src.list()) {
+ if (entity is Directory) {
+ var name = p.basename(entity.path);
+ var newDir = new Directory.fromUri(dst.uri.resolve(name));
+ await merge(
+ entity, newDir, prefix.isEmpty ? name : '$prefix/$name');
+ } else if (entity is File &&
+ !entity.path.endsWith('angel_cli.yaml')) {
+ var name = p.basename(entity.path);
+ var target = dst.uri.resolve(name);
+ var targetFile = new File.fromUri(target);
+ bool allClear = !await targetFile.exists();
+
+ if (!allClear) {
+ print('The file ${entity.absolute.path} already exists.');
+ var p = new Prompter('Overwrite the existing file? [y/N]');
+ var answer = await p.prompt(
+ checker: (s) => s.trim() == 'y' || s.trim() == 'N');
+ allClear = answer == 'y';
+ if (allClear) await targetFile.delete();
+ }
+
+ if (allClear) {
+ try {
+ var path = prefix.isEmpty ? name : '$prefix/$name';
+
+ if (globs.any((g) => g.matches(path))) {
+ print('Rendering Mustache template from ${entity.absolute
+ .path} to ${targetFile.absolute.path}...');
+ var contents = await entity.readAsString();
+ var renderer = mustache.compile(contents);
+ var generated = renderer(values);
+ await targetFile.writeAsString(generated);
+ } else {
+ print(
+ 'Copying ${entity.absolute.path} to ${targetFile.absolute.path}...');
+ await targetFile.parent.create(recursive: true);
+ await entity.copy(targetFile.absolute.path);
+ }
+ } catch (_) {
+ print('Failed to copy.');
+ }
+ } else {
+ print('Skipped ${entity.absolute.path}.');
+ }
+ }
+ }
+ }
+
+ await merge(new Directory.fromUri(packageDir.uri.resolve('files')),
+ Directory.current, '');
+ print('Successfully installed $packageName@${projectPubspec.version}.');
+ }
+ } else {
+ print('No add-ons were specified to be installed.');
+ }
+ }
+
+ Future> list() async {
+ if (!await installRepo.exists()) {
+ throw 'No local add-on database exists. Run `angel install --update` first.';
+ } else {
+ List repos = [];
+
+ await for (var entity in installRepo.list()) {
+ if (entity is Directory) {
+ try {
+ repos.add(await PubSpec.load(entity));
+ } catch (_) {
+ // Ignore failures...
+ }
+ }
+ }
+
+ return repos;
+ }
+ }
+
+ Future update() async {
+ Process git;
+
+ if (!await installRepo.exists()) {
+ git = await Process.start('git', [
+ 'clone',
+ repo,
+ installRepo.absolute.path,
+ ]);
+ } else {
+ git = await Process.start(
+ 'git',
+ [
+ 'pull',
+ 'origin',
+ 'master',
+ ],
+ workingDirectory: installRepo.absolute.path,
+ );
+ }
+
+ git..stdout.listen(stdout.add)..stderr.listen(stderr.add);
+
+ var code = await git.exitCode;
+
+ if (code != 0) {
+ throw 'git exited with code $code.';
+ }
+ }
+}
diff --git a/lib/src/commands/pubspec.update.g.dart b/lib/src/commands/pubspec.update.g.dart
index 7cf40e1..89067c0 100644
--- a/lib/src/commands/pubspec.update.g.dart
+++ b/lib/src/commands/pubspec.update.g.dart
@@ -3,7 +3,7 @@ import 'dart:convert';
import 'package:http/src/base_client.dart' as http;
import 'package:pub_semver/pub_semver.dart';
-final Version PACKAGE_VERSION = new Version(1, 1, 5, build: '2');
+final Version PACKAGE_VERSION = new Version(1, 2, 0);
Future fetchCurrentVersion(http.BaseClient client) async {
var response =
await client.get('https://pub.dartlang.org/api/packages/angel_cli');
diff --git a/lib/src/commands/service_generators/mongo.dart b/lib/src/commands/service_generators/mongo.dart
index c81153e..eb956d4 100644
--- a/lib/src/commands/service_generators/mongo.dart
+++ b/lib/src/commands/service_generators/mongo.dart
@@ -21,7 +21,10 @@ class MongoServiceGenerator extends ServiceGenerator {
@override
void applyToLibrary(LibraryBuilder library, String name, String lower) {
- library.addMember(new ImportBuilder('package:mongo_dart/mongo_dart.dart'));
+ library.addMembers([
+ new ImportBuilder('package:angel_mongo/angel_mongo.dart'),
+ new ImportBuilder('package:mongo_dart/mongo_dart.dart'),
+ ]);
}
@override
diff --git a/pubspec.yaml b/pubspec.yaml
index 8c31aa7..3da2768 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,7 +2,7 @@ author: "Tobe O "
description: "Command-line tools for the Angel framework."
homepage: "https://github.com/angel-dart/angel_cli"
name: "angel_cli"
-version: 1.1.5+2
+version: 1.2.0
dependencies:
# analyzer: "^0.29.0"
args: ^0.13.4
@@ -10,9 +10,11 @@ dependencies:
console: "^2.2.3"
dart_style: ^1.0.0
glob: "^1.1.0"
+ homedir: ^0.0.4
http: ^0.11.3
id: "^1.0.0"
inflection: "^0.4.1"
+ mustache4dart: ^2.1.0
pubspec: "^0.0.14"
random_string: "^0.0.1"
recase: "^1.0.0"