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

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: Code review 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/hosted_source.dart » ('j') | no next file with comments »
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. See issue
18 // 6949.
19 /// Handles the `lish` and `publish` pub commands.
20 class LishCommand extends PubCommand {
21 String get description => "publish the current package to pub.dartlang.org";
22 String get usage => "pub lish [options]";
23
24 ArgParser get commandParser {
25 var parser = new ArgParser();
26 parser.addOption('server', defaultsTo: 'http://pub.dartlang.org',
27 help: 'The package server to which to upload this package');
28 return parser;
29 }
30
31 /// The URL of the server to which to upload the package.
32 Uri get server => new Uri.fromString(commandOptions['server']);
33
34 Future onRun() {
35 return oauth2.withClient(cache, (client) {
36 // TODO(nweiz): Better error-handling. There are a few cases we need to
37 // handle better:
38 //
39 // * The server can tell us we need new credentials (a 401 error). The
40 // oauth2 package should throw an AuthorizationException in this case
41 // (contingent on issue 6813 and 6275). We should have the user
42 // re-authorize the client, then restart the command. We should also do
43 // this in case of an ExpirationException. See issue 6950.
44 //
45 // * Cloud Storage can provide an XML-formatted error. We should report
46 // that error and exit.
47 return Futures.wait([
48 client.get(server.resolve("/packages/versions/new.json")),
49 _filesToPublish.transform((files) {
50 return createTarGz(files, baseDir: entrypoint.root.dir);
51 }).chain(consumeInputStream)
52 ]).chain((results) {
53 var response = results[0];
54 var packageBytes = results[1];
55 var parameters = _parseJson(response);
56 if (response.statusCode != 200) _serverError(parameters, response);
57
58 var url = _expectField(parameters, 'url', response);
59 if (url is! String) _invalidServerResponse(response);
60 var request = new http.MultipartRequest(
61 'POST', new Uri.fromString(url));
62
63 var fields = _expectField(parameters, 'fields', response);
64 if (fields is! Map) _invalidServerResponse(response);
65 fields.forEach((key, value) {
66 if (value is! String) _invalidServerResponse(response);
67 request.fields[key] = value;
68 });
69
70 request.followRedirects = false;
71 request.files.add(new http.MultipartFile.fromBytes(
72 'file', packageBytes, filename: 'package.tar.gz'));
73 return client.send(request);
74 }).chain(http.Response.fromStream).chain((response) {
75 var location = response.headers['location'];
76 if (location == null) {
77 // TODO(nweiz): the response may have XML-formatted information about
78 // the error. Try to parse that out once we have an easily-accessible
79 // XML parser.
80 throw 'Failed to upload the package.';
81 }
82 return client.get(location);
83 }).transform((response) {
84 var parsed = _parseJson(response);
85 if (parsed.containsKey('error')) _serverError(parsed, response);
86 if (parsed['success'] is! Map ||
87 !parsed['success'].containsKey('message') ||
88 parsed['success']['message'] is! String) {
89 _invalidServerResponse(response);
90 }
91 print(parsed['success']['message']);
92 });
93 }).transformException((e) {
94 if (e is! oauth2.ExpirationException) throw e;
95
96 printError("Pub's authorization to upload packages has expired and can't "
97 "be automatically refreshed.");
98 return onRun();
99 });
100 }
101
102 /// Returns a list of files that should be included in the published package.
103 /// If this is a Git repository, this will respect .gitignore; otherwise, it
104 /// will return all non-hidden files.
105 Future<List<String>> get _filesToPublish {
106 var rootDir = entrypoint.root.dir;
107 return Futures.wait([
108 dirExists(join(rootDir, '.git')),
109 git.isInstalled
110 ]).chain((results) {
111 if (results[0] && results[1]) {
112 // List all files that aren't gitignored, including those not checked in
113 // to Git.
114 return git.run(["ls-files", "--cached", "--others"]);
115 }
116
117 return listDir(rootDir, recursive: true).chain((entries) {
118 return Futures.wait(entries.map((entry) {
119 return fileExists(entry).transform((isFile) => isFile ? entry : null);
120 }));
121 });
122 }).transform((files) => files.filter((file) {
123 return file != null && basename(file) != 'packages';
124 }));
125 }
126
127 /// Parses a response body, assuming it's JSON-formatted. Throws a
128 /// user-friendly error if the response body is invalid JSON, or if it's not a
129 /// map.
130 Map _parseJson(http.Response response) {
131 var value;
132 try {
133 value = JSON.parse(response.body);
134 } catch (e) {
135 // TODO(nweiz): narrow this catch clause once issue 6775 is fixed.
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') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698