// Copyright 2015 The Chromium Authors. 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:io'; import 'package:args/command_runner.dart'; import 'package:path/path.dart' as path; import '../android/android.dart' as android; import '../artifacts.dart'; import '../base/utils.dart'; import '../dart/pub.dart'; import '../globals.dart'; import '../template.dart'; class CreateCommand extends Command { final String name = 'create'; final String description = 'Create a new Flutter project.'; final List<String> aliases = <String>['init']; CreateCommand() { argParser.addOption('out', abbr: 'o', hide: true, help: 'The output directory.' ); argParser.addFlag('pub', defaultsTo: true, help: 'Whether to run "pub get" after the project has been created.' ); argParser.addFlag( 'with-driver-test', negatable: true, defaultsTo: false, help: 'Also add Flutter Driver dependencies and generate a sample driver test.' ); } String get invocation => "${runner.executableName} $name <output directory>"; @override Future<int> run() async { if (!argResults.wasParsed('out') && argResults.rest.isEmpty) { printStatus('No option specified for the output directory.'); printStatus(usage); return 2; } if (ArtifactStore.flutterRoot == null) { printError('Neither the --flutter-root command line flag nor the FLUTTER_ROOT environment'); printError('variable was specified. Unable to find package:flutter.'); return 2; } String flutterRoot = path.absolute(ArtifactStore.flutterRoot); String flutterPackagesDirectory = path.join(flutterRoot, 'packages'); String flutterPackagePath = path.join(flutterPackagesDirectory, 'flutter'); if (!FileSystemEntity.isFileSync(path.join(flutterPackagePath, 'pubspec.yaml'))) { printError('Unable to find package:flutter in $flutterPackagePath'); return 2; } String flutterDriverPackagePath = path.join(flutterRoot, 'packages', 'flutter_driver'); if (!FileSystemEntity.isFileSync(path.join(flutterDriverPackagePath, 'pubspec.yaml'))) { printError('Unable to find package:flutter_driver in $flutterDriverPackagePath'); return 2; } Directory projectDir; if (argResults.wasParsed('out')) { projectDir = new Directory(argResults['out']); } else { projectDir = new Directory(argResults.rest.first); } String dirPath = path.normalize(projectDir.absolute.path); String projectName = _normalizeProjectName(path.basename(dirPath)); if (_validateProjectName(projectName) != null) { printError(_validateProjectName(projectName)); return 1; } _renderTemplates(projectName, dirPath, flutterPackagesDirectory, renderDriverTest: argResults['with-driver-test']); printStatus(''); if (argResults['pub']) { int code = await pubGet(directory: dirPath); if (code != 0) return code; } printStatus(''); // Run doctor; tell the user the next steps. if (doctor.canLaunchAnything) { // Let them know a summary of the state of their tooling. doctor.summary(); printStatus(''' All done! In order to run your application, type: \$ cd $dirPath \$ flutter run '''); } else { printStatus("You'll need to install additional components before you can run " "your Flutter app:"); printStatus(''); // Give the user more detailed analysis. doctor.diagnose(); printStatus(''); printStatus("After installing components, run 'flutter doctor' in order to " "re-validate your setup."); printStatus("When complete, type 'flutter run' from the '$dirPath' " "directory in order to launch your app."); } return 0; } void _renderTemplates(String projectName, String dirPath, String flutterPackagesDirectory, { bool renderDriverTest: false }) { new Directory(dirPath).createSync(recursive: true); flutterPackagesDirectory = path.normalize(flutterPackagesDirectory); flutterPackagesDirectory = _relativePath(from: dirPath, to: flutterPackagesDirectory); printStatus('Creating project ${path.basename(projectName)}:'); Map templateContext = <String, dynamic>{ 'projectName': projectName, 'androidIdentifier': _createAndroidIdentifier(projectName), 'iosIdentifier': _createUTIIdentifier(projectName), 'description': description, 'flutterPackagesDirectory': flutterPackagesDirectory, 'androidMinApiLevel': android.minApiLevel }; if (renderDriverTest) templateContext['withDriverTest?'] = <String, dynamic>{}; Template createTemplate = new Template.fromName('create'); createTemplate.render(new Directory(dirPath), templateContext, overwriteExisting: false); if (renderDriverTest) { Template driverTemplate = new Template.fromName('driver'); driverTemplate.render(new Directory(path.join(dirPath, 'test_driver')), templateContext, overwriteExisting: false); } } } String _normalizeProjectName(String name) { name = name.replaceAll('-', '_').replaceAll(' ', '_'); // Strip any extension (like .dart). if (name.contains('.')) name = name.substring(0, name.indexOf('.')); return name; } String _createAndroidIdentifier(String name) { return 'com.yourcompany.${camelCase(name)}'; } String _createUTIIdentifier(String name) { // Create a UTI (https://en.wikipedia.org/wiki/Uniform_Type_Identifier) from a base name RegExp disallowed = new RegExp(r"[^a-zA-Z0-9\-.\u0080-\uffff]+"); name = camelCase(name).replaceAll(disallowed, ''); name = name.isEmpty ? 'untitled' : name; return 'com.yourcompany.$name'; } final Set<String> _packageDependencies = new Set<String>.from(<String>[ 'args', 'async', 'collection', 'convert', 'flutter', 'html', 'intl', 'logging', 'matcher', 'mime', 'path', 'plugin', 'pool', 'test', 'utf', 'watcher', 'yaml' ]); /// Return `null` if the project name is legal. Return a validation message if /// we should disallow the project name. String _validateProjectName(String projectName) { if (_packageDependencies.contains(projectName)) { return "Invalid project name: '$projectName' - this will conflict with Flutter " "package dependencies."; } return null; } String _relativePath({ String from, String to }) { String result = path.relative(to, from: from); // `path.relative()` doesn't always return a correct result: dart-lang/path#12. if (FileSystemEntity.isDirectorySync(path.join(from, result))) return result; return to; }