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

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

Issue 11308212: Add an initial "pub lish" command. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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/hosted_source.dart » ('j') | utils/pub/io.dart » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library command_lish;
6
7 import 'dart:json';
8 import 'dart:uri';
9
10 import '../../pkg/args/lib/args.dart';
11 import '../../pkg/http/lib/http.dart' as http;
12 import 'pub.dart';
13 import 'io.dart';
14 import 'git.dart' as git;
15 import 'oauth2.dart' as oauth2;
16
17 // TODO(nweiz): make "publish" the primary name for this command.
18 /// Handles the `lish` and `publish` pub commands.
19 class LishCommand extends PubCommand {
20 String get description => "publish the current package to pub.dartlang.org";
21 String get usage => "pub lish [options]";
22
23 ArgParser get commandParser {
24 var parser = new ArgParser();
25 parser.addOption('server', defaultsTo: 'http://pub.dartlang.org',
26 help: 'The package server to which to upload this package');
27 return parser;
28 }
29
30 /// The URL of the server to which to upload the package.
Bob Nystrom 2012/11/26 23:39:52 Don't fear the dangling preposition. "The URL of t
nweiz 2012/11/27 20:15:54 Never. :D
Bob Nystrom 2012/11/27 21:00:53 Dangling prepositions are a grammatical offense up
31 Uri get server => new Uri.fromString(commandOptions['server']);
32
33 Future onRun() {
34 return oauth2.withClient(cache, (client) {
35 // TODO(nweiz): Better error-handling. There are a few cases we need to
36 // handle better:
37 //
38 // * The server can tell us we need new credentials (a 401 error). The
39 // oauth2 package should throw an AuthorizationException in this case
40 // (contingent on issue 6813 and 6275). We should have the user
41 // re-authorize the client, then restart the command. We should also do
42 // this in case of an ExpirationException.
43 //
44 // * Cloud Storage can provide an XML-formatted error. We should report
45 // that error and exit.
46 return Futures.wait([
47 client.get(server.resolve("/packages/versions/new.json")),
48 _filesToPublish.transform(createTarGz).chain(consumeInputStream)
49 ]).chain((results) {
50 var response = results[0];
51 var packageBytes = results[1];
52 var parameters = _parseJson(response);
53 if (response.statusCode != 200) _serverError(parameters, response);
54
55 var url = _expectField(parameters, 'url', response);
56 if (url is! String) _invalidServerResponse(response);
Bob Nystrom 2012/11/26 23:39:52 How about moving the condition into the function,
nweiz 2012/11/27 20:15:54 It doesn't really seem worthwhile to move a one-li
Bob Nystrom 2012/11/27 21:00:53 Fair enough. Part of my motivation here is that I'
57 var request = new http.MultipartRequest(
58 'POST', new Uri.fromString(url));
59
60 var fields = _expectField(parameters, 'fields', response);
61 if (fields is! Map) _invalidServerResponse(response);
62 fields.forEach((key, value) {
63 if (value is! String) _invalidServerResponse(response);
64 request.fields[key] = value;
65 });
66
67 request.followRedirects = false;
68 request.files.add(new http.MultipartFile.fromBytes(
69 'file', packageBytes, filename: 'package.tar.gz'));
70 return client.send(request);
71 }).chain(http.Response.fromStream).chain((response) {
72 var location = response.headers['location'];
73 if (location == null) {
74 // TODO(nweiz): the response may have XML-formatted information about
75 // the error. Try to parse that out once we have an easily-accessible
76 // XML parser.
77 throw 'Failed to upload the package.';
78 }
79 return client.get(location);
80 }).transform((response) {
81 var parsed = _parseJson(response);
82 if (parsed.containsKey('error')) _serverError(parsed, response);
83 if (parsed['success'] is! Map ||
84 !parsed['success'].containsKey('message') ||
85 parsed['success']['message'] is! String) {
86 _invalidServerResponse(response);
87 }
88 print(parsed['success']['message']);
89 });
90 }).transformException((e) {
91 if (e is! oauth2.ExpirationException) throw e;
92
93 printError("Pub's authorization to upload packages has expired and can't "
94 "be automatically refreshed.");
Bob Nystrom 2012/11/26 23:39:52 Can we tell the user what they need to do to solve
nweiz 2012/11/27 20:15:54 We restart the command immediately afterwards, whi
95 return onRun();
96 });
97 }
98
99 /// Returns a list of files that should be included in the published package.
100 /// If this is a Git repository, this will respect .gitignore; otherwise, it
Bob Nystrom 2012/11/26 23:39:52 Brilliant.
101 /// will return all non-hidden files.
102 Future<List<String>> get _filesToPublish {
103 var rootDir = entrypoint.root.dir;
104 return Futures.wait([
105 dirExists(join(rootDir, '.git')),
106 git.isInstalled
107 ]).chain((results) {
108 if (results[0] && results[1]) {
109 // List all files that aren't gitignored, including those not checked in
110 // to Git.
Bob Nystrom 2012/11/26 23:39:52 Should we warn if they are about to publish files
nweiz 2012/11/27 20:15:54 I don't think so. It seems like that would get rea
111 return git.run(["ls-files", "--cached", "--others"]);
112 }
113
114 return listDir(rootDir, recursive: true).chain((entries) {
115 return Futures.wait(entries.map((entry) {
116 return fileExists(entry).transform((isFile) => isFile ? entry : null);
117 }));
118 }).transform((files) => files.filter((file) => file != null));
119 }).transform((files) {
120 var prefix = '$rootDir/';
121 return files.map((file) {
Bob Nystrom 2012/11/26 23:39:52 What happens on Windows here?
nweiz 2012/11/27 20:15:54 As far as I can tell it should just work.
122 if (!file.startsWith(prefix)) return file;
123 return file.substring(prefix.length);
124 });
125 });
Bob Nystrom 2012/11/27 21:00:53 Oh, I forgot to mention this in the original revie
nweiz 2012/11/27 22:07:34 Done.
126 }
127
128 /// Parses a response body, assuming it's JSON-formatted. Throws a
129 /// user-friendly error if the response body is invalid JSON, or if it's not a
130 /// map.
131 Map _parseJson(http.Response response) {
132 var value;
133 try {
134 value = JSON.parse(response.body);
135 } catch (e) {
Bob Nystrom 2012/11/26 23:39:52 Bare catches make me break out in hives. This is b
nweiz 2012/11/27 20:15:54 Done.
136 _invalidServerResponse(response);
137 }
138 if (value is! Map) _invalidServerResponse(response);
139 return value;
140 }
141
142 /// Returns the value associated with [key] in [map]. Throws a user-friendly
143 /// error if [map] doens't contain [key].
144 _expectField(Map map, String key, http.Response response) {
145 if (map.containsKey(key)) return map[key];
146 _invalidServerResponse(response);
147 }
148
149 /// Extracts the error message from a JSON error sent from the server. Throws
150 /// an appropriate error if the error map is improperly formatted.
151 void _serverError(Map errorMap, http.Response response) {
152 if (errorMap['error'] is! Map ||
153 !errorMap['error'].containsKey('message') ||
154 errorMap['error']['message'] is! String) {
155 _invalidServerResponse(response);
156 }
157 throw errorMap['error']['message'];
158 }
159
160 /// Throws an error describing an invalid response from the server.
161 void _invalidServerResponse(http.Response response) {
162 throw 'Invalid server response:\n${response.body}';
163 }
164 }
OLDNEW
« no previous file with comments | « no previous file | utils/pub/hosted_source.dart » ('j') | utils/pub/io.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698