diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json index 9f0bf0bc3f1..8a5f7e9e65b 100644 --- a/.dart_tool/package_config.json +++ b/.dart_tool/package_config.json @@ -11,7 +11,7 @@ "constraint, update this by running tools/generate_package_config.dart." ], "configVersion": 2, - "generated": "2021-03-24T12:15:31.538873", + "generated": "2021-03-24T13:42:28.071470", "generator": "tools/generate_package_config.dart", "packages": [ { @@ -637,12 +637,6 @@ "packageUri": "lib/", "languageVersion": "2.12" }, - { - "name": "stagehand", - "rootUri": "../third_party/pkg/stagehand", - "packageUri": "lib/", - "languageVersion": "2.10" - }, { "name": "status_file", "rootUri": "../pkg/status_file", diff --git a/.packages b/.packages index 5fc05a87bae..7646deb7a39 100644 --- a/.packages +++ b/.packages @@ -95,7 +95,6 @@ source_maps:third_party/pkg/source_maps/lib source_span:third_party/pkg/source_span/lib sse:third_party/pkg/sse/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 diff --git a/DEPS b/DEPS index 2948ee694b2..4ecf65bb1ab 100644 --- a/DEPS +++ b/DEPS @@ -152,7 +152,6 @@ vars = { "source_span_rev": "1be3c44045a06dff840d2ed3a13e6082d7a03a23", "sse_tag": "5da8fedcdc56f306933d202e2d204753eecefd36", "stack_trace_tag": "6788afc61875079b71b3d1c3e65aeaa6a25cbc2f", - "stagehand_rev": "e64ac90cac508981011299c4ceb819149e71f1bd", "stream_channel_tag": "d7251e61253ec389ee6e045ee1042311bced8f1d", "string_scanner_rev": "1b63e6e5db5933d7be0a45da6e1129fe00262734", "sync_http_rev": "b59c134f2e34d12acac110d4f17f83e5a7db4330", @@ -430,8 +429,6 @@ deps = { Var("dart_git") + "sse.git" + "@" + Var("sse_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_rev"), Var("dart_root") + "/third_party/pkg/stream_channel": Var("dart_git") + "stream_channel.git" + "@" + Var("stream_channel_tag"), diff --git a/pkg/dartdev/lib/src/commands/create.dart b/pkg/dartdev/lib/src/commands/create.dart index 26d6576e2ba..58dbb4f298d 100644 --- a/pkg/dartdev/lib/src/commands/create.dart +++ b/pkg/dartdev/lib/src/commands/create.dart @@ -8,10 +8,10 @@ import 'dart:io' as io; import 'dart:math' as math; import 'package:path/path.dart' as p; -import 'package:stagehand/stagehand.dart' as stagehand; import '../core.dart'; import '../sdk.dart'; +import '../templates.dart'; /// A command to create a new project from a set of templates. class CreateCommand extends DartdevCommand { @@ -19,18 +19,8 @@ class CreateCommand extends DartdevCommand { static String defaultTemplateId = 'console-simple'; - static List legalTemplateIds = [ - 'console-simple', - 'console-full', - 'package-simple', - 'web-simple' - ]; - - static Iterable get generators => - legalTemplateIds.map(retrieveTemplateGenerator); - - static stagehand.Generator retrieveTemplateGenerator(String templateId) => - stagehand.getGenerator(templateId); + static final List legalTemplateIds = + generators.map((generator) => generator.id).toList(); CreateCommand({bool verbose = false}) : super(cmdName, 'Create a new Dart project.') { @@ -91,8 +81,8 @@ class CreateCommand extends DartdevCommand { ); log.stdout(''); - var generator = retrieveTemplateGenerator(templateId); - await generator.generate( + var generator = getGenerator(templateId); + generator.generate( p.basename(dir), DirectoryGeneratorTarget(generator, io.Directory(dir)), ); @@ -151,7 +141,7 @@ class CreateCommand extends DartdevCommand { } String _availableTemplatesJson() { - var items = generators.map((stagehand.Generator generator) { + var items = generators.map((Generator generator) { var m = { 'name': generator.id, 'label': generator.label, @@ -171,8 +161,8 @@ class CreateCommand extends DartdevCommand { } } -class DirectoryGeneratorTarget extends stagehand.GeneratorTarget { - final stagehand.Generator generator; +class DirectoryGeneratorTarget extends GeneratorTarget { + final Generator generator; final io.Directory dir; DirectoryGeneratorTarget(this.generator, this.dir) { @@ -180,13 +170,13 @@ class DirectoryGeneratorTarget extends stagehand.GeneratorTarget { } @override - Future createFile(String path, List contents) async { + void createFile(String path, List contents) { io.File file = io.File(p.join(dir.path, path)); String name = p.relative(file.path, from: dir.path); log.stdout(' $name'); - await file.create(recursive: true); - await file.writeAsBytes(contents); + file.createSync(recursive: true); + file.writeAsBytesSync(contents); } } diff --git a/pkg/dartdev/lib/src/templates.dart b/pkg/dartdev/lib/src/templates.dart new file mode 100644 index 00000000000..0b5aa0d450c --- /dev/null +++ b/pkg/dartdev/lib/src/templates.dart @@ -0,0 +1,195 @@ +// Copyright (c) 2021, 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' show utf8; + +import 'package:meta/meta.dart'; + +import 'templates/console_full.dart'; +import 'templates/console_simple.dart'; +import 'templates/package_simple.dart'; +import 'templates/server_simple.dart'; +import 'templates/web_simple.dart'; + +final _substituteRegExp = RegExp(r'__([a-zA-Z]+)__'); +final _nonValidSubstituteRegExp = RegExp('[^a-zA-Z]'); + +final List generators = [ + ConsoleSimpleGenerator(), + ConsoleFullGenerator(), + PackageSimpleGenerator(), + ServerSimpleGenerator(), + WebSimpleGenerator(), +]; + +Generator getGenerator(String id) => + generators.firstWhere((g) => g.id == id, orElse: () => null); + +/// An abstract class which both defines a template generator and can generate a +/// user project based on this template. +abstract class Generator implements Comparable { + final String id; + final String label; + final String description; + final List categories; + + final List files = []; + TemplateFile _entrypoint; + + Generator( + this.id, + this.label, + this.description, { + this.categories = const [], + }); + + /// The entrypoint of the application; the main file for the project, which an + /// IDE might open after creating the project. + TemplateFile get entrypoint => _entrypoint; + + TemplateFile addFile(String path, String contents) { + return addTemplateFile(TemplateFile(path, contents)); + } + + /// Add a new template file. + TemplateFile addTemplateFile(TemplateFile file) { + files.add(file); + return file; + } + + /// Return the template file wih the given [path]. + TemplateFile getFile(String path) => + files.firstWhere((file) => file.path == path, orElse: () => null); + + /// Set the main entrypoint of this template. This is the 'most important' + /// file of this template. An IDE might use this information to open this file + /// after the user's project is generated. + void setEntrypoint(TemplateFile entrypoint) { + if (_entrypoint != null) throw StateError('entrypoint already set'); + if (entrypoint == null) throw StateError('entrypoint is null'); + _entrypoint = entrypoint; + } + + void generate( + String projectName, + GeneratorTarget target, { + Map additionalVars, + }) { + final vars = { + 'projectName': projectName, + 'description': description, + 'year': DateTime.now().year.toString(), + 'author': '', + if (additionalVars != null) ...additionalVars, + }; + + for (TemplateFile file in files) { + final resultFile = file.runSubstitution(vars); + final filePath = resultFile.path; + target.createFile(filePath, resultFile.content); + } + } + + int numFiles() => files.length; + + @override + int compareTo(Generator other) => + id.toLowerCase().compareTo(other.id.toLowerCase()); + + /// Return some user facing instructions about how to finish installation of + /// the template. + String getInstallInstructions() => ''; + + @override + String toString() => '[$id: $description]'; +} + +/// An abstract implementation of a [Generator]. +abstract class DefaultGenerator extends Generator { + DefaultGenerator( + String id, + String label, + String description, { + List categories = const [], + }) : super(id, label, description, categories: categories); +} + +/// A target for a [Generator]. This class knows how to create files given a +/// path for the file (relative to the particular [GeneratorTarget] instance), +/// and the binary content for the file. +abstract class GeneratorTarget { + /// Create a file at the given path with the given contents. + void createFile(String path, List contents); +} + +/// This class represents a file in a generator template. The contents could +/// either be binary or text. If text, the contents may contain mustache +/// variables that can be substituted (`__myVar__`). +class TemplateFile { + final String path; + final String content; + + TemplateFile(this.path, this.content); + + FileContents runSubstitution(Map parameters) { + if (path == 'pubspec.yaml' && parameters['author'] == '') { + parameters = Map.from(parameters); + parameters['author'] = 'Your Name'; + } + + final newPath = substituteVars(path, parameters); + final newContents = _createContent(parameters); + + return FileContents(newPath, newContents); + } + + List _createContent(Map vars) { + return utf8.encode(substituteVars(content, vars)); + } +} + +class FileContents { + final String path; + final List content; + + FileContents(this.path, this.content); +} + +/// Given a `String` [str] with mustache templates, and a [Map] of String key / +/// value pairs, substitute all instances of `__key__` for `value`. I.e., +/// +/// ``` +/// Foo __projectName__ baz. +/// ``` +/// +/// and +/// +/// ``` +/// {'projectName': 'bar'} +/// ``` +/// +/// becomes: +/// +/// ``` +/// Foo bar baz. +/// ``` +/// +/// A key value can only be an ASCII string made up of letters: A-Z, a-z. +/// No whitespace, numbers, or other characters are allowed. +@visibleForTesting +String substituteVars(String str, Map vars) { + if (vars.keys.any((element) => element.contains(_nonValidSubstituteRegExp))) { + throw ArgumentError('vars.keys can only contain letters.'); + } + + return str.replaceAllMapped(_substituteRegExp, (match) { + final item = vars[match[1]]; + + if (item == null) { + return match[0]; + } else { + return item; + } + }); +} diff --git a/pkg/dartdev/lib/src/templates/common.dart b/pkg/dartdev/lib/src/templates/common.dart new file mode 100644 index 00000000000..b849fdda41b --- /dev/null +++ b/pkg/dartdev/lib/src/templates/common.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2021, 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. + +final String gitignore = ''' +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build output. +build/ +'''; + +final String analysisOptions = ''' +# Defines a default set of lint rules enforced for projects at Google. For +# details and rationale, see +# https://github.com/dart-lang/pedantic#enabled-lints. + +include: package:pedantic/analysis_options.yaml + +# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. + +# Uncomment to specify additional rules. +# linter: +# rules: +# - camel_case_types + +# analyzer: +# exclude: +# - path/to/excluded/files/** +'''; + +final String changelog = ''' +## 1.0.0 + +- Initial version. +'''; diff --git a/pkg/dartdev/lib/src/templates/console_full.dart b/pkg/dartdev/lib/src/templates/console_full.dart new file mode 100644 index 00000000000..78e1670204c --- /dev/null +++ b/pkg/dartdev/lib/src/templates/console_full.dart @@ -0,0 +1,76 @@ +// Copyright (c) 2021, 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 '../templates.dart'; +import 'common.dart' as common; + +/// A generator for a hello world command-line application. +class ConsoleFullGenerator extends DefaultGenerator { + ConsoleFullGenerator() + : super('console-full', 'Console Application', + 'A command-line application sample.', + categories: const ['dart', 'console']) { + addFile('.gitignore', common.gitignore); + addFile('analysis_options.yaml', common.analysisOptions); + addFile('CHANGELOG.md', common.changelog); + addFile('pubspec.yaml', _pubspec); + addFile('README.md', _readme); + setEntrypoint( + addFile('bin/__projectName__.dart', _mainDart), + ); + addFile('lib/__projectName__.dart', _libDart); + addFile('test/__projectName___test.dart', _testDart); + } + + @override + String getInstallInstructions() => '${super.getInstallInstructions()}\n' + 'run your app using `dart ${entrypoint.path}`.'; +} + +final String _pubspec = ''' +name: __projectName__ +description: A sample command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.12.0 <3.0.0' + +# dependencies: +# path: ^1.8.0 + +dev_dependencies: + pedantic: ^1.10.0 + test: ^1.16.0 +'''; + +final String _readme = ''' +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`, and example unit test in `test/`. +'''; + +final String _mainDart = r''' +import 'package:__projectName__/__projectName__.dart' as __projectName__; + +void main(List arguments) { + print('Hello world: ${__projectName__.calculate()}!'); +} +'''; + +final String _libDart = ''' +int calculate() { + return 6 * 7; +} +'''; + +final String _testDart = ''' +import 'package:__projectName__/__projectName__.dart'; +import 'package:test/test.dart'; + +void main() { + test('calculate', () { + expect(calculate(), 42); + }); +} +'''; diff --git a/pkg/dartdev/lib/src/templates/console_simple.dart b/pkg/dartdev/lib/src/templates/console_simple.dart new file mode 100644 index 00000000000..15384c6e02d --- /dev/null +++ b/pkg/dartdev/lib/src/templates/console_simple.dart @@ -0,0 +1,53 @@ +// Copyright (c) 2021, 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 '../templates.dart'; +import 'common.dart' as common; + +/// A generator for a simple command-line application. +class ConsoleSimpleGenerator extends DefaultGenerator { + ConsoleSimpleGenerator() + : super('console-simple', 'Simple Console Application', + 'A simple command-line application.', + categories: const ['dart', 'console']) { + addFile('.gitignore', common.gitignore); + addFile('analysis_options.yaml', common.analysisOptions); + addFile('CHANGELOG.md', common.changelog); + addFile('pubspec.yaml', _pubspec); + addFile('README.md', _readme); + setEntrypoint( + addFile('bin/__projectName__.dart', main), + ); + } + + @override + String getInstallInstructions() => '${super.getInstallInstructions()}\n' + 'run your app using `dart ${entrypoint.path}`.'; +} + +final String _pubspec = ''' +name: __projectName__ +description: A simple command-line application. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.12.0 <3.0.0' + +# dependencies: +# path: ^1.8.0 + +dev_dependencies: + pedantic: ^1.10.0 +'''; + +final String _readme = ''' +A simple command-line application. +'''; + +final String main = ''' +void main(List arguments) { + print('Hello world!'); +} +'''; diff --git a/pkg/dartdev/lib/src/templates/package_simple.dart b/pkg/dartdev/lib/src/templates/package_simple.dart new file mode 100644 index 00000000000..7d013fa4793 --- /dev/null +++ b/pkg/dartdev/lib/src/templates/package_simple.dart @@ -0,0 +1,130 @@ +// Copyright (c) 2021, 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 '../templates.dart'; +import 'common.dart' as common; + +/// A generator for a simple command-line application. +class PackageSimpleGenerator extends DefaultGenerator { + PackageSimpleGenerator() + : super('package-simple', 'Dart Package', + 'A starting point for Dart libraries or applications.', + categories: const ['dart']) { + addFile('.gitignore', _gitignore); + addFile('analysis_options.yaml', common.analysisOptions); + addFile('CHANGELOG.md', common.changelog); + addFile('pubspec.yaml', _pubspec); + addFile('README.md', _readme); + addFile('example/__projectName___example.dart', _exampleDart); + setEntrypoint( + addFile('lib/__projectName__.dart', _libDart), + ); + addFile('lib/src/__projectName___base.dart', _libSrcDart); + addFile('test/__projectName___test.dart', _testDart); + } + + @override + String getInstallInstructions() => '${super.getInstallInstructions()}\n' + 'run your app using `dart ${entrypoint.path}`.'; +} + +final String _gitignore = ''' +# Files and directories created by pub. +.dart_tool/ +.packages + +# Conventional directory for build outputs. +build/ + +# Omit committing pubspec.lock for library packages; see +# https://dart.dev/guides/libraries/private-files#pubspeclock. +pubspec.lock +'''; + +final String _pubspec = ''' +name: __projectName__ +description: A starting point for Dart libraries or applications. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.12.0 <3.0.0' + +# dependencies: +# path: ^1.8.0 + +dev_dependencies: + pedantic: ^1.10.0 + test: ^1.16.0 +'''; + +final String _readme = ''' +A library for Dart developers. + +## Usage + +A simple usage example: + +```dart +import 'package:__projectName__/__projectName__.dart'; + +main() { + var awesome = new Awesome(); +} +``` + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: http://example.com/issues/replaceme +'''; + +final String _exampleDart = r''' +import 'package:__projectName__/__projectName__.dart'; + +void main() { + var awesome = Awesome(); + print('awesome: ${awesome.isAwesome}'); +} +'''; + +final String _libDart = ''' +/// Support for doing something awesome. +/// +/// More dartdocs go here. +library __projectName__; + +export 'src/__projectName___base.dart'; + +// TODO: Export any libraries intended for clients of this package. +'''; + +final String _libSrcDart = ''' +// TODO: Put public facing types in this file. + +/// Checks if you are awesome. Spoiler: you are. +class Awesome { + bool get isAwesome => true; +} +'''; + +final String _testDart = ''' +import 'package:__projectName__/__projectName__.dart'; +import 'package:test/test.dart'; + +void main() { + group('A group of tests', () { + final awesome = Awesome(); + + setUp(() { + // Additional setup goes here. + }); + + test('First Test', () { + expect(awesome.isAwesome, isTrue); + }); + }); +} +'''; diff --git a/pkg/dartdev/lib/src/templates/server_simple.dart b/pkg/dartdev/lib/src/templates/server_simple.dart new file mode 100644 index 00000000000..6688f237c3d --- /dev/null +++ b/pkg/dartdev/lib/src/templates/server_simple.dart @@ -0,0 +1,85 @@ +// Copyright (c) 2021, 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 '../templates.dart'; +import 'common.dart' as common; + +/// A generator for a server app built on `package:shelf`. +class ServerSimpleGenerator extends DefaultGenerator { + ServerSimpleGenerator() + : super('server-simple', 'Web Server', + 'A web server built using package:shelf.', + categories: const ['dart', 'server']) { + addFile('.gitignore', common.gitignore); + addFile('analysis_options.yaml', common.analysisOptions); + addFile('CHANGELOG.md', common.changelog); + addFile('pubspec.yaml', _pubspec); + addFile('README.md', _readme); + setEntrypoint( + addFile('bin/server.dart', _main), + ); + } + + @override + String getInstallInstructions() => '${super.getInstallInstructions()}\n' + 'run your app using `dart ${entrypoint.path}`.'; +} + +final String _pubspec = ''' +name: __projectName__ +description: A web server built using the shelf package. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + args: ^2.0.0 + shelf: ^1.1.0 + +dev_dependencies: + pedantic: ^1.10.0 +'''; + +final String _readme = ''' +A web server built using [Shelf](https://pub.dev/packages/shelf). +'''; + +final String _main = r''' +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:shelf/shelf.dart' as shelf; +import 'package:shelf/shelf_io.dart' as io; + +// For Google Cloud Run, set _hostname to '0.0.0.0'. +const _hostname = 'localhost'; + +void main(List args) async { + var parser = ArgParser()..addOption('port', abbr: 'p'); + var result = parser.parse(args); + + // For Google Cloud Run, we respect the PORT environment variable + var portStr = result['port'] ?? Platform.environment['PORT'] ?? '8080'; + var port = int.tryParse(portStr); + + if (port == null) { + stdout.writeln('Could not parse port value "$portStr" into a number.'); + // 64: command line usage error + exitCode = 64; + return; + } + + var handler = const shelf.Pipeline() + .addMiddleware(shelf.logRequests()) + .addHandler(_echoRequest); + + var server = await io.serve(handler, _hostname, port); + print('Serving at http://${server.address.host}:${server.port}'); +} + +shelf.Response _echoRequest(shelf.Request request) => + shelf.Response.ok('Request for "${request.url}"'); +'''; diff --git a/pkg/dartdev/lib/src/templates/web_simple.dart b/pkg/dartdev/lib/src/templates/web_simple.dart new file mode 100644 index 00000000000..0fd6c347d7f --- /dev/null +++ b/pkg/dartdev/lib/src/templates/web_simple.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2021, 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 '../templates.dart'; +import 'common.dart' as common; + +/// A generator for a uber-simple web application. +class WebSimpleGenerator extends DefaultGenerator { + WebSimpleGenerator() + : super('web-simple', 'Bare-bones Web App', + 'A web app that uses only core Dart libraries.', + categories: const ['dart', 'web']) { + addFile('.gitignore', common.gitignore); + addFile('analysis_options.yaml', common.analysisOptions); + addFile('CHANGELOG.md', common.changelog); + addFile('pubspec.yaml', _pubspec); + addFile('README.md', _readme); + addFile('web/index.html', _index); + setEntrypoint( + addFile('web/main.dart', _main), + ); + addFile('web/styles.css', _styles); + } +} + +final String _pubspec = ''' +name: __projectName__ +description: An absolute bare-bones web app. +version: 1.0.0 +# homepage: https://www.example.com + +environment: + sdk: '>=2.10.0 <3.0.0' + +# dependencies: +# path: ^1.7.0 + +dev_dependencies: + build_runner: ^1.10.0 + build_web_compilers: ^2.11.0 + pedantic: ^1.9.0 +'''; + +final String _readme = ''' +An absolute bare-bones web app. +'''; + +final String _index = ''' + + + + + + + + + __projectName__ + + + + + + +
+ + + +'''; + +final String _main = ''' +import 'dart:html'; + +void main() { + querySelector('#output').text = 'Your Dart app is running.'; +} +'''; + +final String _styles = ''' +@import url(https://fonts.googleapis.com/css?family=Roboto); + +html, body { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; +} + +#output { + padding: 20px; + text-align: center; +} +'''; diff --git a/pkg/dartdev/pubspec.yaml b/pkg/dartdev/pubspec.yaml index 5acc1612fe0..20071f4e808 100644 --- a/pkg/dartdev/pubspec.yaml +++ b/pkg/dartdev/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: path: ^1.0.0 pedantic: ^1.9.0 pub: any - stagehand: any telemetry: path: ../telemetry usage: ^3.4.0 diff --git a/pkg/dartdev/test/commands/create_integration_test.dart b/pkg/dartdev/test/commands/create_integration_test.dart new file mode 100644 index 00000000000..af112d15f96 --- /dev/null +++ b/pkg/dartdev/test/commands/create_integration_test.dart @@ -0,0 +1,54 @@ +// Copyright (c) 2021, 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:io'; + +import 'package:dartdev/src/commands/create.dart'; +import 'package:test/test.dart'; + +import '../utils.dart'; + +void main() { + group('create integration', defineCreateTests, timeout: longTimeout); +} + +void defineCreateTests() { + TestProject p; + + setUp(() => p = null); + + tearDown(() => p?.dispose()); + + // Create tests for each template. + for (String templateId in CreateCommand.legalTemplateIds) { + test(templateId, () { + p = project(); + + ProcessResult createResult = p.runSync([ + 'create', + '--force', + '--template', + templateId, + p.dir.path, + ]); + expect(createResult.exitCode, 0, reason: createResult.stderr); + + // Validate that the project analyzes cleanly. + // TODO: Should we use --fatal-infos here? + ProcessResult analyzeResult = + p.runSync(['analyze'], workingDir: p.dir.path); + expect(analyzeResult.exitCode, 0, reason: analyzeResult.stdout); + + // Validate that the code is well formatted. + ProcessResult formatResult = p.runSync([ + 'format', + '--output', + 'none', + '--set-exit-if-changed', + p.dir.path, + ]); + expect(formatResult.exitCode, 0, reason: formatResult.stdout); + }); + } +} diff --git a/pkg/dartdev/test/commands/create_test.dart b/pkg/dartdev/test/commands/create_test.dart index 7f5c84683aa..5429b80def0 100644 --- a/pkg/dartdev/test/commands/create_test.dart +++ b/pkg/dartdev/test/commands/create_test.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:dartdev/src/commands/create.dart'; +import 'package:dartdev/src/templates.dart' as templates; import 'package:path/path.dart' as path; import 'package:test/test.dart'; @@ -77,14 +78,19 @@ void defineCreateTests() { test('create $templateId', () { p = project(); - ProcessResult result = p - .runSync(['create', '--force', '--template', templateId, p.dir.path]); + 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; + String entry = templates.getGenerator(templateId).entrypoint.path; entry = entry.replaceAll('__projectName__', projectName); File entryFile = File(path.join(p.dir.path, entry)); diff --git a/pkg/dartdev/test/templates_test.dart b/pkg/dartdev/test/templates_test.dart new file mode 100644 index 00000000000..3b466c9f91b --- /dev/null +++ b/pkg/dartdev/test/templates_test.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2021, 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 'package:dartdev/src/templates.dart'; +import 'package:test/test.dart'; + +void main() { + group('templates', () { + group('substituteVars', () { + test('simple', () { + _expect('foo __bar__ baz', {'bar': 'baz'}, 'foo baz baz'); + }); + + test('nosub', () { + _expect('foo __bar__ baz', {'aaa': 'bbb'}, 'foo __bar__ baz'); + }); + + test('matching input', () { + _expect('foo __bar__ baz', {'bar': '__baz__', 'baz': 'foo'}, + 'foo __baz__ baz'); + }); + + test('vars must be alpha + numeric', () { + expect(() => substituteVars('str', {'with space': 'noop'}), + throwsArgumentError); + expect(() => substituteVars('str', {'with!symbols': 'noop'}), + throwsArgumentError); + expect(() => substituteVars('str', {'with1numbers': 'noop'}), + throwsArgumentError); + expect(() => substituteVars('str', {'with_under': 'noop'}), + throwsArgumentError); + }); + }); + }); +} + +void _expect(String original, Map vars, String result) { + expect(substituteVars(original, vars), result); +} diff --git a/pkg/dartdev/test/utils.dart b/pkg/dartdev/test/utils.dart index 1f3db170083..5a64ced6721 100644 --- a/pkg/dartdev/test/utils.dart +++ b/pkg/dartdev/test/utils.dart @@ -7,7 +7,6 @@ import 'dart:io'; import 'package:path/path.dart' as path; import 'package:pub_semver/pub_semver.dart'; - import 'package:test/test.dart'; /// A long [Timeout] is provided for tests that start a process on @@ -54,7 +53,7 @@ class TestProject { this.name = _defaultProjectName, this.logAnalytics = false, this.sdkConstraint}) { - dir = Directory.systemTemp.createTempSync(name); + dir = Directory.systemTemp.createTempSync('a'); file('pubspec.yaml', ''' name: $name environment: