Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(370)

Side by Side Diff: utils/pub/command_lish.dart

Issue 11557008: Make pub publish more user friendly: (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Merge in path changes. Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | utils/pub/directory_tree.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 command_lish; 5 library command_lish;
6 6
7 import 'dart:io'; 7 import 'dart:io';
8 import 'dart:json'; 8 import 'dart:json';
9 import 'dart:uri'; 9 import 'dart:uri';
10 10
11 import '../../pkg/args/lib/args.dart'; 11 import '../../pkg/args/lib/args.dart';
12 import '../../pkg/http/lib/http.dart' as http; 12 import '../../pkg/http/lib/http.dart' as http;
13 import 'directory_tree.dart';
13 import 'git.dart' as git; 14 import 'git.dart' as git;
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 'pub.dart'; 18 import 'pub.dart';
18 import 'validator.dart'; 19 import 'validator.dart';
19 20
20 /// Handles the `lish` and `publish` pub commands. 21 /// Handles the `lish` and `publish` pub commands.
21 class LishCommand extends PubCommand { 22 class LishCommand extends PubCommand {
22 final description = "Publish the current package to pub.dartlang.org."; 23 final description = "Publish the current package to pub.dartlang.org.";
23 final usage = "pub publish [options]"; 24 final usage = "pub publish [options]";
24 final aliases = const ["lish", "lush"]; 25 final aliases = const ["lish", "lush"];
25 26
26 ArgParser get commandParser { 27 ArgParser get commandParser {
27 var parser = new ArgParser(); 28 var parser = new ArgParser();
28 parser.addOption('server', defaultsTo: 'https://pub.dartlang.org', 29 parser.addOption('server', defaultsTo: 'https://pub.dartlang.org',
29 help: 'The package server to which to upload this package'); 30 help: 'The package server to which to upload this package');
30 return parser; 31 return parser;
31 } 32 }
32 33
33 /// The URL of the server to which to upload the package. 34 /// The URL of the server to which to upload the package.
34 Uri get server => new Uri.fromString(commandOptions['server']); 35 Uri get server => new Uri.fromString(commandOptions['server']);
35 36
36 Future onRun() { 37 Future _publish(packageBytes) {
37 var cloudStorageUrl; 38 var cloudStorageUrl;
38 return oauth2.withClient(cache, (client) { 39 return oauth2.withClient(cache, (client) {
39 // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We 40 // TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
40 // should report that error and exit. 41 // should report that error and exit.
41 return Futures.wait([ 42 var newUri = server.resolve("/packages/versions/new.json");
42 client.get(server.resolve("/packages/versions/new.json")), 43 return client.get(newUri).chain((response) {
43 _filesToPublish.transform((files) {
44 log.fine('Archiving and publishing ${entrypoint.root}.');
45 return createTarGz(files, baseDir: entrypoint.root.dir);
46 }).chain(consumeInputStream),
47 _validate()
48 ]).chain((results) {
49 var response = results[0];
50 var packageBytes = results[1];
51 var parameters = _parseJson(response); 44 var parameters = _parseJson(response);
52 45
53 var url = _expectField(parameters, 'url', response); 46 var url = _expectField(parameters, 'url', response);
54 if (url is! String) _invalidServerResponse(response); 47 if (url is! String) _invalidServerResponse(response);
55 cloudStorageUrl = new Uri.fromString(url); 48 cloudStorageUrl = new Uri.fromString(url);
56 var request = new http.MultipartRequest('POST', cloudStorageUrl); 49 var request = new http.MultipartRequest('POST', cloudStorageUrl);
57 50
58 var fields = _expectField(parameters, 'fields', response); 51 var fields = _expectField(parameters, 'fields', response);
59 if (fields is! Map) _invalidServerResponse(response); 52 if (fields is! Map) _invalidServerResponse(response);
60 fields.forEach((key, value) { 53 fields.forEach((key, value) {
(...skipping 30 matching lines...) Expand all
91 var errorMap = _parseJson(e.response); 84 var errorMap = _parseJson(e.response);
92 if (errorMap['error'] is! Map || 85 if (errorMap['error'] is! Map ||
93 !errorMap['error'].containsKey('message') || 86 !errorMap['error'].containsKey('message') ||
94 errorMap['error']['message'] is! String) { 87 errorMap['error']['message'] is! String) {
95 _invalidServerResponse(e.response); 88 _invalidServerResponse(e.response);
96 } 89 }
97 throw errorMap['error']['message']; 90 throw errorMap['error']['message'];
98 } 91 }
99 } else if (e is oauth2.ExpirationException) { 92 } else if (e is oauth2.ExpirationException) {
100 log.error("Pub's authorization to upload packages has expired and " 93 log.error("Pub's authorization to upload packages has expired and "
101 "can't be automatically refreshed."); 94 "can't be automatically refreshed.");
102 return onRun(); 95 return _publish(packageBytes);
103 } else if (e is oauth2.AuthorizationException) { 96 } else if (e is oauth2.AuthorizationException) {
104 var message = "OAuth2 authorization failed"; 97 var message = "OAuth2 authorization failed";
105 if (e.description != null) message = "$message (${e.description})"; 98 if (e.description != null) message = "$message (${e.description})";
106 log.error("$message."); 99 log.error("$message.");
107 return oauth2.clearCredentials(cache).chain((_) => onRun()); 100 return oauth2.clearCredentials(cache).chain((_) =>
101 _publish(packageBytes));
108 } else { 102 } else {
109 throw e; 103 throw e;
110 } 104 }
111 }); 105 });
112 } 106 }
113 107
108 Future onRun() {
109 var files;
110 return _filesToPublish.transform((f) {
111 files = f;
112 log.fine('Archiving and publishing ${entrypoint.root}.');
113 return createTarGz(files, baseDir: entrypoint.root.dir);
114 }).chain(consumeInputStream).chain((packageBytes) {
115 // Show the package contents so the user can verify they look OK.
116 var package = entrypoint.root;
117 log.message(
118 'Publishing "${package.name}" ${package.version}:\n'
119 '${generateTree(files)}');
120
121 // Validate the package.
122 return _validate().chain((_) => _publish(packageBytes));
123 });
124 }
125
114 /// The basenames of files that are automatically excluded from archives. 126 /// The basenames of files that are automatically excluded from archives.
115 final _BLACKLISTED_FILES = const ['pubspec.lock']; 127 final _BLACKLISTED_FILES = const ['pubspec.lock'];
116 128
117 /// The basenames of directories that are automatically excluded from 129 /// The basenames of directories that are automatically excluded from
118 /// archives. 130 /// archives.
119 final _BLACKLISTED_DIRECTORIES = const ['packages']; 131 final _BLACKLISTED_DIRECTORIES = const ['packages'];
120 132
121 /// Returns a list of files that should be included in the published package. 133 /// Returns a list of files that should be included in the published package.
122 /// If this is a Git repository, this will respect .gitignore; otherwise, it 134 /// If this is a Git repository, this will respect .gitignore; otherwise, it
123 /// will return all non-hidden files. 135 /// will return all non-hidden files.
124 Future<List<String>> get _filesToPublish { 136 Future<List<String>> get _filesToPublish {
125 var rootDir = entrypoint.root.dir; 137 var rootDir = entrypoint.root.dir;
138
139 // TODO(rnystrom): listDir() returns real file paths after symlinks are
140 // resolved. This means if libDir contains a symlink, the resulting paths
141 // won't appear to be within it, which confuses relativeTo(). Work around
142 // that here by making sure we have the real path to libDir. Remove this
143 // when #7346 is fixed.
144 rootDir = new File(rootDir).fullPathSync();
145
126 return Futures.wait([ 146 return Futures.wait([
127 dirExists(join(rootDir, '.git')), 147 dirExists(join(rootDir, '.git')),
128 git.isInstalled 148 git.isInstalled
129 ]).chain((results) { 149 ]).chain((results) {
130 if (results[0] && results[1]) { 150 if (results[0] && results[1]) {
131 // List all files that aren't gitignored, including those not checked in 151 // List all files that aren't gitignored, including those not checked in
132 // to Git. 152 // to Git.
133 return git.run(["ls-files", "--cached", "--others"]); 153 return git.run(["ls-files", "--cached", "--others"]);
134 } 154 }
135 155
136 return listDir(rootDir, recursive: true).chain((entries) { 156 return listDir(rootDir, recursive: true).chain((entries) {
137 return Futures.wait(entries.map((entry) { 157 return Futures.wait(entries.map((entry) {
138 return fileExists(entry).transform((isFile) => isFile ? entry : null); 158 return fileExists(entry).transform((isFile) {
159 // Skip directories.
160 if (!isFile) return null;
161
162 // TODO(rnystrom): Making these relative will break archive
163 // creation if the cwd is ever *not* the package root directory.
164 // Should instead only make these relative right before generating
165 // the tree display (which is what really needs them to be).
166 // Make it relative to the package root.
167 return relativeTo(entry, rootDir);
168 });
139 })); 169 }));
140 }); 170 });
141 }).transform((files) => files.filter((file) { 171 }).transform((files) => files.filter((file) {
142 if (file == null || _BLACKLISTED_FILES.contains(basename(file))) { 172 if (file == null || _BLACKLISTED_FILES.contains(basename(file))) {
143 return false; 173 return false;
144 } 174 }
145 return !splitPath(relativeTo(file, rootDir)) 175
146 .some(_BLACKLISTED_DIRECTORIES.contains); 176 return !splitPath(file).some(_BLACKLISTED_DIRECTORIES.contains);
147 })); 177 }));
148 } 178 }
149 179
150 /// Parses a response body, assuming it's JSON-formatted. Throws a 180 /// Parses a response body, assuming it's JSON-formatted. Throws a
151 /// user-friendly error if the response body is invalid JSON, or if it's not a 181 /// user-friendly error if the response body is invalid JSON, or if it's not a
152 /// map. 182 /// map.
153 Map _parseJson(http.Response response) { 183 Map _parseJson(http.Response response) {
154 var value; 184 var value;
155 try { 185 try {
156 value = JSON.parse(response.body); 186 value = JSON.parse(response.body);
(...skipping 16 matching lines...) Expand all
173 void _invalidServerResponse(http.Response response) { 203 void _invalidServerResponse(http.Response response) {
174 throw 'Invalid server response:\n${response.body}'; 204 throw 'Invalid server response:\n${response.body}';
175 } 205 }
176 206
177 /// Validates the package. Throws an exception if it's invalid. 207 /// Validates the package. Throws an exception if it's invalid.
178 Future _validate() { 208 Future _validate() {
179 return Validator.runAll(entrypoint).chain((pair) { 209 return Validator.runAll(entrypoint).chain((pair) {
180 var errors = pair.first; 210 var errors = pair.first;
181 var warnings = pair.last; 211 var warnings = pair.last;
182 212
183 if (errors.isEmpty && warnings.isEmpty) return new Future.immediate(null); 213 if (!errors.isEmpty) {
184 if (!errors.isEmpty) throw "Package validation failed."; 214 throw "Sorry, your package is missing "
215 "${(errors.length > 1) ? 'some requirements' : 'a requirement'} "
216 "and can't be published yet.\nFor more information, see: "
217 "http://pub.dartlang.org/doc/pub-lish.html.\n";
218 }
185 219
186 var s = warnings.length == 1 ? '' : 's'; 220 var message = 'Looks great! Are you ready to upload your package';
187 stdout.writeString("Package has ${warnings.length} warning$s. Upload " 221
188 "anyway (y/n)? "); 222 if (!warnings.isEmpty) {
189 return readLine().transform((line) { 223 var s = warnings.length == 1 ? '' : 's';
190 if (new RegExp(r"^[yY]").hasMatch(line)) return; 224 message = "Package has ${warnings.length} warning$s. Upload anyway";
191 throw "Package upload canceled."; 225 }
226
227 return confirm(message).transform((confirmed) {
228 if (!confirmed) throw "Package upload canceled.";
192 }); 229 });
193 }); 230 });
194 } 231 }
195 } 232 }
OLDNEW
« no previous file with comments | « no previous file | utils/pub/directory_tree.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698