blob: d387bd4fcfea1bee4394214976321915d4fe0b6b [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io;
import 'dart:math' as math;
import 'package:path/path.dart' as p;
import '../core.dart';
import '../sdk.dart';
import '../templates.dart';
import '../utils.dart';
/// A command to create a new project from a set of templates.
class CreateCommand extends DartdevCommand {
static const String cmdName = 'create';
static String defaultTemplateId = 'console-simple';
static final List<String> legalTemplateIds =
generators.map((generator) => generator.id).toList();
CreateCommand({bool verbose = false})
: super(cmdName, 'Create a new Dart project.') {
argParser.addOption(
'template',
allowed: legalTemplateIds,
help: 'The project template to use.',
defaultsTo: defaultTemplateId,
abbr: 't',
);
argParser.addFlag('pub',
defaultsTo: true,
help: "Whether to run 'pub get' after the project has been created.");
argParser.addFlag(
'list-templates',
negatable: false,
hide: !verbose,
help: 'List the available templates in JSON format.',
);
argParser.addFlag(
'force',
negatable: false,
help: 'Force project generation, even if the target directory already '
'exists.',
);
}
@override
String get invocation => '${super.invocation} <directory>';
@override
FutureOr<int> run() async {
if (argResults['list-templates']) {
log.stdout(_availableTemplatesJson());
return 0;
}
if (argResults.rest.isEmpty) {
printUsage();
return 1;
}
String templateId = argResults['template'];
String dir = argResults.rest.first;
var targetDir = io.Directory(dir).absolute;
dir = targetDir.path;
if (targetDir.existsSync() && !argResults['force']) {
log.stderr(
"Directory '$dir' already exists "
"(use '--force' to force project generation).",
);
return 73;
}
String projectName = p.basename(dir);
if (projectName == '.') {
projectName = p.basename(io.Directory.current.path);
}
projectName = normalizeProjectName(projectName);
if (!isValidPackageName(projectName)) {
log.stderr('"$projectName" is not a valid Dart project name.\n\n'
'See https://dart.dev/tools/pub/pubspec#name for more information.');
return 73;
}
log.stdout(
'Creating ${log.ansi.emphasized(projectName)} '
'using template $templateId...',
);
log.stdout('');
var generator = getGenerator(templateId);
generator.generate(
projectName,
DirectoryGeneratorTarget(generator, io.Directory(dir)),
);
if (argResults['pub']) {
if (!Sdk.checkArtifactExists(sdk.pubSnapshot)) {
return 255;
}
log.stdout('');
var progress = log.progress('Running pub get');
var process = await startDartProcess(
sdk,
[sdk.pubSnapshot, 'get', '--no-precompile'],
cwd: dir,
);
// Run 'pub get'. We display output from the pub command, but keep the
// output terse. This is to give the user a sense of the work that pub
// did without scrolling the previous stdout sections off the screen.
var buffer = StringBuffer();
routeToStdout(
process,
logToTrace: true,
listener: (str) {
// Filter lines like '+ multi_server_socket 1.0.2'.
if (!str.startsWith('+ ')) {
buffer.writeln(' $str');
}
},
);
int code = await process.exitCode;
if (code != 0) return code;
progress.finish(showTiming: true);
log.stdout(buffer.toString().trimRight());
}
log.stdout('');
log.stdout(
'Created project $projectName in ${p.relative(dir)}! In order to get '
'started, run the following commands:');
log.stdout('');
log.stdout(log.ansi.emphasized(' cd ${p.relative(dir)}'));
log.stdout(log.ansi.emphasized(' dart run'));
log.stdout('');
return 0;
}
@override
String get usageFooter {
int width = legalTemplateIds.map((s) => s.length).reduce(math.max);
String desc = generators.map((g) {
String suffix = g.id == defaultTemplateId ? ' (default)' : '';
return ' ${g.id.padLeft(width)}: ${g.description}$suffix';
}).join('\n');
return '\nAvailable templates:\n$desc';
}
String _availableTemplatesJson() {
var items = generators.map((Generator generator) {
var m = {
'name': generator.id,
'label': generator.label,
'description': generator.description,
'categories': generator.categories
};
if (generator.entrypoint != null) {
m['entrypoint'] = generator.entrypoint.path;
}
return m;
});
JsonEncoder encoder = const JsonEncoder.withIndent(' ');
return encoder.convert(items.toList());
}
}
class DirectoryGeneratorTarget extends GeneratorTarget {
final Generator generator;
final io.Directory dir;
DirectoryGeneratorTarget(this.generator, this.dir) {
if (!dir.existsSync()) {
dir.createSync();
}
}
@override
void createFile(String path, List<int> contents) {
io.File file = io.File(p.join(dir.path, path));
String name = p.relative(file.path, from: dir.path);
log.stdout(' $name');
file.createSync(recursive: true);
file.writeAsBytesSync(contents);
}
}