Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐Ÿ”€ :: (#424) ๋ชจ๋“ˆ ์ƒ์„ฑ ์Šคํฌ๋ฆฝํŠธ ์ตœ์‹  ์ฝ”๋“œ ๋™๊ธฐํ™” #427

Merged
merged 5 commits into from
Mar 6, 2024
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ reset:
rm -rf *.xcworkspace

feature:
echo "\033[0;31m์ด ๋ช…๋ น์–ด๋Š” deprecated ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 'make module'์„ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”! \033[0m"
python3 Scripts/generate_new_feature.py

pg:
swift Scripts/GeneratePlugin.swift
swift Scripts/GeneratePlugin.swift

module:
swift Scripts/GenerateModule.swift
252 changes: 252 additions & 0 deletions Scripts/GenerateModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
#!/usr/bin/swift
import Foundation

func handleSIGINT(_ signal: Int32) {
exit(0)
}

signal(SIGINT, handleSIGINT)

enum LayerType: String {
case feature = "Feature"
case domain = "Domain"
@available(*, deprecated, message: "Service ๋ ˆ์ด์–ด๋Š” ๋ฏธ๋ž˜์— ์ง€์›Œ์ง‘๋‹ˆ๋‹ค. Service ๋ ˆ์ด์–ด์˜ ๋‚ด์šฉ์€ Domain ๋ ˆ์ด์–ด๋กœ ์ด์ „๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.")
case service = "Service"
case module = "Module"
case userInterface = "UserInterface"
}

enum MicroTargetType: String {
case interface = "Interface"
case sources = ""
case testing = "Testing"
case unitTest = "Tests"
case uiTest = "UITests"
case demo = "Demo"
}

let fileManager = FileManager.default
let currentPath = "./"
let bash = Bash()

func registerModuleDependency() {
registerModulePaths()
makeProjectDirectory()

let layerPrefix = layer.rawValue.lowercased()
let moduleEnum = ".\(layerPrefix)(.\(moduleName))"
var targetString = "[\n"
if hasInterface {
makeScaffold(target: .interface)
targetString += "\(tab(2)).interface(module: \(moduleEnum)),\n"
}
targetString += "\(tab(2)).implements(module: \(moduleEnum)"
if hasInterface {
targetString += ", dependencies: [\n\(tab(3)).\(layerPrefix)(target: .\(moduleName), type: .interface)\n\(tab(2))])"
} else {
targetString += ")"
}
if hasTesting {
makeScaffold(target: .testing)
let interfaceDependency = ".\(layerPrefix)(target: .\(moduleName), type: .interface)"
targetString += ",\n\(tab(2)).testing(module: \(moduleEnum), dependencies: [\n\(tab(3))\(interfaceDependency)\n\(tab(2))])"
}
if hasUnitTests {
makeScaffold(target: .unitTest)
targetString += ",\n\(tab(2)).tests(module: \(moduleEnum), dependencies: [\n\(tab(3)).\(layerPrefix)(target: .\(moduleName))\n\(tab(2))])"
}
if hasUITests {
makeScaffold(target: .uiTest)
// TODO: - ui test ํƒ€๊ฒŸ ์„ค์ • ๋กœ์ง ์ถ”๊ฐ€

Check warning on line 60 in Scripts/GenerateModule.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Todo Violation: TODOs should be resolved (- ui test ํƒ€๊ฒŸ ์„ค์ • ๋กœ์ง ์ถ”๊ฐ€) (todo)
}
if hasDemo {
makeScaffold(target: .demo)
targetString += ",\n\(tab(2)).demo(module: \(moduleEnum), dependencies: [\n\(tab(3)).\(layerPrefix)(target: .\(moduleName))\n\(tab(2))])"
}
targetString += "\n\(tab(1))]"
makeProjectSwift(targetString: targetString)
makeSourceScaffold()
}

func tab(_ count: Int) -> String {
var tabString = ""
for _ in 0..<count {
tabString += " "
}
return tabString
}

func registerModulePaths() {
updateFileContent(
filePath: currentPath + "Plugin/DependencyPlugin/ProjectDescriptionHelpers/ModulePaths.swift",
finding: "enum \(layer.rawValue): String, MicroTargetPathConvertable {\n",
inserting: " case \(moduleName)\n"
)
print("Register \(moduleName) to ModulePaths.swift")
}

func makeDirectory(path: String) {
do {
try fileManager.createDirectory(atPath: path, withIntermediateDirectories: false, attributes: nil)
} catch {
fatalError("โŒ failed to create directory: \(path)")
}
}

func makeDirectories(_ paths: [String]) {
paths.forEach(makeDirectory(path:))
}

func makeProjectSwift(targetString: String) {
let projectSwift = """
import DependencyPlugin
import ProjectDescription
import ProjectDescriptionHelpers

let project = Project.module(
name: ModulePaths.\(layer.rawValue).\(moduleName).rawValue,
targets: \(targetString)
)

"""
writeContentInFile(
path: currentPath + "Projects/\(layer.rawValue)s/\(moduleName)/Project.swift",
content: projectSwift
)
#warning("TODO: Layer ์ด๋ฆ„(๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„)๊ณผ ModulePaths ์ด๋ฆ„์ด ๋‹ค๋ฅด๊ธฐ์— ๊ฒฝ๋กœ์— Layer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„๋“ค์€ Projects/{์ด๋ฆ„}s ๋กœ ๋’ค์— s๋ฅผ ๋ถ™์ž„")
}

func makeProjectDirectory() {
makeDirectory(path: currentPath + "Projects/\(layer.rawValue)s/\(moduleName)")
}

func makeSourceScaffold() {
_ = try? bash.run(
commandName: "tuist",
arguments: ["scaffold", "Sources", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)s"]
)
}

func makeScaffold(target: MicroTargetType) {
_ = try? bash.run(
commandName: "tuist",
arguments: ["scaffold", "\(target.rawValue)", "--name", "\(moduleName)", "--layer", "\(layer.rawValue)s"]
)
}

func writeContentInFile(path: String, content: String) {
let fileURL = URL(fileURLWithPath: path)
let data = Data(content.utf8)
try? data.write(to: fileURL)
}

func updateFileContent(
filePath: String,
finding findingString: String,
inserting insertString: String
) {
let fileURL = URL(fileURLWithPath: filePath)
guard let readHandle = try? FileHandle(forReadingFrom: fileURL) else {
fatalError("โŒ Failed to find \(filePath)")
}
guard let readData = try? readHandle.readToEnd() else {
fatalError("โŒ Failed to find \(filePath)")
}
try? readHandle.close()

guard var fileString = String(data: readData, encoding: .utf8) else { fatalError() }
fileString.insert(contentsOf: insertString, at: fileString.range(of: findingString)?.upperBound ?? fileString.endIndex)

guard let writeHandle = try? FileHandle(forWritingTo: fileURL) else {
fatalError("โŒ Failed to find \(filePath)")
}
writeHandle.seek(toFileOffset: 0)
try? writeHandle.write(contentsOf: Data(fileString.utf8))
try? writeHandle.close()
}

// MARK: - Starting point

print("๋ ˆ์ด์–ด ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!\n(Feature | Domain | Service | Module | UserInterface)", terminator: " : ")
let layerInput = readLine()
guard
let layerInput,
!layerInput.isEmpty,
let layerUnwrapping = LayerType(rawValue: layerInput)
else {
print("์ž…๋ ฅ์ด ๋น„์—ˆ๊ฑฐ๋‚˜ ์ž˜๋ชป๋˜์—ˆ์–ด์š”!")
exit(1)
}
let layer = layerUnwrapping
print("Layer: \(layer.rawValue)\n")

print("๋ชจ๋“ˆ ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!", terminator: " : ")
let moduleInput = readLine()
guard let moduleNameUnwrapping = moduleInput, !moduleNameUnwrapping.isEmpty else {
print("๋ชจ๋“ˆ ์ด๋ฆ„์ด ๋น„์—ˆ์–ด์š”!")
exit(1)
}
var moduleName = moduleNameUnwrapping
print("๋ชจ๋“ˆ ์ด๋ฆ„: \(moduleName)\n")

print("'Interface' Target์„ ํฌํ•จํ•˜๋‚˜์š”? (y\\n, default = n)", terminator: " : ")
let hasInterface = readLine()?.lowercased() == "y"

print("'Testing' Target์„ ํฌํ•จํ•˜๋‚˜์š”? (y\\n, default = n)", terminator: " : ")
let hasTesting = readLine()?.lowercased() == "y"

print("'UnitTests' Target์„ ํฌํ•จํ•˜๋‚˜์š”? (y\\n, default = n)", terminator: " : ")
let hasUnitTests = readLine()?.lowercased() == "y"

print("'UITests' Target์„ ํฌํ•จํ•˜๋‚˜์š”? (y\\n, default = n)", terminator: " : ")
let hasUITests = readLine()?.lowercased() == "y"

print("'Demo' Target์„ ํฌํ•จํ•˜๋‚˜์š”? (y\\n, default = n)", terminator: " : ")
let hasDemo = readLine()?.lowercased() == "y"

print("")

registerModuleDependency()

print("")
print("------------------------------------------------------------------------------------------------------------------------")
print("๋ ˆ์ด์–ด: \(layer.rawValue)")
print("๋ชจ๋“ˆ ์ด๋ฆ„: \(moduleName)")
print("interface: \(hasInterface), testing: \(hasTesting), unitTests: \(hasUnitTests), uiTests: \(hasUITests), demo: \(hasDemo)")
print("------------------------------------------------------------------------------------------------------------------------")
print("โœ… ๋ชจ๋“ˆ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์ƒ์„ฑํ–ˆ์–ด์š”!")

// MARK: - Bash
protocol CommandExecuting {
func run(commandName: String, arguments: [String]) throws -> String
}

enum BashError: Error {
case commandNotFound(name: String)
}

struct Bash: CommandExecuting {
func run(commandName: String, arguments: [String] = []) throws -> String {
return try run(resolve(commandName), with: arguments)
}

private func resolve(_ command: String) throws -> String {
guard var bashCommand = try? run("/bin/bash", with: ["-l", "-c", "which \(command)"]) else {
throw BashError.commandNotFound(name: command)
}
bashCommand = bashCommand.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
return bashCommand
}

private func run(_ command: String, with arguments: [String] = []) throws -> String {
let process = Process()
process.launchPath = command
process.arguments = arguments
let outputPipe = Pipe()
process.standardOutput = outputPipe
process.launch()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
return output
}
}

Check warning on line 252 in Scripts/GenerateModule.swift

View workflow job for this annotation

GitHub Actions / swiftlint

Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)
Loading