diff --git a/Sources/VaporToolbox/New.swift b/Sources/VaporToolbox/New.swift index 86eb8284..ce792f08 100644 --- a/Sources/VaporToolbox/New.swift +++ b/Sources/VaporToolbox/New.swift @@ -1,5 +1,6 @@ import ArgumentParser import Foundation +import Yams extension Vapor { struct New: ParsableCommand { @@ -16,13 +17,13 @@ extension Vapor { /// They control the build process of the project. struct BuildOptions: ParsableArguments { @Option(name: .shortAndLong, help: ArgumentHelp("The URL of a Git repository to use as a template.", valueName: "url")) - var template: String? + var template: String = "https://github.com/vapor/template" @Option(help: "Template repository branch to use.") var branch: String? - @Option(help: ArgumentHelp("The path of the manifest file. Defaults to `manifest.yml`.", valueName: "file")) - var manifest: String? + @Option(help: ArgumentHelp("The path of the manifest file.", valueName: "file")) + var manifest: String = "manifest.yml" @Option( name: .shortAndLong, @@ -56,7 +57,80 @@ extension Vapor { @OptionGroup(title: "Build Options") var buildOptions: BuildOptions + static let gitURL = { + do { + let path = try Process.shell.which("git") + return path + } catch { + print("error:".colored(.red) + " unable to find git") + print("Do you have git installed?") + Vapor.New.exit(withError: ExitCode(1)) + } + }() + + struct PreProcessArgs { + let template: String + let branch: String? + let manifest: String + + init(template: String, branch: String?, manifest: String) { + self.template = template + self.branch = branch + self.manifest = manifest + } + } + + /// Get the template's manifest file, decode it and save it. + /// + /// Clones the template repository, decodes the manifest file and stores it in the ``Vapor/manifest`` `static` property for later use. + /// + /// + func preprocess(options: PreProcessArgs, gitURL: URL, templateURL: URL) throws { + if options.template == "https://github.com/vapor/template", + FileManager.default.fileExists(atPath: templateURL.path) + { + let pullArgs = ["-C", templateURL.path, "pull"] + try Process.runUntilExit(gitURL, arguments: pullArgs) + } else { + try? FileManager.default.removeItem(at: templateURL) + var cloneArgs = ["clone", "--depth", "1"] + if options.branch != nil, + let branch = options.branch + { + cloneArgs.append("--branch") + cloneArgs.append(branch) + } + cloneArgs.append(options.template) + cloneArgs.append(templateURL.path()) + print("Cloning template...".colored(.cyan)) + try Process.runUntilExit(gitURL, arguments: cloneArgs) + } + + let manifestURL = templateURL.appending(path: options.manifest) + + var result: TemplateManifest? = nil + + if FileManager.default.fileExists(atPath: manifestURL.path()) { + let manifestData = try Data(contentsOf: manifestURL) + result = + if manifestURL.pathExtension == "json" { + try JSONDecoder().decode(TemplateManifest.self, from: manifestData) + } else { + try YAMLDecoder().decode(TemplateManifest.self, from: manifestData) + } + } + Vapor.manifest = result + + } + mutating func run() throws { + + let preProcessArgs = PreProcessArgs( + template: self.buildOptions.template, + branch: self.buildOptions.branch, + manifest: self.buildOptions.manifest) + try preprocess(options: preProcessArgs, gitURL: Self.gitURL, templateURL: Vapor.templateURL) + if self.buildOptions.dumpVariables { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted @@ -78,7 +152,6 @@ extension Vapor { } if let manifest = Vapor.manifest { - defer { try? FileManager.default.removeItem(at: Vapor.templateURL) } try FileManager.default.createDirectory(at: projectURL, withIntermediateDirectories: false) @@ -95,7 +168,7 @@ extension Vapor { ) } else { // If the template doesn't have a manifest (AKA doesn't need templating), just move the files - try FileManager.default.moveItem(at: Vapor.templateURL, to: projectURL) + try FileManager.default.copyItem(at: Vapor.templateURL, to: projectURL) } if !self.buildOptions.noGit { @@ -105,14 +178,15 @@ extension Vapor { if (try? gitDir.checkResourceIsReachable()) ?? false { try FileManager.default.removeItem(at: gitDir) // Clear existing git history } - try Process.runUntilExit(Vapor.gitURL, arguments: ["--git-dir=\(gitDir.path(percentEncoded: false))", "init"]) + try Process.runUntilExit(Vapor.New.gitURL, arguments: ["--git-dir=\(gitDir.path(percentEncoded: false))", "init"]) if !self.buildOptions.noCommit { print("Adding first commit".colored(.cyan)) let gitDirFlag = "--git-dir=\(gitDir.path())" let workTreeFlag = "--work-tree=\(projectURL.path())" - try Process.runUntilExit(Vapor.gitURL, arguments: [gitDirFlag, workTreeFlag, "add", "."]) - try Process.runUntilExit(Vapor.gitURL, arguments: [gitDirFlag, workTreeFlag, "commit", "-m", "Generate Vapor project"]) + try Process.runUntilExit(Vapor.New.gitURL, arguments: [gitDirFlag, workTreeFlag, "add", "."]) + try Process.runUntilExit( + Vapor.New.gitURL, arguments: [gitDirFlag, workTreeFlag, "commit", "-m", "Generate Vapor project"]) } } @@ -244,7 +318,6 @@ extension Vapor.New: CustomReflectable { let container = try decoder.container(keyedBy: CodingKeys.self) self.name = try container.decode(Argument.self, forKey: .name).wrappedValue self.buildOptions = try container.decode(OptionGroup.self, forKey: .buildOptions).wrappedValue - guard let variables = Vapor.manifest?.variables else { return } func decodeVariable(_ variable: TemplateManifest.Variable, path: String) throws -> Any? { diff --git a/Sources/VaporToolbox/TemplateRenderer.swift b/Sources/VaporToolbox/TemplateRenderer.swift index 63274cf5..8550fd0c 100644 --- a/Sources/VaporToolbox/TemplateRenderer.swift +++ b/Sources/VaporToolbox/TemplateRenderer.swift @@ -211,7 +211,7 @@ struct TemplateRenderer { try MustacheTemplate(string: template).render(context) .write(to: destinationFileURL, atomically: true, encoding: .utf8) } else { - try FileManager.default.moveItem(at: sourceURL.appending(path: file.name), to: destinationFileURL) + try FileManager.default.copyItem(at: sourceURL.appending(path: file.name), to: destinationFileURL) } if self.verbose { print("+ " + file.name) } case .folder(let files): diff --git a/Sources/VaporToolbox/Vapor.swift b/Sources/VaporToolbox/Vapor.swift index a69b0a1e..1b14412c 100644 --- a/Sources/VaporToolbox/Vapor.swift +++ b/Sources/VaporToolbox/Vapor.swift @@ -11,13 +11,12 @@ struct Vapor: ParsableCommand { defaultSubcommand: New.self ) - nonisolated(unsafe) static var manifest: TemplateManifest? = nil - static let templateURL = URL.temporaryDirectory.appending(path: ".vapor-template", directoryHint: .isDirectory) - static let gitURL = try! Process.shell.which("git") + static let templateURL = URL.homeDirectory.appending(path: ".vapor-template", directoryHint: .isDirectory) + nonisolated(unsafe) public static var manifest: TemplateManifest? = nil static func main() { do { - try Self.preprocess(CommandLine.arguments) + try loadManifest() var command = try parseAsRoot(nil) try command.run() } catch { @@ -25,72 +24,21 @@ struct Vapor: ParsableCommand { } } - /// Get the template's manifest file, decode it and save it. - /// - /// Clones the template repository, decodes the manifest file and stores it in the ``Vapor/manifest`` `static` property for later use. - /// - /// - Parameter arguments: The command line arguments. - static func preprocess(_ arguments: [String]) throws { - guard !arguments.contains("--version") else { - return - } - - let templateWebURL = - if let index = arguments.firstIndex(of: "--template") { - arguments[index + 1] - } else if let index = arguments.firstIndex(of: "-t") { - arguments[index + 1] - } else { - "https://github.com/vapor/template" - } - - let branch: String? = - if let index = arguments.firstIndex(of: "--branch") { - arguments[index + 1] - } else { - nil - } + static func loadManifest() throws { + let manifestURL = templateURL.appending(path: "manifest.yml") - try? FileManager.default.removeItem(at: Self.templateURL) - - if !arguments.contains("-h"), - !arguments.contains("--help"), - !arguments.contains("-help"), - !arguments.contains("--help-hidden"), - !arguments.contains("-help-hidden"), - !arguments.contains("--generate-completion-script"), - !arguments.contains("--dump-variables") - { - print("Cloning template...".colored(.cyan)) - } - var cloneArgs = ["clone"] - if let branch { - cloneArgs.append("--branch") - cloneArgs.append(branch) - } - cloneArgs.append(templateWebURL) - cloneArgs.append(Self.templateURL.path()) - try Process.runUntilExit(Self.gitURL, arguments: cloneArgs) - - var manifestURL: URL - if let index = arguments.firstIndex(of: "--manifest") { - manifestURL = Self.templateURL.appending(path: arguments[index + 1]) - } else { - manifestURL = Self.templateURL.appending(path: "manifest.yml") - if !FileManager.default.fileExists(atPath: manifestURL.path()) { - manifestURL = Self.templateURL.appending(path: "manifest.json") - } - } + var result: TemplateManifest? = nil if FileManager.default.fileExists(atPath: manifestURL.path()) { let manifestData = try Data(contentsOf: manifestURL) - Self.manifest = + result = if manifestURL.pathExtension == "json" { try JSONDecoder().decode(TemplateManifest.self, from: manifestData) } else { try YAMLDecoder().decode(TemplateManifest.self, from: manifestData) } } + Vapor.manifest = result } /// The version of this Vapor Toolbox. diff --git a/Tests/VaporToolboxTests/VaporToolboxTests.swift b/Tests/VaporToolboxTests/VaporToolboxTests.swift index d17458a0..2891dda4 100644 --- a/Tests/VaporToolboxTests/VaporToolboxTests.swift +++ b/Tests/VaporToolboxTests/VaporToolboxTests.swift @@ -7,10 +7,12 @@ import Yams @Suite("VaporToolbox Tests") struct VaporToolboxTests { #if !os(Android) - @Test("Vapor.preprocess") + @Test("Vapor.New.preprocess") func preprocess() throws { + let options = Vapor.New.PreProcessArgs(template: "https://github.com/vapor/template", branch: nil, manifest: "manifest.yml") + let vaporNew = Vapor.New() #expect(Vapor.manifest == nil) - try Vapor.preprocess([]) + try vaporNew.preprocess(options: options, gitURL: Vapor.New.gitURL, templateURL: Vapor.templateURL) #expect(Vapor.manifest != nil) } #endif