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

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: Make sure to use real path to package. 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 return client.get(server.resolve("/packages/versions/new.json")).chain((re sponse) {
42 client.get(server.resolve("/packages/versions/new.json")),
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); 43 var parameters = _parseJson(response);
52 44
53 var url = _expectField(parameters, 'url', response); 45 var url = _expectField(parameters, 'url', response);
54 if (url is! String) _invalidServerResponse(response); 46 if (url is! String) _invalidServerResponse(response);
55 cloudStorageUrl = new Uri.fromString(url); 47 cloudStorageUrl = new Uri.fromString(url);
56 var request = new http.MultipartRequest('POST', cloudStorageUrl); 48 var request = new http.MultipartRequest('POST', cloudStorageUrl);
57 49
58 var fields = _expectField(parameters, 'fields', response); 50 var fields = _expectField(parameters, 'fields', response);
59 if (fields is! Map) _invalidServerResponse(response); 51 if (fields is! Map) _invalidServerResponse(response);
60 fields.forEach((key, value) { 52 fields.forEach((key, value) {
(...skipping 30 matching lines...) Expand all
91 var errorMap = _parseJson(e.response); 83 var errorMap = _parseJson(e.response);
92 if (errorMap['error'] is! Map || 84 if (errorMap['error'] is! Map ||
93 !errorMap['error'].containsKey('message') || 85 !errorMap['error'].containsKey('message') ||
94 errorMap['error']['message'] is! String) { 86 errorMap['error']['message'] is! String) {
95 _invalidServerResponse(e.response); 87 _invalidServerResponse(e.response);
96 } 88 }
97 throw errorMap['error']['message']; 89 throw errorMap['error']['message'];
98 } 90 }
99 } else if (e is oauth2.ExpirationException) { 91 } else if (e is oauth2.ExpirationException) {
100 log.error("Pub's authorization to upload packages has expired and " 92 log.error("Pub's authorization to upload packages has expired and "
101 "can't be automatically refreshed."); 93 "can't be automatically refreshed.");
102 return onRun(); 94 return _publish(packageBytes);
103 } else if (e is oauth2.AuthorizationException) { 95 } else if (e is oauth2.AuthorizationException) {
104 var message = "OAuth2 authorization failed"; 96 var message = "OAuth2 authorization failed";
105 if (e.description != null) message = "$message (${e.description})"; 97 if (e.description != null) message = "$message (${e.description})";
106 log.error("$message."); 98 log.error("$message.");
107 return oauth2.clearCredentials(cache).chain((_) => onRun()); 99 return oauth2.clearCredentials(cache).chain((_) =>
100 _publish(packageBytes));
108 } else { 101 } else {
109 throw e; 102 throw e;
110 } 103 }
111 }); 104 });
112 } 105 }
113 106
107 Future onRun() {
108 var files;
109 return _filesToPublish.transform((f) {
110 files = f;
111 log.fine('Archiving and publishing ${entrypoint.root}.');
112 return createTarGz(files, baseDir: entrypoint.root.dir);
113 }).chain(consumeInputStream).chain((packageBytes) {
114 // Show the package contents so the user can verify they look OK.
115 var package = entrypoint.root;
116 log.message(
117 'Publishing "${package.name}" ${package.version}:\n'
118 '${generateTree(files)}');
119
120 // Validate the package.
121 return _validate().chain((_) => _publish(packageBytes));
122 });
123 }
124
114 /// The basenames of files that are automatically excluded from archives. 125 /// The basenames of files that are automatically excluded from archives.
115 final _BLACKLISTED_FILES = const ['pubspec.lock']; 126 final _BLACKLISTED_FILES = const ['.DS_Store', 'pubspec.lock'];
116 127
117 /// The basenames of directories that are automatically excluded from 128 /// The basenames of directories that are automatically excluded from
118 /// archives. 129 /// archives.
119 final _BLACKLISTED_DIRECTORIES = const ['packages']; 130 final _BLACKLISTED_DIRECTORIES = const ['packages'];
120 131
121 /// Returns a list of files that should be included in the published package. 132 /// 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 133 /// If this is a Git repository, this will respect .gitignore; otherwise, it
123 /// will return all non-hidden files. 134 /// will return all non-hidden files.
124 Future<List<String>> get _filesToPublish { 135 Future<List<String>> get _filesToPublish {
125 var rootDir = entrypoint.root.dir; 136 var rootDir = entrypoint.root.dir;
137
138 // TODO(rnystrom): listDir() returns real file paths after symlinks are
139 // resolved. This means if libDir contains a symlink, the resulting paths
140 // won't appear to be within it, which confuses relativeTo(). Work around
141 // that here by making sure we have the real path to libDir. Remove this
142 // when #7346 is fixed.
143 rootDir = new File(rootDir).fullPathSync();
144
126 return Futures.wait([ 145 return Futures.wait([
127 dirExists(join(rootDir, '.git')), 146 dirExists(join(rootDir, '.git')),
128 git.isInstalled 147 git.isInstalled
129 ]).chain((results) { 148 ]).chain((results) {
130 if (results[0] && results[1]) { 149 if (results[0] && results[1]) {
131 // List all files that aren't gitignored, including those not checked in 150 // List all files that aren't gitignored, including those not checked in
132 // to Git. 151 // to Git.
133 return git.run(["ls-files", "--cached", "--others"]); 152 return git.run(["ls-files", "--cached", "--others"]);
134 } 153 }
135 154
136 return listDir(rootDir, recursive: true).chain((entries) { 155 return listDir(rootDir, recursive: true).chain((entries) {
137 return Futures.wait(entries.map((entry) { 156 return Futures.wait(entries.map((entry) {
138 return fileExists(entry).transform((isFile) => isFile ? entry : null); 157 return fileExists(entry).transform((isFile) {
158 // Skip directories.
159 if (!isFile) return null;
160
161 // Make it relative to the package root.
162 return relativeTo(entry, rootDir);
163 });
139 })); 164 }));
140 }); 165 });
141 }).transform((files) => files.filter((file) { 166 }).transform((files) => files.filter((file) {
142 if (file == null || _BLACKLISTED_FILES.contains(basename(file))) { 167 if (file == null || _BLACKLISTED_FILES.contains(basename(file))) {
143 return false; 168 return false;
144 } 169 }
145 return !splitPath(relativeTo(file, rootDir)) 170
146 .some(_BLACKLISTED_DIRECTORIES.contains); 171 return !splitPath(file).some(_BLACKLISTED_DIRECTORIES.contains);
147 })); 172 }));
148 } 173 }
149 174
150 /// Parses a response body, assuming it's JSON-formatted. Throws a 175 /// 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 176 /// user-friendly error if the response body is invalid JSON, or if it's not a
152 /// map. 177 /// map.
153 Map _parseJson(http.Response response) { 178 Map _parseJson(http.Response response) {
154 var value; 179 var value;
155 try { 180 try {
156 value = JSON.parse(response.body); 181 value = JSON.parse(response.body);
(...skipping 16 matching lines...) Expand all
173 void _invalidServerResponse(http.Response response) { 198 void _invalidServerResponse(http.Response response) {
174 throw 'Invalid server response:\n${response.body}'; 199 throw 'Invalid server response:\n${response.body}';
175 } 200 }
176 201
177 /// Validates the package. Throws an exception if it's invalid. 202 /// Validates the package. Throws an exception if it's invalid.
178 Future _validate() { 203 Future _validate() {
179 return Validator.runAll(entrypoint).chain((pair) { 204 return Validator.runAll(entrypoint).chain((pair) {
180 var errors = pair.first; 205 var errors = pair.first;
181 var warnings = pair.last; 206 var warnings = pair.last;
182 207
183 if (errors.isEmpty && warnings.isEmpty) return new Future.immediate(null); 208 if (!errors.isEmpty) {
184 if (!errors.isEmpty) throw "Package validation failed."; 209 throw "Sorry, your package is missing "
210 "${(errors.length > 1) ? 'some requirements' : 'a requirement'} "
211 "and can't be published yet.\nFor more information, see: "
212 "http://pub.dartlang.org/doc/pub-lish.html.\n";
213 }
185 214
186 var s = warnings.length == 1 ? '' : 's'; 215 var message = 'Looks great! Are you ready to upload your package';
187 stdout.writeString("Package has ${warnings.length} warning$s. Upload " 216
188 "anyway (y/n)? "); 217 if (!warnings.isEmpty) {
189 return readLine().transform((line) { 218 var s = warnings.length == 1 ? '' : 's';
190 if (new RegExp(r"^[yY]").hasMatch(line)) return; 219 message = "Package has ${warnings.length} warning$s. Upload anyway";
nweiz 2012/12/12 21:21:21 It's potentially confusing that warnings are refer
191 throw "Package upload canceled."; 220 }
221
222 return confirm(message).transform((confirmed) {
223 if (!confirmed) throw "Package upload canceled.";
192 }); 224 });
193 }); 225 });
194 } 226 }
195 } 227 }
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