1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2021-2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import ArgumentParser
import Basics
import CoreCommands
import Foundation
import PackageModel
import PackageRegistry
import Workspace
public struct PackageRegistryCommand: AsyncParsableCommand {
public static var configuration = CommandConfiguration(
commandName: "package-registry",
_superCommandName: "swift",
abstract: "Interact with package registry and manage related configuration",
discussion: "SEE ALSO: swift package",
version: SwiftVersion.current.completeDisplayString,
subcommands: [
Set.self,
Unset.self,
Login.self,
Logout.self,
Publish.self,
],
helpNames: [.short, .long, .customLong("help", withSingleDash: true)]
)
@OptionGroup()
var globalOptions: GlobalOptions
public init() {}
struct Set: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Set a custom registry"
)
@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions
@Flag(help: "Apply settings to all projects for this user")
var global: Bool = false
@Option(help: "Associate the registry with a given scope")
var scope: String?
@Flag(name: .customLong("allow-insecure-http"), help: "Allow using a non-HTTPS registry URL")
var allowInsecureHTTP: Bool = false
@Argument(help: "The registry URL")
var url: URL
var registryURL: URL {
self.url
}
func run(_ swiftCommandState: SwiftCommandState) async throws {
try self.registryURL.validateRegistryURL(allowHTTP: self.allowInsecureHTTP)
let scope = try scope.map(PackageIdentity.Scope.init(validating:))
let set: (inout RegistryConfiguration) throws -> Void = { configuration in
let registry = Registry(url: self.registryURL, supportsAvailability: false)
if let scope {
configuration.scopedRegistries[scope] = registry
} else {
configuration.defaultRegistry = registry
}
}
let configuration = try getRegistriesConfig(swiftCommandState, global: self.global)
if self.global {
try configuration.updateShared(with: set)
} else {
try configuration.updateLocal(with: set)
}
}
}
struct Unset: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Remove a configured registry"
)
@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions
@Flag(help: "Apply settings to all projects for this user")
var global: Bool = false
@Option(help: "Associate the registry with a given scope")
var scope: String?
func run(_ swiftCommandState: SwiftCommandState) async throws {
let scope = try scope.map(PackageIdentity.Scope.init(validating:))
let unset: (inout RegistryConfiguration) throws -> Void = { configuration in
if let scope {
guard let _ = configuration.scopedRegistries[scope] else {
throw ConfigurationError.missingScope(scope)
}
configuration.scopedRegistries.removeValue(forKey: scope)
} else {
guard let _ = configuration.defaultRegistry else {
throw ConfigurationError.missingScope()
}
configuration.defaultRegistry = nil
}
}
let configuration = try getRegistriesConfig(swiftCommandState, global: self.global)
if self.global {
try configuration.updateShared(with: unset)
} else {
try configuration.updateLocal(with: unset)
}
}
}
// common utility
enum ConfigurationError: Swift.Error {
case missingScope(PackageIdentity.Scope? = nil)
}
enum ValidationError: Swift.Error {
case invalidURL(URL)
case invalidPackageIdentity(PackageIdentity)
case unknownRegistry
case unknownCredentialStore
case invalidCredentialStore(Error)
case credentialLengthLimitExceeded(Int)
}
static func getRegistriesConfig(_ swiftCommandState: SwiftCommandState, global: Bool) throws -> Workspace.Configuration.Registries {
if global {
let sharedRegistriesFile = Workspace.DefaultLocations.registriesConfigurationFile(
at: swiftCommandState.sharedConfigurationDirectory
)
// Workspace not needed when working with user-level registries config
return try .init(
fileSystem: swiftCommandState.fileSystem,
localRegistriesFile: .none,
sharedRegistriesFile: sharedRegistriesFile
)
} else {
let workspace = try swiftCommandState.getActiveWorkspace()
return try .init(
fileSystem: swiftCommandState.fileSystem,
localRegistriesFile: workspace.location.localRegistriesConfigurationFile,
sharedRegistriesFile: workspace.location.sharedRegistriesConfigurationFile
)
}
}
}
extension URL {
func validateRegistryURL(allowHTTP: Bool = false) throws {
guard self.scheme == "https" || (self.scheme == "http" && allowHTTP) else {
throw PackageRegistryCommand.ValidationError.invalidURL(self)
}
}
}
extension PackageRegistryCommand.ConfigurationError: CustomStringConvertible {
var description: String {
switch self {
case .missingScope(let scope?):
return "No existing entry for scope: \(scope)"
case .missingScope:
return "No existing entry for default scope"
}
}
}
extension PackageRegistryCommand.ValidationError: CustomStringConvertible {
var description: String {
switch self {
case .invalidURL(let url):
return "invalid URL: \(url)"
case .invalidPackageIdentity(let identity):
return "invalid package identifier '\(identity)'"
case .unknownRegistry:
return "unknown registry, is one configured?"
case .unknownCredentialStore:
return "no credential store available"
case .invalidCredentialStore(let error):
return "credential store is invalid: \(error.interpolationDescription)"
case .credentialLengthLimitExceeded(let limit):
return "password or access token must be \(limit) characters or less"
}
}
}
|