platform-cli/lib/src/commands/init.dart
2022-04-26 09:29:35 +08:00

335 lines
11 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:io/ansi.dart';
import 'package:path/path.dart' as p;
import 'package:prompts/prompts.dart' as prompts;
import 'package:recase/recase.dart';
import '../random_string.dart' as rs;
import '../util.dart';
import 'key.dart';
import 'rename.dart';
class InitCommand extends Command {
final KeyCommand _key = KeyCommand();
@override
String get name => 'init';
@override
String get description =>
'Initializes a new Angel3 project in the current directory.';
InitCommand() {
argParser
..addFlag('offline',
help:
'Disable online fetching of boilerplates. Also disables `pub-get`.',
negatable: false)
..addFlag('pub-get', defaultsTo: true)
..addOption('project-name',
abbr: 'n', help: 'The name for this project.');
}
@override
void run() async {
if (argResults == null) {
print('Invalid arguements');
return;
}
var projectDir =
Directory(argResults!.rest.isEmpty ? '.' : argResults!.rest[0]);
print('Creating new Angel3 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(
File.fromUri(projectDir.uri.resolve('config/default.yaml')), secret);
secret = rs.randomAlphaNumeric(32);
print('Generated new production JWT secret: $secret');
await _key.changeSecret(
File.fromUri(projectDir.uri.resolve('config/production.yaml')), secret);
var name = argResults!.wasParsed('project-name')
? (argResults!['project-name'] as String)
: p.basenameWithoutExtension(
projectDir.absolute.uri.normalizePath().toFilePath());
name = ReCase(name).snakeCase;
print('Renaming project from "angel" to "$name"...');
await renamePubspec(projectDir, 'angel', name);
await renameDartFiles(projectDir, 'angel', name);
// Renaming executable files
if (argResults!['pub-get'] != false && argResults!['offline'] == false) {
print('Now running dart pub get...');
await _pubGet(projectDir);
}
print(green.wrap('$checkmark Successfully initialized Angel3 project.'));
stdout
..writeln()
..writeln(
'Congratulations! You are ready to start developing with Angel3!')
..write('To start the server (with ')
..write(cyan.wrap('hot-reloading'))
..write('), run ')
..write(magenta.wrap('`dart --observe bin/dev.dart`'))
..writeln(' in your terminal.')
..writeln()
..writeln('Find more documentation about Angel3:')
..writeln(' * https://angel3-framework.web.app')
..writeln(' * https://angel3-docs.dukefirehawk.com')
// ..writeln(
// ' * https://www.youtube.com/playlist?list=PLl3P3tmiT-frEV50VdH_cIrA2YqIyHkkY')
// ..writeln(' * https://medium.com/the-angel-framework')
// ..writeln(' * https://dart.academy/tag/angel')
..writeln()
..writeln('Happy coding!');
}
Future _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) {
print(e);
}
}
try {
if (self != false) await entity.delete(recursive: true);
} catch (e) {
print(e);
}
} else if (entity is File) {
try {
await entity.delete(recursive: true);
} catch (e) {
print(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(Directory(path));
case FileSystemEntityType.file:
return await _deleteRecursive(File(path));
default:
break;
}
}
}
Future _cloneRepo(Directory projectDir) async {
Directory boilerplateDir = Directory("./empty");
try {
if (await projectDir.exists()) {
var shouldDelete = prompts.getBool(
"Directory '${projectDir.absolute.path}' already exists. Overwrite it?");
if (!shouldDelete) {
throw '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);
}
}
// var boilerplate = basicBoilerplate;
print('Choose a project type before continuing:');
var boilerplate = prompts.choose(
'Choose a project type before continuing', boilerplates) ??
basicBoilerplate;
// Ultimately, we want a clone of every boilerplate locally on the system.
var boilerplateRootDir = Directory(p.join(angelDir.path, 'boilerplates'));
var boilerplateBasename = p.basenameWithoutExtension(boilerplate.url);
if (boilerplate.ref != '') {
boilerplateBasename += '.${boilerplate.ref}';
}
boilerplateDir =
Directory(p.join(boilerplateRootDir.path, boilerplateBasename));
await boilerplateRootDir.create(recursive: true);
var branch = boilerplate.ref;
if (branch == '') {
branch = 'master';
}
// If there is no clone existing, clone it.
if (!await boilerplateDir.exists()) {
if (argResults!['offline'] as bool) {
throw Exception(
'--offline was selected, but the "${boilerplate.name}" boilerplate has not yet been downloaded.');
}
print(
'Cloning "${boilerplate.name}" boilerplate from "${boilerplate.url}"...');
Process git;
if (boilerplate.ref == '') {
print(darkGray.wrap(
'\$ git clone --depth 1 ${boilerplate.url} ${boilerplateDir.absolute.path}'));
git = await Process.start(
'git',
[
'clone',
'--depth',
'1',
boilerplate.url,
boilerplateDir.absolute.path
],
mode: ProcessStartMode.inheritStdio,
);
} else {
// git clone --single-branch -b branch host:/dir.git
print(darkGray.wrap(
'\$ git clone --depth 1 --single-branch -b ${boilerplate.ref} ${boilerplate.url} ${boilerplateDir.absolute.path}'));
git = await Process.start(
'git',
[
'clone',
'--depth',
'1',
'--single-branch',
'-b',
boilerplate.ref,
boilerplate.url,
boilerplateDir.absolute.path
],
mode: ProcessStartMode.inheritStdio,
);
}
if (await git.exitCode != 0) {
throw Exception('Could not clone repo.');
}
}
// Otherwise, pull from git.
else if (!(argResults!['offline'] as bool)) {
print(darkGray.wrap('\$ git pull origin $branch'));
var git = await Process.start('git', ['pull', 'origin', branch],
mode: ProcessStartMode.inheritStdio,
workingDirectory: boilerplateDir.absolute.path);
if (await git.exitCode != 0) {
print(yellow.wrap(
'Update of $branch failed. Attempting to continue with existing contents.'));
}
} else {
print(darkGray.wrap(
'Using existing contents of "${boilerplate.name}" boilerplate.'));
}
// Next, just copy everything into the given directory.
await copyDirectory(boilerplateDir, projectDir);
if (boilerplate.needsPrebuild) {
await preBuild(projectDir).catchError((_) => null);
}
var gitDir = Directory.fromUri(projectDir.uri.resolve('.git'));
if (await gitDir.exists()) await gitDir.delete(recursive: true);
} catch (e) {
await boilerplateDir.delete(recursive: true).catchError((e) {
print('Got error: ${e.error}');
});
if (e is! String) {
print(red.wrap('$ballot Could not initialize Angel3 project.'));
}
rethrow;
}
}
Future _pubGet(Directory projectDir) async {
var dartPath = "dart";
print(darkGray.wrap('Running "$dartPath"...'));
print(darkGray.wrap('\$ $dartPath pub get'));
var dart = await Process.start(dartPath, ['pub', 'get'],
workingDirectory: projectDir.absolute.path,
mode: ProcessStartMode.inheritStdio);
var code = await dart.exitCode;
print('Dart process exited with code $code');
}
}
Future preBuild(Directory projectDir) async {
// Run build
// print('Running `dart run build_runner build`...');
print(darkGray.wrap('\$ dart run build_runner build'));
var build = await Process.start("dart", ['run', 'build_runner', 'build'],
workingDirectory: projectDir.absolute.path,
mode: ProcessStartMode.inheritStdio);
var buildCode = await build.exitCode;
if (buildCode != 0) throw Exception('Failed to pre-build resources.');
}
const repoLocation = 'https://github.com/dukefirehawk';
const BoilerplateInfo graphQLBoilerplate = BoilerplateInfo(
'GraphQL',
'A starter application with GraphQL support.',
'$repoLocation/boilerplates.git',
ref: 'angel3-graphql',
);
const BoilerplateInfo ormBoilerplate = BoilerplateInfo(
'ORM',
'A starter application with ORM support.',
'$repoLocation/boilerplates.git',
ref: 'angel3-orm',
);
const BoilerplateInfo basicBoilerplate = BoilerplateInfo(
'Basic',
'A basic starter application with minimal packages.',
'$repoLocation/boilerplates.git',
ref: 'angel3-basic');
const BoilerplateInfo sharedBoilerplate = BoilerplateInfo(
'Shared',
'Holds common models and files shared across multiple Dart projects.',
'$repoLocation/boilerplate_shared.git');
const BoilerplateInfo sharedOrmBoilerplate = BoilerplateInfo(
'Shared (ORM)',
'Holds common models and files shared across multiple Dart projects.',
'$repoLocation/boilerplate_shared.git',
ref: 'orm',
);
const List<BoilerplateInfo> boilerplates = [
basicBoilerplate,
ormBoilerplate,
graphQLBoilerplate,
//sharedBoilerplate,
//sharedOrmBoilerplate,
];
class BoilerplateInfo {
final String name, description, url;
final String ref;
final bool needsPrebuild;
const BoilerplateInfo(this.name, this.description, this.url,
{this.ref = '', this.needsPrebuild = false});
@override
String toString() => '$name ($description)';
}