From fca3108c1c2156d85cbfca97af6161f8805f1978 Mon Sep 17 00:00:00 2001
From: "rnystrom@google.com" <rnystrom@google.com>
Date: Mon, 4 Aug 2014 21:16:00 +0000
Subject: [PATCH] Add publishTo to pubspec and use it in pub lish.

RELNOTE=Pubspecs can have a "publishTo" field which is either a URL to
  specify the default server that publishing should use or "none" to
  specify that the package is private and cannot be published.
BUG=https://code.google.com/p/dart/issues/detail?id=20046
R=nweiz@google.com

Review URL: https://codereview.chromium.org//417043005

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@38880 260f80e4-7a28-3924-810f-c04153c831b5
---
 lib/src/command/lish.dart                     | 21 ++++++++++-
 lib/src/pubspec.dart                          | 36 +++++++++++++++++++
 .../does_not_publish_if_private_test.dart     | 22 ++++++++++++
 ...blish_if_private_with_server_arg_test.dart | 23 ++++++++++++
 ...orce_does_not_publish_if_private_test.dart | 22 ++++++++++++
 test/lish/preview_errors_if_private_test.dart | 22 ++++++++++++
 ...er_arg_does_not_override_private_test.dart | 22 ++++++++++++
 ...ver_arg_overrides_publish_to_url_test.dart | 20 +++++++++++
 test/lish/uses_publish_to_url_test.dart       | 20 +++++++++++
 test/pubspec_test.dart                        | 31 ++++++++++++++++
 10 files changed, 238 insertions(+), 1 deletion(-)
 create mode 100644 test/lish/does_not_publish_if_private_test.dart
 create mode 100644 test/lish/does_not_publish_if_private_with_server_arg_test.dart
 create mode 100644 test/lish/force_does_not_publish_if_private_test.dart
 create mode 100644 test/lish/preview_errors_if_private_test.dart
 create mode 100644 test/lish/server_arg_does_not_override_private_test.dart
 create mode 100644 test/lish/server_arg_overrides_publish_to_url_test.dart
 create mode 100644 test/lish/uses_publish_to_url_test.dart

diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index a58ae3c6..82635056 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -26,7 +26,20 @@ class LishCommand extends PubCommand {
   List<String> get aliases => const ["lish", "lush"];
 
   /// The URL of the server to which to upload the package.
-  Uri get server => Uri.parse(commandOptions['server']);
+  Uri get server {
+    // An explicit argument takes precedence.
+    if (commandOptions.wasParsed('server')) {
+      return Uri.parse(commandOptions['server']);
+    }
+
+    // Otherwise, use the one specified in the pubspec.
+    if (entrypoint.root.pubspec.publishTo != null) {
+      return Uri.parse(entrypoint.root.pubspec.publishTo);
+    }
+
+    // Otherwise, use the default.
+    return Uri.parse(HostedSource.defaultUrl);
+  }
 
   /// Whether the publish is just a preview.
   bool get dryRun => commandOptions['dry-run'];
@@ -98,6 +111,12 @@ class LishCommand extends PubCommand {
       usageError('Cannot use both --force and --dry-run.');
     }
 
+    if (entrypoint.root.pubspec.isPrivate) {
+      dataError('A private package cannot be published.\n'
+          'You can enable this by changing the "publishTo" field in your '
+              'pubspec.');
+    }
+
     var files = entrypoint.root.listFiles();
     log.fine('Archiving and publishing ${entrypoint.root}.');
 
diff --git a/lib/src/pubspec.dart b/lib/src/pubspec.dart
index 883a77c9..e9ea5966 100644
--- a/lib/src/pubspec.dart
+++ b/lib/src/pubspec.dart
@@ -209,6 +209,41 @@ class Pubspec {
   }
   PubspecEnvironment _environment;
 
+  /// The URL of the server that the package should default to being published
+  /// to, "none" if the package should not be published, or `null` if it should
+  /// be published to the default server.
+  ///
+  /// If this does return a URL string, it will be a valid parseable URL.
+  String get publishTo {
+    if (_parsedPublishTo) return _publishTo;
+
+    var publishTo = fields['publishTo'];
+    if (publishTo != null) {
+      var span = fields.nodes['publishTo'].span;
+
+      if (publishTo is! String) {
+        _error('"publishTo" field must be a string.', span);
+      }
+
+      // It must be "none" or a valid URL.
+      if (publishTo != "none") {
+        _wrapFormatException('"publishTo" field', span,
+            () => Uri.parse(publishTo));
+      }
+    }
+
+    _parsedPublishTo = true;
+    _publishTo = publishTo;
+    return _publishTo;
+  }
+  bool _parsedPublishTo = false;
+  String _publishTo;
+
+  /// Whether the package is private and cannot be published.
+  ///
+  /// This is specified in the pubspec by setting "publishTo" to "none".
+  bool get isPrivate => publishTo == "none";
+
   /// Whether or not the pubspec has no contents.
   bool get isEmpty =>
     name == null && version == Version.none && dependencies.isEmpty;
@@ -304,6 +339,7 @@ class Pubspec {
     _getError(() => this.devDependencies);
     _getError(() => this.transformers);
     _getError(() => this.environment);
+    _getError(() => this.publishTo);
     return errors;
   }
 
diff --git a/test/lish/does_not_publish_if_private_test.dart b/test/lish/does_not_publish_if_private_test.dart
new file mode 100644
index 00000000..69e843ec
--- /dev/null
+++ b/test/lish/does_not_publish_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('does not publish if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/test/lish/does_not_publish_if_private_with_server_arg_test.dart b/test/lish/does_not_publish_if_private_with_server_arg_test.dart
new file mode 100644
index 00000000..f5dbcc73
--- /dev/null
+++ b/test/lish/does_not_publish_if_private_with_server_arg_test.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('does not publish if the package is private even if a server '
+      'argument is provided', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--server", "http://example.com"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/test/lish/force_does_not_publish_if_private_test.dart b/test/lish/force_does_not_publish_if_private_test.dart
new file mode 100644
index 00000000..b92ea728
--- /dev/null
+++ b/test/lish/force_does_not_publish_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('force does not publish if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--force"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/test/lish/preview_errors_if_private_test.dart b/test/lish/preview_errors_if_private_test.dart
new file mode 100644
index 00000000..cc538a7c
--- /dev/null
+++ b/test/lish/preview_errors_if_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('preview shows an error if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/test/lish/server_arg_does_not_override_private_test.dart b/test/lish/server_arg_does_not_override_private_test.dart
new file mode 100644
index 00000000..d3a56674
--- /dev/null
+++ b/test/lish/server_arg_does_not_override_private_test.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../../lib/src/exit_codes.dart' as exit_codes;
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('an explicit --server argument does not override privacy', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "none";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--server", "http://arg.com"],
+        error: startsWith("A private package cannot be published."),
+        exitCode: exit_codes.DATA);
+  });
+}
diff --git a/test/lish/server_arg_overrides_publish_to_url_test.dart b/test/lish/server_arg_overrides_publish_to_url_test.dart
new file mode 100644
index 00000000..5c04d664
--- /dev/null
+++ b/test/lish/server_arg_overrides_publish_to_url_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('an explicit --server argument overrides a "publishTo" url', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "http://pubspec.com";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run", "--server", "http://arg.com"],
+        output: contains("http://arg.com"));
+  });
+}
diff --git a/test/lish/uses_publish_to_url_test.dart b/test/lish/uses_publish_to_url_test.dart
new file mode 100644
index 00000000..ae011fe0
--- /dev/null
+++ b/test/lish/uses_publish_to_url_test.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../descriptor.dart' as d;
+import '../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('preview shows an error if the package is private', () {
+    var pkg = packageMap("test_pkg", "1.0.0");
+    pkg["publishTo"] = "http://example.com";
+    d.dir(appPath, [d.pubspec(pkg)]).create();
+
+    schedulePub(args: ["lish", "--dry-run"],
+        output: contains("Publishing test_pkg 1.0.0 to http://example.com"));
+  });
+}
diff --git a/test/pubspec_test.dart b/test/pubspec_test.dart
index 1d38c6c5..41e6d7c9 100644
--- a/test/pubspec_test.dart
+++ b/test/pubspec_test.dart
@@ -413,5 +413,36 @@ environment:
             (pubspec) => pubspec.environment);
       });
     });
+
+    group("publishTo", () {
+      test("defaults to null if omitted", () {
+        var pubspec = new Pubspec.parse('', sources);
+        expect(pubspec.publishTo, isNull);
+      });
+
+      test("throws if not a string", () {
+        expectPubspecException('publishTo: 123',
+            (pubspec) => pubspec.publishTo);
+      });
+
+      test("allows a URL", () {
+        var pubspec = new Pubspec.parse('''
+publishTo: http://example.com
+''', sources);
+        expect(pubspec.publishTo, equals("http://example.com"));
+      });
+
+      test("allows none", () {
+        var pubspec = new Pubspec.parse('''
+publishTo: none
+''', sources);
+        expect(pubspec.publishTo, equals("none"));
+      });
+
+      test("throws on other strings", () {
+        expectPubspecException('publishTo: http://bad.url:not-port',
+            (pubspec) => pubspec.publishTo);
+      });
+    });
   });
 }
-- 
GitLab