├── .gitignore ├── .swiftformat ├── .swiftlint.yml ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── vintage │ └── main.swift └── vintage_core │ ├── CommandError.swift │ ├── Constants.swift │ ├── Core.swift │ ├── Extensions │ ├── File+Extensions.swift │ ├── String+Extensions.swift │ ├── Substring+Extensions.swift │ └── URL+Extensions.swift │ └── Model.swift ├── Tests ├── LinuxMain.swift └── vintageTests │ ├── XCTestManifests.swift │ └── vintageTests.swift └── screenshots ├── run_demo.png └── vintage_and_spawn.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | .swiftpm 6 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | # file options 2 | 3 | --exclude Tests/XCTestManifests.swift,Snapshots 4 | 5 | # format options 6 | 7 | --allman false 8 | --binarygrouping 4,8 9 | --commas always 10 | --comments indent 11 | --decimalgrouping 3,6 12 | --elseposition same-line 13 | --empty void 14 | --exponentcase lowercase 15 | --exponentgrouping disabled 16 | --fractiongrouping disabled 17 | --header ignore 18 | --hexgrouping 4,8 19 | --hexliteralcase uppercase 20 | --ifdef indent 21 | --indent 4 22 | --indentcase false 23 | --importgrouping testable-top 24 | --linebreaks lf 25 | --octalgrouping 4,8 26 | --operatorfunc spaced 27 | --patternlet hoist 28 | --ranges spaced 29 | --self remove 30 | --semicolons inline 31 | --stripunusedargs always 32 | --trimwhitespace always 33 | --wraparguments preserve 34 | --wrapcollections preserve 35 | 36 | # rules 37 | 38 | --enable isEmpty 39 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: # rule identifiers to exclude from running 2 | - line_length 3 | - todo 4 | - trailing_whitespace 5 | - variable_name 6 | - void_return 7 | - colon 8 | - notification_center_detachment 9 | - discarded_notification_center_observer 10 | - type_name 11 | 12 | opt_in_rules: 13 | - sorted_first_last 14 | - contains_over_first_not_nil 15 | # - explicit_top_level_acl 16 | - empty_count 17 | - closure_spacing 18 | # - explicit_type_interface 19 | - fatal_error_message 20 | - first_where 21 | - force_unwrapping 22 | - implicit_return 23 | - implicitly_unwrapped_optional 24 | - no_extension_access_modifier 25 | - operator_usage_whitespace 26 | - overridden_super_call 27 | # - private_outlet 28 | - redundant_nil_coalescing 29 | # - switch_case_on_newline 30 | - vertical_parameter_alignment_on_call 31 | 32 | excluded: # paths to ignore during linting. overridden by `included`. 33 | - .build 34 | 35 | # parameterized rules can be customized from this configuration file 36 | # first line is warning, second is error 37 | file_length: 38 | - 700 39 | - 1000 40 | line_length: 41 | - 230 42 | - 250 43 | 44 | # parameterized rules are first parameterized as a warning level, then error level. 45 | function_body_length: 46 | - 150 47 | - 200 48 | type_body_length: 49 | - 300 50 | - 400 51 | 52 | # naming rules can set warnings/errors for min_length and max_length 53 | # additionally they can set excluded names 54 | type_name: 55 | min_length: 3 # only warning 56 | max_length: # warning and error 57 | warning: 40 58 | error: 50 59 | excluded: iPhone # excluded via string 60 | identifier_name: 61 | min_length: # only min_length 62 | error: 4 # only error 63 | excluded: # excluded via string array 64 | - id 65 | - URL 66 | - GlobalAPIKey 67 | 68 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji) 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Vinh Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=/usr/local 2 | INSTALL_NAME = vintage 3 | 4 | install: build install_bin 5 | 6 | build: 7 | swift package update 8 | swift build -c release 9 | 10 | install_bin: 11 | mkdir -p $(PREFIX)/bin 12 | install .build/Release/$(INSTALL_NAME) $(PREFIX)/bin 13 | 14 | uninstall: 15 | rm -f $(PREFIX)/bin/$(INSTALL_NAME) -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Chalk", 6 | "repositoryURL": "https://github.com/mxcl/Chalk.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "9aa9f348b86db3cf6702a3e43c081ecec80cf3c7", 10 | "version": "0.4.0" 11 | } 12 | }, 13 | { 14 | "package": "Files", 15 | "repositoryURL": "git@github.com:JohnSundell/Files.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "92b57bea0e737e7d92b5ff281f46ec2b59faf91c", 19 | "version": "3.1.0" 20 | } 21 | }, 22 | { 23 | "package": "Releases", 24 | "repositoryURL": "git@github.com:JohnSundell/Releases.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "0a3c1ba8ce8bb27c02c225979bfd02888b38f0cc", 28 | "version": "2.0.1" 29 | } 30 | }, 31 | { 32 | "package": "Require", 33 | "repositoryURL": "https://github.com/JohnSundell/Require.git", 34 | "state": { 35 | "branch": null, 36 | "revision": "7cfbd0d8a2dede0e01f6f0d8ab2c7acef1df112e", 37 | "version": "2.0.1" 38 | } 39 | }, 40 | { 41 | "package": "ShellOut", 42 | "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "e1577acf2b6e90086d01a6d5e2b8efdaae033568", 46 | "version": "2.3.0" 47 | } 48 | }, 49 | { 50 | "package": "Sweep", 51 | "repositoryURL": "git@github.com:JohnSundell/Sweep.git", 52 | "state": { 53 | "branch": null, 54 | "revision": "801c2878e4c6c5baf32fe132e1f3f3af6f9fd1b0", 55 | "version": "0.4.0" 56 | } 57 | }, 58 | { 59 | "package": "swift-argument-parser", 60 | "repositoryURL": "https://github.com/apple/swift-argument-parser", 61 | "state": { 62 | "branch": null, 63 | "revision": "9f04d1ff1afbccd02279338a2c91e5f27c45e93a", 64 | "version": "0.0.5" 65 | } 66 | } 67 | ] 68 | }, 69 | "version": 1 70 | } 71 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "vintage", 8 | products: [ 9 | .executable(name: "vintage", targets: ["vintage"]), 10 | .library(name: "vintage_core", targets: ["vintage_core"]) 11 | ], 12 | dependencies: [ 13 | .package(url: "git@github.com:JohnSundell/Sweep.git", from: "0.1.0"), 14 | .package(url: "git@github.com:JohnSundell/Files.git", from: "3.0.0"), 15 | .package(url: "git@github.com:JohnSundell/Releases.git", from: "2.0.0"), 16 | .package(url: "https://github.com/mxcl/Chalk.git", from: "0.1.0"), 17 | .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1") 18 | ], 19 | targets: [ 20 | .target(name: "vintage", dependencies: ["vintage_core"]), 21 | .target(name: "vintage_core", 22 | dependencies: [ 23 | "Sweep", 24 | "Files", 25 | "Releases", 26 | "Chalk", 27 | "ArgumentParser" 28 | ], path: "Sources/vintage_core") 29 | ], 30 | swiftLanguageVersions: [.v5] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vintage 2 | 3 | ### UNMAINTAINED 4 | 5 | [![Swift 5.0](https://img.shields.io/badge/swift-5.0-orange.svg)](#) 6 | [![Swift Package Manager](https://img.shields.io/badge/spm-compatible-brightgreen.svg?style=flat)](https://swift.org/package-manager) 7 | [![@vinhnx](https://img.shields.io/badge/contact-%40vinhnx-blue.svg)](https://twitter.com/vinhnx) 8 | 9 | `vintage` is a small command-line tool to check outdated Swift Package Manager dependencies. 10 | 11 | 📦 pseudo `swift package outdated` command. 12 | 13 | Think `pod outdated` or `carthage outdated`, but for Swift Package Manager. 14 | 15 | ![demo](screenshots/run_demo.png) 16 | 17 | ## Usage 18 | 19 | Without any specifications (have to be executed in the directory where [Swift Package Manager manifest file (Package.swift)](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md) is located): 20 | 21 | ```bash 22 | $ vintage 23 | ``` 24 | 25 | Specifies path of Swift Package Manager directory to update: 26 | 27 | ```bash 28 | $ vintage -p path/to/dependencies 29 | ``` 30 | 31 | Help page: 32 | 33 | ```bash 34 | $ vintage --help 35 | USAGE: vintage [--path ] 36 | 37 | OPTIONS: 38 | -p, --path Path to the folder contains Swift Package manifest file (Package.swift). (default: .) 39 | -h, --help Show help information. 40 | ``` 41 | 42 | ## Installation 43 | 44 | 🆕 **[swiftbrew](https://github.com/swiftbrew/Swiftbrew)** 45 | 46 | "A package manager that installs prebuilt Swift command line tool packages, or Homebrew for Swift packages." 47 | 48 | ``` 49 | $ swift brew install vinhnx/vintage 50 | ``` 51 | 52 | **[homebrew](https://brew.sh)** 53 | 54 | ```bash 55 | $ brew tap vinhnx/homebrew-formulae 56 | $ brew install vinhnx/formulae/vintage 57 | ``` 58 | 59 | to upgrade existing vintage executable 60 | 61 | ```bash 62 | $ brew upgrade vinhnx/formulae/vintage 63 | ``` 64 | 65 | or 66 | 67 | ```bash 68 | $ brew install vinhnx/homebrew-formulae/vintage 69 | ``` 70 | 71 | **[Mint](https://github.com/yonaskolb/mint)** 72 | 73 | ```bash 74 | $ mint install vinhnx/vintage 75 | ``` 76 | 77 | **[Marathon](https://github.com/JohnSundell/Marathon)** 78 | 79 | ```bash 80 | $ marathon install vinhnx/vintage 81 | ``` 82 | 83 | **Make** 84 | 85 | ```bash 86 | $ git clone https://github.com/vinhnx/vintage.git 87 | $ cd vintage 88 | $ make 89 | ``` 90 | 91 | **Swift Package Manager** 92 | 93 | ```bash 94 | $ git clone https://github.com/vinhnx/vintage.git 95 | $ cd vintage 96 | $ swift build -c release 97 | $ cp -f .build/release/vintage /usr/local/bin/vintage 98 | ``` 99 | 100 | ## Related projects 101 | 102 | If you like this tool, checkout my [spawn](https://github.com/vinhnx/spawn), it's a tool to generate and/or update Swift packages and open a Xcode project for you. 103 | 104 | Combo: 105 | 106 | ```bash 107 | $ vintage && spawn # vintage: check for any outdated packages, spawn: update packages then open an generated Xcode project for you 108 | ``` 109 | 110 | ![demo](screenshots/vintage_and_spawn.png) 111 | 112 | I hope you like it! :) 113 | 114 | ## Dependencies 115 | 116 | - [Sweep](https://github.com/JohnSundell/Sweep) 117 | - [Files](https://github.com/JohnSundell/Files) 118 | - [Releases](https://github.com/JohnSundell/Releases) 119 | - [Chalk](https://github.com/mxcl/Chalk) 120 | 121 | ## Reference 122 | 123 | - [Swift Package Manager usage document](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md#create-a-package) 124 | - [git ls-remote](https://git-scm.com/docs/git-ls-remote.html) 125 | - [carthage outdated](https://github.com/Carthage/Carthage/blob/master/Source/carthage/Outdated.swift) 126 | - I was heavily inspired by these awesome talks: 127 | - [Swift Scripting by Ayaka Nonaka](https://academy.realm.io/posts/swift-scripting/) 128 | - [John Sundell: Swift scripting in practice](https://www.youtube.com/watch?v=PFdh5G3BJqM) 129 | 130 | ## swift-outdated 131 | 132 | Check out https://github.com/kiliankoe/swift-outdated for similiar approach to checking outdated depedencies. 133 | 134 | ## Help, feedback or suggestions? 135 | 136 | Feel free to contact me on [Twitter](https://twitter.com/vinhnx) for discussions, news & announcements & other projects. Thank you! :rocket: 137 | -------------------------------------------------------------------------------- /Sources/vintage/main.swift: -------------------------------------------------------------------------------- 1 | import ArgumentParser 2 | import vintage_core 3 | 4 | /* 5 | ### TODO 6 | + offer to update deps if needed 7 | + spawn integration 8 | + check out https://github.com/kiliankoe/swift-outdated 9 | + popularize this (submit to ios newsletter/weekly...) 10 | + [plan] handle .package's path 11 | extension Package.Dependency { 12 | /// Add a dependency to a local package on the filesystem. 13 | public static func package(path: String) -> PackageDescription.Package.Dependency 14 | } 15 | + [plan] checkout checkouts-state.json > build dependencies graph > check outdated sub-dependencies too!") 16 | 17 | ### Done 18 | + [done] [plan] choose a better name 19 | + [done] check if resolved package file exists (Package.resolve), if not run `swift package resolve` 20 | + [done] modularize 21 | + [done] mint/brew/installation publish 22 | + [done] using Files and Sweep, https://github.com/JohnSundell/Releases 23 | + [done] plan: color output 24 | + [done] IMPORTANT: handle `throws` error 25 | 26 | */ 27 | 28 | struct Vintage: ParsableCommand { 29 | @Option( 30 | name: .shortAndLong, 31 | default: ".", 32 | help: "Path to the folder contains Swift Package manifest file (Package.swift)." 33 | ) var path: String 34 | 35 | func run() throws { try execute(path) } 36 | } 37 | 38 | Vintage.main() 39 | -------------------------------------------------------------------------------- /Sources/vintage_core/CommandError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandError.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Chalk 9 | import Foundation 10 | 11 | public enum CommandError: Error, CustomStringConvertible { 12 | case convertDataFromString 13 | case decoder 14 | case noMatchedPackageRepositoryURLFound 15 | case noPackageFileFound(at: String) 16 | case invalidURL(URL) 17 | case rawError(CustomStringConvertible) 18 | } 19 | 20 | extension CommandError { 21 | var message: String { 22 | switch self { 23 | case .convertDataFromString: 24 | return "Failed to convert data from string" 25 | case .decoder: 26 | return "Failed to decode JSON from data" 27 | case .noMatchedPackageRepositoryURLFound: 28 | return "No matched package repository URL found" 29 | case .noPackageFileFound(let path): 30 | return "No package file found in current path: `\(path)`. Try running with `--help` option" 31 | case .invalidURL(let url): 32 | return "URL is not a valid URL. Expected: https, git. Got: \(url.scheme ?? "")" 33 | case .rawError(let error): 34 | return "[Error] \(error.description)" 35 | } 36 | } 37 | 38 | public var description: String { 39 | return "❗️ \(self.message, color: .red)" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/vintage_core/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct PackageConstant { 11 | public static let manifestResolvedFileName = "Package.resolved" 12 | public static let manifestFileName = "Package.swift" 13 | } 14 | 15 | public struct PackageDependencyConstant { 16 | public static let rootToken = ".package" 17 | public static let URLStartToken = "url:\"" 18 | public static let URLEndToken = "\"," 19 | public static let gitExtension = ".git" 20 | } 21 | -------------------------------------------------------------------------------- /Sources/vintage_core/Core.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Core.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Chalk 9 | import Files 10 | import Foundation 11 | import Releases 12 | 13 | // MARK: - Public 14 | 15 | /// Execute parsing command 16 | /// 17 | /// - Parameter path: path to the folder contains Swift Package manifest file (Package.swift). 18 | /// - Throws: error, if any throwing operations occurs 19 | public func execute(_ path: String) throws { 20 | // IMPORTANT: first, we check for "Package.swift" (if no package manifest file found, 21 | // meaning this is not a Swift package manager) 22 | let manifestFile = try file(path, name: PackageConstant.manifestFileName) 23 | try scan(manifestFile, path: path) 24 | } 25 | 26 | // MARK: - Private 27 | 28 | func scan(_ manifestFile: File, path: String) throws { 29 | try manifestFile 30 | .trimmedWhiteSpacesContent() 31 | .scanPackageURL { substring in 32 | parse(substring, path: path) 33 | } 34 | } 35 | 36 | /// Return file name at a given path 37 | /// 38 | /// - Parameters: 39 | /// - path: path to find File 40 | /// - name: the name of the File to search 41 | /// - Returns: file at a given path 42 | /// - Throws: error, if any throwing operations occurs 43 | @discardableResult 44 | func file(_ path: String, name: String) throws -> File { 45 | let folder = try Folder(path: path) 46 | let file = try File(path: path.appending("/\(name)")) 47 | guard folder.contains(file) else { 48 | throw CommandError.noPackageFileFound(at: path) 49 | } 50 | 51 | return file 52 | } 53 | 54 | /// Find version tag for a given Package 55 | /// 56 | /// - Parameters: 57 | /// - packageURL: package remote URL (TODO: handle local package) 58 | /// - path: path to the folder contains Swift Package manifest resolve file (Package.resolve). 59 | /// - Returns: version tag for the given Package dependency 60 | /// - Throws: error, if any throwing operations occurs 61 | func findVersionTagForPackage(_ packageURL: String, path: String) throws -> String { 62 | let packageResolveFile = try file(path, name: PackageConstant.manifestResolvedFileName) 63 | let content = try packageResolveFile.readAsString() 64 | guard let data = content.data(using: .utf8) else { 65 | throw CommandError.convertDataFromString 66 | } 67 | 68 | let result = try JSONDecoder().decode(RootState.self, from: data) 69 | let package = result.object.pins.first { $0.repositoryURL == packageURL } 70 | guard let unwrappedPackage = package else { 71 | throw CommandError.noMatchedPackageRepositoryURLFound 72 | } 73 | 74 | return unwrappedPackage.state.version ?? "" 75 | } 76 | 77 | /// Parse and output print from comparing versions 78 | /// 79 | /// - Parameters: 80 | /// - substring: parsed substring 81 | /// - path: path to the folder contains Swift Package manifest file (Package.swift). 82 | func parse(_ substring: Substring, path: String) { 83 | do { 84 | let url = substring.gitURL 85 | let releases = try Releases.versions(for: url) 86 | let localVersion = try findVersionTagForPackage(substring.toString, path: path).toVersion 87 | outputPrint(url: url, releases: releases, localVersion: localVersion) 88 | } catch let error as CustomStringConvertible { 89 | print(CommandError.rawError(error)) 90 | } 91 | } 92 | 93 | /// Color code from comparing version 94 | /// 95 | /// - Parameters: 96 | /// - localVersion: local version 97 | /// - remoteVersion: remote version 98 | /// - Returns: color code 99 | func colorCode(localVersion: Version, remoteVersion: Version) -> Color { 100 | let color: Color 101 | let versionComparisionResult = localVersion.string.compare(remoteVersion.string, 102 | options: .numeric) 103 | 104 | switch versionComparisionResult { 105 | case .orderedSame, 106 | .orderedDescending: // local version can not be higher than remote version 107 | color = .white 108 | case .orderedAscending: 109 | color = .red 110 | } 111 | 112 | return color 113 | } 114 | 115 | /// Output print to terminal 116 | /// 117 | /// - Parameters: 118 | /// - url: path to the folder contains Swift Package manifest file (Package.swift). 119 | /// - releases: released versions 120 | /// - localVersion: local version 121 | func outputPrint(url: URL, releases: [Version], localVersion: Version?) { 122 | print("📦 " + "\(url.absoluteString, style: .bold)") 123 | guard let latestVersion = (releases.last?.string ?? "").toVersion else { return } 124 | 125 | var color: Chalk.Color = .white 126 | localVersion.flatMap { local in 127 | color = colorCode(localVersion: local, remoteVersion: latestVersion) 128 | print("> 🏷 local version: \(local, color: color)") 129 | } 130 | 131 | print("> ☁️ latest version: \(latestVersion, color: color)") 132 | } 133 | -------------------------------------------------------------------------------- /Sources/vintage_core/Extensions/File+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File+Extensions.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Foundation 9 | import Files 10 | 11 | public extension File { 12 | func trimmedWhiteSpacesContent() throws -> String { 13 | return try self.readAsString().replacingOccurrences(of: " ", with: "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/vintage_core/Extensions/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extensions.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Foundation 9 | import Sweep 10 | import Releases 11 | 12 | public extension String { 13 | var toVersion: Version? { 14 | do { 15 | return try Version(string: self) 16 | } catch { 17 | return nil 18 | } 19 | } 20 | 21 | var toIdentifier: Identifier { 22 | return Identifier(stringLiteral: self) 23 | } 24 | 25 | var toTerminator: Terminator { 26 | return Terminator(stringLiteral: self) 27 | } 28 | 29 | func scanPackage(handler: @escaping Matcher.Handler) { 30 | // .package(url: "https://github.com/JohnSundell/Sweep", from: "0.1.0"), 31 | self.scan(using: [ 32 | Matcher(identifiers: [PackageDependencyConstant.rootToken.toIdentifier], 33 | terminators: ["\n", .end], 34 | handler: handler) 35 | ]) 36 | } 37 | 38 | func scanPackageURL(completion: @escaping (Substring) -> Void) { 39 | self.scanPackage { substring, _ in 40 | substring.scanPackageURL { innerSubstring in 41 | completion(innerSubstring) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/vintage_core/Extensions/Substring+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Substring+Extensions.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Foundation 9 | import Sweep 10 | 11 | public extension Substring { 12 | var gitURL: URL { 13 | // swiftlint:disable:next force_unwrapping 14 | return URL(string: self.gitURLString)! 15 | // swiftlint:disable:previous force_unwrapping 16 | } 17 | 18 | var gitURLString: String { 19 | var result = self.toString 20 | 21 | // append `.git` extension if needed 22 | if result.hasSuffix(PackageDependencyConstant.gitExtension) == false { 23 | result.append(PackageDependencyConstant.gitExtension) 24 | } 25 | 26 | return result 27 | } 28 | 29 | var toString: String { 30 | return String(self) 31 | } 32 | 33 | func scanPackageURL(completion: @escaping (Substring) -> Void) { 34 | // url: "https://github.com/JohnSundell/Sweep" 35 | self.scan(using: [ 36 | Matcher(identifier: PackageDependencyConstant.URLStartToken.toIdentifier, 37 | terminator: PackageDependencyConstant.URLEndToken.toTerminator, 38 | handler: { substring, _ in 39 | completion(substring) 40 | }) 41 | ]) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/vintage_core/Extensions/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+Extensions.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // 7 | 8 | import Foundation 9 | import Releases 10 | 11 | public extension URL { 12 | var isValidRemoteURL: Bool { 13 | guard let scheme = self.scheme else { return false } 14 | return ["http", "https", "git"].contains(scheme) 15 | } 16 | 17 | func fetchVersionsFromRemoteGitURL() -> [Version] { 18 | guard isValidRemoteURL else { 19 | print(CommandError.invalidURL(self).description) 20 | return [] 21 | } 22 | 23 | do { 24 | return try Releases.versions(for: self) 25 | } catch let error as CustomStringConvertible { 26 | print(CommandError.rawError(error)) 27 | return [] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/vintage_core/Model.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Model.swift 3 | // package_outdated_core 4 | // 5 | // Created by Vinh Nguyen on 17/4/19. 6 | // Copyright © 2019 Vinh Nguyen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct RootState: Decodable { 12 | public let object: Object 13 | } 14 | 15 | public struct Object: Decodable { 16 | public let pins: [Pin] 17 | } 18 | 19 | public struct Pin: Decodable { 20 | public let package: String 21 | public let repositoryURL: String 22 | public let state: State 23 | } 24 | 25 | public struct State: Decodable { 26 | public let branch: String? 27 | public let revision: String 28 | public let version: String? 29 | } 30 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import package_outdatedTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += package_outdatedTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/vintageTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(package_outdatedTests.allTests) 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/vintageTests/vintageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class package_outdatedTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("package_outdated") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample) 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /screenshots/run_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinhnx/vintage/f7fc94e6394e8bb5619fabdc69c65a24a3e28ae6/screenshots/run_demo.png -------------------------------------------------------------------------------- /screenshots/vintage_and_spawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vinhnx/vintage/f7fc94e6394e8bb5619fabdc69c65a24a3e28ae6/screenshots/vintage_and_spawn.png --------------------------------------------------------------------------------