├── .gitignore ├── Package.resolved ├── Package.swift ├── README.md ├── Resources ├── ExampleFirstViewController.swift └── ExampleSecondViewController.swift ├── Sources └── RouterBuilder │ └── main.swift ├── Tests ├── LinuxMain.swift └── RouterBuilderTests │ ├── RouterBuilderTests.swift │ └── XCTestManifests.swift ├── arguments-passed-on-launch.png ├── build.sh ├── generate-xcodeproj.sh ├── graffle.graffle ├── main.xcconfig ├── routerbuilderapp ├── routerbuilder.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── routerbuilder.xcscheme └── routerbuilder │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── routes.swift ├── screenshot.png └── web ├── .browserslistrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .graphqlconfig.yml ├── README.md ├── apollo-server ├── channels.js ├── connectors │ └── devices.js ├── context.js ├── data-sources.js ├── directives.js ├── mocks.js ├── pubsub.js ├── resolvers.js ├── schema │ └── devices.js ├── server.js └── type-defs.js ├── babel.config.js ├── package.json ├── postcss.config.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── appRoutes.json ├── assets │ └── logo.png ├── components │ ├── DeviceManager.vue │ └── RouteForm.vue ├── graphql │ ├── deviceBoot.gql │ ├── deviceFragment.gql │ ├── deviceShutdown.gql │ ├── devices.gql │ ├── devicesChanged.gql │ └── openUrl.gql ├── main.js ├── plugins.js ├── register-components.js └── vue-apollo.js ├── vue.config.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | 6 | # Created by https://www.gitignore.io/api/osx,xcode,swift 7 | # Edit at https://www.gitignore.io/?templates=osx,xcode,swift 8 | 9 | ### OSX ### 10 | # General 11 | .DS_Store 12 | .AppleDouble 13 | .LSOverride 14 | 15 | # Icon must end with two \r 16 | Icon 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | .com.apple.timemachine.donotpresent 29 | 30 | # Directories potentially created on remote AFP share 31 | .AppleDB 32 | .AppleDesktop 33 | Network Trash Folder 34 | Temporary Items 35 | .apdisk 36 | 37 | ### Swift ### 38 | # Xcode 39 | # 40 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 41 | 42 | ## Build generated 43 | build/ 44 | DerivedData/ 45 | 46 | ## Various settings 47 | *.pbxuser 48 | !default.pbxuser 49 | *.mode1v3 50 | !default.mode1v3 51 | *.mode2v3 52 | !default.mode2v3 53 | *.perspectivev3 54 | !default.perspectivev3 55 | xcuserdata/ 56 | 57 | ## Other 58 | *.moved-aside 59 | *.xccheckout 60 | *.xcscmblueprint 61 | 62 | ## Obj-C/Swift specific 63 | *.hmap 64 | *.ipa 65 | *.dSYM.zip 66 | *.dSYM 67 | 68 | ## Playgrounds 69 | timeline.xctimeline 70 | playground.xcworkspace 71 | 72 | # Swift Package Manager 73 | # 74 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 75 | # Packages/ 76 | # Package.pins 77 | # Package.resolved 78 | .build/ 79 | 80 | # CocoaPods 81 | # 82 | # We recommend against adding the Pods directory to your .gitignore. However 83 | # you should judge for yourself, the pros and cons are mentioned at: 84 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 85 | # 86 | # Pods/ 87 | # 88 | # Add this line if you want to avoid checking in source code from the Xcode workspace 89 | # *.xcworkspace 90 | 91 | # Carthage 92 | # 93 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 94 | # Carthage/Checkouts 95 | 96 | Carthage/Build 97 | 98 | # fastlane 99 | # 100 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 101 | # screenshots whenever they are needed. 102 | # For more information about the recommended setup visit: 103 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 104 | 105 | fastlane/report.xml 106 | fastlane/Preview.html 107 | fastlane/screenshots/**/*.png 108 | fastlane/test_output 109 | 110 | # Code Injection 111 | # 112 | # After new code Injection tools there's a generated folder /iOSInjectionProject 113 | # https://github.com/johnno1962/injectionforxcode 114 | 115 | iOSInjectionProject/ 116 | 117 | ### Xcode ### 118 | # Xcode 119 | # 120 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 121 | 122 | ## User settings 123 | 124 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 125 | 126 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 127 | 128 | ### Xcode Patch ### 129 | *.xcodeproj/* 130 | !*.xcodeproj/project.pbxproj 131 | !*.xcodeproj/xcshareddata/ 132 | !*.xcworkspace/contents.xcworkspacedata 133 | /*.gcno 134 | **/xcshareddata/WorkspaceSettings.xcsettings 135 | 136 | # End of https://www.gitignore.io/api/osx,xcode,swift 137 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "SwiftSyntax", 6 | "repositoryURL": "https://github.com/apple/swift-syntax.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "43aa4a19b8105a803d8149ad2a86aa53a77efef3", 10 | "version": "0.50000.0" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /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: "RouterBuilder", 8 | dependencies: [ 9 | .package(url: "https://github.com/apple/swift-syntax.git", .exact("0.50000.0")), 10 | ], 11 | targets: [ 12 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 13 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 14 | .target( 15 | name: "RouterBuilder", 16 | dependencies: ["SwiftSyntax"]), 17 | .testTarget( 18 | name: "RouterBuilderTests", 19 | dependencies: ["RouterBuilder"]), 20 | ] 21 | ) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RouterBuilder 2 | 3 | ![](screenshot.png) 4 | 5 | 1. 通过 SwiftSyntax 解析 ViewController 的初始化方法,生成可以填写的 Web 表单 6 | 2. 本地开启一个 WebSocket,Web 页面和本地的 WebSocket 交互完成页面的跳转展示 7 | 8 | > 当然这个 WebSocket 还可以做更多的事情,请自行发挥想象力。这个交互操作灵感来自 vue-cli。 9 | 10 | 如何运行本工程: 11 | 12 | ## 准备工作 13 | 14 | > 当然这部分可以做成自动的,比如在 Web 页面切换模拟器使用的 App 版本。 15 | 16 | ### 安装 App 到模拟器(必要操作) 17 | 18 | 编译运行工程 `routerbuilderapp/routerbuilder.xcodeproj` 到对应的模拟器 19 | 20 | ### 生成路由信息相关文件 21 | 22 | 生成 `web/src/appRoutes.json` 和 `routerbuilderapp/routerbuilder/routes.swift`。 23 | 这一步有可能会出问题,所以 git 提交中已经带上了两个文件。 24 | 25 | 建议通过 Xcode 运行。 26 | 27 | 执行 `./generate-xcodeproj.sh` 创建 Xcode 工程文件。 28 | 29 | 配置启动参数: 30 | 31 | ``` 32 | $SRCROOT/Resources 33 | $SRCROOT/web/src/appRoutes.json 34 | $SRCROOT/routerbuilderapp/routerbuilder/routes.swift 35 | ``` 36 | 37 | 如图: 38 | ![](arguments-passed-on-launch.png) 39 | 40 | 运行生成 `web/src/appRoutes.json` 和 `routerbuilderapp/routerbuilder/routes.swift` 即可。 41 | 42 | ## 配置运行 Web 工程 43 | 44 | Web 相关工程在 `web` 目录下。 45 | 46 | ### 启动 Apollo(必要) 47 | 48 | `yarn apollo:run` 49 | 50 | ### 启动 Web 页面 51 | 52 | `yarn serve` 或者打开 https://routerbuilder.netlify.com/ 。 -------------------------------------------------------------------------------- /Resources/ExampleFirstViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ExampleFirstViewController: UIViewController { 4 | 5 | let a: Int 6 | let b: String 7 | let c: String? 8 | let d: Float 9 | 10 | required init(a: Int, b: String, c: String?, d: Float) { 11 | self.a = a 12 | self.b = b 13 | self.c = c 14 | self.d = d 15 | super.init(nibName: nil, bundle: nil) 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | view.backgroundColor = UIColor.white 25 | let label = UILabel(frame: .zero) 26 | label.font = UIFont.systemFont(ofSize: 25) 27 | label.translatesAutoresizingMaskIntoConstraints = false 28 | label.numberOfLines = 0 29 | label.textAlignment = .center 30 | view.addSubview(label) 31 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 32 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 33 | 34 | label.text = """ 35 | \(type(of: self)) 36 | a: \(self.a) \(type(of: self.a)) 37 | b: \(self.b) \(type(of: self.b)) 38 | c: \(String(describing: self.c)) 39 | d: \(self.d) \(type(of: self.d)) 40 | """ 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /Resources/ExampleSecondViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ExampleSecondViewController: UIViewController { 4 | 5 | let a: Int 6 | let c: String? 7 | let g: Int? 8 | 9 | required init(a: Int, c: String?, g: Int?) { 10 | self.a = a 11 | self.c = c 12 | self.g = g 13 | super.init(nibName: nil, bundle: nil) 14 | } 15 | 16 | required init?(coder aDecoder: NSCoder) { 17 | fatalError("init(coder:) has not been implemented") 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | view.backgroundColor = UIColor.white 23 | let label = UILabel(frame: .zero) 24 | label.font = UIFont.systemFont(ofSize: 25) 25 | label.translatesAutoresizingMaskIntoConstraints = false 26 | label.numberOfLines = 0 27 | label.textAlignment = .center 28 | view.addSubview(label) 29 | label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 30 | label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 31 | 32 | label.text = """ 33 | \(type(of: self)) 34 | a: \(self.a) 35 | c: \(String(describing: self.c)) 36 | g: \(String(describing: self.g)) 37 | """ 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Sources/RouterBuilder/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftSyntax 3 | 4 | class TokenVisitor : SyntaxVisitor { 5 | var tree = [Node]() 6 | var current: Node! 7 | 8 | override func visitPre(_ node: Syntax) { 9 | var syntax = "\(type(of: node))" 10 | if syntax.hasSuffix("Syntax") { 11 | syntax = String(syntax.dropLast(6)) 12 | } 13 | 14 | let node = Node(text: syntax) 15 | if current == nil { 16 | tree.append(node) 17 | } else { 18 | current.add(node: node) 19 | } 20 | current = node 21 | } 22 | 23 | override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind { 24 | current.text = token.text 25 | processToken(token) 26 | return .visitChildren 27 | } 28 | 29 | override func visitPost(_ node: Syntax) { 30 | current = current.parent 31 | } 32 | 33 | private func processToken(_ token: TokenSyntax) { 34 | var kind = "\(token.tokenKind)" 35 | if let index = kind.index(of: "(") { 36 | kind = String(kind.prefix(upTo: index)) 37 | } 38 | if kind.hasSuffix("Keyword") { 39 | kind = "keyword" 40 | } 41 | } 42 | 43 | } 44 | 45 | class Node : Encodable { 46 | var text: String 47 | var children = [Node]() 48 | weak var parent: Node? 49 | 50 | enum CodingKeys : CodingKey { 51 | case text 52 | case children 53 | case range 54 | case token 55 | } 56 | 57 | init(text: String) { 58 | self.text = text 59 | } 60 | 61 | func add(node: Node) { 62 | node.parent = self 63 | children.append(node) 64 | } 65 | 66 | func encode(to encoder: Encoder) throws { 67 | var container = encoder.container(keyedBy: CodingKeys.self) 68 | try container.encode(text, forKey: .text) 69 | try container.encode(children, forKey: .children) 70 | } 71 | } 72 | 73 | struct RouteInfo: Encodable { 74 | 75 | let viewControllerClassName: String 76 | 77 | struct FunctionParameter: Encodable { 78 | let name: String 79 | let type: String 80 | let required: Bool 81 | let value = "" 82 | } 83 | 84 | let initializerFunctionParameterList: [FunctionParameter] 85 | 86 | } 87 | 88 | func getViewControllerRouteInfo(filePath: URL) -> RouteInfo { 89 | let sourceFile = try! SyntaxTreeParser.parse(filePath) 90 | 91 | let visitor = TokenVisitor() 92 | sourceFile.walk(visitor) 93 | 94 | let tree = visitor.tree 95 | 96 | let codeBlockItemList = tree.first!.children.first(where: { $0.text == "CodeBlockItemList" })! 97 | let controllerClassDecl = codeBlockItemList.children.flatMap({ $0.children }) 98 | .first(where: { $0.text == "ClassDecl" && $0.children.contains(where: { $0.text.contains("ViewController") }) })! 99 | 100 | let viewControllerClassName = controllerClassDecl.children.first(where: { $0.text.contains("ViewController") })!.text 101 | 102 | let initializerDecl = controllerClassDecl 103 | .children.first(where: { $0.text == "MemberDeclBlock" })! 104 | .children.first(where: { $0.text == "MemberDeclList" })! 105 | .children.flatMap({ $0.children }) // List 类型解包出 Item 的内容 106 | .first(where: { $0.text == "InitializerDecl" })! 107 | 108 | let initializerFunctionParameterList = initializerDecl 109 | .children.first(where: { $0.text == "ParameterClause" })! 110 | .children.first(where: { $0.text == "FunctionParameterList" })! 111 | .children.map { (functionParameter) -> RouteInfo.FunctionParameter in 112 | let name = functionParameter.children[0].text 113 | var simpleTypeIdentifierSuperNode = functionParameter.children.first(where: { $0.text == "SimpleTypeIdentifier" }) 114 | var required = true 115 | if let optionalType = functionParameter.children.first(where: { $0.text == "OptionalType" }) { 116 | simpleTypeIdentifierSuperNode = optionalType.children.first(where: { $0.text == "SimpleTypeIdentifier" }) 117 | required = false 118 | } 119 | let type = simpleTypeIdentifierSuperNode!.children.first!.text 120 | return RouteInfo.FunctionParameter(name: name, type: type, required: required) 121 | } 122 | 123 | return RouteInfo(viewControllerClassName: viewControllerClassName, initializerFunctionParameterList: initializerFunctionParameterList) 124 | } 125 | 126 | let arguments = Array(CommandLine.arguments.dropFirst()) 127 | let folderPath = URL(fileURLWithPath: arguments[0]) // $SRCROOT/Resources 128 | let files = try! FileManager.default.contentsOfDirectory(at: folderPath, includingPropertiesForKeys: [], options: []) 129 | 130 | let viewControllerURLInfoList = files.map(getViewControllerRouteInfo) 131 | 132 | let encoder = JSONEncoder() 133 | encoder.outputFormatting = .prettyPrinted 134 | let json = String(data: try! encoder.encode(viewControllerURLInfoList), encoding: .utf8)! 135 | 136 | let outputPath = URL(fileURLWithPath: arguments[1]) // $SRCROOT/web/src/appRoutes.json 137 | try! json.write(to: outputPath, atomically: true, encoding: .utf8) 138 | 139 | let routesContentDictionaryElementList: [DictionaryElementSyntax] = viewControllerURLInfoList.map { (routeInfo) -> DictionaryElementSyntax in 140 | let initializerFunctionParameterListString = routeInfo.initializerFunctionParameterList 141 | .map { (parameter) -> String in 142 | var result = "\n \(parameter.name): " 143 | if parameter.required { 144 | var value = "queryItems.first(where: { $0.name == \"\(parameter.name)\" })!.value!" 145 | if parameter.type != "String" { 146 | value = "\(parameter.type)(\(value))!" 147 | } 148 | result.append(value) 149 | } else { 150 | var value = "queryItems.first(where: { $0.name == \"\(parameter.name)\" })?.value" 151 | if parameter.type != "String" { 152 | value = "\(value).flatMap(\(parameter.type).init)" 153 | } 154 | result.append(value) 155 | } 156 | return result 157 | } 158 | .joined(separator: ",") 159 | let result = "return \(routeInfo.viewControllerClassName)(\(initializerFunctionParameterListString)\n )" 160 | return DictionaryElementSyntax { (builder: inout DictionaryElementSyntaxBuilder) in 161 | builder.useKeyExpression(SyntaxFactory.makeStringLiteralExpr(routeInfo.viewControllerClassName, leadingTrivia: .spaces(4))) 162 | builder.useColon(SyntaxFactory.makeColonToken()) 163 | builder.useValueExpression(SyntaxFactory.makeClosureExpr( 164 | leftBrace: SyntaxFactory.makeLeftBraceToken(leadingTrivia: .spaces(1)), 165 | signature: SyntaxFactory.makeClosureSignature( 166 | capture: nil, 167 | input: SyntaxFactory.makeIdentifier("queryItems", leadingTrivia: .spaces(1), trailingTrivia: .spaces(1)), 168 | throwsTok: nil, 169 | output: nil, 170 | inTok: SyntaxFactory.makeInKeyword(trailingTrivia: .newlines(1)) 171 | ), 172 | statements: SyntaxFactory.makeCodeBlockItemList([ 173 | SyntaxFactory.makeCodeBlockItem(item: SyntaxFactory.makeIdentifier(result, leadingTrivia: .spaces(8)), semicolon: SyntaxFactory.makeIdentifier("", leadingTrivia: [.newlines(1), .spaces(4)]), errorTokens: nil) 174 | ]), 175 | rightBrace: SyntaxFactory.makeRightBraceToken()) 176 | ) 177 | builder.useTrailingComma(SyntaxFactory.makeCommaToken(trailingTrivia: Trivia.newlines(1))) 178 | } 179 | } 180 | 181 | var routesSourceFile = SyntaxFactory.makeBlankSourceFile() 182 | routesSourceFile = routesSourceFile.addCodeBlockItem(CodeBlockItemSyntax({ (builder) in 183 | builder.useItem(ImportDeclSyntax { (builder) in 184 | builder.useImportTok(SyntaxFactory.makeImportKeyword(trailingTrivia: .spaces(1))) 185 | builder.useImportKind(SyntaxFactory.makeIdentifier("UIKit", trailingTrivia: .newlines(2))) 186 | }) 187 | })) 188 | 189 | routesSourceFile = routesSourceFile.addCodeBlockItem(CodeBlockItemSyntax({ (builder) in 190 | let variableDeclSyntax = VariableDeclSyntax { (builder) in 191 | builder.useLetOrVarKeyword(SyntaxFactory.makeLetKeyword(trailingTrivia: .spaces(1))) 192 | builder.addPatternBinding(SyntaxFactory.makePatternBinding( 193 | pattern: IdentifierPatternSyntax({ (builder) in 194 | builder.useIdentifier(SyntaxFactory.makeIdentifier("routes")) 195 | }), 196 | typeAnnotation: SyntaxFactory.makeTypeAnnotation( 197 | colon: SyntaxFactory.makeColonToken(trailingTrivia: .spaces(1)), 198 | type: SyntaxFactory.makeTypeIdentifier("[String: ([URLQueryItem]) -> UIViewController]", trailingTrivia: .spaces(1)) 199 | ), 200 | initializer: InitializerClauseSyntax({ (builder) in 201 | builder.useEqual(SyntaxFactory.makeEqualToken(trailingTrivia: .spaces(1))) 202 | builder.useValue(SyntaxFactory.makeDictionaryExpr( 203 | leftSquare: SyntaxFactory.makeLeftSquareBracketToken(trailingTrivia: .newlines(1)), 204 | content: SyntaxFactory.makeDictionaryElementList(routesContentDictionaryElementList), 205 | rightSquare: SyntaxFactory.makeRightSquareBracketToken(trailingTrivia: .newlines(1)) 206 | )) 207 | }), 208 | accessor: nil, 209 | trailingComma: nil 210 | ) 211 | ) 212 | } 213 | builder.useItem(variableDeclSyntax) 214 | })) 215 | 216 | 217 | print(routesSourceFile) 218 | let routesSwiftOutputPath = URL(fileURLWithPath: arguments[2]) // $SRCROOT/routerbuilderapp/routerbuilder/routes.swift 219 | try! routesSourceFile.description.write(to: routesSwiftOutputPath, atomically: true, encoding: .utf8) 220 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import RouterBuilderTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += RouterBuilderTests.allTests() 7 | XCTMain(tests) -------------------------------------------------------------------------------- /Tests/RouterBuilderTests/RouterBuilderTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class RouterBuilderTests: 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("RouterBuilder") 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 | -------------------------------------------------------------------------------- /Tests/RouterBuilderTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !os(macOS) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(RouterBuilderTests.allTests), 7 | ] 8 | } 9 | #endif -------------------------------------------------------------------------------- /arguments-passed-on-launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/RouterBuilder/e7fba78e4fbb9f88f6b7afb67743013012ea9c90/arguments-passed-on-launch.png -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | swift build -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.14" -------------------------------------------------------------------------------- /generate-xcodeproj.sh: -------------------------------------------------------------------------------- 1 | swift package generate-xcodeproj --xcconfig-overrides ./main.xcconfig -------------------------------------------------------------------------------- /graffle.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/RouterBuilder/e7fba78e4fbb9f88f6b7afb67743013012ea9c90/graffle.graffle -------------------------------------------------------------------------------- /main.xcconfig: -------------------------------------------------------------------------------- 1 | MACOSX_DEPLOYMENT_TARGET = 10.14 -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 913B302322112A4B00FFFAAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913B302222112A4B00FFFAAD /* AppDelegate.swift */; }; 11 | 913B302522112A4B00FFFAAD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913B302422112A4B00FFFAAD /* ViewController.swift */; }; 12 | 913B302822112A4B00FFFAAD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 913B302622112A4B00FFFAAD /* Main.storyboard */; }; 13 | 913B302A22112A4C00FFFAAD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 913B302922112A4C00FFFAAD /* Assets.xcassets */; }; 14 | 913B302D22112A4C00FFFAAD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 913B302B22112A4C00FFFAAD /* LaunchScreen.storyboard */; }; 15 | 913B304F22114F5300FFFAAD /* routes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913B304E22114F5300FFFAAD /* routes.swift */; }; 16 | 913B305322114F6200FFFAAD /* ExampleFirstViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913B305122114F6200FFFAAD /* ExampleFirstViewController.swift */; }; 17 | 913B305422114F6200FFFAAD /* ExampleSecondViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913B305222114F6200FFFAAD /* ExampleSecondViewController.swift */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXFileReference section */ 21 | 913B301F22112A4B00FFFAAD /* routerbuilder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = routerbuilder.app; sourceTree = BUILT_PRODUCTS_DIR; }; 22 | 913B302222112A4B00FFFAAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 23 | 913B302422112A4B00FFFAAD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 913B302722112A4B00FFFAAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 913B302922112A4C00FFFAAD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 913B302C22112A4C00FFFAAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 913B302E22112A4C00FFFAAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 913B304E22114F5300FFFAAD /* routes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = routes.swift; sourceTree = ""; }; 29 | 913B305122114F6200FFFAAD /* ExampleFirstViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleFirstViewController.swift; sourceTree = ""; }; 30 | 913B305222114F6200FFFAAD /* ExampleSecondViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleSecondViewController.swift; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 913B301C22112A4B00FFFAAD /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 913B301622112A4B00FFFAAD = { 45 | isa = PBXGroup; 46 | children = ( 47 | 913B302122112A4B00FFFAAD /* routerbuilder */, 48 | 913B302022112A4B00FFFAAD /* Products */, 49 | ); 50 | sourceTree = ""; 51 | }; 52 | 913B302022112A4B00FFFAAD /* Products */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 913B301F22112A4B00FFFAAD /* routerbuilder.app */, 56 | ); 57 | name = Products; 58 | sourceTree = ""; 59 | }; 60 | 913B302122112A4B00FFFAAD /* routerbuilder */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 913B305022114F6200FFFAAD /* Resources */, 64 | 913B304E22114F5300FFFAAD /* routes.swift */, 65 | 913B302222112A4B00FFFAAD /* AppDelegate.swift */, 66 | 913B302422112A4B00FFFAAD /* ViewController.swift */, 67 | 913B302622112A4B00FFFAAD /* Main.storyboard */, 68 | 913B302922112A4C00FFFAAD /* Assets.xcassets */, 69 | 913B302B22112A4C00FFFAAD /* LaunchScreen.storyboard */, 70 | 913B302E22112A4C00FFFAAD /* Info.plist */, 71 | ); 72 | path = routerbuilder; 73 | sourceTree = ""; 74 | }; 75 | 913B305022114F6200FFFAAD /* Resources */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 913B305122114F6200FFFAAD /* ExampleFirstViewController.swift */, 79 | 913B305222114F6200FFFAAD /* ExampleSecondViewController.swift */, 80 | ); 81 | name = Resources; 82 | path = ../../Resources; 83 | sourceTree = ""; 84 | }; 85 | /* End PBXGroup section */ 86 | 87 | /* Begin PBXNativeTarget section */ 88 | 913B301E22112A4B00FFFAAD /* routerbuilder */ = { 89 | isa = PBXNativeTarget; 90 | buildConfigurationList = 913B303122112A4C00FFFAAD /* Build configuration list for PBXNativeTarget "routerbuilder" */; 91 | buildPhases = ( 92 | 913B301B22112A4B00FFFAAD /* Sources */, 93 | 913B301C22112A4B00FFFAAD /* Frameworks */, 94 | 913B301D22112A4B00FFFAAD /* Resources */, 95 | ); 96 | buildRules = ( 97 | ); 98 | dependencies = ( 99 | ); 100 | name = routerbuilder; 101 | productName = routerbuilder; 102 | productReference = 913B301F22112A4B00FFFAAD /* routerbuilder.app */; 103 | productType = "com.apple.product-type.application"; 104 | }; 105 | /* End PBXNativeTarget section */ 106 | 107 | /* Begin PBXProject section */ 108 | 913B301722112A4B00FFFAAD /* Project object */ = { 109 | isa = PBXProject; 110 | attributes = { 111 | LastSwiftUpdateCheck = 1010; 112 | LastUpgradeCheck = 1010; 113 | ORGANIZATIONNAME = DianQK; 114 | TargetAttributes = { 115 | 913B301E22112A4B00FFFAAD = { 116 | CreatedOnToolsVersion = 10.1; 117 | LastSwiftMigration = 1020; 118 | }; 119 | }; 120 | }; 121 | buildConfigurationList = 913B301A22112A4B00FFFAAD /* Build configuration list for PBXProject "routerbuilder" */; 122 | compatibilityVersion = "Xcode 9.3"; 123 | developmentRegion = en; 124 | hasScannedForEncodings = 0; 125 | knownRegions = ( 126 | en, 127 | Base, 128 | ); 129 | mainGroup = 913B301622112A4B00FFFAAD; 130 | productRefGroup = 913B302022112A4B00FFFAAD /* Products */; 131 | projectDirPath = ""; 132 | projectRoot = ""; 133 | targets = ( 134 | 913B301E22112A4B00FFFAAD /* routerbuilder */, 135 | ); 136 | }; 137 | /* End PBXProject section */ 138 | 139 | /* Begin PBXResourcesBuildPhase section */ 140 | 913B301D22112A4B00FFFAAD /* Resources */ = { 141 | isa = PBXResourcesBuildPhase; 142 | buildActionMask = 2147483647; 143 | files = ( 144 | 913B302D22112A4C00FFFAAD /* LaunchScreen.storyboard in Resources */, 145 | 913B302A22112A4C00FFFAAD /* Assets.xcassets in Resources */, 146 | 913B302822112A4B00FFFAAD /* Main.storyboard in Resources */, 147 | ); 148 | runOnlyForDeploymentPostprocessing = 0; 149 | }; 150 | /* End PBXResourcesBuildPhase section */ 151 | 152 | /* Begin PBXSourcesBuildPhase section */ 153 | 913B301B22112A4B00FFFAAD /* Sources */ = { 154 | isa = PBXSourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | 913B305322114F6200FFFAAD /* ExampleFirstViewController.swift in Sources */, 158 | 913B302522112A4B00FFFAAD /* ViewController.swift in Sources */, 159 | 913B302322112A4B00FFFAAD /* AppDelegate.swift in Sources */, 160 | 913B305422114F6200FFFAAD /* ExampleSecondViewController.swift in Sources */, 161 | 913B304F22114F5300FFFAAD /* routes.swift in Sources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXSourcesBuildPhase section */ 166 | 167 | /* Begin PBXVariantGroup section */ 168 | 913B302622112A4B00FFFAAD /* Main.storyboard */ = { 169 | isa = PBXVariantGroup; 170 | children = ( 171 | 913B302722112A4B00FFFAAD /* Base */, 172 | ); 173 | name = Main.storyboard; 174 | sourceTree = ""; 175 | }; 176 | 913B302B22112A4C00FFFAAD /* LaunchScreen.storyboard */ = { 177 | isa = PBXVariantGroup; 178 | children = ( 179 | 913B302C22112A4C00FFFAAD /* Base */, 180 | ); 181 | name = LaunchScreen.storyboard; 182 | sourceTree = ""; 183 | }; 184 | /* End PBXVariantGroup section */ 185 | 186 | /* Begin XCBuildConfiguration section */ 187 | 913B302F22112A4C00FFFAAD /* Debug */ = { 188 | isa = XCBuildConfiguration; 189 | buildSettings = { 190 | ALWAYS_SEARCH_USER_PATHS = NO; 191 | CLANG_ANALYZER_NONNULL = YES; 192 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 193 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 194 | CLANG_CXX_LIBRARY = "libc++"; 195 | CLANG_ENABLE_MODULES = YES; 196 | CLANG_ENABLE_OBJC_ARC = YES; 197 | CLANG_ENABLE_OBJC_WEAK = YES; 198 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 199 | CLANG_WARN_BOOL_CONVERSION = YES; 200 | CLANG_WARN_COMMA = YES; 201 | CLANG_WARN_CONSTANT_CONVERSION = YES; 202 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 203 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 204 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 205 | CLANG_WARN_EMPTY_BODY = YES; 206 | CLANG_WARN_ENUM_CONVERSION = YES; 207 | CLANG_WARN_INFINITE_RECURSION = YES; 208 | CLANG_WARN_INT_CONVERSION = YES; 209 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 210 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 211 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 214 | CLANG_WARN_STRICT_PROTOTYPES = YES; 215 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 216 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 217 | CLANG_WARN_UNREACHABLE_CODE = YES; 218 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 219 | CODE_SIGN_IDENTITY = "iPhone Developer"; 220 | COPY_PHASE_STRIP = NO; 221 | DEBUG_INFORMATION_FORMAT = dwarf; 222 | ENABLE_STRICT_OBJC_MSGSEND = YES; 223 | ENABLE_TESTABILITY = YES; 224 | GCC_C_LANGUAGE_STANDARD = gnu11; 225 | GCC_DYNAMIC_NO_PIC = NO; 226 | GCC_NO_COMMON_BLOCKS = YES; 227 | GCC_OPTIMIZATION_LEVEL = 0; 228 | GCC_PREPROCESSOR_DEFINITIONS = ( 229 | "DEBUG=1", 230 | "$(inherited)", 231 | ); 232 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 233 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 234 | GCC_WARN_UNDECLARED_SELECTOR = YES; 235 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 236 | GCC_WARN_UNUSED_FUNCTION = YES; 237 | GCC_WARN_UNUSED_VARIABLE = YES; 238 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 239 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 240 | MTL_FAST_MATH = YES; 241 | ONLY_ACTIVE_ARCH = YES; 242 | SDKROOT = iphoneos; 243 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 244 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 245 | }; 246 | name = Debug; 247 | }; 248 | 913B303022112A4C00FFFAAD /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_ANALYZER_NONNULL = YES; 253 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 254 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 255 | CLANG_CXX_LIBRARY = "libc++"; 256 | CLANG_ENABLE_MODULES = YES; 257 | CLANG_ENABLE_OBJC_ARC = YES; 258 | CLANG_ENABLE_OBJC_WEAK = YES; 259 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_COMMA = YES; 262 | CLANG_WARN_CONSTANT_CONVERSION = YES; 263 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 264 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 265 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 271 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | CODE_SIGN_IDENTITY = "iPhone Developer"; 281 | COPY_PHASE_STRIP = NO; 282 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 283 | ENABLE_NS_ASSERTIONS = NO; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu11; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 294 | MTL_ENABLE_DEBUG_INFO = NO; 295 | MTL_FAST_MATH = YES; 296 | SDKROOT = iphoneos; 297 | SWIFT_COMPILATION_MODE = wholemodule; 298 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 299 | VALIDATE_PRODUCT = YES; 300 | }; 301 | name = Release; 302 | }; 303 | 913B303222112A4C00FFFAAD /* Debug */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | CODE_SIGN_STYLE = Automatic; 308 | DEVELOPMENT_TEAM = HY634B3FW2; 309 | INFOPLIST_FILE = routerbuilder/Info.plist; 310 | LD_RUNPATH_SEARCH_PATHS = ( 311 | "$(inherited)", 312 | "@executable_path/Frameworks", 313 | ); 314 | PRODUCT_BUNDLE_IDENTIFIER = org.dianqk.routerbuilder; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | SWIFT_VERSION = 5.0; 317 | TARGETED_DEVICE_FAMILY = "1,2"; 318 | }; 319 | name = Debug; 320 | }; 321 | 913B303322112A4C00FFFAAD /* Release */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | CODE_SIGN_STYLE = Automatic; 326 | DEVELOPMENT_TEAM = HY634B3FW2; 327 | INFOPLIST_FILE = routerbuilder/Info.plist; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | ); 332 | PRODUCT_BUNDLE_IDENTIFIER = org.dianqk.routerbuilder; 333 | PRODUCT_NAME = "$(TARGET_NAME)"; 334 | SWIFT_VERSION = 5.0; 335 | TARGETED_DEVICE_FAMILY = "1,2"; 336 | }; 337 | name = Release; 338 | }; 339 | /* End XCBuildConfiguration section */ 340 | 341 | /* Begin XCConfigurationList section */ 342 | 913B301A22112A4B00FFFAAD /* Build configuration list for PBXProject "routerbuilder" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 913B302F22112A4C00FFFAAD /* Debug */, 346 | 913B303022112A4C00FFFAAD /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | 913B303122112A4C00FFFAAD /* Build configuration list for PBXNativeTarget "routerbuilder" */ = { 352 | isa = XCConfigurationList; 353 | buildConfigurations = ( 354 | 913B303222112A4C00FFFAAD /* Debug */, 355 | 913B303322112A4C00FFFAAD /* Release */, 356 | ); 357 | defaultConfigurationIsVisible = 0; 358 | defaultConfigurationName = Release; 359 | }; 360 | /* End XCConfigurationList section */ 361 | }; 362 | rootObject = 913B301722112A4B00FFFAAD /* Project object */; 363 | } 364 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder.xcodeproj/xcshareddata/xcschemes/routerbuilder.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // routerbuilder 4 | // 5 | // Created by DianQK on 2019/2/11. 6 | // Copyright © 2019 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 17 | guard 18 | let viewControllerClassName = url.host, 19 | let components = URLComponents(url: url, resolvingAgainstBaseURL: true), 20 | let queryItems = components.queryItems, 21 | let viewController = routes[viewControllerClassName]?(queryItems) else { // TODO: 参数校验 22 | return false 23 | } 24 | window?.rootViewController = viewController 25 | return true 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleTypeRole 23 | Viewer 24 | CFBundleURLName 25 | routerbuilder 26 | CFBundleURLSchemes 27 | 28 | routerbuilder 29 | 30 | 31 | 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // routerbuilder 4 | // 5 | // Created by DianQK on 2019/2/11. 6 | // Copyright © 2019 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /routerbuilderapp/routerbuilder/routes.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | let routes: [String: ([URLQueryItem]) -> UIViewController] = [ 4 | "ExampleFirstViewController": { queryItems in 5 | return ExampleFirstViewController( 6 | a: Int(queryItems.first(where: { $0.name == "a" })!.value!)!, 7 | b: queryItems.first(where: { $0.name == "b" })!.value!, 8 | c: queryItems.first(where: { $0.name == "c" })?.value, 9 | d: Float(queryItems.first(where: { $0.name == "d" })!.value!)! 10 | ) 11 | }, 12 | "ExampleSecondViewController": { queryItems in 13 | return ExampleSecondViewController( 14 | a: Int(queryItems.first(where: { $0.name == "a" })!.value!)!, 15 | c: queryItems.first(where: { $0.name == "c" })?.value, 16 | g: queryItems.first(where: { $0.name == "g" })?.value.flatMap(Int.init) 17 | ) 18 | }, 19 | ] 20 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/RouterBuilder/e7fba78e4fbb9f88f6b7afb67743013012ea9c90/screenshot.png -------------------------------------------------------------------------------- /web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 1 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | schema.graphql 3 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | env: { 5 | node: true 6 | }, 7 | 8 | 'extends': [ 9 | 'plugin:vue/essential', 10 | '@vue/standard' 11 | ], 12 | 13 | rules: { 14 | 'no-console': 'off', 15 | 'no-debugger': 'off' 16 | // 'graphql/template-strings': [ 17 | // 'error', 18 | // { 19 | // env: 'literal', 20 | // projectName: 'app' 21 | // } 22 | // ] 23 | }, 24 | 25 | parserOptions: { 26 | parser: 'babel-eslint' 27 | }, 28 | 29 | plugins: [ 30 | 'graphql' 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | 23 | /live/ 24 | -------------------------------------------------------------------------------- /web/.graphqlconfig.yml: -------------------------------------------------------------------------------- 1 | projects: 2 | app: 3 | schemaPath: apollo-server/schema.graphql 4 | includes: 5 | - '**/*.gql' 6 | extensions: 7 | endpoints: 8 | default: 'http://localhost:4000/graphql' 9 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Happy-Hacking-Web 2 | 3 | > 为 Web 提供与本地交互功能,打开思路,这个可以做很多事情。 4 | 5 | - 启动 apollo,`yarn apollo:run` 6 | - 打开 https://happy-hacking-web.netlify.com 7 | -------------------------------------------------------------------------------- /web/apollo-server/channels.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DEVICEDS_CHANGED: 'devices_changed' 3 | } 4 | -------------------------------------------------------------------------------- /web/apollo-server/connectors/devices.js: -------------------------------------------------------------------------------- 1 | import execa from 'execa' 2 | import channels from '../channels' 3 | const simctl = require('node-simctl') 4 | import pubsub from '../pubsub' 5 | 6 | const concat = (x, y) => x.concat(y) 7 | const flatMap = (f, xs) => xs.map(f).reduce(concat, []) 8 | 9 | async function getDevices () { 10 | let devices = await simctl.getDevices() 11 | devices = Object.values(devices) 12 | if (devices.length === 0) { 13 | console.log('无可用模拟器') 14 | // TODO: 增加安装模拟器过程 15 | return [] 16 | } 17 | devices = flatMap(v => v, devices).sort(l => l.state === 'Shutdown') 18 | return devices 19 | } 20 | 21 | async function boot (udid, context) { 22 | await simctl.bootDevice(udid) 23 | execa.shellSync(`open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app`) 24 | // execa.shellSync(`open -n /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app --args -CurrentDeviceUDID ${udid}`) 25 | // try { 26 | // execa.shellSync(`xcrun instruments -w ${udid}`) // workaround: 这是一个可以 100% 启动对应设备模拟器的方案 27 | // } catch (error) { 28 | // } 29 | // let devices = await getDevices() 30 | // context.pubsub.publish(channels.DEVICEDS_CHANGED, { 31 | // devices 32 | // }) 33 | } 34 | 35 | async function shutdown (udid, context) { 36 | await simctl.shutdown(udid) 37 | } 38 | 39 | async function openUrl (udid, url) { 40 | await simctl.openUrl(udid, url) 41 | execa.shellSync(`open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app`) 42 | } 43 | 44 | const timeout = ms => new Promise(res => setTimeout(res, ms)) 45 | 46 | async function loopDetectDevicesState (preDevices = undefined) { 47 | let devices = await getDevices() 48 | if (JSON.stringify(preDevices) !== JSON.stringify(devices)) { 49 | pubsub.publish(channels.DEVICEDS_CHANGED, { 50 | devicesChanged: devices 51 | }) 52 | } 53 | await timeout(1000) 54 | loopDetectDevicesState(devices) 55 | } 56 | 57 | loopDetectDevicesState() 58 | 59 | export default { 60 | boot, 61 | getDevices, 62 | shutdown, 63 | openUrl 64 | } 65 | -------------------------------------------------------------------------------- /web/apollo-server/context.js: -------------------------------------------------------------------------------- 1 | import pubsub from './pubsub' 2 | 3 | // Context passed to all resolvers (third argument) 4 | // req => Query 5 | // connection => Subscription 6 | // eslint-disable-next-line no-unused-vars 7 | export default ({ req, connection }) => { 8 | return { 9 | pubsub 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web/apollo-server/data-sources.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return {} 3 | } 4 | -------------------------------------------------------------------------------- /web/apollo-server/directives.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // Schema directives 3 | // https://www.apollographql.com/docs/graphql-tools/schema-directives.html 4 | } 5 | -------------------------------------------------------------------------------- /web/apollo-server/mocks.js: -------------------------------------------------------------------------------- 1 | // Enable mocking in vue.config.js with `"pluginOptions": { "enableMocks": true }` 2 | // Customize mocking: https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks 3 | export default { 4 | // Mock resolvers here 5 | } 6 | -------------------------------------------------------------------------------- /web/apollo-server/pubsub.js: -------------------------------------------------------------------------------- 1 | import { PubSub } from 'graphql-subscriptions' 2 | 3 | const pubsub = new PubSub() 4 | pubsub.ee.setMaxListeners(0) 5 | 6 | export default pubsub 7 | -------------------------------------------------------------------------------- /web/apollo-server/resolvers.js: -------------------------------------------------------------------------------- 1 | import GraphQLJSON from 'graphql-type-json' 2 | import shortid from 'shortid' 3 | import merge from 'lodash.merge' 4 | import globby from 'globby' 5 | import path from 'path' 6 | 7 | const resolvers = [{ 8 | JSON: GraphQLJSON, 9 | DescribedEntity: { 10 | __resolveType (obj, context, info) { 11 | return null 12 | } 13 | } 14 | }] 15 | 16 | // Load resolvers in './schema' 17 | const paths = globby.sync([path.join(__dirname, './schema/*.js')]) 18 | paths.forEach(file => { 19 | const { resolvers: r } = require(file) 20 | r && resolvers.push(r) 21 | }) 22 | 23 | export default merge.apply(null, resolvers) -------------------------------------------------------------------------------- /web/apollo-server/schema/devices.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | // Subs 3 | import { withFilter } from 'graphql-subscriptions' 4 | import channels from '../channels' 5 | import devices from '../connectors/devices' 6 | 7 | export const types = gql` 8 | extend type Query { 9 | devices: [Device] 10 | } 11 | 12 | extend type Mutation { 13 | deviceBoot (udid: String!): Device 14 | deviceShutdown (udid: String!): Device 15 | openUrl (udid: String!, url: String!): Boolean 16 | } 17 | 18 | extend type Subscription { 19 | devicesChanged: [Device] 20 | } 21 | 22 | type Device { 23 | name: String! 24 | udid: String! 25 | state: String! 26 | } 27 | ` 28 | 29 | export const resolvers = { 30 | Query: { 31 | devices: async () => await devices.getDevices(), 32 | }, 33 | Mutation: { 34 | deviceBoot: (root, { udid }, context) => devices.boot(udid, context), 35 | deviceShutdown: (root, { udid }, context) => devices.shutdown(udid, context), 36 | openUrl: (root, { udid, url }, context) => devices.openUrl(udid, url) 37 | }, 38 | Subscription: { 39 | devicesChanged: { 40 | subscribe: withFilter( 41 | (parent, args, { pubsub }) => pubsub.asyncIterator(channels.DEVICEDS_CHANGED), 42 | (payload, vars) => true 43 | ) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /web/apollo-server/server.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import express from 'express' 3 | 4 | export default app => { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /web/apollo-server/type-defs.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag' 2 | import path from 'path' 3 | import globby from 'globby' 4 | 5 | const typeDefs = [gql` 6 | scalar JSON 7 | 8 | enum PackageManager { 9 | npm 10 | yarn 11 | } 12 | 13 | interface DescribedEntity { 14 | name: String 15 | description: String 16 | link: String 17 | } 18 | 19 | type Version { 20 | current: String 21 | latest: String 22 | wanted: String 23 | range: String 24 | localPath: String 25 | } 26 | 27 | type GitHubStats { 28 | stars: Int 29 | } 30 | 31 | type Progress { 32 | id: ID! 33 | status: String 34 | info: String 35 | error: String 36 | # Progress from 0 to 1 (-1 means disabled) 37 | progress: Float 38 | args: [String] 39 | } 40 | 41 | input OpenInEditorInput { 42 | file: String! 43 | line: Int 44 | column: Int 45 | gitPath: Boolean 46 | } 47 | 48 | type ClientAddon { 49 | id: ID! 50 | url: String! 51 | } 52 | 53 | type SharedData { 54 | id: ID! 55 | value: JSON 56 | } 57 | 58 | type Locale { 59 | lang: String! 60 | strings: JSON! 61 | } 62 | 63 | type Query { 64 | progress (id: ID!): Progress 65 | cwd: String! 66 | clientAddons: [ClientAddon] 67 | sharedData (id: ID!, projectId: ID!): SharedData 68 | locales: [Locale] 69 | } 70 | 71 | type Mutation { 72 | fileOpenInEditor (input: OpenInEditorInput!): Boolean 73 | sharedDataUpdate (id: ID!, projectId: ID!, value: JSON!): SharedData 74 | } 75 | 76 | type Subscription { 77 | progressChanged (id: ID!): Progress 78 | progressRemoved (id: ID!): ID 79 | cwdChanged: String! 80 | clientAddonAdded: ClientAddon 81 | sharedDataUpdated (id: ID!, projectId: ID!): SharedData 82 | localeAdded: Locale 83 | routeRequested: JSON! 84 | } 85 | `] 86 | 87 | // Load types in './schema' 88 | const paths = globby.sync([path.join(__dirname, './schema/*.js')]) 89 | paths.forEach(file => { 90 | const { types } = require(file) 91 | types && typeDefs.push(types) 92 | }) 93 | 94 | export default typeDefs 95 | -------------------------------------------------------------------------------- /web/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happy-web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build --modern", 8 | "lint": "vue-cli-service lint", 9 | "apollo": "vue-cli-service apollo:watch", 10 | "apollo:run": "vue-cli-service apollo:run" 11 | }, 12 | "dependencies": { 13 | "@vue/ui": "^0.5.6", 14 | "graphql-type-json": "^0.2.1", 15 | "lowdb": "^1.0.0", 16 | "mkdirp": "^0.5.1", 17 | "shortid": "^2.2.8", 18 | "vue": "^2.5.22", 19 | "vue-apollo": "^3.0.0-beta.11" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli": "^3.4.0", 23 | "@vue/cli-plugin-babel": "^3.4.0", 24 | "@vue/cli-plugin-eslint": "^3.4.0", 25 | "@vue/cli-service": "^3.4.0", 26 | "@vue/eslint-config-standard": "^4.0.0", 27 | "babel-eslint": "^10.0.1", 28 | "eslint": "^5.13.0", 29 | "eslint-plugin-graphql": "^3.0.3", 30 | "eslint-plugin-vue": "^5.0.0", 31 | "globby": "^9.0.0", 32 | "graphql-tag": "^2.9.0", 33 | "node-simctl": "^4.0.1", 34 | "stylus": "^0.54.5", 35 | "stylus-loader": "^3.0.2", 36 | "vue-cli-plugin-apollo": "^0.19.1", 37 | "vue-template-compiler": "^2.5.21", 38 | "xterm": "^3.11.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/RouterBuilder/e7fba78e4fbb9f88f6b7afb67743013012ea9c90/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | happy-web 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /web/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /web/src/appRoutes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "initializerFunctionParameterList" : [ 4 | { 5 | "value" : "", 6 | "name" : "a", 7 | "type" : "Int", 8 | "required" : true 9 | }, 10 | { 11 | "value" : "", 12 | "name" : "b", 13 | "type" : "String", 14 | "required" : true 15 | }, 16 | { 17 | "value" : "", 18 | "name" : "c", 19 | "type" : "String", 20 | "required" : false 21 | }, 22 | { 23 | "value" : "", 24 | "name" : "d", 25 | "type" : "Float", 26 | "required" : true 27 | } 28 | ], 29 | "viewControllerClassName" : "ExampleFirstViewController" 30 | }, 31 | { 32 | "initializerFunctionParameterList" : [ 33 | { 34 | "value" : "", 35 | "name" : "a", 36 | "type" : "Int", 37 | "required" : true 38 | }, 39 | { 40 | "value" : "", 41 | "name" : "c", 42 | "type" : "String", 43 | "required" : false 44 | }, 45 | { 46 | "value" : "", 47 | "name" : "g", 48 | "type" : "Int", 49 | "required" : false 50 | } 51 | ], 52 | "viewControllerClassName" : "ExampleSecondViewController" 53 | } 54 | ] -------------------------------------------------------------------------------- /web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/RouterBuilder/e7fba78e4fbb9f88f6b7afb67743013012ea9c90/web/src/assets/logo.png -------------------------------------------------------------------------------- /web/src/components/DeviceManager.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 55 | 56 | 68 | 69 | 117 | -------------------------------------------------------------------------------- /web/src/components/RouteForm.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 83 | 84 | 89 | -------------------------------------------------------------------------------- /web/src/graphql/deviceBoot.gql: -------------------------------------------------------------------------------- 1 | #import "./deviceFragment.gql" 2 | 3 | mutation deviceBoot ($udid: String!) { 4 | deviceBoot (udid: $udid) { 5 | ...device 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/graphql/deviceFragment.gql: -------------------------------------------------------------------------------- 1 | fragment device on Device { 2 | name 3 | udid 4 | state 5 | } 6 | -------------------------------------------------------------------------------- /web/src/graphql/deviceShutdown.gql: -------------------------------------------------------------------------------- 1 | #import "./deviceFragment.gql" 2 | 3 | mutation deviceShutdown ($udid: String!) { 4 | deviceShutdown (udid: $udid) { 5 | ...device 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/graphql/devices.gql: -------------------------------------------------------------------------------- 1 | #import "./deviceFragment.gql" 2 | 3 | query devices { 4 | devices { 5 | ...device 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/graphql/devicesChanged.gql: -------------------------------------------------------------------------------- 1 | #import "./deviceFragment.gql" 2 | 3 | subscription devicesChanged { 4 | devicesChanged { 5 | ...device 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/graphql/openUrl.gql: -------------------------------------------------------------------------------- 1 | mutation openUrl ($udid: String!, $url: String!) { 2 | openUrl (udid: $udid, url: $url) 3 | } 4 | -------------------------------------------------------------------------------- /web/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './register-components' 4 | import './plugins' 5 | import { createProvider } from './vue-apollo' 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | apolloProvider: createProvider(), 11 | render: h => h(App) 12 | }).$mount('#app') 13 | -------------------------------------------------------------------------------- /web/src/plugins.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueUi from '@vue/ui' 3 | import '@vue/ui/dist/vue-ui.css' 4 | 5 | Vue.use(VueUi) 6 | -------------------------------------------------------------------------------- /web/src/register-components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We register all the components so future cli-ui plugins 3 | * could use them directly 4 | */ 5 | 6 | import Vue from 'vue' 7 | 8 | // https://webpack.js.org/guides/dependency-management/#require-context 9 | const requireComponent = require.context('./components', true, /[a-z0-9]+\.(jsx?|vue)$/i) 10 | 11 | // For each matching file name... 12 | requireComponent.keys().forEach(fileName => { 13 | const componentConfig = requireComponent(fileName) 14 | const componentName = fileName 15 | .substr(fileName.lastIndexOf('/') + 1) 16 | // Remove the file extension from the end 17 | .replace(/\.\w+$/, '') 18 | // Globally register the component 19 | Vue.component(componentName, componentConfig.default || componentConfig) 20 | }) 21 | -------------------------------------------------------------------------------- /web/src/vue-apollo.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueApollo from 'vue-apollo' 3 | import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client' 4 | 5 | // Install the vue plugin 6 | Vue.use(VueApollo) 7 | 8 | // Name of the localStorage item 9 | const AUTH_TOKEN = 'apollo-token' 10 | 11 | // Http endpoint 12 | const wsEndpoint = process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql' 13 | 14 | // Config 15 | const defaultOptions = { 16 | // You can use `https` for secure connection (recommended in production) 17 | // httpEndpoint, 18 | // You can use `wss` for secure connection (recommended in production) 19 | // Use `null` to disable subscriptions 20 | wsEndpoint, 21 | // LocalStorage token 22 | tokenName: AUTH_TOKEN, 23 | // Enable Automatic Query persisting with Apollo Engine 24 | persisting: false, 25 | // Use websockets for everything (no HTTP) 26 | // You need to pass a `wsEndpoint` for this to work 27 | websocketsOnly: true, 28 | // Is being rendered on the server? 29 | ssr: false 30 | 31 | // Override default apollo link 32 | // note: don't override httpLink here, specify httpLink options in the 33 | // httpLinkOptions property of defaultOptions. 34 | // link: myLink 35 | 36 | // Override default cache 37 | // cache: myCache 38 | 39 | // Override the way the Authorization header is set 40 | // getAuth: (tokenName) => ... 41 | 42 | // Additional ApolloClient options 43 | // apollo: { ... } 44 | 45 | // Client local data (see apollo-link-state) 46 | // clientState: { resolvers: { ... }, defaults: { ... } } 47 | } 48 | 49 | // Call this in the Vue app file 50 | export function createProvider (options = {}) { 51 | // Create apollo client 52 | const { apolloClient, wsClient } = createApolloClient({ 53 | ...defaultOptions, 54 | ...options 55 | }) 56 | apolloClient.wsClient = wsClient 57 | 58 | // Create vue apollo provider 59 | const apolloProvider = new VueApollo({ 60 | defaultClient: apolloClient, 61 | defaultOptions: { 62 | $query: { 63 | fetchPolicy: 'cache-and-network', 64 | errorPolicy: 'all' 65 | } 66 | }, 67 | errorHandler (error) { 68 | // eslint-disable-next-line no-console 69 | console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message) 70 | } 71 | }) 72 | 73 | return apolloProvider 74 | } 75 | 76 | // Manually call this when user log in 77 | export async function onLogin (apolloClient, token) { 78 | if (typeof localStorage !== 'undefined' && token) { 79 | localStorage.setItem(AUTH_TOKEN, token) 80 | } 81 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient) 82 | try { 83 | await apolloClient.resetStore() 84 | } catch (e) { 85 | // eslint-disable-next-line no-console 86 | console.log('%cError on cache reset (login)', 'color: orange;', e.message) 87 | } 88 | } 89 | 90 | // Manually call this when user log out 91 | export async function onLogout (apolloClient) { 92 | if (typeof localStorage !== 'undefined') { 93 | localStorage.removeItem(AUTH_TOKEN) 94 | } 95 | if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient) 96 | try { 97 | await apolloClient.resetStore() 98 | } catch (e) { 99 | // eslint-disable-next-line no-console 100 | console.log('%cError on cache reset (logout)', 'color: orange;', e.message) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /web/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pluginOptions: { 3 | apollo: { 4 | enableMocks: true, 5 | enableEngine: true 6 | } 7 | }, 8 | configureWebpack: { 9 | resolve: { 10 | symlinks: false 11 | } 12 | } 13 | } 14 | --------------------------------------------------------------------------------