diff --git a/Package.resolved b/Package.resolved index 1a46105..476d9b4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -9,6 +9,15 @@ "revision": "2fd91357e90efe82bfa6845d1e7d5bc2f5025d35", "version": "1.8.1" } + }, + { + "package": "SwiftShell", + "repositoryURL": "https://github.com/kareman/SwiftShell", + "state": { + "branch": null, + "revision": "99680b2efc7c7dbcace1da0b3979d266f02e213c", + "version": "5.1.0" + } } ] }, diff --git a/Package.swift b/Package.swift index 6852aa2..ec04996 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,6 @@ import PackageDescription let package = Package( name: "SwiftShortcuts", platforms: [ - .iOS(.v13), .macOS(.v10_15), ], products: [ @@ -14,6 +13,7 @@ let package = Package( ], dependencies: [ .package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1"), + .package(url: "https://github.com/kareman/SwiftShell", from: "5.1.0"), ], targets: [ .target( @@ -21,7 +21,7 @@ let package = Package( dependencies: []), .target( name: "SwiftShortcuts", - dependencies: ["CSymbols"]), + dependencies: ["CSymbols", "SwiftShell"]), .testTarget( name: "SwiftShortcutsTests", dependencies: ["SwiftShortcuts", "SnapshotTesting"], diff --git a/README.md b/README.md index 13d50b9..9dccb8d 100644 --- a/README.md +++ b/README.md @@ -58,11 +58,10 @@ To create a file that you can import into the Shortcuts app, call the `build()` // continued from above let shortcut = HelloWorldShortcut() -let data = try shortcut.build() -try data.write(to: URL(fileURLWithPath: "Hello World.shortcut")) +try shortcut.buildAndSave(atPath: URL(fileURLWithPath: "HelloWorld.shortcut")) ``` -Now you can share (for example, via AirDrop) the _Hello World.shortcut_ file to your device and it will open in the Shortcuts app. Unfortunately iOS 13 does not support opening serialized `.shortcut` files. +Now you can share (for example, via AirDrop) the _Hello World.shortcut_ file to your device and it will open in the Shortcuts app. ## Examples diff --git a/Sources/SwiftShortcuts/Core/Shortcut+Build.swift b/Sources/SwiftShortcuts/Core/Shortcut+Build.swift index 133afa7..914aab2 100644 --- a/Sources/SwiftShortcuts/Core/Shortcut+Build.swift +++ b/Sources/SwiftShortcuts/Core/Shortcut+Build.swift @@ -1,4 +1,9 @@ import Foundation +import SwiftShell + +enum BuildError: Error { + case pathNotValid +} extension Shortcut { var payload: ShortcutPayload { @@ -10,9 +15,56 @@ extension Shortcut { /// Serializes the body of the Shortcut to a file format suitable for opening in the Shortcuts app. /// - Throws: An error of type `EncodingError`, if one is thrown during the encoding process. /// - Returns: The binary data representing this Shortcut. + @available(*, deprecated, message: "Please use buildAndSave(toPath:sign:) instead") public func build() throws -> Data { let encoder = PropertyListEncoder() encoder.outputFormat = .binary return try encoder.encode(payload) } + + /// Build the Shortcut, sign it with Apple and save it to a file. + /// - Parameters: + /// - path: path to save the file + /// - sign: After iOS 13.0, you can't install unsigned shortcut file. You must sign the file in order to install it into `Shortcut` app. You should be aware that this `shortcuts sign` command can only run on mac with AppleID login. + /// - Throws: An error of type `EncodingError`, if one is thrown during the encoding process. Or any error during the `shortcuts sign` and save file process. + public func buildAndSave(toPath path: URL, sign: Bool = true) throws { + let data = try build() + try data.write(to: path) + try ShortcutSign(path: path, signMode: .anyone).run() + } +} + +struct ShortcutSign { + enum SignMode: String { + case peopleWhoKnowMe = "people-who-know-me" + case anyone + } + + enum Error: Swift.Error { + case signFailed + } + + let path: URL + let signMode: SignMode + + private let fm = FileManager.default + + func run() throws { + let outputPath = URL(fileURLWithPath: path.relativeString.replacingOccurrences(of: ".shortcut", with: "-signed.shortcut")) + if fm.fileExists(atPath: outputPath.relativeString) { + try fm.removeItem(at: outputPath) + } + let output = SwiftShell.run( + "shortcuts", "sign", + "--mode", signMode.rawValue, + "--input", path.relativeString, + "--output", outputPath.relativeString + ) + print(output.stdout) + if !fm.fileExists(atPath: outputPath.relativeString) { + throw output.error ?? Error.signFailed + } + try fm.removeItem(at: path) + try fm.moveItem(at: outputPath, to: path) + } }