mirror of
https://github.com/dart-lang/sdk
synced 2024-07-19 20:17:27 +00:00
[dartdev] add a dartdev 'create' command
Change-Id: I95625a9c422335ba5de92c887afce9eb564d6a04 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/133460 Commit-Queue: Devon Carew <devoncarew@google.com> Reviewed-by: Jaime Wren <jwren@google.com>
This commit is contained in:
parent
ed441e48d0
commit
c8ed304e97
|
@ -94,6 +94,7 @@ sourcemap_testing:pkg/sourcemap_testing/lib
|
|||
source_maps:third_party/pkg/source_maps/lib
|
||||
source_span:third_party/pkg/source_span/lib
|
||||
stack_trace:third_party/pkg/stack_trace/lib
|
||||
stagehand:third_party/pkg/stagehand/lib
|
||||
status_file:pkg/status_file/lib
|
||||
stream_channel:third_party/pkg/stream_channel/lib
|
||||
string_scanner:third_party/pkg/string_scanner/lib
|
||||
|
|
3
DEPS
3
DEPS
|
@ -134,6 +134,7 @@ vars = {
|
|||
"source_maps_tag": "8af7cc1a1c3a193c1fba5993ce22a546a319c40e",
|
||||
"source_span_tag": "1.5.5",
|
||||
"stack_trace_tag": "1.9.3",
|
||||
"stagehand_tag": "v3.3.6",
|
||||
"stream_channel_tag": "2.0.0",
|
||||
"string_scanner_tag": "1.0.3",
|
||||
"test_descriptor_tag": "1.1.1",
|
||||
|
@ -380,6 +381,8 @@ deps = {
|
|||
"@" + Var("source_map_stack_trace_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/stack_trace":
|
||||
Var("dart_git") + "stack_trace.git" + "@" + Var("stack_trace_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/stagehand":
|
||||
Var("dart_git") + "stagehand.git" + "@" + Var("stagehand_tag"),
|
||||
Var("dart_root") + "/third_party/pkg/stream_channel":
|
||||
Var("dart_git") + "stream_channel.git" +
|
||||
"@" + Var("stream_channel_tag"),
|
||||
|
|
3
pkg/dartdev/.gitignore
vendored
3
pkg/dartdev/.gitignore
vendored
|
@ -8,3 +8,6 @@ pubspec.lock
|
|||
|
||||
# Directory created by dartdoc
|
||||
doc/api/
|
||||
|
||||
# Directory created by pub
|
||||
.dart_tool/
|
||||
|
|
|
@ -4,4 +4,4 @@ A command-line utility for Dart development.
|
|||
|
||||
## Docs
|
||||
|
||||
This tool is currently under active development.
|
||||
This tool is currently under active development.
|
||||
|
|
|
@ -9,7 +9,7 @@ import 'package:dartdev/dartdev.dart';
|
|||
|
||||
/// The entry point for dartdev.
|
||||
main(List<String> args) async {
|
||||
final runner = DartdevRunner();
|
||||
final runner = DartdevRunner(args);
|
||||
try {
|
||||
dynamic result = await runner.run(args);
|
||||
exit(result is int ? result : 0);
|
||||
|
|
|
@ -6,25 +6,29 @@ import 'package:args/args.dart';
|
|||
import 'package:args/command_runner.dart';
|
||||
import 'package:cli_util/cli_logging.dart';
|
||||
|
||||
import 'src/commands/create.dart';
|
||||
import 'src/commands/format.dart';
|
||||
import 'src/core.dart';
|
||||
|
||||
class DartdevRunner extends CommandRunner {
|
||||
class DartdevRunner<int> extends CommandRunner {
|
||||
static const String dartdevDescription =
|
||||
'A command-line utility for Dart development';
|
||||
|
||||
DartdevRunner() : super('dartdev', '$dartdevDescription.') {
|
||||
DartdevRunner(List<String> args) : super('dartdev', '$dartdevDescription.') {
|
||||
final bool verbose = args.contains('-v') || args.contains('--verbose');
|
||||
|
||||
argParser.addFlag('verbose',
|
||||
abbr: 'v', negatable: false, help: 'Show verbose output.');
|
||||
|
||||
// The list of currently supported commands:
|
||||
addCommand(FormatCommand());
|
||||
addCommand(CreateCommand(verbose: verbose));
|
||||
addCommand(FormatCommand(verbose: verbose));
|
||||
}
|
||||
|
||||
@override
|
||||
Future runCommand(ArgResults results) async {
|
||||
Future<int> runCommand(ArgResults results) async {
|
||||
isVerbose = results['verbose'];
|
||||
|
||||
final Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
|
||||
log = isVerbose ? Logger.verbose(ansi: ansi) : Logger.standard(ansi: ansi);
|
||||
|
||||
return await super.runCommand(results);
|
||||
|
|
180
pkg/dartdev/lib/src/commands/create.dart
Normal file
180
pkg/dartdev/lib/src/commands/create.dart
Normal file
|
@ -0,0 +1,180 @@
|
|||
// 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 path;
|
||||
import 'package:stagehand/stagehand.dart' as stagehand;
|
||||
|
||||
import '../core.dart';
|
||||
import '../sdk.dart';
|
||||
|
||||
/// A command to create a new project from a set of templates.
|
||||
class CreateCommand extends DartdevCommand {
|
||||
static String defaultTemplateId = 'console-full';
|
||||
|
||||
static List<String> legalTemplateIds = [
|
||||
'console-full',
|
||||
'package-simple',
|
||||
'web-simple'
|
||||
];
|
||||
|
||||
static Iterable<stagehand.Generator> get generators =>
|
||||
stagehand.generators.where((g) => legalTemplateIds.contains(g.id));
|
||||
|
||||
static stagehand.Generator retrieveTemplateGenerator(String templateId) =>
|
||||
stagehand.getGenerator(templateId);
|
||||
|
||||
CreateCommand({bool verbose = false})
|
||||
: super('create', 'Create a new project.') {
|
||||
argParser.addOption(
|
||||
'template',
|
||||
allowed: legalTemplateIds,
|
||||
help: 'The project template to use.',
|
||||
defaultsTo: defaultTemplateId,
|
||||
);
|
||||
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);
|
||||
if (targetDir.existsSync() && !(argResults['force'])) {
|
||||
log.stderr(
|
||||
"Directory '$dir' already exists (use '--force' to force project generation).");
|
||||
return 73;
|
||||
}
|
||||
|
||||
log.stdout(
|
||||
'Creating ${log.ansi.emphasized(path.absolute(dir))} using template $templateId...');
|
||||
log.stdout('');
|
||||
|
||||
var generator = retrieveTemplateGenerator(templateId);
|
||||
await generator.generate(
|
||||
path.basename(dir),
|
||||
DirectoryGeneratorTarget(generator, io.Directory(dir)),
|
||||
);
|
||||
|
||||
if (argResults['pub']) {
|
||||
log.stdout('');
|
||||
var progress = log.progress('Running pub get');
|
||||
var process = await startProcess(
|
||||
sdk.pub,
|
||||
['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 $dir! In order to get started, type:');
|
||||
log.stdout('');
|
||||
log.stdout(log.ansi.emphasized(' cd ${path.relative(dir)}'));
|
||||
// TODO(devoncarew): Once we have a 'run' command, print out here how to run
|
||||
// the app.
|
||||
log.stdout('');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
String get usageFooter {
|
||||
int width = legalTemplateIds.map((s) => s.length).reduce(math.max);
|
||||
String desc = generators
|
||||
.map((g) => ' ${g.id.padLeft(width)}: ${g.description}')
|
||||
.join('\n');
|
||||
return '\nAvailable templates:\n$desc';
|
||||
}
|
||||
|
||||
String _availableTemplatesJson() {
|
||||
var items = generators.map((stagehand.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 = JsonEncoder.withIndent(' ');
|
||||
return encoder.convert(items.toList());
|
||||
}
|
||||
}
|
||||
|
||||
class DirectoryGeneratorTarget extends stagehand.GeneratorTarget {
|
||||
final stagehand.Generator generator;
|
||||
final io.Directory dir;
|
||||
|
||||
DirectoryGeneratorTarget(this.generator, this.dir) {
|
||||
dir.createSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future createFile(String filePath, List<int> contents) async {
|
||||
io.File file = io.File(path.join(dir.path, filePath));
|
||||
|
||||
String name = path.relative(file.path, from: dir.path);
|
||||
log.stdout(' $name');
|
||||
|
||||
await file.create(recursive: true);
|
||||
await file.writeAsBytes(contents);
|
||||
}
|
||||
}
|
|
@ -2,16 +2,19 @@
|
|||
// 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 '../core.dart';
|
||||
import '../sdk.dart';
|
||||
|
||||
class FormatCommand extends DartdevCommand {
|
||||
FormatCommand() : super('format', 'Format one or more Dart files.') {
|
||||
FormatCommand({bool verbose = false})
|
||||
: super('format', 'Format one or more Dart files.') {
|
||||
// TODO(jwren) add all options and flags
|
||||
}
|
||||
|
||||
@override
|
||||
run() async {
|
||||
FutureOr<int> run() async {
|
||||
// TODO(jwren) implement verbose in dart_style
|
||||
// dartfmt doesn't have '-v' or '--verbose', so remove from the argument list
|
||||
var args = List.from(argResults.arguments)
|
||||
|
|
|
@ -8,11 +8,10 @@ import 'dart:io';
|
|||
import 'package:args/command_runner.dart';
|
||||
import 'package:cli_util/cli_logging.dart';
|
||||
|
||||
Ansi ansi = Ansi(Ansi.terminalSupportsAnsi);
|
||||
Logger log;
|
||||
bool isVerbose = false;
|
||||
|
||||
abstract class DartdevCommand extends Command {
|
||||
abstract class DartdevCommand<int> extends Command {
|
||||
final String _name;
|
||||
final String _description;
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ dependencies:
|
|||
cli_util: ^0.1.0
|
||||
intl: ^0.16.0
|
||||
path: ^1.6.2
|
||||
stagehand: 3.3.6
|
||||
watcher: ^0.9.7+13
|
||||
yaml: ^2.2.0
|
||||
|
||||
dev_dependencies:
|
||||
test: ^1.0.0
|
||||
pedantic: ^1.8.0
|
||||
pedantic: ^1.8.0
|
||||
|
|
97
pkg/dartdev/test/commands/create_test.dart
Normal file
97
pkg/dartdev/test/commands/create_test.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
// 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:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dartdev/src/commands/create.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../utils.dart';
|
||||
|
||||
void main() {
|
||||
group('create', defineCreate);
|
||||
}
|
||||
|
||||
void defineCreate() {
|
||||
TestProject p;
|
||||
|
||||
setUp(() => p = null);
|
||||
|
||||
tearDown(() => p?.dispose());
|
||||
|
||||
test('default template exists', () {
|
||||
expect(CreateCommand.legalTemplateIds,
|
||||
contains(CreateCommand.defaultTemplateId));
|
||||
});
|
||||
|
||||
test('all templates exist', () {
|
||||
for (String templateId in CreateCommand.legalTemplateIds) {
|
||||
expect(CreateCommand.legalTemplateIds, contains(templateId));
|
||||
}
|
||||
});
|
||||
|
||||
test('list templates', () {
|
||||
p = project();
|
||||
|
||||
ProcessResult result = p.runSync('create', ['--list-templates']);
|
||||
expect(result.exitCode, 0);
|
||||
|
||||
String output = result.stdout.toString();
|
||||
var parsedResult = jsonDecode(output);
|
||||
expect(parsedResult, hasLength(CreateCommand.legalTemplateIds.length));
|
||||
expect(parsedResult[0]['name'], isNotNull);
|
||||
expect(parsedResult[0]['label'], isNotNull);
|
||||
expect(parsedResult[0]['description'], isNotNull);
|
||||
});
|
||||
|
||||
test('no directory given', () {
|
||||
p = project();
|
||||
|
||||
ProcessResult result = p.runSync('create');
|
||||
expect(result.exitCode, 1);
|
||||
});
|
||||
|
||||
test('directory already exists', () {
|
||||
p = project();
|
||||
|
||||
ProcessResult result = p.runSync('create', [
|
||||
'--no-pub',
|
||||
'--template',
|
||||
CreateCommand.defaultTemplateId,
|
||||
p.dir.path
|
||||
]);
|
||||
expect(result.exitCode, 73);
|
||||
});
|
||||
|
||||
test('bad template id', () {
|
||||
p = project();
|
||||
|
||||
ProcessResult result =
|
||||
p.runSync('create', ['--no-pub', '--template', 'foo-bar', p.dir.path]);
|
||||
expect(result.exitCode, isNot(0));
|
||||
});
|
||||
|
||||
// Create tests for each template.
|
||||
for (String templateId in CreateCommand.legalTemplateIds) {
|
||||
test('create $templateId', () {
|
||||
p = project();
|
||||
|
||||
ProcessResult result = p.runSync('create',
|
||||
['--force', '--no-pub', '--template', templateId, p.dir.path]);
|
||||
expect(result.exitCode, 0);
|
||||
|
||||
String projectName = path.basename(p.dir.path);
|
||||
|
||||
String entry =
|
||||
CreateCommand.retrieveTemplateGenerator(templateId).entrypoint.path;
|
||||
entry = entry.replaceAll('__projectName__', projectName);
|
||||
File entryFile = File(path.join(p.dir.path, entry));
|
||||
|
||||
expect(entryFile.existsSync(), true,
|
||||
reason: 'File not found: ${entryFile.path}');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -13,7 +13,9 @@ void main() {
|
|||
|
||||
void help() {
|
||||
TestProject p;
|
||||
|
||||
tearDown(() => p?.dispose());
|
||||
|
||||
test('--help', () {
|
||||
p = project();
|
||||
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'commands/create_test.dart' as create;
|
||||
import 'commands/flag_test.dart' as flag;
|
||||
import 'commands/format_test.dart' as format;
|
||||
import 'utils_test.dart' as utils;
|
||||
|
||||
main() {
|
||||
group('dartdev', () {
|
||||
create.main();
|
||||
flag.main();
|
||||
format.main();
|
||||
utils.main();
|
||||
|
|
|
@ -73,7 +73,7 @@ analyzer_plugin/test/*: SkipByDesign # Only meant to run on vm
|
|||
analyzer_plugin/tool/*: SkipByDesign # Only meant to run on vm
|
||||
build_integration/test/*: SkipByDesign # Only meant to run on vm, most use dart:mirrors and dart:io
|
||||
compiler/tool/*: SkipByDesign # Only meant to run on vm
|
||||
dartdev/test/command_test: SkipByDesign # Only meant to run on vm (uses dart:io)
|
||||
dartdev/test/*: SkipByDesign # Only meant to run on vm
|
||||
dartfix/test/*: SkipByDesign # Only meant to run on vm
|
||||
front_end/test/*: SkipByDesign # Only meant to run on vm, most use dart:mirrors and dart:io
|
||||
front_end/tool/*: SkipByDesign # Only meant to run on vm
|
||||
|
|
Loading…
Reference in a new issue