| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library pub.command.lish; | 5 library pub.command.lish; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'package:http/http.dart' as http; | 9 import 'package:http/http.dart' as http; |
| 10 | 10 |
| 11 import '../command.dart'; | 11 import '../command.dart'; |
| 12 import '../exit_codes.dart' as exit_codes; |
| 12 import '../ascii_tree.dart' as tree; | 13 import '../ascii_tree.dart' as tree; |
| 13 import '../http.dart'; | 14 import '../http.dart'; |
| 14 import '../io.dart'; | 15 import '../io.dart'; |
| 15 import '../log.dart' as log; | 16 import '../log.dart' as log; |
| 16 import '../oauth2.dart' as oauth2; | 17 import '../oauth2.dart' as oauth2; |
| 17 import '../source/hosted.dart'; | 18 import '../source/hosted.dart'; |
| 18 import '../utils.dart'; | 19 import '../utils.dart'; |
| 19 import '../validator.dart'; | 20 import '../validator.dart'; |
| 20 | 21 |
| 21 /// Handles the `lish` and `publish` pub commands. | 22 /// Handles the `lish` and `publish` pub commands. |
| (...skipping 29 matching lines...) Expand all Loading... |
| 51 | 52 |
| 52 LishCommand() { | 53 LishCommand() { |
| 53 argParser.addFlag('dry-run', abbr: 'n', negatable: false, | 54 argParser.addFlag('dry-run', abbr: 'n', negatable: false, |
| 54 help: 'Validate but do not publish the package.'); | 55 help: 'Validate but do not publish the package.'); |
| 55 argParser.addFlag('force', abbr: 'f', negatable: false, | 56 argParser.addFlag('force', abbr: 'f', negatable: false, |
| 56 help: 'Publish without confirmation if there are no errors.'); | 57 help: 'Publish without confirmation if there are no errors.'); |
| 57 argParser.addOption('server', defaultsTo: HostedSource.defaultUrl, | 58 argParser.addOption('server', defaultsTo: HostedSource.defaultUrl, |
| 58 help: 'The package server to which to upload this package.'); | 59 help: 'The package server to which to upload this package.'); |
| 59 } | 60 } |
| 60 | 61 |
| 61 Future _publish(packageBytes) { | 62 Future _publish(packageBytes) async { |
| 62 var cloudStorageUrl; | 63 var cloudStorageUrl; |
| 63 return oauth2.withClient(cache, (client) { | 64 try { |
| 64 return log.progress('Uploading', () { | 65 await oauth2.withClient(cache, (client) { |
| 65 // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We | 66 return log.progress('Uploading', () async { |
| 66 // should report that error and exit. | 67 // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We |
| 67 var newUri = server.resolve("/api/packages/versions/new"); | 68 // should report that error and exit. |
| 68 return client.get(newUri, headers: PUB_API_HEADERS).then((response) { | 69 var newUri = server.resolve("/api/packages/versions/new"); |
| 70 var response = await client.get(newUri, headers: PUB_API_HEADERS); |
| 69 var parameters = parseJsonResponse(response); | 71 var parameters = parseJsonResponse(response); |
| 70 | 72 |
| 71 var url = _expectField(parameters, 'url', response); | 73 var url = _expectField(parameters, 'url', response); |
| 72 if (url is! String) invalidServerResponse(response); | 74 if (url is! String) invalidServerResponse(response); |
| 73 cloudStorageUrl = Uri.parse(url); | 75 cloudStorageUrl = Uri.parse(url); |
| 74 var request = new http.MultipartRequest('POST', cloudStorageUrl); | 76 var request = new http.MultipartRequest('POST', cloudStorageUrl); |
| 75 request.headers['Pub-Request-Timeout'] = 'None'; | 77 request.headers['Pub-Request-Timeout'] = 'None'; |
| 76 | 78 |
| 77 var fields = _expectField(parameters, 'fields', response); | 79 var fields = _expectField(parameters, 'fields', response); |
| 78 if (fields is! Map) invalidServerResponse(response); | 80 if (fields is! Map) invalidServerResponse(response); |
| 79 fields.forEach((key, value) { | 81 fields.forEach((key, value) { |
| 80 if (value is! String) invalidServerResponse(response); | 82 if (value is! String) invalidServerResponse(response); |
| 81 request.fields[key] = value; | 83 request.fields[key] = value; |
| 82 }); | 84 }); |
| 83 | 85 |
| 84 request.followRedirects = false; | 86 request.followRedirects = false; |
| 85 request.files.add(new http.MultipartFile.fromBytes( | 87 request.files.add(new http.MultipartFile.fromBytes( |
| 86 'file', packageBytes, filename: 'package.tar.gz')); | 88 'file', packageBytes, filename: 'package.tar.gz')); |
| 87 return client.send(request); | 89 var postResponse = await http.Response.fromStream( |
| 88 }).then(http.Response.fromStream).then((response) { | 90 await client.send(request)); |
| 89 var location = response.headers['location']; | 91 |
| 90 if (location == null) throw new PubHttpException(response); | 92 var location = postResponse.headers['location']; |
| 91 return location; | 93 if (location == null) throw new PubHttpException(postResponse); |
| 92 }).then((location) => client.get(location, headers: PUB_API_HEADERS)) | 94 handleJsonSuccess( |
| 93 .then(handleJsonSuccess); | 95 await client.get(location, headers: PUB_API_HEADERS)); |
| 96 }); |
| 94 }); | 97 }); |
| 95 }).catchError((error) { | 98 } on PubHttpException catch (error) { |
| 96 if (error is! PubHttpException) throw error; | |
| 97 var url = error.response.request.url; | 99 var url = error.response.request.url; |
| 98 if (urisEqual(url, cloudStorageUrl)) { | 100 if (urisEqual(url, cloudStorageUrl)) { |
| 99 // TODO(nweiz): the response may have XML-formatted information about | 101 // TODO(nweiz): the response may have XML-formatted information about |
| 100 // the error. Try to parse that out once we have an easily-accessible | 102 // the error. Try to parse that out once we have an easily-accessible |
| 101 // XML parser. | 103 // XML parser. |
| 102 fail('Failed to upload the package.'); | 104 fail('Failed to upload the package.'); |
| 103 } else if (urisEqual(Uri.parse(url.origin), Uri.parse(server.origin))) { | 105 } else if (urisEqual(Uri.parse(url.origin), Uri.parse(server.origin))) { |
| 104 handleJsonError(error.response); | 106 handleJsonError(error.response); |
| 105 } else { | 107 } else { |
| 106 throw error; | 108 rethrow; |
| 107 } | 109 } |
| 108 }); | 110 } |
| 109 } | 111 } |
| 110 | 112 |
| 111 Future run() { | 113 Future run() async { |
| 112 if (force && dryRun) { | 114 if (force && dryRun) { |
| 113 usageException('Cannot use both --force and --dry-run.'); | 115 usageException('Cannot use both --force and --dry-run.'); |
| 114 } | 116 } |
| 115 | 117 |
| 116 if (entrypoint.root.pubspec.isPrivate) { | 118 if (entrypoint.root.pubspec.isPrivate) { |
| 117 dataError('A private package cannot be published.\n' | 119 dataError('A private package cannot be published.\n' |
| 118 'You can enable this by changing the "publish_to" field in your ' | 120 'You can enable this by changing the "publish_to" field in your ' |
| 119 'pubspec.'); | 121 'pubspec.'); |
| 120 } | 122 } |
| 121 | 123 |
| 122 var files = entrypoint.root.listFiles(useGitIgnore: true); | 124 var files = entrypoint.root.listFiles(useGitIgnore: true); |
| 123 log.fine('Archiving and publishing ${entrypoint.root}.'); | 125 log.fine('Archiving and publishing ${entrypoint.root}.'); |
| 124 | 126 |
| 125 // Show the package contents so the user can verify they look OK. | 127 // Show the package contents so the user can verify they look OK. |
| 126 var package = entrypoint.root; | 128 var package = entrypoint.root; |
| 127 log.message( | 129 log.message( |
| 128 'Publishing ${package.name} ${package.version} to $server:\n' | 130 'Publishing ${package.name} ${package.version} to $server:\n' |
| 129 '${tree.fromFiles(files, baseDir: entrypoint.root.dir)}'); | 131 '${tree.fromFiles(files, baseDir: entrypoint.root.dir)}'); |
| 130 | 132 |
| 131 var packageBytesFuture = createTarGz(files, baseDir: entrypoint.root.dir) | 133 var packageBytesFuture = createTarGz(files, baseDir: entrypoint.root.dir) |
| 132 .toBytes(); | 134 .toBytes(); |
| 133 | 135 |
| 134 // Validate the package. | 136 // Validate the package. |
| 135 return _validate(packageBytesFuture.then((bytes) => bytes.length)) | 137 var isValid = await _validate( |
| 136 .then((isValid) { | 138 packageBytesFuture.then((bytes) => bytes.length)); |
| 137 if (isValid) return packageBytesFuture.then(_publish); | 139 if (!isValid) { |
| 138 }); | 140 await flushThenExit(exit_codes.DATA); |
| 141 } else if (dryRun) { |
| 142 await flushThenExit(exit_codes.SUCCESS); |
| 143 } else { |
| 144 await _publish(await packageBytesFuture); |
| 145 } |
| 139 } | 146 } |
| 140 | 147 |
| 141 /// Returns the value associated with [key] in [map]. Throws a user-friendly | 148 /// Returns the value associated with [key] in [map]. Throws a user-friendly |
| 142 /// error if [map] doens't contain [key]. | 149 /// error if [map] doens't contain [key]. |
| 143 _expectField(Map map, String key, http.Response response) { | 150 _expectField(Map map, String key, http.Response response) { |
| 144 if (map.containsKey(key)) return map[key]; | 151 if (map.containsKey(key)) return map[key]; |
| 145 invalidServerResponse(response); | 152 invalidServerResponse(response); |
| 146 } | 153 } |
| 147 | 154 |
| 148 /// Validates the package. Completes to false if the upload should not | 155 /// Validates the package. Completes to false if the upload should not |
| 149 /// proceed. | 156 /// proceed. |
| 150 Future<bool> _validate(Future<int> packageSize) { | 157 Future<bool> _validate(Future<int> packageSize) async { |
| 151 return Validator.runAll(entrypoint, packageSize).then((pair) { | 158 var pair = await Validator.runAll(entrypoint, packageSize); |
| 152 var errors = pair.first; | 159 var errors = pair.first; |
| 153 var warnings = pair.last; | 160 var warnings = pair.last; |
| 154 | 161 |
| 155 if (!errors.isEmpty) { | 162 if (!errors.isEmpty) { |
| 156 log.error("Sorry, your package is missing " | 163 log.error("Sorry, your package is missing " |
| 157 "${(errors.length > 1) ? 'some requirements' : 'a requirement'} " | 164 "${(errors.length > 1) ? 'some requirements' : 'a requirement'} " |
| 158 "and can't be published yet.\nFor more information, see: " | 165 "and can't be published yet.\nFor more information, see: " |
| 159 "http://pub.dartlang.org/doc/pub-lish.html.\n"); | 166 "http://pub.dartlang.org/doc/pub-lish.html.\n"); |
| 160 return false; | 167 return false; |
| 161 } | 168 } |
| 162 | 169 |
| 163 if (force) return true; | 170 if (force) return true; |
| 164 | 171 |
| 165 if (dryRun) { | 172 if (dryRun) { |
| 166 var s = warnings.length == 1 ? '' : 's'; | 173 var s = warnings.length == 1 ? '' : 's'; |
| 167 log.warning("\nPackage has ${warnings.length} warning$s."); | 174 log.warning("\nPackage has ${warnings.length} warning$s."); |
| 168 return false; | 175 return warnings.isEmpty; |
| 169 } | 176 } |
| 170 | 177 |
| 171 var message = '\nLooks great! Are you ready to upload your package'; | 178 var message = '\nLooks great! Are you ready to upload your package'; |
| 172 | 179 |
| 173 if (!warnings.isEmpty) { | 180 if (!warnings.isEmpty) { |
| 174 var s = warnings.length == 1 ? '' : 's'; | 181 var s = warnings.length == 1 ? '' : 's'; |
| 175 message = "\nPackage has ${warnings.length} warning$s. Upload anyway"; | 182 message = "\nPackage has ${warnings.length} warning$s. Upload anyway"; |
| 176 } | 183 } |
| 177 | 184 |
| 178 return confirm(message).then((confirmed) { | 185 var confirmed = await confirm(message); |
| 179 if (!confirmed) { | 186 if (!confirmed) { |
| 180 log.error("Package upload canceled."); | 187 log.error("Package upload canceled."); |
| 181 return false; | 188 return false; |
| 182 } | 189 } |
| 183 return true; | 190 return true; |
| 184 }); | |
| 185 }); | |
| 186 } | 191 } |
| 187 } | 192 } |
| OLD | NEW |