mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Automatically open a PR to bump the calver (#4167)
* Update our tools package to Swift 6.1
Also improves the package layout with subdirectories 📁
* Update GenerateSDKMocks to be an Async command.
* Add a tool to bump the project CalVer every month.
* Add a workflow to automatically bump the calendar version.
Note: This only does year & month, the patch is handled by the release script.
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import ArgumentParser
|
||||
import SwiftUI
|
||||
|
||||
struct AppIconBanner: AsyncParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A Swift command-line tool to add a banner to an app icons.")
|
||||
|
||||
@Argument(help: "Path to the input image.")
|
||||
var path: String
|
||||
|
||||
@Option(help: "Text for the banner.")
|
||||
var bannerText: String
|
||||
|
||||
@MainActor
|
||||
func run() async throws {
|
||||
let currentDirectoryURL = URL(filePath: FileManager.default.currentDirectoryPath)
|
||||
let pathURL = currentDirectoryURL.appending(path: path)
|
||||
|
||||
guard let image = NSImage(contentsOf: pathURL) else {
|
||||
throw ValidationError("Could not load the image at \(pathURL).")
|
||||
}
|
||||
|
||||
let renderer = ImageRenderer(content: BannerImage(image: image,
|
||||
text: bannerText))
|
||||
|
||||
do {
|
||||
guard let cgImage = renderer.cgImage else {
|
||||
throw ValidationError("Couldn't generate CG image.")
|
||||
}
|
||||
|
||||
let bitmap = NSBitmapImageRep(cgImage: cgImage)
|
||||
|
||||
guard let pngData = bitmap.representation(using: .png, properties: [:]) else {
|
||||
throw ValidationError("Couldn't create png data from image.")
|
||||
}
|
||||
|
||||
try pngData.write(to: pathURL)
|
||||
print("Successfully saved the image with a banner to \(pathURL).")
|
||||
} catch {
|
||||
throw ValidationError("Failed to save the image: \(error).")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BannerImage: View {
|
||||
let image: NSImage
|
||||
let text: String
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottom) {
|
||||
Image(nsImage: image)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
|
||||
Text(text)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.background(Color.black.opacity(0.5))
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 140))
|
||||
.lineLimit(1)
|
||||
.allowsTightening(true)
|
||||
}
|
||||
.frame(width: image.size.width, height: image.size.height)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
import Yams
|
||||
|
||||
enum Profile: String, ExpressibleByArgument {
|
||||
case debug = "dbg"
|
||||
case reldbg, release
|
||||
}
|
||||
|
||||
enum Target: String, ExpressibleByArgument, CaseIterable {
|
||||
case iOS = "aarch64-apple-ios"
|
||||
case simulatorARM64 = "aarch64-apple-ios-sim"
|
||||
case simulatorIntel = "x86_64-apple-ios"
|
||||
case macARM64 = "aarch64-apple-darwin"
|
||||
case macIntel = "x86_64-apple-darwin"
|
||||
}
|
||||
|
||||
struct BuildSDK: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to checkout and build MatrixRustSDK locally for development.")
|
||||
|
||||
@Argument(help: "An optional argument to specify a branch of the SDK.")
|
||||
var branch: String?
|
||||
|
||||
@Flag(help: "Build the SDK to run on an iPhone or iPad. This flag takes precedence over --simulator and --target.")
|
||||
var device
|
||||
|
||||
@Flag(help: "Build the SDK to run on the simulator. This flag takes precedence over --target.")
|
||||
var simulator
|
||||
|
||||
@Option(help: "The target to build for such as aarch64-apple-ios. Omit this option to build for all targets.")
|
||||
var target: [Target] = []
|
||||
|
||||
@Option(help: "The profile to use when building the SDK. Omit this option to build in debug mode.")
|
||||
var profile: Profile = .reldbg
|
||||
|
||||
enum Error: LocalizedError {
|
||||
case rustupOutputFailure
|
||||
case missingRustTargets([String])
|
||||
case failureParsingProjectYAML
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .missingRustTargets(let missingTargets):
|
||||
return """
|
||||
Rust is missing the necessary targets to build the SDK.
|
||||
Run the following command to install them:
|
||||
|
||||
rustup target add \(missingTargets.joined(separator: " "))
|
||||
|
||||
"""
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
try checkRustupTargets()
|
||||
try cloneSDKIfNeeded()
|
||||
try checkoutBranchIfSupplied()
|
||||
try buildFramework()
|
||||
try updateXcodeProject()
|
||||
try generateSDKMocks()
|
||||
}
|
||||
|
||||
/// Checks that all of the required targets have been added through rustup
|
||||
/// but only when the ``target`` option hasn't been supplied.
|
||||
func checkRustupTargets() throws {
|
||||
guard target.isEmpty, device == 0, simulator == 0 else { return }
|
||||
guard let output = try Zsh.run(command: "rustup target list --toolchain stable --installed") else { throw Error.rustupOutputFailure }
|
||||
|
||||
var requiredTargets = Target.allCases.reduce(into: [String: Bool]()) { partialResult, target in
|
||||
partialResult[target.rawValue] = false
|
||||
}
|
||||
|
||||
output.enumerateLines { line, _ in
|
||||
if requiredTargets.keys.contains(line) {
|
||||
requiredTargets[line] = true
|
||||
}
|
||||
}
|
||||
|
||||
let missingTargets = requiredTargets.compactMap { !$0.value ? $0.key : nil }
|
||||
guard missingTargets.isEmpty else { throw Error.missingRustTargets(missingTargets) }
|
||||
}
|
||||
|
||||
/// Clones the Rust SDK if a copy isn't found in the parent directory.
|
||||
func cloneSDKIfNeeded() throws {
|
||||
guard !FileManager.default.fileExists(atPath: URL.sdkDirectory.path) else { return }
|
||||
try Zsh.run(command: "git clone https://github.com/matrix-org/matrix-rust-sdk", directory: .parentDirectory)
|
||||
}
|
||||
|
||||
/// Checkout the specified branch of the SDK if supplied.
|
||||
func checkoutBranchIfSupplied() throws {
|
||||
guard let branch else { return }
|
||||
try Zsh.run(command: "git checkout \(branch)", directory: .sdkDirectory)
|
||||
}
|
||||
|
||||
/// Build the Rust SDK as an XCFramework with the debug profile.
|
||||
func buildFramework() throws {
|
||||
// unset fixes an issue where swift compilation prevents building for targets other than macOS X
|
||||
var buildCommand = "unset SDKROOT && cargo xtask swift build-framework"
|
||||
buildCommand.append(" --profile \(profile.rawValue)")
|
||||
if device > 0 {
|
||||
buildCommand.append(" --target \(Target.iOS.rawValue)")
|
||||
} else if simulator > 0 {
|
||||
let hostArchitecture = try Zsh.run(command: "arch")
|
||||
if hostArchitecture?.trimmingCharacters(in: .whitespacesAndNewlines) == "arm64" {
|
||||
buildCommand.append(" --target \(Target.simulatorARM64.rawValue)")
|
||||
} else {
|
||||
buildCommand.append(" --target \(Target.simulatorIntel.rawValue)")
|
||||
}
|
||||
} else if !target.isEmpty {
|
||||
target.forEach { buildCommand.append(" --target \($0.rawValue)") }
|
||||
}
|
||||
try Zsh.run(command: buildCommand, directory: .sdkDirectory)
|
||||
}
|
||||
|
||||
/// Update the Xcode project to use the build of the SDK.
|
||||
func updateXcodeProject() throws {
|
||||
try updateProjectYAML()
|
||||
try Zsh.run(command: "xcodegen")
|
||||
}
|
||||
|
||||
/// Update project.yml with the local path of the SDK.
|
||||
func updateProjectYAML() throws {
|
||||
let yamlURL = URL.projectDirectory.appendingPathComponent("project.yml")
|
||||
let yamlString = try String(contentsOf: yamlURL)
|
||||
guard var projectConfig = try Yams.compose(yaml: yamlString) else { throw Error.failureParsingProjectYAML }
|
||||
|
||||
projectConfig["packages"]?.mapping?["MatrixRustSDK"]? = ["path": "../matrix-rust-sdk"]
|
||||
|
||||
let updatedYAMLString = try Yams.serialize(node: projectConfig)
|
||||
try updatedYAMLString.write(to: yamlURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
func generateSDKMocks() throws {
|
||||
var command = GenerateSDKMocks()
|
||||
command.version = "local"
|
||||
try command.run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct BumpCalendarVersion: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool that bumps the CalVer every month (if needed), setting the patch back to 0.",
|
||||
discussion: "The tool assumes the release will be published in 6-days so bumps early.")
|
||||
|
||||
func run() throws {
|
||||
try updateProjectYAML()
|
||||
try Zsh.run(command: "xcodegen")
|
||||
}
|
||||
|
||||
/// Updates the project YAML with the new version.
|
||||
private func updateProjectYAML() throws {
|
||||
let yamlURL = URL.projectDirectory.appendingPathComponent("project.yml")
|
||||
let yamlString = try String(contentsOf: yamlURL)
|
||||
|
||||
// Use regex instead of Yams to preserve any whitespace, comments etc in the file.
|
||||
let marketingVersionRegex = /MARKETING_VERSION:\s*([^\s]+)/
|
||||
var updatedYAMLString = ""
|
||||
|
||||
yamlString.enumerateLines { line, _ in
|
||||
let processedLine = if let match = line.firstMatch(of: marketingVersionRegex),
|
||||
let newVersion = try? generateNewVersion(from: String(match.1)) {
|
||||
line.replacingOccurrences(of: match.1, with: newVersion)
|
||||
} else {
|
||||
line
|
||||
}
|
||||
|
||||
updatedYAMLString.append(processedLine + "\n")
|
||||
}
|
||||
|
||||
try updatedYAMLString.write(to: yamlURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
|
||||
/// Returns the new version string if a change is necessary.
|
||||
///
|
||||
/// **Note:** This tool does *not* handle patch bumps, those are done automatically in the release script.
|
||||
private func generateNewVersion(from currentVersion: String) throws -> String? {
|
||||
let releaseDate = Date.now.addingTimeInterval(6 * 24 * 60 * 60) // Always assume we're building the RC.
|
||||
let releaseYear = Calendar.current.component(.year, from: releaseDate) % 1000 // We use the short year.
|
||||
let releaseMonth = Calendar.current.component(.month, from: releaseDate)
|
||||
let versionComponents = currentVersion.split(separator: ".").compactMap { Int($0) }
|
||||
|
||||
guard versionComponents.count == 3 else { fatalError("Unexpected version format: \(currentVersion)") }
|
||||
|
||||
if versionComponents[0] != releaseYear || versionComponents[1] != releaseMonth {
|
||||
return "\(releaseYear).\(String(format: "%02d", releaseMonth)).0"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct DownloadStrings: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to download localizable strings from localazy")
|
||||
|
||||
@Flag(help: "Use to download translation keys for all languages")
|
||||
var allLanguages = false
|
||||
|
||||
func run() throws {
|
||||
try localazyDownload()
|
||||
try swiftgen()
|
||||
}
|
||||
|
||||
private func localazyDownload() throws {
|
||||
let arguments = allLanguages ? " all" : ""
|
||||
try Zsh.run(command: "localazy download\(arguments)")
|
||||
}
|
||||
|
||||
private func swiftgen() throws {
|
||||
try Zsh.run(command: "swiftgen config run --config Tools/SwiftGen/swiftgen-config.yml")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import ArgumentParser
|
||||
import Foundation
|
||||
|
||||
struct GenerateSAS: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to download and generate SAS localization strings")
|
||||
private static let defaultLanguage = "en"
|
||||
|
||||
@Flag(name: .shortAndLong, help: "Increase output verbosity.")
|
||||
var verbose = false
|
||||
|
||||
func run() throws {
|
||||
let baseURL = URL(string: "https://raw.githubusercontent.com/matrix-org/matrix-spec/main/data-definitions/sas-emoji.json")!
|
||||
|
||||
printIfVerbose("Argument:")
|
||||
printIfVerbose(self)
|
||||
|
||||
print("Downloading \(baseURL.absoluteString)…")
|
||||
|
||||
guard let data = try? Data(contentsOf: baseURL),
|
||||
let json = try? JSONDecoder().decode([SASEmoji].self, from: data) else {
|
||||
print("Failed to download or parse JSON.")
|
||||
return
|
||||
}
|
||||
|
||||
printIfVerbose("Json data:")
|
||||
printIfVerbose(json)
|
||||
|
||||
var defaultTranslations = [String: String]()
|
||||
var cumulativeTranslations = [String: [String: String]]()
|
||||
|
||||
cumulativeTranslations[Self.defaultLanguage] = [String: String]()
|
||||
for emoji in json {
|
||||
let description = emoji.description
|
||||
|
||||
printIfVerbose("Description: \(description)")
|
||||
|
||||
defaultTranslations[description] = description
|
||||
|
||||
let translations = emoji.translatedDescriptions
|
||||
// en is not included since is the default language so we need to add it ourselves
|
||||
cumulativeTranslations[Self.defaultLanguage]![description] = description
|
||||
for (lang, translation) in translations {
|
||||
printIfVerbose("Lang: \(lang)")
|
||||
|
||||
if cumulativeTranslations[lang] == nil {
|
||||
cumulativeTranslations[lang] = [String: String]()
|
||||
}
|
||||
|
||||
cumulativeTranslations[lang]![description] = translation
|
||||
}
|
||||
}
|
||||
|
||||
printIfVerbose(defaultTranslations)
|
||||
printIfVerbose(cumulativeTranslations)
|
||||
|
||||
for (lang, translations) in cumulativeTranslations {
|
||||
let iosLang = lang
|
||||
.replacingOccurrences(of: "_", with: "-")
|
||||
.appending(".lproj")
|
||||
|
||||
writeToFile(file: "SAS.strings", dict: translations, subdirectory: iosLang)
|
||||
}
|
||||
|
||||
print("Write completed")
|
||||
}
|
||||
|
||||
private func writeToFile(file: String, dict: [String: String], subdirectory: String) {
|
||||
let fileDirectory = URL.projectDirectory.appendingPathComponent("ElementX/Resources/Localizations").appendingPathComponent(subdirectory)
|
||||
|
||||
let filePath = fileDirectory.appendingPathComponent(file)
|
||||
|
||||
print("Writing file \(filePath.path)")
|
||||
|
||||
printIfVerbose("With")
|
||||
printIfVerbose(dict)
|
||||
|
||||
do {
|
||||
// This will fail if the .lproj dir does not exist already, which is fine since we don't want to add translations for unsupported languages.
|
||||
try dict
|
||||
.sorted(by: { $0.key < $1.key })
|
||||
.map { "\"\($0.key.lowercased().replacingOccurrences(of: " ", with: "_"))\" = \"\($0.value)\";" }
|
||||
.joined(separator: "\n")
|
||||
.write(to: filePath, atomically: true, encoding: .utf8)
|
||||
} catch {
|
||||
print("Failed to write file \(filePath.path): \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private struct SASEmoji: Codable {
|
||||
let number: Int
|
||||
let emoji, description, unicode: String
|
||||
let translatedDescriptions: [String: String?]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case number, emoji, description, unicode
|
||||
case translatedDescriptions = "translated_descriptions"
|
||||
}
|
||||
}
|
||||
|
||||
private func printIfVerbose(_ items: Any...) {
|
||||
if verbose {
|
||||
print(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct GenerateSDKMocks: AsyncParsableCommand {
|
||||
enum GenerateSDKMocksError: Error {
|
||||
case invalidFileUrl
|
||||
}
|
||||
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to setup the mocks for the Matrix Rust SDK")
|
||||
|
||||
@Argument(help: "The argument to specify a branch of the SDK. Use `local` to use your local version")
|
||||
var version: String
|
||||
|
||||
private var fileURLFormat = "https://raw.githubusercontent.com/element-hq/matrix-rust-components-swift/%@/Sources/MatrixRustSDK/matrix_sdk_ffi.swift"
|
||||
|
||||
func run() async throws {
|
||||
if version == "local" {
|
||||
try generateSDKMocks(ffiPath: "\(URL.sdkDirectory.path)/bindings/apple/generated/swift")
|
||||
} else {
|
||||
let path = try await downloadSDK(version: version)
|
||||
try generateSDKMocks(ffiPath: path)
|
||||
try FileManager.default.removeItem(atPath: path)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the SDK mocks using Sourcery.
|
||||
func generateSDKMocks(ffiPath: String) throws {
|
||||
try Zsh.run(command: "sourcery --sources \(ffiPath) --templates Tools/Sourcery/SDKAutoMockable.stencil --output ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift")
|
||||
}
|
||||
|
||||
/// Downloads the specified version of the `matrix_sdk_ffi.swift` file and returns the path to the downloaded file.
|
||||
func downloadSDK(version: String) async throws -> String {
|
||||
let fileURLString = String(format: fileURLFormat, version)
|
||||
guard let fileURL = URL(string: fileURLString) else {
|
||||
throw GenerateSDKMocksError.invalidFileUrl
|
||||
}
|
||||
|
||||
let (tempURL, _) = try await URLSession.shared.download(from: fileURL)
|
||||
let sdkFilePath = NSTemporaryDirectory().appending("matrix_sdk_ffi.swift")
|
||||
try FileManager.default.moveItem(at: tempURL, to: URL(fileURLWithPath: sdkFilePath))
|
||||
return sdkFilePath
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct Locheck: ParsableCommand {
|
||||
enum LocheckError: LocalizedError {
|
||||
case missingMint
|
||||
case outputError
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .missingMint:
|
||||
return "💥 Unable to find mint. Fix by running:\nbrew install mint\n"
|
||||
case .outputError:
|
||||
return "💥 Failed to read the output from locheck."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let configuration = CommandConfiguration(abstract: "A tool that verifies bad strings contained in localization files")
|
||||
|
||||
private var stringsDirectoryURL: URL {
|
||||
.projectDirectory.appendingPathComponent("ElementX/Resources/Localizations")
|
||||
}
|
||||
|
||||
func run() throws {
|
||||
try checkMint()
|
||||
try checkStrings()
|
||||
}
|
||||
|
||||
func checkStrings() throws {
|
||||
guard let output = try Zsh.run(command: "mint run locheck discoverlproj --ignore-missing --ignore lproj_file_missing_from_translation --treat-warnings-as-errors \(stringsDirectoryURL.path)") else {
|
||||
throw LocheckError.missingMint
|
||||
}
|
||||
print(output)
|
||||
}
|
||||
|
||||
private func checkMint() throws {
|
||||
let result = try Zsh.run(command: "which mint")
|
||||
|
||||
if result?.contains("not found") == true {
|
||||
throw LocheckError.missingMint
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct OutdatedPackages: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to check outdated package dependencies. Please make sure you have already run setup-project before using this tool.")
|
||||
|
||||
private var projectSwiftPMDirectoryURL: URL { .projectDirectory.appendingPathComponent("ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm") }
|
||||
|
||||
func run() throws {
|
||||
try checkToolsDependencies()
|
||||
try checkProjectDependencies()
|
||||
}
|
||||
|
||||
func checkToolsDependencies() throws {
|
||||
guard let output = try Zsh.run(command: "swift outdated"), !output.isEmpty else { return }
|
||||
print("outdated tools Swift packages:\n\(output)")
|
||||
}
|
||||
|
||||
func checkProjectDependencies() throws {
|
||||
guard let output = try Zsh.run(command: "swift outdated", directory: projectSwiftPMDirectoryURL), !output.isEmpty else { return }
|
||||
print("outdated project Swift packages:\n\(output)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct SetupProject: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "A tool to setup the required components to efficiently run and contribute to Element X iOS")
|
||||
|
||||
func run() throws {
|
||||
try setupGitHooks()
|
||||
try brewInstall()
|
||||
try mintPackagesInstall()
|
||||
try xcodegen()
|
||||
}
|
||||
|
||||
func setupGitHooks() throws {
|
||||
try Zsh.run(command: "git config core.hooksPath .githooks")
|
||||
}
|
||||
|
||||
func brewInstall() throws {
|
||||
try Zsh.run(command: "brew install xcodegen swiftgen swiftformat git-lfs sourcery mint pkl kiliankoe/formulae/swift-outdated localazy/tools/localazy peripheryapp/periphery/periphery FelixHerrmann/tap/swift-package-list")
|
||||
}
|
||||
|
||||
func mintPackagesInstall() throws {
|
||||
try Zsh.run(command: "mint install Asana/locheck")
|
||||
}
|
||||
|
||||
func xcodegen() throws {
|
||||
try Zsh.run(command: "xcodegen")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import ArgumentParser
|
||||
import CommandLineTools
|
||||
import Foundation
|
||||
|
||||
struct UnusedStrings: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(abstract: "Generates a report showing which strings aren't used in the project.")
|
||||
|
||||
@Flag(help: "Save the results to disk instead of printing them.")
|
||||
var saveToFile = false
|
||||
|
||||
func run() throws {
|
||||
try peripheryScan()
|
||||
}
|
||||
|
||||
func peripheryScan() throws {
|
||||
print("Analysing project, this may take a while…")
|
||||
|
||||
// Uses the existing .periphery.yml with small tweaks to the output.
|
||||
let command = "periphery scan --quiet --relative-results --report-include ElementX/Sources/Generated/Strings.swift"
|
||||
let output = try Zsh.run(command: command)
|
||||
|
||||
guard let output else {
|
||||
print("Nothing reported.")
|
||||
return
|
||||
}
|
||||
|
||||
if saveToFile {
|
||||
try output.write(to: .projectDirectory.appending(component: "Unused Strings.txt"), atomically: true, encoding: .utf8)
|
||||
print("Report saved: Unused Strings.txt")
|
||||
} else {
|
||||
print(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user