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 |