2017-10-20 01:06:15 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:io';
|
|
|
|
import 'package:args/command_runner.dart';
|
|
|
|
import 'package:glob/glob.dart';
|
|
|
|
import 'package:homedir/homedir.dart';
|
2018-07-14 22:35:45 +00:00
|
|
|
import 'package:io/ansi.dart';
|
2017-10-20 01:06:15 +00:00
|
|
|
import 'package:mustache4dart/mustache4dart.dart' as mustache;
|
|
|
|
import 'package:path/path.dart' as p;
|
2018-07-14 22:35:45 +00:00
|
|
|
import 'package:prompts/prompts.dart' as prompts;
|
2018-07-14 21:47:49 +00:00
|
|
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
2017-10-20 01:06:15 +00:00
|
|
|
import 'package:yaml/yaml.dart' as yaml;
|
2018-07-14 22:35:45 +00:00
|
|
|
import '../util.dart';
|
2017-10-20 01:06:15 +00:00
|
|
|
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 {
|
2018-07-14 22:35:45 +00:00
|
|
|
if (argResults['wipe'] as bool) {
|
2017-10-20 14:32:10 +00:00
|
|
|
if (await installRepo.exists()) await installRepo.delete(recursive: true);
|
2018-07-14 22:35:45 +00:00
|
|
|
} else if (argResults['list'] as bool) {
|
2017-10-20 01:06:15 +00:00
|
|
|
var addons = await list();
|
|
|
|
print('${addons.length} add-on(s) installed:');
|
|
|
|
|
|
|
|
for (var addon in addons) {
|
|
|
|
print(' * ${addon.name}@${addon.version}: ${addon.description}');
|
|
|
|
}
|
2018-07-14 22:35:45 +00:00
|
|
|
} else if (argResults['update'] as bool) {
|
2017-10-20 01:06:15 +00:00
|
|
|
await update();
|
|
|
|
} else if (argResults.rest.isNotEmpty) {
|
|
|
|
if (!await installRepo.exists())
|
|
|
|
throw 'No local add-on database exists. Run `angel install --update` first.';
|
|
|
|
|
2018-07-14 22:35:45 +00:00
|
|
|
var pubspec = await loadPubspec();
|
2017-10-20 01:06:15 +00:00
|
|
|
|
|
|
|
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`.';
|
2017-10-20 06:29:23 +00:00
|
|
|
print('Installing $packageName...');
|
2017-10-20 01:06:15 +00:00
|
|
|
|
|
|
|
Map<String, dynamic> values = {
|
|
|
|
'project_name': pubspec.name,
|
|
|
|
'pubspec': pubspec,
|
|
|
|
};
|
|
|
|
|
|
|
|
List<Glob> globs = [];
|
|
|
|
|
2018-07-14 22:35:45 +00:00
|
|
|
var projectPubspec = await loadPubspec(packageDir);
|
2017-10-20 06:29:23 +00:00
|
|
|
var deps = projectPubspec.dependencies.keys
|
|
|
|
.map((k) {
|
2018-07-14 22:35:45 +00:00
|
|
|
var dep = projectPubspec.dependencies[k];
|
|
|
|
if (dep is HostedDependency)
|
|
|
|
return new MakerDependency(k, dep.version.toString());
|
|
|
|
return null;
|
|
|
|
})
|
2017-10-20 06:29:23 +00:00
|
|
|
.where((d) => d != null)
|
|
|
|
.toList();
|
|
|
|
|
|
|
|
deps.addAll(projectPubspec.devDependencies.keys.map((k) {
|
|
|
|
var dep = projectPubspec.devDependencies[k];
|
2018-07-14 22:35:45 +00:00
|
|
|
if (dep is HostedDependency)
|
|
|
|
return new MakerDependency(k, dep.version.toString(), dev: true);
|
2017-10-20 06:29:23 +00:00
|
|
|
return null;
|
|
|
|
}).where((d) => d != null));
|
|
|
|
|
|
|
|
await depend(deps);
|
|
|
|
|
2017-10-20 01:06:15 +00:00
|
|
|
var promptFile =
|
|
|
|
new File.fromUri(packageDir.uri.resolve('angel_cli.yaml'));
|
|
|
|
|
|
|
|
if (await promptFile.exists()) {
|
|
|
|
var contents = await promptFile.readAsString();
|
|
|
|
var y = yaml.loadYamlDocument(contents);
|
|
|
|
var cfg = y.contents.value as Map<String, dynamic>;
|
|
|
|
|
|
|
|
// Loads globs
|
|
|
|
if (cfg['templates'] is List) {
|
2018-07-14 22:35:45 +00:00
|
|
|
globs.addAll(
|
|
|
|
(cfg['templates'] as List).map((p) => new Glob(p.toString())));
|
2017-10-20 01:06:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (cfg['values'] is Map) {
|
|
|
|
var val = cfg['values'] as Map<String, dynamic>;
|
|
|
|
|
|
|
|
for (var key in val.keys) {
|
|
|
|
var desc = val[key]['description'] ?? key;
|
|
|
|
|
|
|
|
if (val[key]['type'] == 'prompt') {
|
2018-07-14 22:43:10 +00:00
|
|
|
values[key] = prompts.get(desc.toString(),
|
|
|
|
defaultsTo: val[key]['default']?.toString());
|
2017-10-20 01:06:15 +00:00
|
|
|
} else if (val[key]['type'] == 'choice') {
|
2018-07-14 22:43:10 +00:00
|
|
|
values[key] = prompts.choose(
|
|
|
|
desc.toString(), val[key]['choices'] as Iterable);
|
2017-10-20 01:06:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.');
|
2018-07-14 22:35:45 +00:00
|
|
|
allClear = prompts.getBool('Overwrite the existing file?');
|
2017-10-20 01:06:15 +00:00
|
|
|
if (allClear) await targetFile.delete();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (allClear) {
|
|
|
|
try {
|
|
|
|
var path = prefix.isEmpty ? name : '$prefix/$name';
|
|
|
|
|
|
|
|
if (globs.any((g) => g.matches(path))) {
|
2018-07-14 22:35:45 +00:00
|
|
|
print(
|
|
|
|
'Rendering Mustache template from ${entity.absolute.path} to ${targetFile.absolute.path}...');
|
2017-10-20 01:06:15 +00:00
|
|
|
var contents = await entity.readAsString();
|
|
|
|
var renderer = mustache.compile(contents);
|
|
|
|
var generated = renderer(values);
|
2018-07-14 22:35:45 +00:00
|
|
|
await targetFile.writeAsString(generated.toString());
|
2017-10-20 01:06:15 +00:00
|
|
|
} 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.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-14 21:47:49 +00:00
|
|
|
Future<List<Pubspec>> list() async {
|
2017-10-20 01:06:15 +00:00
|
|
|
if (!await installRepo.exists()) {
|
|
|
|
throw 'No local add-on database exists. Run `angel install --update` first.';
|
|
|
|
} else {
|
2018-07-14 21:47:49 +00:00
|
|
|
List<Pubspec> repos = [];
|
2017-10-20 01:06:15 +00:00
|
|
|
|
|
|
|
await for (var entity in installRepo.list()) {
|
|
|
|
if (entity is Directory) {
|
|
|
|
try {
|
2018-07-14 22:35:45 +00:00
|
|
|
repos.add(await loadPubspec(entity));
|
2017-10-20 01:06:15 +00:00
|
|
|
} 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.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|