From a47a8af2d52a29c0225d62ebca8daf495ec9abbd Mon Sep 17 00:00:00 2001 From: Zach FettersMoore <4425109+BobaFetters@users.noreply.github.com> Date: Wed, 26 Jul 2023 11:21:05 -0400 Subject: [PATCH] feature: Adding CLI command for Operation Manifest (#3152) --- Apollo.xcodeproj/project.pbxproj | 16 +- Sources/ApolloCodegenLib/ApolloCodegen.swift | 31 ++ Sources/CodegenCLI/Commands/Generate.swift | 40 +-- .../Commands/GenerateOperationManifest.swift | 61 ++++ .../Extensions/ParsableCommand+Apollo.swift | 45 +++ .../ParsableCommand+RootOutputURL.swift | 12 - .../OptionGroups/InputOptions.swift | 6 + .../Protocols/CodegenProvider.swift | 6 + Sources/apollo-ios-cli/README.md | 17 + Sources/apollo-ios-cli/main.swift | 1 + .../GenerateOperationManifestTests.swift | 319 ++++++++++++++++++ .../Support/MockApolloCodegen.swift | 16 + .../MockApolloCodegenConfiguration.swift | 6 +- docs/source/code-generation/codegen-cli.mdx | 35 +- 14 files changed, 551 insertions(+), 60 deletions(-) create mode 100644 Sources/CodegenCLI/Commands/GenerateOperationManifest.swift create mode 100644 Sources/CodegenCLI/Extensions/ParsableCommand+Apollo.swift delete mode 100644 Sources/CodegenCLI/Extensions/ParsableCommand+RootOutputURL.swift create mode 100644 Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift diff --git a/Apollo.xcodeproj/project.pbxproj b/Apollo.xcodeproj/project.pbxproj index 071aaee3c..783a72378 100644 --- a/Apollo.xcodeproj/project.pbxproj +++ b/Apollo.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 54DDB0921EA045870009DD99 /* InMemoryNormalizedCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */; }; 5AC6CA4322AAF7B200B7C94D /* GraphQLHTTPMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */; }; 5BB2C0232380836100774170 /* VersionNumberTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BB2C0222380836100774170 /* VersionNumberTests.swift */; }; + 662EA65E2A701483008A1931 /* GenerateOperationManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662EA65D2A701483008A1931 /* GenerateOperationManifest.swift */; }; + 662EA6602A705BD7008A1931 /* GenerateOperationManifestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662EA65F2A705BD7008A1931 /* GenerateOperationManifestTests.swift */; }; 66321AE72A126C4400CC35CB /* IR+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66321AE62A126C4400CC35CB /* IR+Formatting.swift */; }; 66B18E872A15367300525DFB /* URLSessionClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B4F4542244A2AD300C2CF7D /* URLSessionClientTests.swift */; }; 66B18E902A16BF9B00525DFB /* MockURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66B18E8E2A16BF9B00525DFB /* MockURLProtocol.swift */; }; @@ -769,7 +771,7 @@ E687B3DC28B398E600A9551C /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3CA28B398E600A9551C /* Constants.swift */; }; E687B3DD28B398E600A9551C /* InputOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3CC28B398E600A9551C /* InputOptions.swift */; }; E687B3DE28B398E600A9551C /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3CD28B398E600A9551C /* Error.swift */; }; - E687B3DF28B398E600A9551C /* ParsableCommand+RootOutputURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3CF28B398E600A9551C /* ParsableCommand+RootOutputURL.swift */; }; + E687B3DF28B398E600A9551C /* ParsableCommand+Apollo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3CF28B398E600A9551C /* ParsableCommand+Apollo.swift */; }; E687B3E028B398E600A9551C /* FileManager+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3D028B398E600A9551C /* FileManager+Data.swift */; }; E687B3E128B398E600A9551C /* String+Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3D128B398E600A9551C /* String+Data.swift */; }; E687B3E328B398E600A9551C /* FetchSchema.swift in Sources */ = {isa = PBXBuildFile; fileRef = E687B3D428B398E600A9551C /* FetchSchema.swift */; }; @@ -1138,6 +1140,8 @@ 54DDB0911EA045870009DD99 /* InMemoryNormalizedCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InMemoryNormalizedCache.swift; sourceTree = ""; }; 5AC6CA4222AAF7B200B7C94D /* GraphQLHTTPMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLHTTPMethod.swift; sourceTree = ""; }; 5BB2C0222380836100774170 /* VersionNumberTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionNumberTests.swift; sourceTree = ""; }; + 662EA65D2A701483008A1931 /* GenerateOperationManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateOperationManifest.swift; sourceTree = ""; }; + 662EA65F2A705BD7008A1931 /* GenerateOperationManifestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateOperationManifestTests.swift; sourceTree = ""; }; 66321AE62A126C4400CC35CB /* IR+Formatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IR+Formatting.swift"; sourceTree = ""; }; 66B18E8E2A16BF9B00525DFB /* MockURLProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockURLProtocol.swift; sourceTree = ""; }; 90690D05224333DA00FC2E54 /* Apollo-Project-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apollo-Project-Debug.xcconfig"; sourceTree = ""; }; @@ -1957,7 +1961,7 @@ E687B3CA28B398E600A9551C /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; E687B3CC28B398E600A9551C /* InputOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputOptions.swift; sourceTree = ""; }; E687B3CD28B398E600A9551C /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; - E687B3CF28B398E600A9551C /* ParsableCommand+RootOutputURL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParsableCommand+RootOutputURL.swift"; sourceTree = ""; }; + E687B3CF28B398E600A9551C /* ParsableCommand+Apollo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ParsableCommand+Apollo.swift"; sourceTree = ""; }; E687B3D028B398E600A9551C /* FileManager+Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "FileManager+Data.swift"; sourceTree = ""; }; E687B3D128B398E600A9551C /* String+Data.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Data.swift"; sourceTree = ""; }; E687B3D428B398E600A9551C /* FetchSchema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchSchema.swift; sourceTree = ""; }; @@ -4046,7 +4050,7 @@ E687B3CE28B398E600A9551C /* Extensions */ = { isa = PBXGroup; children = ( - E687B3CF28B398E600A9551C /* ParsableCommand+RootOutputURL.swift */, + E687B3CF28B398E600A9551C /* ParsableCommand+Apollo.swift */, DE2A20E729032E1F008ADE48 /* VersionChecker.swift */, E687B3D028B398E600A9551C /* FileManager+Data.swift */, E687B3D128B398E600A9551C /* String+Data.swift */, @@ -4060,6 +4064,7 @@ E687B3D428B398E600A9551C /* FetchSchema.swift */, E687B3D528B398E600A9551C /* Initialize.swift */, E687B3D628B398E600A9551C /* Generate.swift */, + 662EA65D2A701483008A1931 /* GenerateOperationManifest.swift */, ); path = Commands; sourceTree = ""; @@ -4113,6 +4118,7 @@ E6DC0AD228B3AC490064A68F /* GenerateTests.swift */, DE2A20EC29033ABE008ADE48 /* VersionCheckerTests.swift */, E6DC0AD328B3AC490064A68F /* FetchSchemaTests.swift */, + 662EA65F2A705BD7008A1931 /* GenerateOperationManifestTests.swift */, ); path = Commands; sourceTree = ""; @@ -5833,10 +5839,11 @@ E687B3E528B398E600A9551C /* Generate.swift in Sources */, E687B3DE28B398E600A9551C /* Error.swift in Sources */, DE2A20E829032E1F008ADE48 /* VersionChecker.swift in Sources */, - E687B3DF28B398E600A9551C /* ParsableCommand+RootOutputURL.swift in Sources */, + E687B3DF28B398E600A9551C /* ParsableCommand+Apollo.swift in Sources */, E687B3E028B398E600A9551C /* FileManager+Data.swift in Sources */, E687B3E828B398E600A9551C /* SchemaDownloadProvider.swift in Sources */, E64F226D28B8B3FE0011292F /* LogLevelSetter.swift in Sources */, + 662EA65E2A701483008A1931 /* GenerateOperationManifest.swift in Sources */, E687B3E428B398E600A9551C /* Initialize.swift in Sources */, E687B3E128B398E600A9551C /* String+Data.swift in Sources */, E687B3DD28B398E600A9551C /* InputOptions.swift in Sources */, @@ -5853,6 +5860,7 @@ E64F227128B8BEE10011292F /* MockLogLevelSetter.swift in Sources */, E6DC0ADA28B3AC490064A68F /* MockApolloSchemaDownloader.swift in Sources */, E6DC0AD928B3AC490064A68F /* TestSupport.swift in Sources */, + 662EA6602A705BD7008A1931 /* GenerateOperationManifestTests.swift in Sources */, E6DC0AD728B3AC490064A68F /* MockApolloCodegen.swift in Sources */, E6DC0ADC28B3AC490064A68F /* InitializeTests.swift in Sources */, E6DC0AD828B3AC490064A68F /* MockApolloCodegenConfiguration.swift in Sources */, diff --git a/Sources/ApolloCodegenLib/ApolloCodegen.swift b/Sources/ApolloCodegenLib/ApolloCodegen.swift index 998772d96..93e0f2542 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegen.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegen.swift @@ -124,6 +124,37 @@ public class ApolloCodegen { ) } } + + public static func generateOperationManifest( + with configuration: ApolloCodegenConfiguration, + withRootURL rootURL: URL? = nil, + fileManager: ApolloFileManager = .default + ) throws { + let configContext = ConfigurationContext( + config: configuration, + rootURL: rootURL + ) + + try validate(configContext) + + let compilationResult = try compileGraphQLResult( + configContext, + experimentalFeatures: configuration.experimentalFeatures + ) + + try validate(configContext, with: compilationResult) + + let ir = IR(compilationResult: compilationResult) + + var operationIDsFileGenerator = OperationManifestFileGenerator(config: configContext) + + for operation in compilationResult.operations { + let irOperation = ir.build(operation: operation) + operationIDsFileGenerator?.collectOperationIdentifier(irOperation) + } + + try operationIDsFileGenerator?.generate(fileManager: fileManager) + } // MARK: Internal diff --git a/Sources/CodegenCLI/Commands/Generate.swift b/Sources/CodegenCLI/Commands/Generate.swift index b4184050e..d6006c19f 100644 --- a/Sources/CodegenCLI/Commands/Generate.swift +++ b/Sources/CodegenCLI/Commands/Generate.swift @@ -18,12 +18,6 @@ public struct Generate: ParsableCommand { ) var fetchSchema: Bool = false - @Flag( - name: .long, - help: "Ignore Apollo version mismatch errors. Warning: This may lead to incompatible generated objects." - ) - var ignoreVersionMismatch: Bool = false - // MARK: - Implementation public init() { } @@ -40,7 +34,9 @@ public struct Generate: ParsableCommand { ) throws { logger.SetLoggingLevel(verbose: inputs.verbose) - try checkForCLIVersionMismatch() + try checkForCLIVersionMismatch( + with: inputs + ) switch (inputs.string, inputs.path) { case let (.some(string), _): @@ -60,36 +56,6 @@ public struct Generate: ParsableCommand { } } - private func checkForCLIVersionMismatch() throws { - if case let .versionMismatch(cliVersion, apolloVersion) = - try VersionChecker.matchCLIVersionToApolloVersion(projectRootURL: rootOutputURL(for: inputs)) { - let errorMessage = """ - Apollo Version Mismatch - We've detected that the version of the Apollo Codegen CLI does not match the version of the - Apollo library used in your project. This may lead to incompatible generated objects. - - Please update your version of the Codegen CLI by following the instructions at: - https://www.apollographql.com/docs/ios/code-generation/codegen-cli/#installation - - CLI version: \(cliVersion) - Apollo version: \(apolloVersion) - """ - - if ignoreVersionMismatch { - print(""" - Warning: \(errorMessage) - """) - } else { - - throw Error(errorDescription: """ - Error: \(errorMessage) - - To ignore this error during code generation, use the argument: --ignore-version-mismatch. - """) - } - } - } - private func generate( data: Data, codegenProvider: CodegenProvider.Type, diff --git a/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift b/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift new file mode 100644 index 000000000..faef37325 --- /dev/null +++ b/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift @@ -0,0 +1,61 @@ +import Foundation +import ArgumentParser +import ApolloCodegenLib + +public struct GenerateOperationManifest: ParsableCommand { + + // MARK: - Configuration + + public static var configuration = CommandConfiguration( + abstract: "Generate Persisted Queries operation manifest based on a code generation configuration." + ) + + @OptionGroup var inputs: InputOptions + + // MARK: - Implementation + + public init() { } + + public func run() throws { + try _run() + } + + func _run( + fileManager: FileManager = .default, + codegenProvider: CodegenProvider.Type = ApolloCodegen.self, + logger: LogLevelSetter.Type = CodegenLogger.self + ) throws { + logger.SetLoggingLevel(verbose: inputs.verbose) + + try checkForCLIVersionMismatch( + with: inputs + ) + + switch (inputs.string, inputs.path) { + case let (.some(string), _): + try generateManifest( + data: try string.asData(), + codegenProvider: codegenProvider + ) + case let (nil, path): + try generateManifest( + data: try fileManager.unwrappedContents(atPath: path), + codegenProvider: codegenProvider + ) + } + } + + private func generateManifest( + data: Data, + codegenProvider: CodegenProvider.Type + ) throws { + let configuration = try JSONDecoder().decode(ApolloCodegenConfiguration.self, from: data) + + try codegenProvider.generateOperationManifest( + with: configuration, + withRootURL: rootOutputURL(for: inputs), + fileManager: .default + ) + } + +} diff --git a/Sources/CodegenCLI/Extensions/ParsableCommand+Apollo.swift b/Sources/CodegenCLI/Extensions/ParsableCommand+Apollo.swift new file mode 100644 index 000000000..518c36511 --- /dev/null +++ b/Sources/CodegenCLI/Extensions/ParsableCommand+Apollo.swift @@ -0,0 +1,45 @@ +import Foundation +import ArgumentParser +import ApolloCodegenLib + +extension ParsableCommand { + func rootOutputURL(for inputOptions: InputOptions) -> URL? { + if inputOptions.string != nil { return nil } + let rootURL = URL(fileURLWithPath: inputOptions.path).deletingLastPathComponent() + if rootURL.path == FileManager.default.currentDirectoryPath { return nil } + return rootURL + } + + func checkForCLIVersionMismatch( + with inputs: InputOptions, + ignoreVersionMismatch: Bool = false + ) throws { + if case let .versionMismatch(cliVersion, apolloVersion) = + try VersionChecker.matchCLIVersionToApolloVersion(projectRootURL: rootOutputURL(for: inputs)) { + let errorMessage = """ + Apollo Version Mismatch + We've detected that the version of the Apollo Codegen CLI does not match the version of the + Apollo library used in your project. This may lead to incompatible generated objects. + + Please update your version of the Codegen CLI by following the instructions at: + https://www.apollographql.com/docs/ios/code-generation/codegen-cli/#installation + + CLI version: \(cliVersion) + Apollo version: \(apolloVersion) + """ + + if inputs.ignoreVersionMismatch { + print(""" + Warning: \(errorMessage) + """) + } else { + + throw Error(errorDescription: """ + Error: \(errorMessage) + + To ignore this error during code generation, use the argument: --ignore-version-mismatch. + """) + } + } + } +} diff --git a/Sources/CodegenCLI/Extensions/ParsableCommand+RootOutputURL.swift b/Sources/CodegenCLI/Extensions/ParsableCommand+RootOutputURL.swift deleted file mode 100644 index 35377216e..000000000 --- a/Sources/CodegenCLI/Extensions/ParsableCommand+RootOutputURL.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import ArgumentParser -import ApolloCodegenLib - -extension ParsableCommand { - func rootOutputURL(for inputOptions: InputOptions) -> URL? { - if inputOptions.string != nil { return nil } - let rootURL = URL(fileURLWithPath: inputOptions.path).deletingLastPathComponent() - if rootURL.path == FileManager.default.currentDirectoryPath { return nil } - return rootURL - } -} diff --git a/Sources/CodegenCLI/OptionGroups/InputOptions.swift b/Sources/CodegenCLI/OptionGroups/InputOptions.swift index a562bae97..d076b466b 100644 --- a/Sources/CodegenCLI/OptionGroups/InputOptions.swift +++ b/Sources/CodegenCLI/OptionGroups/InputOptions.swift @@ -22,4 +22,10 @@ struct InputOptions: ParsableArguments { help: "Increase verbosity to include debug output." ) var verbose: Bool = false + + @Flag( + name: .long, + help: "Ignore Apollo version mismatch errors. Warning: This may lead to incompatible generated objects." + ) + var ignoreVersionMismatch: Bool = false } diff --git a/Sources/CodegenCLI/Protocols/CodegenProvider.swift b/Sources/CodegenCLI/Protocols/CodegenProvider.swift index 1f4fe3e13..9ed705178 100644 --- a/Sources/CodegenCLI/Protocols/CodegenProvider.swift +++ b/Sources/CodegenCLI/Protocols/CodegenProvider.swift @@ -7,6 +7,12 @@ public protocol CodegenProvider { with configuration: ApolloCodegenConfiguration, withRootURL rootURL: URL? ) throws + + static func generateOperationManifest( + with configuration: ApolloCodegenConfiguration, + withRootURL rootURL: URL?, + fileManager: ApolloFileManager + ) throws } extension ApolloCodegen: CodegenProvider { } diff --git a/Sources/apollo-ios-cli/README.md b/Sources/apollo-ios-cli/README.md index c419683c8..de6f933c0 100644 --- a/Sources/apollo-ios-cli/README.md +++ b/Sources/apollo-ios-cli/README.md @@ -13,6 +13,8 @@ SUBCOMMANDS: init Initialize a new configuration with defaults. generate Generate Swift source code based on a code generation configuration. fetch-schema Download a GraphQL schema from the Apollo Registry or GraphQL introspection. + generate-operation-manifest + Generate Persisted Queries operation manifest based on a code generation configuration. See 'apollo-ios-cli help ' for detailed help. ``` @@ -75,3 +77,18 @@ OPTIONS: --version Show the version. -h, --help Show help information. ``` +## Generate Operation Manifest +``` +OVERVIEW: Generate Persisted Queries operation manifest based on a code generation configuration. + +USAGE: apollo-ios-cli generate-operation-manifest [--path ] [--string ] [--verbose] [--ignore-version-mismatch] + +OPTIONS: + -p, --path Read the configuration from a file at the path. --string overrides this option if used together. (default: ./apollo-codegen-config.json) + -s, --string Configuration string in JSON format. This option overrides --path. + -v, --verbose Increase verbosity to include debug output. + --ignore-version-mismatch + Ignore Apollo version mismatch errors. Warning: This may lead to incompatible generated objects. + --version Show the version. + -h, --help Show help information. +``` \ No newline at end of file diff --git a/Sources/apollo-ios-cli/main.swift b/Sources/apollo-ios-cli/main.swift index 93f20cd36..59f5d4663 100644 --- a/Sources/apollo-ios-cli/main.swift +++ b/Sources/apollo-ios-cli/main.swift @@ -11,6 +11,7 @@ struct Apollo_iOS_CLI: ParsableCommand { CodegenCLI.Initialize.self, CodegenCLI.Generate.self, CodegenCLI.FetchSchema.self, + CodegenCLI.GenerateOperationManifest.self ] ) } diff --git a/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift b/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift new file mode 100644 index 000000000..a035cd593 --- /dev/null +++ b/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift @@ -0,0 +1,319 @@ +import XCTest +import Nimble +import ApolloInternalTestHelpers +@testable import CodegenCLI +import ApolloCodegenLib +import ArgumentParser + +class GenerateOperationManifestTests: XCTestCase { + + // MARK: - Test Helpers + + func parse(_ options: [String]?) throws -> GenerateOperationManifest { + try GenerateOperationManifest.parse(options) + } + + // MARK: - Parsing Tests + + func test__parsing__givenParameters_none_shouldUseDefaults() throws { + // when + let command = try parse([]) + + // then + expect(command.inputs.path).to(equal(Constants.defaultFilePath)) + expect(command.inputs.string).to(beNil()) + expect(command.inputs.verbose).to(beFalse()) + } + + // MARK: - Generate Tests + + func test__generate__givenParameters_pathCustom_shouldBuildWithFileData() throws { + // given + let inputPath = "./config.json" + + let options = [ + "--path=\(inputPath)" + ] + + let mockConfiguration = ApolloCodegenConfiguration.mock() + let mockFileManager = MockApolloFileManager(strict: true) + + mockFileManager.mock(closure: .contents({ path in + let actualPath = URL(fileURLWithPath: path).standardizedFileURL.path + let expectedPath = URL(fileURLWithPath: inputPath).standardizedFileURL.path + + expect(actualPath).to(equal(expectedPath)) + + return try! JSONEncoder().encode(mockConfiguration) + })) + + var didCallBuild = false + MockApolloCodegen.buildHandler = { configuration in + expect(configuration).to(equal(mockConfiguration)) + + didCallBuild = true + } + + // when + let command = try parse(options) + + try command._run(fileManager: mockFileManager.base, codegenProvider: MockApolloCodegen.self) + + // then + expect(didCallBuild).to(beTrue()) + } + + func test__generate__givenParameters_stringCustom_shouldBuildWithStringData() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)" + ] + + var didCallBuild = false + MockApolloCodegen.buildHandler = { configuration in + expect(configuration).to(equal(mockConfiguration)) + + didCallBuild = true + } + + // when + let command = try parse(options) + + try command._run(codegenProvider: MockApolloCodegen.self) + + // then + expect(didCallBuild).to(beTrue()) + } + + func test__generate__givenParameters_bothPathAndString_shouldBuildWithStringData() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--path=./path/to/file", + "--string=\(jsonString)" + ] + + var didCallBuild = false + MockApolloCodegen.buildHandler = { configuration in + expect(configuration).to(equal(mockConfiguration)) + + didCallBuild = true + } + + // when + let command = try parse(options) + + try command._run(codegenProvider: MockApolloCodegen.self) + + // then + expect(didCallBuild).to(beTrue()) + } + + func test__generate__givenDefaultParameter_verbose_shouldSetLogLevelWarning() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)" + ] + + MockApolloCodegen.buildHandler = { configuration in } + MockApolloSchemaDownloader.fetchHandler = { configuration in } + + var level: CodegenLogger.LogLevel? + MockLogLevelSetter.levelHandler = { value in + level = value + } + + // when + let command = try parse(options) + + try command._run( + codegenProvider: MockApolloCodegen.self, + logger: CodegenLogger.mock + ) + + // then + expect(level).toEventually(equal(.warning)) + } + + func test__generate__givenParameter_verbose_shouldSetLogLevelDebug() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)", + "--verbose" + ] + + MockApolloCodegen.buildHandler = { configuration in } + MockApolloSchemaDownloader.fetchHandler = { configuration in } + + var level: CodegenLogger.LogLevel? + MockLogLevelSetter.levelHandler = { value in + level = value + } + + // when + let command = try parse(options) + + try command._run( + codegenProvider: MockApolloCodegen.self, + logger: CodegenLogger.mock + ) + + // then + expect(level).toEventually(equal(.debug)) + } + + // MARK: Version Checking Tests + + func test__generate__givenCLIVersionMismatch_shouldThrowVersionMismatchError() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)", + "--verbose" + ] + + MockApolloCodegen.buildHandler = { configuration in } + MockApolloSchemaDownloader.fetchHandler = { configuration in } + + try self.testIsolatedFileManager().createFile( + body: """ + { + "pins": [ + { + "identity": "apollo-ios", + "kind" : "remoteSourceControl", + "location": "https://github.com/apollographql/apollo-ios.git", + "state": { + "revision": "5349afb4e9d098776cc44280258edd5f2ae571ed", + "version": "1.0.0-test.123" + } + } + ], + "version": 2 + } + """, + named: "Package.resolved" + ) + + // when + let command = try parse(options) + + // then + expect( + try command._run( + codegenProvider: MockApolloCodegen.self + ) + ).to(throwError()) + } + + func test__generate__givenCLIVersionMismatch_withIgnoreVersionMismatchArgument_shouldNotThrowVersionMismatchError() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)", + "--verbose", + "--ignore-version-mismatch" + ] + + MockApolloCodegen.buildHandler = { configuration in } + MockApolloSchemaDownloader.fetchHandler = { configuration in } + + try self.testIsolatedFileManager().createFile( + body: """ + { + "pins": [ + { + "identity": "apollo-ios", + "kind" : "remoteSourceControl", + "location": "https://github.com/apollographql/apollo-ios.git", + "state": { + "revision": "5349afb4e9d098776cc44280258edd5f2ae571ed", + "version": "1.0.0-test.123" + } + } + ], + "version": 2 + } + """, + named: "Package.resolved" + ) + + // when + let command = try parse(options) + + // then + expect( + try command._run( + codegenProvider: MockApolloCodegen.self + ) + ).toNot(throwError()) + } + + func test__generate__givenNoPackageResolvedFile__shouldNotThrowVersionMismatchError() throws { + // given + let mockConfiguration = ApolloCodegenConfiguration.mock() + + let jsonString = String( + data: try! JSONEncoder().encode(mockConfiguration), + encoding: .utf8 + )! + + let options = [ + "--string=\(jsonString)", + "--verbose" + ] + + MockApolloCodegen.buildHandler = { configuration in } + MockApolloSchemaDownloader.fetchHandler = { configuration in } + + // when + let command = try parse(options) + + // then + expect( + try command._run( + codegenProvider: MockApolloCodegen.self + ) + ).toNot(throwError()) + } + +} diff --git a/Tests/CodegenCLITests/Support/MockApolloCodegen.swift b/Tests/CodegenCLITests/Support/MockApolloCodegen.swift index acf80c639..d1ca6ac2f 100644 --- a/Tests/CodegenCLITests/Support/MockApolloCodegen.swift +++ b/Tests/CodegenCLITests/Support/MockApolloCodegen.swift @@ -19,4 +19,20 @@ class MockApolloCodegen: CodegenProvider { try handler(configuration) } + + static func generateOperationManifest( + with configuration: ApolloCodegenLib.ApolloCodegenConfiguration, + withRootURL rootURL: URL?, + fileManager: ApolloCodegenLib.ApolloFileManager + ) throws { + guard let handler = buildHandler else { + fatalError("You must set buildHandler before calling \(#function)!") + } + + defer { + buildHandler = nil + } + + try handler(configuration) + } } diff --git a/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift b/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift index 355a2f31f..a337ab2a6 100644 --- a/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift +++ b/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift @@ -9,7 +9,11 @@ extension ApolloCodegenConfiguration { schemaPath: "./schema.graphqls" ), output: .init( - schemaTypes: .init(path: ".", moduleType: .swiftPackageManager) + schemaTypes: .init(path: ".", moduleType: .swiftPackageManager), + operationManifest: .init(path: "./manifest", version: .persistedQueries) + ), + options: .init( + operationDocumentFormat: [.definition, .operationId] ), schemaDownloadConfiguration: .init( using: .introspection(endpointURL: URL(string: "http://some.server")!), diff --git a/docs/source/code-generation/codegen-cli.mdx b/docs/source/code-generation/codegen-cli.mdx index 4acf2f032..742e0cf59 100644 --- a/docs/source/code-generation/codegen-cli.mdx +++ b/docs/source/code-generation/codegen-cli.mdx @@ -14,6 +14,7 @@ The Codegen CLI has three primary commands: - [**Fetch Schema**](#fetch-schema): Fetches your GraphQL schema and writes it to a file. The schema is required in order to run code generation. - To learn how to configure schema fetching, see [Downloading a schema](./downloading-schema). - [**Generate**](#generate): Runs the code generation engine using the configuration in your `apollo-codegen-configuration.json` file. +- [**Generate Operation Manifest**](#generate-operation-manifest): Generates the operation manifest for persisted queries using the configuration in your `apollo-codegen-configuration.json` file. > For detailed usage documentation of these commands, see the [Usage](#usage) section. @@ -155,9 +156,31 @@ Runs the code generation engine to generate Swift source code using the configur | Option | Description | | ---------- | ----------- | -| `-p, --path ` | Read the configuration from a file at the path. `--string` overrides this option if used together. (default: `./apollo-codegen-config.json`) | -| `-s, --string ` | Provide the configuration string in JSON format. This option overrides `--path`. | -| `-v, --verbose ` | Increase verbosity to include debug output. | -| `-f, --fetch-schema` | Fetch the GraphQL schema before Swift code generation. This runs the [`fetch-schema`](#fetch-schema) command. | -| `--version` | Show the version of the CLI. | -| `-h, --help` | Show help information.| +| `-p, --path ` | Read the configuration from a file at the path. `--string` overrides this option if used together. (default: `./apollo-codegen-config.json`) | +| `-s, --string ` | Provide the configuration string in JSON format. This option overrides `--path`. | +| `-v, --verbose ` | Increase verbosity to include debug output. | +| `-f, --fetch-schema` | Fetch the GraphQL schema before Swift code generation. This runs the [`fetch-schema`](#fetch-schema) command. | +| `--ignore-version-mismatch` | Ignores version mismatches between the `apollo-ios-cli` and the version of the Apollo sdk being used. | +| `--version` | Show the version of the CLI. | +| `-h, --help` | Show help information.| + +### Generate Operation Manifest + +Generates the operation manifest for persisted queries using the configuration in your `apollo-codegen-configuration.json` file. + +> For more information on configuring code generation, see the [configuration documentation](./codegen-configuration). + +#### Command: + +`apollo-ios-cli generate-operation-manifest [--path ] [--string ]` + +#### Options: + +| Option | Description | +| ---------- | ----------- | +| `-p, --path ` | Read the configuration from a file at the path. `--string` overrides this option if used together. (default: `./apollo-codegen-config.json`) | +| `-s, --string ` | Provide the configuration string in JSON format. This option overrides `--path`. | +| `-v, --verbose ` | Increase verbosity to include debug output. | +| `--ignore-version-mismatch` | Ignores version mismatches between the `apollo-ios-cli` and the version of the Apollo sdk being used. | +| `--version` | Show the version of the CLI. | +| `-h, --help` | Show help information.| \ No newline at end of file