Changes to project structure to allow for better testability (#191)
* Changed sources folder to separate the CLI from the 'Kit' to allow for testability of each command * Fixed Swift 5.2 Support * Removed Path.swift (for now) * Fixed OpenCombine and other version issues for 5.2 support * Removed Path.swift from test target dependency Co-authored-by: thecb4 <cavelle@tehcb4.io>
This commit is contained in:
parent
dd452fb6f1
commit
5fe8c8edac
|
@ -0,0 +1,127 @@
|
||||||
|
{
|
||||||
|
"entries": {
|
||||||
|
"brew": {
|
||||||
|
"pre-commit": {
|
||||||
|
"version": "2.9.3",
|
||||||
|
"bottle": {
|
||||||
|
"cellar": ":any",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"files": {
|
||||||
|
"big_sur": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/pre-commit-2.9.3.big_sur.bottle.tar.gz",
|
||||||
|
"sha256": "81f18e83e858feffacc9a9441c836a2abcf5b1a880f09ee99a261f12ac8ceca7"
|
||||||
|
},
|
||||||
|
"catalina": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/pre-commit-2.9.3.catalina.bottle.tar.gz",
|
||||||
|
"sha256": "16977b9f715b4330e7975761edddbaad4b891b25e6c8e4c4186ca4e2e99b11e7"
|
||||||
|
},
|
||||||
|
"mojave": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/pre-commit-2.9.3.mojave.bottle.tar.gz",
|
||||||
|
"sha256": "82af5cc062aed6375aff5dfc759bac2cbc549ae0709f1a720ec805368944b46e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"swiftformat": {
|
||||||
|
"version": "0.47.8",
|
||||||
|
"bottle": {
|
||||||
|
"cellar": ":any_skip_relocation",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"files": {
|
||||||
|
"big_sur": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.8.big_sur.bottle.tar.gz",
|
||||||
|
"sha256": "47e3aa92792a53413eb0866d3b6709e88c7164184f406c3e49c560e82a2fe589"
|
||||||
|
},
|
||||||
|
"catalina": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.8.catalina.bottle.tar.gz",
|
||||||
|
"sha256": "c268a44da4ed4e7836d2b43f917a1411f3b8f13488ee25c66f4bd8bb2f4d5e05"
|
||||||
|
},
|
||||||
|
"mojave": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/swiftformat-0.47.8.mojave.bottle.tar.gz",
|
||||||
|
"sha256": "38abb30c870974c65ee0a3afed11cd5f983ab05a13923061fc67c4b6566df6f8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"swiftlint": {
|
||||||
|
"version": "0.42.0",
|
||||||
|
"bottle": {
|
||||||
|
"cellar": ":any_skip_relocation",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"files": {
|
||||||
|
"big_sur": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/swiftlint-0.42.0.big_sur.bottle.tar.gz",
|
||||||
|
"sha256": "1a0540f0ff6cac2da0a51672db7963aef71cc58954c34791e9b01dafd63c5898"
|
||||||
|
},
|
||||||
|
"catalina": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/swiftlint-0.42.0.catalina.bottle.tar.gz",
|
||||||
|
"sha256": "e9023ed754eb8cb78a9f2b469a90875ca42a7afffd3e96f8142252e81d889793"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"binaryen": {
|
||||||
|
"version": "98",
|
||||||
|
"bottle": {
|
||||||
|
"cellar": ":any",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"files": {
|
||||||
|
"big_sur": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/binaryen-98.big_sur.bottle.tar.gz",
|
||||||
|
"sha256": "5d5ac79ec3aabebd8e62433c84bd0d61b0f4ee44d56e4c4f47475276a6428bd8"
|
||||||
|
},
|
||||||
|
"catalina": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/binaryen-98.catalina.bottle.tar.gz",
|
||||||
|
"sha256": "260a8deac0c78a9fead982cf6d05fd818330bc4e28cdc73ecd5de6952ded6e8b"
|
||||||
|
},
|
||||||
|
"mojave": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/binaryen-98.mojave.bottle.tar.gz",
|
||||||
|
"sha256": "7a68230a62307fbd61638803e942e4d556f99b8e4d6fbbdd3dbaf6997b3b2843"
|
||||||
|
},
|
||||||
|
"high_sierra": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/binaryen-98.high_sierra.bottle.tar.gz",
|
||||||
|
"sha256": "4656a9b10a7db143e3619126ea1e0c169d05b06b27c7e75d5f641c86135c7b1f"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wasmer": {
|
||||||
|
"version": "0.16.2",
|
||||||
|
"bottle": {
|
||||||
|
"cellar": ":any_skip_relocation",
|
||||||
|
"prefix": "/usr/local",
|
||||||
|
"files": {
|
||||||
|
"big_sur": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/wasmer-0.16.2.big_sur.bottle.tar.gz",
|
||||||
|
"sha256": "63d91bbfece68628e7bb464cd8d3a90a6dc89344c4889ada157f174db62f05da"
|
||||||
|
},
|
||||||
|
"catalina": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/wasmer-0.16.2.catalina.bottle.tar.gz",
|
||||||
|
"sha256": "751b4b059036dbca254eef935bc03240e1fd559465a376a0cff8f5a41dcd3980"
|
||||||
|
},
|
||||||
|
"mojave": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/wasmer-0.16.2.mojave.bottle.tar.gz",
|
||||||
|
"sha256": "725d2b857e0954b1e2fd8a01021847e168d5daec33cd76c32f90a0ae12fdf422"
|
||||||
|
},
|
||||||
|
"high_sierra": {
|
||||||
|
"url": "https://homebrew.bintray.com/bottles/wasmer-0.16.2.high_sierra.bottle.tar.gz",
|
||||||
|
"sha256": "42ea898c1ebd9c0ac58bf21117c05df6a4726123590444c19173a01586c80c63"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"macos": {
|
||||||
|
"big_sur": {
|
||||||
|
"HOMEBREW_VERSION": "2.6.2",
|
||||||
|
"HOMEBREW_PREFIX": "/usr/local",
|
||||||
|
"Homebrew/homebrew-core": "386111a04c8f09f72c60548e519a3e27fade9c34",
|
||||||
|
"CLT": "",
|
||||||
|
"Xcode": "12.2",
|
||||||
|
"macOS": "11.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,15 @@
|
||||||
"version": "0.11.0"
|
"version": "0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"package": "Path.swift",
|
||||||
|
"repositoryURL": "https://github.com/mxcl/Path.swift.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f062ed9ce3729fb3ba3f43573136d2a0e0c6d209",
|
||||||
|
"version": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"package": "routing-kit",
|
"package": "routing-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/routing-kit.git",
|
"repositoryURL": "https://github.com/vapor/routing-kit.git",
|
||||||
|
|
|
@ -12,6 +12,14 @@ let openCombineProduct = Target.Dependency.product(
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "carton",
|
name: "carton",
|
||||||
platforms: [.macOS(.v10_15)],
|
platforms: [.macOS(.v10_15)],
|
||||||
|
products: [
|
||||||
|
.library(name: "SwiftToolchain", targets: ["SwiftToolchain"]),
|
||||||
|
.library(name: "CartonHelpers", targets: ["CartonHelpers"]),
|
||||||
|
.library(name: "CartonKit", targets: ["CartonKit"]),
|
||||||
|
.library(name: "CartonCLI", targets: ["CartonCLI"]),
|
||||||
|
.executable(name: "carton", targets: ["Carton"]),
|
||||||
|
.executable(name: "carton-release", targets: ["carton-release"]),
|
||||||
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.2.2"),
|
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.2.2"),
|
||||||
.package(
|
.package(
|
||||||
|
@ -27,13 +35,33 @@ let package = Package(
|
||||||
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.1.0"),
|
.package(url: "https://github.com/apple/swift-crypto.git", from: "1.1.0"),
|
||||||
.package(url: "https://github.com/JohnSundell/Splash.git", from: "0.14.0"),
|
.package(url: "https://github.com/JohnSundell/Splash.git", from: "0.14.0"),
|
||||||
.package(url: "https://github.com/swiftwasm/WasmTransformer", .upToNextMinor(from: "0.0.2")),
|
.package(url: "https://github.com/swiftwasm/WasmTransformer", .upToNextMinor(from: "0.0.2")),
|
||||||
|
.package(url: "https://github.com/mxcl/Path.swift.git", .exact("1.0.0")),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package. A target can define a module
|
// Targets are the basic building blocks of a package. A target can define a module
|
||||||
// or a test suite. Targets can depend on other targets in this package, and on
|
// or a test suite. Targets can depend on other targets in this package, and on
|
||||||
// products in packages which this package depends on.
|
// products in packages which this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "carton",
|
name: "Carton",
|
||||||
|
dependencies: [
|
||||||
|
"CartonCLI",
|
||||||
|
// commented out for now. Will remove once confirmed working
|
||||||
|
// .product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
|
// .product(name: "AsyncHTTPClient", package: "async-http-client"),
|
||||||
|
// .product(name: "Crypto", package: "swift-crypto"),
|
||||||
|
// .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
|
||||||
|
// .product(name: "Vapor", package: "vapor"),
|
||||||
|
// "CartonHelpers",
|
||||||
|
// openCombineProduct,
|
||||||
|
// "SwiftToolchain",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "CartonCLI",
|
||||||
|
dependencies: ["CartonKit"]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "CartonKit",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
.product(name: "AsyncHTTPClient", package: "async-http-client"),
|
.product(name: "AsyncHTTPClient", package: "async-http-client"),
|
||||||
|
@ -78,9 +106,17 @@ let package = Package(
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "CartonTests",
|
name: "CartonTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"carton",
|
"Carton",
|
||||||
"CartonHelpers",
|
"CartonHelpers",
|
||||||
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
|
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
|
||||||
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
|
.product(name: "Path", package: "Path.swift"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CartonCLITests",
|
||||||
|
dependencies: [
|
||||||
|
"CartonCLI",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,10 +6,18 @@ import PackageDescription
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "carton",
|
name: "carton",
|
||||||
platforms: [.macOS(.v10_15)],
|
platforms: [.macOS(.v10_15)],
|
||||||
|
products: [
|
||||||
|
.library(name: "SwiftToolchain", targets: ["SwiftToolchain"]),
|
||||||
|
.library(name: "CartonHelpers", targets: ["CartonHelpers"]),
|
||||||
|
.library(name: "CartonKit", targets: ["CartonKit"]),
|
||||||
|
.library(name: "CartonCLI", targets: ["CartonCLI"]),
|
||||||
|
.executable(name: "carton", targets: ["Carton"]),
|
||||||
|
.executable(name: "carton-release", targets: ["carton-release"]),
|
||||||
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.1.1"),
|
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.1.1"),
|
||||||
.package(
|
.package(
|
||||||
url: "https://github.com/apple/swift-argument-parser",
|
url: "https://github.com/apple/swift-argument-parser.git",
|
||||||
.upToNextMinor(from: "0.3.0")
|
.upToNextMinor(from: "0.3.0")
|
||||||
),
|
),
|
||||||
.package(
|
.package(
|
||||||
|
@ -27,7 +35,26 @@ let package = Package(
|
||||||
// or a test suite. Targets can depend on other targets in this package, and on
|
// or a test suite. Targets can depend on other targets in this package, and on
|
||||||
// products in packages which this package depends on.
|
// products in packages which this package depends on.
|
||||||
.target(
|
.target(
|
||||||
name: "carton",
|
name: "Carton",
|
||||||
|
dependencies: [
|
||||||
|
"CartonCLI",
|
||||||
|
// commented out for now. Will remove once confirmed working
|
||||||
|
// .product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
|
// .product(name: "AsyncHTTPClient", package: "async-http-client"),
|
||||||
|
// .product(name: "Crypto", package: "swift-crypto"),
|
||||||
|
// .product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
|
||||||
|
// .product(name: "Vapor", package: "vapor"),
|
||||||
|
// "CartonHelpers",
|
||||||
|
// openCombineProduct,
|
||||||
|
// "SwiftToolchain",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "CartonCLI",
|
||||||
|
dependencies: ["CartonKit"]
|
||||||
|
),
|
||||||
|
.target(
|
||||||
|
name: "CartonKit",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
.product(name: "AsyncHTTPClient", package: "async-http-client"),
|
.product(name: "AsyncHTTPClient", package: "async-http-client"),
|
||||||
|
@ -71,7 +98,18 @@ let package = Package(
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "CartonTests",
|
name: "CartonTests",
|
||||||
dependencies: ["carton"]
|
dependencies: [
|
||||||
|
"Carton",
|
||||||
|
"CartonHelpers",
|
||||||
|
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
|
||||||
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "CartonCLITests",
|
||||||
|
dependencies: [
|
||||||
|
"CartonCLI",
|
||||||
|
]
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,4 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import CartonCLI
|
||||||
|
|
||||||
Carton.main()
|
Carton.main()
|
|
@ -15,10 +15,12 @@
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
|
||||||
struct Carton: ParsableCommand {
|
public struct Carton: ParsableCommand {
|
||||||
static let configuration = CommandConfiguration(
|
public static let configuration = CommandConfiguration(
|
||||||
abstract: "📦 Watcher, bundler, and test runner for your SwiftWasm apps.",
|
abstract: "📦 Watcher, bundler, and test runner for your SwiftWasm apps.",
|
||||||
version: cartonVersion,
|
version: cartonVersion,
|
||||||
subcommands: [Bundle.self, Dev.self, Init.self, SDK.self, Test.self, Package.self]
|
subcommands: [Bundle.self, Dev.self, Init.self, SDK.self, Test.self, Package.self]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
public init() {}
|
||||||
}
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
import CartonKit
|
||||||
import Crypto
|
import Crypto
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
|
@ -20,6 +20,7 @@ import Combine
|
||||||
#else
|
#else
|
||||||
import OpenCombine
|
import OpenCombine
|
||||||
#endif
|
#endif
|
||||||
|
import CartonKit
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
import CartonKit
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
import CartonKit
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
||||||
struct ListTemplates: ParsableCommand {
|
struct ListTemplates: ParsableCommand {
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import ArgumentParser
|
import ArgumentParser
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
import CartonKit
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
|
@ -19,6 +19,7 @@ import Combine
|
||||||
import OpenCombine
|
import OpenCombine
|
||||||
#endif
|
#endif
|
||||||
import CartonHelpers
|
import CartonHelpers
|
||||||
|
import CartonKit
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
import Crypto
|
import Crypto
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
||||||
extension ByteString {
|
public extension ByteString {
|
||||||
var hexSHA256: String {
|
var hexSHA256: String {
|
||||||
ByteString(SHA256.hash(data: contents)).hexadecimalRepresentation
|
ByteString(SHA256.hash(data: contents)).hexadecimalRepresentation
|
||||||
}
|
}
|
|
@ -33,15 +33,20 @@ private let verifyHash = Equality<ByteString, String> {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EntrypointError: Error {
|
public enum EntrypointError: Error {
|
||||||
case downloadFailed(url: String)
|
case downloadFailed(url: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Entrypoint {
|
public struct Entrypoint {
|
||||||
let fileName: String
|
let fileName: String
|
||||||
let sha256: ByteString
|
let sha256: ByteString
|
||||||
|
|
||||||
func paths(
|
public init(fileName: String, sha256: ByteString) {
|
||||||
|
self.fileName = fileName
|
||||||
|
self.sha256 = sha256
|
||||||
|
}
|
||||||
|
|
||||||
|
public func paths(
|
||||||
on fileSystem: FileSystem
|
on fileSystem: FileSystem
|
||||||
// swiftlint:disable:next large_tuple
|
// swiftlint:disable:next large_tuple
|
||||||
) -> (cartonDir: AbsolutePath, staticDir: AbsolutePath, filePath: AbsolutePath) {
|
) -> (cartonDir: AbsolutePath, staticDir: AbsolutePath, filePath: AbsolutePath) {
|
||||||
|
@ -50,7 +55,7 @@ struct Entrypoint {
|
||||||
return (cartonDir, staticDir, staticDir.appending(component: fileName))
|
return (cartonDir, staticDir, staticDir.appending(component: fileName))
|
||||||
}
|
}
|
||||||
|
|
||||||
func check(on fileSystem: FileSystem, _ terminal: InteractiveWriter) throws {
|
public func check(on fileSystem: FileSystem, _ terminal: InteractiveWriter) throws {
|
||||||
let (cartonDir, staticDir, filePath) = paths(on: fileSystem)
|
let (cartonDir, staticDir, filePath) = paths(on: fileSystem)
|
||||||
|
|
||||||
// If hash check fails, download the `static.zip` archive and unpack it
|
// If hash check fails, download the `static.zip` archive and unpack it
|
|
@ -14,8 +14,14 @@
|
||||||
|
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
||||||
struct Project {
|
public struct Project {
|
||||||
let name: String
|
let name: String
|
||||||
let path: AbsolutePath
|
let path: AbsolutePath
|
||||||
let inPlace: Bool
|
let inPlace: Bool
|
||||||
|
|
||||||
|
public init(name: String, path: AbsolutePath, inPlace: Bool) {
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.inPlace = inPlace
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,11 +16,11 @@ import CartonHelpers
|
||||||
import SwiftToolchain
|
import SwiftToolchain
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
||||||
enum Templates: String, CaseIterable {
|
public enum Templates: String, CaseIterable {
|
||||||
case basic
|
case basic
|
||||||
case tokamak
|
case tokamak
|
||||||
|
|
||||||
var template: Template.Type {
|
public var template: Template.Type {
|
||||||
switch self {
|
switch self {
|
||||||
case .basic: return Basic.self
|
case .basic: return Basic.self
|
||||||
case .tokamak: return Tokamak.self
|
case .tokamak: return Tokamak.self
|
||||||
|
@ -28,7 +28,7 @@ enum Templates: String, CaseIterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol Template {
|
public protocol Template {
|
||||||
static var description: String { get }
|
static var description: String { get }
|
||||||
static func create(
|
static func create(
|
||||||
on fileSystem: FileSystem,
|
on fileSystem: FileSystem,
|
|
@ -21,7 +21,7 @@ enum HTMLError: String, Error {
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HTML {
|
public struct HTML {
|
||||||
let value: String
|
let value: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,9 @@ extension HTML: ResponseEncodable {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
static func readCustomIndexPage(at path: String?, on fileSystem: FileSystem) throws -> String? {
|
public static func readCustomIndexPage(at path: String?,
|
||||||
|
on fileSystem: FileSystem) throws -> String?
|
||||||
|
{
|
||||||
if let customIndexPage = path {
|
if let customIndexPage = path {
|
||||||
let content = try localFileSystem.readFileContents(customIndexPage.isAbsolutePath ?
|
let content = try localFileSystem.readFileContents(customIndexPage.isAbsolutePath ?
|
||||||
AbsolutePath(customIndexPage) :
|
AbsolutePath(customIndexPage) :
|
||||||
|
@ -50,7 +52,7 @@ extension HTML: ResponseEncodable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func indexPage(customContent: String?, entrypointName: String) -> String {
|
public static func indexPage(customContent: String?, entrypointName: String) -> String {
|
||||||
let scriptTag = #"<script type="text/javascript" src="\#(entrypointName)"></script>"#
|
let scriptTag = #"<script type="text/javascript" src="\#(entrypointName)"></script>"#
|
||||||
if let customContent = customContent {
|
if let customContent = customContent {
|
||||||
return customContent.replacingOccurrences(
|
return customContent.replacingOccurrences(
|
|
@ -66,7 +66,7 @@ extension WebSocket: Hashable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class Server {
|
public final class Server {
|
||||||
private let decoder = JSONDecoder()
|
private let decoder = JSONDecoder()
|
||||||
private var connections = Set<WebSocket>()
|
private var connections = Set<WebSocket>()
|
||||||
private var subscriptions = [AnyCancellable]()
|
private var subscriptions = [AnyCancellable]()
|
||||||
|
@ -75,7 +75,7 @@ final class Server {
|
||||||
private let localURL: String
|
private let localURL: String
|
||||||
private let skipAutoOpen: Bool
|
private let skipAutoOpen: Bool
|
||||||
|
|
||||||
struct Configuration {
|
public struct Configuration {
|
||||||
let builder: Builder?
|
let builder: Builder?
|
||||||
let mainWasmPath: AbsolutePath
|
let mainWasmPath: AbsolutePath
|
||||||
let verbose: Bool
|
let verbose: Bool
|
||||||
|
@ -85,9 +85,31 @@ final class Server {
|
||||||
let package: SwiftToolchain.Package
|
let package: SwiftToolchain.Package
|
||||||
let product: Product?
|
let product: Product?
|
||||||
let entrypoint: Entrypoint
|
let entrypoint: Entrypoint
|
||||||
|
|
||||||
|
public init(
|
||||||
|
builder: Builder?,
|
||||||
|
mainWasmPath: AbsolutePath,
|
||||||
|
verbose: Bool,
|
||||||
|
skipAutoOpen: Bool,
|
||||||
|
port: Int,
|
||||||
|
customIndexContent: String?,
|
||||||
|
package: SwiftToolchain.Package,
|
||||||
|
product: Product?,
|
||||||
|
entrypoint: Entrypoint
|
||||||
|
) {
|
||||||
|
self.builder = builder
|
||||||
|
self.mainWasmPath = mainWasmPath
|
||||||
|
self.verbose = verbose
|
||||||
|
self.skipAutoOpen = skipAutoOpen
|
||||||
|
self.port = port
|
||||||
|
self.customIndexContent = customIndexContent
|
||||||
|
self.package = package
|
||||||
|
self.product = product
|
||||||
|
self.entrypoint = entrypoint
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(
|
public init(
|
||||||
with configuration: Configuration,
|
with configuration: Configuration,
|
||||||
_ terminal: InteractiveWriter
|
_ terminal: InteractiveWriter
|
||||||
) throws {
|
) throws {
|
||||||
|
@ -151,7 +173,7 @@ final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocking function that starts the HTTP server
|
/// Blocking function that starts the HTTP server
|
||||||
func run() throws {
|
public func run() throws {
|
||||||
defer { app.shutdown() }
|
defer { app.shutdown() }
|
||||||
try app.run()
|
try app.run()
|
||||||
for conn in connections {
|
for conn in connections {
|
|
@ -1,21 +1,21 @@
|
||||||
import TSCBasic
|
import TSCBasic
|
||||||
|
|
||||||
let devEntrypointSHA256 = ByteString([
|
public let devEntrypointSHA256 = ByteString([
|
||||||
0xBF, 0x7E, 0x84, 0x1D, 0x61, 0x40, 0x88, 0x83, 0xBC, 0x4C, 0x80, 0x24, 0x35, 0xEB, 0xED, 0x0D,
|
0xBF, 0x7E, 0x84, 0x1D, 0x61, 0x40, 0x88, 0x83, 0xBC, 0x4C, 0x80, 0x24, 0x35, 0xEB, 0xED, 0x0D,
|
||||||
0xF1, 0x5D, 0x22, 0x80, 0xC8, 0x70, 0x3D, 0xF0, 0xC6, 0x0C, 0x8A, 0x6F, 0x55, 0xD4, 0x8A, 0xF8,
|
0xF1, 0x5D, 0x22, 0x80, 0xC8, 0x70, 0x3D, 0xF0, 0xC6, 0x0C, 0x8A, 0x6F, 0x55, 0xD4, 0x8A, 0xF8,
|
||||||
])
|
])
|
||||||
|
|
||||||
let bundleEntrypointSHA256 = ByteString([
|
public let bundleEntrypointSHA256 = ByteString([
|
||||||
0x2B, 0xD2, 0xB5, 0x0C, 0x08, 0xFB, 0x5A, 0x8D, 0x55, 0xC4, 0x4B, 0x3A, 0x51, 0x63, 0x80, 0x1F,
|
0x2B, 0xD2, 0xB5, 0x0C, 0x08, 0xFB, 0x5A, 0x8D, 0x55, 0xC4, 0x4B, 0x3A, 0x51, 0x63, 0x80, 0x1F,
|
||||||
0xB9, 0x15, 0x50, 0xD2, 0xB6, 0x12, 0xC3, 0xDE, 0x3A, 0x7D, 0xEB, 0xB0, 0x1F, 0x40, 0x06, 0x3F,
|
0xB9, 0x15, 0x50, 0xD2, 0xB6, 0x12, 0xC3, 0xDE, 0x3A, 0x7D, 0xEB, 0xB0, 0x1F, 0x40, 0x06, 0x3F,
|
||||||
])
|
])
|
||||||
|
|
||||||
let testEntrypointSHA256 = ByteString([
|
public let testEntrypointSHA256 = ByteString([
|
||||||
0x51, 0x8C, 0xAF, 0x03, 0x7C, 0x12, 0x0A, 0xF4, 0xBC, 0xD1, 0xA0, 0x1B, 0xA2, 0xA8, 0x2B, 0x22,
|
0x51, 0x8C, 0xAF, 0x03, 0x7C, 0x12, 0x0A, 0xF4, 0xBC, 0xD1, 0xA0, 0x1B, 0xA2, 0xA8, 0x2B, 0x22,
|
||||||
0xDE, 0x93, 0x4D, 0x68, 0x6C, 0x09, 0xAC, 0x20, 0x58, 0x24, 0x31, 0x0F, 0xD6, 0xAA, 0x99, 0x8A,
|
0xDE, 0x93, 0x4D, 0x68, 0x6C, 0x09, 0xAC, 0x20, 0x58, 0x24, 0x31, 0x0F, 0xD6, 0xAA, 0x99, 0x8A,
|
||||||
])
|
])
|
||||||
|
|
||||||
let staticArchiveHash = ByteString([
|
public let staticArchiveHash = ByteString([
|
||||||
0x49, 0x84, 0x3D, 0x18, 0xC5, 0x43, 0x32, 0xFE, 0x80, 0x0B, 0x5D, 0xBE, 0xF3, 0x6A, 0x52, 0x9F,
|
0x49, 0x84, 0x3D, 0x18, 0xC5, 0x43, 0x32, 0xFE, 0x80, 0x0B, 0x5D, 0xBE, 0xF3, 0x6A, 0x52, 0x9F,
|
||||||
0xD8, 0x27, 0x8C, 0xB6, 0x39, 0xAB, 0xAB, 0x6E, 0xE6, 0xCB, 0x82, 0x36, 0x21, 0x09, 0x9B, 0x50,
|
0xD8, 0x27, 0x8C, 0xB6, 0x39, 0xAB, 0xAB, 0x6E, 0xE6, 0xCB, 0x82, 0x36, 0x21, 0x09, 0x9B, 0x50,
|
||||||
])
|
])
|
|
@ -0,0 +1,247 @@
|
||||||
|
//===----------------------------------------------------------*- swift -*-===//
|
||||||
|
//
|
||||||
|
// This source file is part of the Swift Argument Parser open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||||
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||||
|
//
|
||||||
|
// See https://swift.org/LICENSE.txt for license information
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
import ArgumentParser
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
// extensions to the ParsableArguments protocol to facilitate XCTestExpectation support
|
||||||
|
public protocol TestableParsableArguments: ParsableArguments {
|
||||||
|
var didValidateExpectation: XCTestExpectation { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension TestableParsableArguments {
|
||||||
|
mutating func validate() throws {
|
||||||
|
didValidateExpectation.fulfill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensions to the ParsableCommand protocol to facilitate XCTestExpectation support
|
||||||
|
public protocol TestableParsableCommand: ParsableCommand, TestableParsableArguments {
|
||||||
|
var didRunExpectation: XCTestExpectation { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension TestableParsableCommand {
|
||||||
|
mutating func run() throws {
|
||||||
|
didRunExpectation.fulfill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension XCTestExpectation {
|
||||||
|
convenience init(singleExpectation description: String) {
|
||||||
|
self.init(description: description)
|
||||||
|
expectedFulfillmentCount = 1
|
||||||
|
assertForOverFulfill = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertResultFailure<T, U: Error>(
|
||||||
|
_ expression: @autoclosure () -> Result<T, U>,
|
||||||
|
_ message: @autoclosure () -> String = "",
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line
|
||||||
|
) {
|
||||||
|
switch expression() {
|
||||||
|
case .success:
|
||||||
|
let msg = message()
|
||||||
|
XCTFail(msg.isEmpty ? "Incorrectly succeeded" : msg, file: file, line: line)
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertErrorMessage<A>(
|
||||||
|
_ type: A.Type,
|
||||||
|
_ arguments: [String],
|
||||||
|
_ errorMessage: String,
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line
|
||||||
|
) where A: ParsableArguments {
|
||||||
|
do {
|
||||||
|
_ = try A.parse(arguments)
|
||||||
|
XCTFail("Parsing should have failed.", file: file, line: line)
|
||||||
|
} catch {
|
||||||
|
// We expect to hit this path, i.e. getting an error:
|
||||||
|
XCTAssertEqual(A.message(for: error), errorMessage, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertFullErrorMessage<A>(
|
||||||
|
_ type: A.Type,
|
||||||
|
_ arguments: [String],
|
||||||
|
_ errorMessage: String,
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line
|
||||||
|
) where A: ParsableArguments {
|
||||||
|
do {
|
||||||
|
_ = try A.parse(arguments)
|
||||||
|
XCTFail("Parsing should have failed.", file: file, line: line)
|
||||||
|
} catch {
|
||||||
|
// We expect to hit this path, i.e. getting an error:
|
||||||
|
XCTAssertEqual(A.fullMessage(for: error), errorMessage, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertParse<A>(
|
||||||
|
_ type: A.Type,
|
||||||
|
_ arguments: [String],
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line,
|
||||||
|
closure: (A) throws -> ()
|
||||||
|
) where A: ParsableArguments {
|
||||||
|
do {
|
||||||
|
let parsed = try type.parse(arguments)
|
||||||
|
try closure(parsed)
|
||||||
|
} catch {
|
||||||
|
let message = type.message(for: error)
|
||||||
|
XCTFail("\"\(message)\" — \(error)", file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertParseCommand<A: ParsableCommand>(
|
||||||
|
_ rootCommand: ParsableCommand.Type,
|
||||||
|
_ type: A.Type,
|
||||||
|
_ arguments: [String],
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line,
|
||||||
|
closure: (A) throws -> ()
|
||||||
|
) {
|
||||||
|
do {
|
||||||
|
let command = try rootCommand.parseAsRoot(arguments)
|
||||||
|
guard let aCommand = command as? A else {
|
||||||
|
XCTFail("Command is of unexpected type: \(command)", file: file, line: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try closure(aCommand)
|
||||||
|
} catch {
|
||||||
|
let message = rootCommand.message(for: error)
|
||||||
|
XCTFail("\"\(message)\" — \(error)", file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertEqualStringsIgnoringTrailingWhitespace(
|
||||||
|
_ string1: String,
|
||||||
|
_ string2: String,
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line
|
||||||
|
) {
|
||||||
|
let lines1 = string1.split(separator: "\n", omittingEmptySubsequences: false)
|
||||||
|
let lines2 = string2.split(separator: "\n", omittingEmptySubsequences: false)
|
||||||
|
|
||||||
|
XCTAssertEqual(
|
||||||
|
lines1.count,
|
||||||
|
lines2.count,
|
||||||
|
"Strings have different numbers of lines.",
|
||||||
|
file: file,
|
||||||
|
line: line
|
||||||
|
)
|
||||||
|
for (line1, line2) in zip(lines1, lines2) {
|
||||||
|
XCTAssertEqual(line1.trimmed(), line2.trimmed(), file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertHelp<T: ParsableArguments>(
|
||||||
|
for _: T.Type, equals expected: String,
|
||||||
|
file: StaticString = #file, line: UInt = #line
|
||||||
|
) {
|
||||||
|
do {
|
||||||
|
_ = try T.parse(["-h"])
|
||||||
|
XCTFail(file: file, line: line)
|
||||||
|
} catch {
|
||||||
|
let helpString = T.fullMessage(for: error)
|
||||||
|
AssertEqualStringsIgnoringTrailingWhitespace(
|
||||||
|
helpString, expected, file: file, line: line
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let helpString = T.helpMessage()
|
||||||
|
AssertEqualStringsIgnoringTrailingWhitespace(
|
||||||
|
helpString, expected, file: file, line: line
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func AssertHelp<T: ParsableCommand, U: ParsableCommand>(
|
||||||
|
for _: T.Type, root _: U.Type, equals expected: String,
|
||||||
|
file: StaticString = #file, line: UInt = #line
|
||||||
|
) {
|
||||||
|
let helpString = U.helpMessage(for: T.self)
|
||||||
|
AssertEqualStringsIgnoringTrailingWhitespace(
|
||||||
|
helpString, expected, file: file, line: line
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension XCTest {
|
||||||
|
var debugURL: URL {
|
||||||
|
let bundleURL = Bundle(for: type(of: self)).bundleURL
|
||||||
|
return bundleURL.lastPathComponent.hasSuffix("xctest")
|
||||||
|
? bundleURL.deletingLastPathComponent()
|
||||||
|
: bundleURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func AssertExecuteCommand(
|
||||||
|
command: String,
|
||||||
|
expected: String? = nil,
|
||||||
|
exitCode: ExitCode = .success,
|
||||||
|
file: StaticString = #file, line: UInt = #line
|
||||||
|
) {
|
||||||
|
let splitCommand = command.split(separator: " ")
|
||||||
|
let arguments = splitCommand.dropFirst().map(String.init)
|
||||||
|
|
||||||
|
let commandName = String(splitCommand.first!)
|
||||||
|
let commandURL = debugURL.appendingPathComponent(commandName)
|
||||||
|
guard (try? commandURL.checkResourceIsReachable()) ?? false else {
|
||||||
|
XCTFail("No executable at '\(commandURL.standardizedFileURL.path)'.",
|
||||||
|
file: file, line: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let process = Process()
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
process.executableURL = commandURL
|
||||||
|
} else {
|
||||||
|
process.launchPath = commandURL.path
|
||||||
|
}
|
||||||
|
process.arguments = arguments
|
||||||
|
|
||||||
|
let output = Pipe()
|
||||||
|
process.standardOutput = output
|
||||||
|
let error = Pipe()
|
||||||
|
process.standardError = error
|
||||||
|
|
||||||
|
if #available(macOS 10.13, *) {
|
||||||
|
guard (try? process.run()) != nil else {
|
||||||
|
XCTFail("Couldn't run command process.", file: file, line: line)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
process.launch()
|
||||||
|
}
|
||||||
|
process.waitUntilExit()
|
||||||
|
|
||||||
|
let outputData = output.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
let outputActual = String(data: outputData, encoding: .utf8)!
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
let errorData = error.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
let errorActual = String(data: errorData, encoding: .utf8)!
|
||||||
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
|
if let expected = expected {
|
||||||
|
AssertEqualStringsIgnoringTrailingWhitespace(
|
||||||
|
expected,
|
||||||
|
errorActual + outputActual,
|
||||||
|
file: file,
|
||||||
|
line: line
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(process.terminationStatus, exitCode.rawValue, file: file, line: line)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2020 Carton contributors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
// Created by Cavelle Benjamin on Dec/20/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
@testable import CartonCLI
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class DevCommandTests: XCTestCase {
|
||||||
|
func testDefaultArgumentParsing() throws {
|
||||||
|
// given
|
||||||
|
let arguments: [String] = []
|
||||||
|
|
||||||
|
// when
|
||||||
|
|
||||||
|
AssertParse(Dev.self, arguments) { command in
|
||||||
|
// then
|
||||||
|
XCTAssertNotNil(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHelpString() throws {
|
||||||
|
// given
|
||||||
|
let expectation =
|
||||||
|
"""
|
||||||
|
OVERVIEW: Watch the current directory, host the app, rebuild on change.
|
||||||
|
|
||||||
|
USAGE: carton dev [--product <product>] [--destination <destination>] [--custom-index-page <custom-index-page>] [--release] [--verbose] [--port <port>] [--skip-auto-open]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--product <product> Specify name of an executable product in development.
|
||||||
|
--destination <destination>
|
||||||
|
This option has no effect and will be removed in a
|
||||||
|
future version of `carton`
|
||||||
|
--custom-index-page <custom-index-page>
|
||||||
|
Specify a path to a custom `index.html` file to be
|
||||||
|
used for your app.
|
||||||
|
--release When specified, build in the release mode.
|
||||||
|
-v, --verbose Don't clear terminal window after files change.
|
||||||
|
-p, --port <port> Set the HTTP port the development server will run on.
|
||||||
|
(default: 8080)
|
||||||
|
--skip-auto-open Skip automatically opening app in system browser.
|
||||||
|
--version Show the version.
|
||||||
|
-h, --help Show help information.
|
||||||
|
"""
|
||||||
|
// when
|
||||||
|
// then
|
||||||
|
|
||||||
|
AssertExecuteCommand(command: "carton dev -h", expected: expectation)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
//===----------------------------------------------------------*- swift -*-===//
|
||||||
|
//
|
||||||
|
// This source file is part of the Swift Argument Parser open source project
|
||||||
|
//
|
||||||
|
// Copyright (c) 2020 Apple Inc. and the Swift project authors
|
||||||
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
||||||
|
//
|
||||||
|
// See https://swift.org/LICENSE.txt for license information
|
||||||
|
//
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
extension Substring {
|
||||||
|
func trimmed() -> Substring {
|
||||||
|
guard let i = lastIndex(where: { $0 != " " }) else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return self[...i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension String {
|
||||||
|
func trimmingLines() -> String {
|
||||||
|
split(separator: "\n", omittingEmptySubsequences: false)
|
||||||
|
.map { $0.trimmed() }
|
||||||
|
.joined(separator: "\n")
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,34 @@ final class CartonTests: XCTestCase {
|
||||||
XCTAssertEqual(output?.trimmingCharacters(in: .whitespacesAndNewlines), "0.9.1")
|
XCTAssertEqual(output?.trimmingCharacters(in: .whitespacesAndNewlines), "0.9.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func testDev() throws {
|
||||||
|
// // This is an example of a functional test case.
|
||||||
|
// // Use XCTAssert and related functions to verify your tests produce the correct
|
||||||
|
// // results.
|
||||||
|
//
|
||||||
|
// // Some of the APIs that we use below are available in macOS 10.13 and above.
|
||||||
|
// guard #available(macOS 10.13, *) else {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let fooBinary = productsDirectory.appendingPathComponent("carton")
|
||||||
|
//
|
||||||
|
// let process = Process()
|
||||||
|
// process.executableURL = fooBinary
|
||||||
|
//
|
||||||
|
// let pipe = Pipe()
|
||||||
|
// process.standardOutput = pipe
|
||||||
|
//
|
||||||
|
// process.arguments = ["dev"]
|
||||||
|
// try process.run()
|
||||||
|
// process.waitUntilExit()
|
||||||
|
//
|
||||||
|
// let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
||||||
|
// let output = String(data: data, encoding: .utf8)
|
||||||
|
//
|
||||||
|
// XCTAssertEqual(output?.trimmingCharacters(in: .whitespacesAndNewlines), "0.9.1")
|
||||||
|
// }
|
||||||
|
|
||||||
final class TestOutputStream: OutputByteStream {
|
final class TestOutputStream: OutputByteStream {
|
||||||
var bytes: [UInt8] = []
|
var bytes: [UInt8] = []
|
||||||
var currentOutput: String {
|
var currentOutput: String {
|
||||||
|
|
Loading…
Reference in New Issue