diff --git a/Apollo.xcodeproj/project.pbxproj b/Apollo.xcodeproj/project.pbxproj index 783a723782..eb68289c3c 100644 --- a/Apollo.xcodeproj/project.pbxproj +++ b/Apollo.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 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 */; }; + 6608B3362A7D402B006FB655 /* ApolloCodegenConfiguration+OperationManifestConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6608B3342A7D3FF5006FB655 /* ApolloCodegenConfiguration+OperationManifestConfiguration.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 */; }; @@ -1140,6 +1141,7 @@ 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 = ""; }; + 6608B3342A7D3FF5006FB655 /* ApolloCodegenConfiguration+OperationManifestConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ApolloCodegenConfiguration+OperationManifestConfiguration.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 = ""; }; @@ -2206,6 +2208,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 6608B3332A7D3FCB006FB655 /* CodegenConfiguration */ = { + isa = PBXGroup; + children = ( + 6608B3342A7D3FF5006FB655 /* ApolloCodegenConfiguration+OperationManifestConfiguration.swift */, + ); + path = CodegenConfiguration; + sourceTree = ""; + }; 66B18E862A15366400525DFB /* Network */ = { isa = PBXGroup; children = ( @@ -2548,6 +2558,7 @@ 9BD681332405F6BB000874CB /* Codegen */ = { isa = PBXGroup; children = ( + 6608B3332A7D3FCB006FB655 /* CodegenConfiguration */, 9B7B6F57233C287100F32205 /* ApolloCodegen.swift */, 9B7B6F58233C287100F32205 /* ApolloCodegenConfiguration.swift */, E674DB40274C0A9B009BB90E /* Glob.swift */, @@ -5053,6 +5064,7 @@ E6203342284F1C9600A291D1 /* MockUnionsFileGenerator.swift in Sources */, DE6D07F927BC3B6D009F5F33 /* GraphQLInputField+Rendered.swift in Sources */, 9F1A966C258F34BB00A06EEB /* GraphQLSchema.swift in Sources */, + 6608B3362A7D402B006FB655 /* ApolloCodegenConfiguration+OperationManifestConfiguration.swift in Sources */, 9BE74D3D23FB4A8E006D354F /* FileFinder.swift in Sources */, E64F7EBC27A11A510059C021 /* GraphQLNamedType+SwiftName.swift in Sources */, 9B7B6F59233C287200F32205 /* ApolloCodegen.swift in Sources */, diff --git a/Sources/ApolloCodegenLib/ApolloCodegen.swift b/Sources/ApolloCodegenLib/ApolloCodegen.swift index 9d0d50ad8f..2b6cd3fa45 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegen.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegen.swift @@ -67,6 +67,22 @@ public class ApolloCodegen { } } } + + public struct ItemsToGenerate: OptionSet { + public var rawValue: Int + + public static let code = ItemsToGenerate(rawValue: 1 << 0) + public static let operationManifest = ItemsToGenerate(rawValue: 1 << 1) + public static let all: ItemsToGenerate = [ + .code, + .operationManifest + ] + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + } /// Executes the code generation engine with a specified configuration. /// @@ -77,15 +93,17 @@ public class ApolloCodegen { /// If `nil`, the current working directory of the executing process will be used. public static func build( with configuration: ApolloCodegenConfiguration, - withRootURL rootURL: URL? = nil + withRootURL rootURL: URL? = nil, + itemsToGenerate: ItemsToGenerate = [.code] ) throws { - try build(with: configuration, rootURL: rootURL) + try build(with: configuration, rootURL: rootURL, itemsToGenerate: itemsToGenerate) } internal static func build( with configuration: ApolloCodegenConfiguration, rootURL: URL? = nil, - fileManager: ApolloFileManager = .default + fileManager: ApolloFileManager = .default, + itemsToGenerate: ItemsToGenerate ) throws { let configContext = ConfigurationContext( @@ -103,58 +121,43 @@ public class ApolloCodegen { try validate(configContext, with: compilationResult) let ir = IR(compilationResult: compilationResult) + + if itemsToGenerate == .operationManifest { + var operationIDsFileGenerator = OperationManifestFileGenerator(config: configContext) + + for operation in compilationResult.operations { + autoreleasepool { + let irOperation = ir.build(operation: operation) + operationIDsFileGenerator?.collectOperationIdentifier(irOperation) + } + } + + try operationIDsFileGenerator?.generate(fileManager: fileManager) + } - var existingGeneratedFilePaths = configuration.options.pruneGeneratedFiles ? - try findExistingGeneratedFilePaths( - config: configContext, - fileManager: fileManager - ) : [] - - try generateFiles( - compilationResult: compilationResult, - ir: ir, - config: configContext, - fileManager: fileManager - ) + if itemsToGenerate.contains(.code) { + var existingGeneratedFilePaths = configuration.options.pruneGeneratedFiles ? + try findExistingGeneratedFilePaths( + config: configContext, + fileManager: fileManager + ) : [] - if configuration.options.pruneGeneratedFiles { - try deleteExtraneousGeneratedFiles( - from: &existingGeneratedFilePaths, - afterCodeGenerationUsing: fileManager + try generateFiles( + compilationResult: compilationResult, + ir: ir, + config: configContext, + fileManager: fileManager, + itemsToGenerate: itemsToGenerate ) + + if configuration.options.pruneGeneratedFiles { + try deleteExtraneousGeneratedFiles( + from: &existingGeneratedFilePaths, + afterCodeGenerationUsing: fileManager + ) + } } } - - 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 @@ -411,7 +414,8 @@ public class ApolloCodegen { compilationResult: CompilationResult, ir: IR, config: ConfigurationContext, - fileManager: ApolloFileManager = .default + fileManager: ApolloFileManager = .default, + itemsToGenerate: ItemsToGenerate ) throws { for fragment in compilationResult.fragments { try autoreleasepool { @@ -431,11 +435,15 @@ public class ApolloCodegen { try OperationFileGenerator(irOperation: irOperation, config: config) .generate(forConfig: config, fileManager: fileManager) - operationIDsFileGenerator?.collectOperationIdentifier(irOperation) + if itemsToGenerate.contains(.operationManifest) { + operationIDsFileGenerator?.collectOperationIdentifier(irOperation) + } } } - try operationIDsFileGenerator?.generate(fileManager: fileManager) + if itemsToGenerate.contains(.operationManifest) { + try operationIDsFileGenerator?.generate(fileManager: fileManager) + } operationIDsFileGenerator = nil for graphQLObject in ir.schema.referencedTypes.objects { diff --git a/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift b/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift index 63b5348a8d..974b8a0b59 100644 --- a/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift +++ b/Sources/ApolloCodegenLib/ApolloCodegenConfiguration.swift @@ -164,16 +164,15 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { public let operations: OperationsFileOutput /// The local path structure for the test mock operation object files. public let testMocks: TestMockFileOutput - /// Configures the generation of an operation manifest JSON file for use with persisted queries - /// or [Automatic Persisted Queries (APQs)](https://www.apollographql.com/docs/apollo-server/performance/apq). - /// Defaults to `nil`. - public let operationManifest: OperationManifestFileOutput? + + /// This var helps maintain backwards compatibility with legacy operation manifest generation + /// with the new `OperationManifestConfiguration` and will be fully removed in v2.0 + fileprivate let operationIDsPath: String? /// Default property values public struct Default { public static let operations: OperationsFileOutput = .inSchemaModule public static let testMocks: TestMockFileOutput = .none - public static let operationManifest: OperationManifestFileOutput? = nil } /// Designated initializer. @@ -191,13 +190,12 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { public init( schemaTypes: SchemaTypesFileOutput, operations: OperationsFileOutput = Default.operations, - testMocks: TestMockFileOutput = Default.testMocks, - operationManifest: OperationManifestFileOutput? = Default.operationManifest + testMocks: TestMockFileOutput = Default.testMocks ) { self.schemaTypes = schemaTypes self.operations = operations self.testMocks = testMocks - self.operationManifest = operationManifest + self.operationIDsPath = nil } // MARK: Codable @@ -206,7 +204,6 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { case schemaTypes case operations case testMocks - case operationManifest case operationIdentifiersPath } @@ -228,21 +225,10 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { forKey: .testMocks ) - if let operationManifest = try values.decodeIfPresent( - OperationManifestFileOutput.self, - forKey: .operationManifest - ) { - self.operationManifest = operationManifest - - } else if let operationIdsPath = try values.decodeIfPresent( + operationIDsPath = try values.decodeIfPresent( String.self, forKey: .operationIdentifiersPath - ){ - self.operationManifest = .init(path: operationIdsPath, version: .legacyAPQ) - - } else { - self.operationManifest = nil - } + ) } public func encode(to encoder: Encoder) throws { @@ -251,7 +237,6 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { try container.encode(self.schemaTypes, forKey: .schemaTypes) try container.encode(self.operations, forKey: .operations) try container.encode(self.testMocks, forKey: .testMocks) - try container.encode(self.operationManifest, forKey: .operationManifest) } } @@ -470,41 +455,6 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { } } - /// Configures the generation of an operation manifest JSON file for use with persisted queries - /// or [Automatic Persisted Queries (APQs)](https://www.apollographql.com/docs/apollo-server/performance/apq). - /// - /// The operation manifest is a JSON file that maps all generated GraphQL operations to an - /// operation identifier. This manifest can be used to register operations with a server utilizing - /// persisted queries - /// or [Automatic Persisted Queries (APQs)](https://www.apollographql.com/docs/apollo-server/performance/apq). - /// Defaults to `nil`. - public struct OperationManifestFileOutput: Codable, Equatable { - /// Local path where the generated operation manifest file should be written. - let path: String - /// The version format to use when generating the operation manifest. Defaults to `.persistedQueries`. - let version: Version - - public enum Version: String, Codable, Equatable { - /// Generates an operation manifest for use with persisted queries. - case persistedQueries - /// Generates an operation manifest for pre-registering operations with the legacy - /// [Automatic Persisted Queries (APQs)](https://www.apollographql.com/docs/apollo-server/performance/apq). - /// functionality of Apollo Server/Router. - case legacyAPQ - } - - /// Designated Initializer - /// - Parameters: - /// - path: Local path where the generated operation manifest file should be written. - /// - version: The version format to use when generating the operation manifest. - /// Defaults to `.persistedQueries`. - public init(path: String, version: Version = .persistedQueries) { - self.path = path - self.version = version - } - - } - // MARK: - Other Types public struct OutputOptions: Codable, Equatable { /// Any non-default rules for pluralization or singularization you wish to include. @@ -750,7 +700,9 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { ) ?? Default.enumCases } } - + + // MARK: - OperationDocumentFormat + public struct OperationDocumentFormat: OptionSet, Codable, Equatable { /// Include the GraphQL source document for the operation in the generated operation models. public static let definition = Self(rawValue: 1) @@ -944,7 +896,7 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { /// The input files required for code generation. public let input: FileInput /// The paths and files output by code generation. - public let output: FileOutput + public var output: FileOutput /// Rules and options to customize the generated code. public let options: OutputOptions /// Allows users to enable experimental features. @@ -953,12 +905,15 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { /// available. public let experimentalFeatures: ExperimentalFeatures /// Schema download configuration. - public let schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration? + public let schemaDownload: ApolloSchemaDownloadConfiguration? + /// Configuration for generating an operation manifest for use with persisted queries. + public let operationManifest: OperationManifestConfiguration? public struct Default { public static let options: OutputOptions = OutputOptions() public static let experimentalFeatures: ExperimentalFeatures = ExperimentalFeatures() - public static let schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration? = nil + public static let schemaDownload: ApolloSchemaDownloadConfiguration? = nil + public static let operationManifest: OperationManifestConfiguration? = nil } // MARK: - Helper Properties @@ -981,14 +936,16 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { output: FileOutput, options: OutputOptions = Default.options, experimentalFeatures: ExperimentalFeatures = Default.experimentalFeatures, - schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration? = Default.schemaDownloadConfiguration + schemaDownload: ApolloSchemaDownloadConfiguration? = Default.schemaDownload, + operationManifest: OperationManifestConfiguration? = Default.operationManifest ) { self.schemaNamespace = schemaNamespace self.input = input self.output = output self.options = options self.experimentalFeatures = experimentalFeatures - self.schemaDownloadConfiguration = schemaDownloadConfiguration + self.schemaDownload = schemaDownload + self.operationManifest = operationManifest self.ApolloAPITargetName = options.cocoapodsCompatibleImportStatements ? "Apollo" : "ApolloAPI" } @@ -1002,6 +959,8 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { case options case experimentalFeatures case schemaDownloadConfiguration + case schemaDownload + case operationManifest } public func encode(to encoder: Encoder) throws { @@ -1013,8 +972,12 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { try container.encode(self.options, forKey: .options) try container.encode(experimentalFeatures, forKey: .experimentalFeatures) - if let schemaDownloadConfiguration { - try container.encode(schemaDownloadConfiguration, forKey: .schemaDownloadConfiguration) + if let schemaDownload { + try container.encode(schemaDownload, forKey: .schemaDownload) + } + + if let operationManifest { + try container.encode(operationManifest, forKey: .operationManifest) } } @@ -1038,23 +1001,39 @@ public struct ApolloCodegenConfiguration: Codable, Equatable { ) ) } + + let fileOutput = try values.decode(FileOutput.self, forKey: .output) + let options = try values.decodeIfPresent( + OutputOptions.self, + forKey: .options + ) ?? Default.options + + var operationManifest = try values.decodeIfPresent(OperationManifestConfiguration.self, forKey: .operationManifest) + if operationManifest == nil { + if let operationIDsPath = fileOutput.operationIDsPath { + operationManifest = OperationManifestConfiguration( + path: operationIDsPath, + version: .legacy + ) + } + } + + var schemaDownload = try values.decodeIfPresent(ApolloSchemaDownloadConfiguration.self, forKey: .schemaDownload) + if schemaDownload == nil { + schemaDownload = try values.decodeIfPresent(ApolloSchemaDownloadConfiguration.self, forKey: .schemaDownloadConfiguration) + } self.init( schemaNamespace: try getSchemaNamespaceValue(), input: try values.decode(FileInput.self, forKey: .input), - output: try values.decode(FileOutput.self, forKey: .output), - options: try values.decodeIfPresent( - OutputOptions.self, - forKey: .options - ) ?? Default.options, + output: fileOutput, + options: options, experimentalFeatures: try values.decodeIfPresent( ExperimentalFeatures.self, forKey: .experimentalFeatures ) ?? Default.experimentalFeatures, - schemaDownloadConfiguration: try values.decodeIfPresent( - ApolloSchemaDownloadConfiguration.self, - forKey: .schemaDownloadConfiguration - ) ?? Default.schemaDownloadConfiguration + schemaDownload: schemaDownload ?? Default.schemaDownload, + operationManifest: operationManifest ?? Default.operationManifest ) } } @@ -1193,7 +1172,7 @@ extension ApolloCodegenConfiguration { @available(*, deprecated, renamed: "schemaNamespace") public var schemaName: String { schemaNamespace } - /// Deprecated initializer - use `init(schemaNamespace:input:output:options:experimentalFeatures:schemaDownloadConfiguration:)` + /// Deprecated initializer - use `init(schemaNamespace:input:output:options:experimentalFeatures:schemaDownload:operationManifest:)` /// instead. /// /// - Parameters: @@ -1202,14 +1181,14 @@ extension ApolloCodegenConfiguration { /// - output: The paths and files output by code generation. /// - options: Rules and options to customize the generated code. /// - experimentalFeatures: Allows users to enable experimental features. - @available(*, deprecated, renamed: "init(schemaNamespace:input:output:options:experimentalFeatures:schemaDownloadConfiguration:)") + @available(*, deprecated, renamed: "init(schemaNamespace:input:output:options:experimentalFeatures:schemaDownload:operationManifest:)") public init( schemaName: String, input: FileInput, output: FileOutput, options: OutputOptions = Default.options, experimentalFeatures: ExperimentalFeatures = Default.experimentalFeatures, - schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration? = Default.schemaDownloadConfiguration + schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration? = Default.schemaDownload ) { self.init( schemaNamespace: schemaName, @@ -1217,7 +1196,7 @@ extension ApolloCodegenConfiguration { output: output, options: options, experimentalFeatures: experimentalFeatures, - schemaDownloadConfiguration: schemaDownloadConfiguration) + schemaDownload: schemaDownloadConfiguration) } /// Enum to enable using @@ -1271,7 +1250,7 @@ extension ApolloCodegenConfiguration.FileOutput { /// If `.none`, test mocks will not be generated. Defaults to `.none`. /// - operationIdentifiersPath: An absolute location to an operation id JSON map file /// for use with APQ registration. Defaults to `nil`. - @available(*, deprecated, renamed: "init(schemaTypes:operations:testMocks:operationManifest:)") + @available(*, deprecated, renamed: "init(schemaTypes:operations:testMocks:)") public init( schemaTypes: ApolloCodegenConfiguration.SchemaTypesFileOutput, operations: ApolloCodegenConfiguration.OperationsFileOutput = Default.operations, @@ -1281,16 +1260,12 @@ extension ApolloCodegenConfiguration.FileOutput { self.schemaTypes = schemaTypes self.operations = operations self.testMocks = testMocks - if let operationIdentifiersPath { - self.operationManifest = .init(path: operationIdentifiersPath, version: .legacyAPQ) - } else { - self.operationManifest = nil - } + self.operationIDsPath = operationIdentifiersPath } /// An absolute location to an operation id JSON map file. - @available(*, deprecated, renamed: "operationManifest.path") - public var operationIdentifiersPath: String? { operationManifest?.path } + @available(*, deprecated, message: "Moved to ApolloCodegenConfiguration.OperationManifestConfiguration.OperationManifest.path") + public var operationIdentifiersPath: String? { operationIDsPath } } extension ApolloCodegenConfiguration.OutputOptions { @@ -1391,17 +1366,17 @@ extension ApolloCodegenConfiguration.OutputOptions { /// See `APQConfig` for more information on Automatic Persisted Queries. @available(*, deprecated, message: "Use OperationDocumentFormat instead.") public var apqs: ApolloCodegenConfiguration.APQConfig { - switch self.operationDocumentFormat { - case .definition: - return .disabled - case .operationId: - return .persistedOperationsOnly - case [.operationId, .definition]: - return .automaticallyPersist - default: - return .disabled + switch self.operationDocumentFormat { + case .definition: + return .disabled + case .operationId: + return .persistedOperationsOnly + case [.operationId, .definition]: + return .automaticallyPersist + default: + return .disabled + } } - } /// Formatting of the GraphQL query string literal that is included in each /// generated operation object. @@ -1436,7 +1411,7 @@ private struct AnyCodingKey: CodingKey { } } -private func throwIfContainsUnexpectedKey( +func throwIfContainsUnexpectedKey( container: KeyedDecodingContainer, type: T.Type, decoder: Decoder diff --git a/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+OperationManifestConfiguration.swift b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+OperationManifestConfiguration.swift new file mode 100644 index 0000000000..fef96343b7 --- /dev/null +++ b/Sources/ApolloCodegenLib/CodegenConfiguration/ApolloCodegenConfiguration+OperationManifestConfiguration.swift @@ -0,0 +1,91 @@ +import Foundation + +extension ApolloCodegenConfiguration { + + public struct OperationManifestConfiguration: Codable, Equatable { + + // MARK: - Properties + + /// Local path where the generated operation manifest file should be written. + public let path: String + /// The version format to use when generating the operation manifest. Defaults to `.persistedQueries`. + public let version: Version + + public enum Version: String, Codable, Equatable { + /// Generates an operation manifest for use with persisted queries. + case persistedQueries + /// Generates an operation manifest in the legacy safelisting format used prior to the + /// [Persisted Queries](https://www.apollographql.com/docs/ios/fetching/persisted-queries) feature. + case legacy + } + + /// If set to `true` will generate the operation manifest every time code generation is run. Defaults to `false` + public let generateManifestOnCodeGeneration: Bool + + /// Default property values + public struct Default { + public static let version: Version = .persistedQueries + public static let generateManifestOnCodeGeneration: Bool = false + } + + // MARK: - Initializers + + /// Designated initializer + /// + /// - Parameters: + /// - path: Local path where the generated operation manifest file should be written. + /// - version: The version format to use when generating the operation manifest. Defaults to `.persistedQueries`. + /// - generateManifestOnCodeGeneration: Whether or nor the operation manifest should be generated whenever code generation is run. Defaults to `false`. + public init( + path: String, + version: Version = Default.version, + generateManifestOnCodeGeneration: Bool = Default.generateManifestOnCodeGeneration + ) { + self.path = path + self.version = version + self.generateManifestOnCodeGeneration = generateManifestOnCodeGeneration + } + + // MARK: - Codable + + enum CodingKeys: CodingKey, CaseIterable { + case path + case version + case generateManifestOnCodeGeneration + } + + public init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + try throwIfContainsUnexpectedKey( + container: values, + type: Self.self, + decoder: decoder + ) + + path = try values.decode( + String.self, + forKey: .path + ) + + version = try values.decode( + Version.self, + forKey: .version + ) + + generateManifestOnCodeGeneration = try values.decode( + Bool.self, + forKey: .generateManifestOnCodeGeneration + ) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.path, forKey: .path) + try container.encode(self.version, forKey: .version) + try container.encode(self.generateManifestOnCodeGeneration, forKey: .generateManifestOnCodeGeneration) + } + + } + +} diff --git a/Sources/ApolloCodegenLib/FileGenerators/OperationManifestFileGenerator.swift b/Sources/ApolloCodegenLib/FileGenerators/OperationManifestFileGenerator.swift index e0fb47485e..6db47952e1 100644 --- a/Sources/ApolloCodegenLib/FileGenerators/OperationManifestFileGenerator.swift +++ b/Sources/ApolloCodegenLib/FileGenerators/OperationManifestFileGenerator.swift @@ -37,7 +37,7 @@ struct OperationManifestFileGenerator { /// Parameters: /// - config: A configuration object specifying output behavior. init?(config: ApolloCodegen.ConfigurationContext) { - guard config.output.operationManifest != nil else { + guard config.operationManifest != nil else { return nil } @@ -57,7 +57,7 @@ struct OperationManifestFileGenerator { func generate(fileManager: ApolloFileManager = .default) throws { let rendered: String = try template.render(operations: operationManifest) - var manifestPath = config.output.operationManifest.unsafelyUnwrapped.path + var manifestPath = config.operationManifest.unsafelyUnwrapped.path let relativePrefix = "./" // if path begins with './' the path should be relative to the config.rootURL @@ -80,10 +80,10 @@ struct OperationManifestFileGenerator { } var template: any OperationManifestTemplate { - switch config.output.operationManifest.unsafelyUnwrapped.version { + switch config.operationManifest.unsafelyUnwrapped.version { case .persistedQueries: return PersistedQueriesOperationManifestTemplate(config: config) - case .legacyAPQ: + case .legacy: return LegacyAPQOperationManifestTemplate() } } diff --git a/Sources/CodegenCLI/Commands/FetchSchema.swift b/Sources/CodegenCLI/Commands/FetchSchema.swift index df6bf2c108..061f223f96 100644 --- a/Sources/CodegenCLI/Commands/FetchSchema.swift +++ b/Sources/CodegenCLI/Commands/FetchSchema.swift @@ -28,32 +28,26 @@ public struct FetchSchema: ParsableCommand { ) throws { logger.SetLoggingLevel(verbose: inputs.verbose) - switch (inputs.string, inputs.path) { - case let (.some(string), _): - try fetchSchema(data: try string.asData(), schemaDownloadProvider: schemaDownloadProvider) - - case let (nil, path): - let data = try fileManager.unwrappedContents(atPath: path) - try fetchSchema(data: data, schemaDownloadProvider: schemaDownloadProvider) - } + try fetchSchema( + configuration: inputs.getCodegenConfiguration(fileManager: fileManager), + schemaDownloadProvider: schemaDownloadProvider + ) } private func fetchSchema( - data: Data, + configuration codegenConfiguration: ApolloCodegenConfiguration, schemaDownloadProvider: SchemaDownloadProvider.Type ) throws { - let codegenConfiguration = try JSONDecoder().decode(ApolloCodegenConfiguration.self, from: data) - - guard let schemaDownloadConfiguration = codegenConfiguration.schemaDownloadConfiguration else { + guard let schemaDownload = codegenConfiguration.schemaDownload else { throw Error(errorDescription: """ - Missing schema download configuration. Hint: check the `schemaDownloadConfiguration` \ + Missing schema download configuration. Hint: check the `schemaDownload` \ property of your configuration. """ ) } try schemaDownloadProvider.fetch( - configuration: schemaDownloadConfiguration, + configuration: schemaDownload, withRootURL: rootOutputURL(for: inputs) ) } diff --git a/Sources/CodegenCLI/Commands/Generate.swift b/Sources/CodegenCLI/Commands/Generate.swift index d6006c19f2..270c9a08d9 100644 --- a/Sources/CodegenCLI/Commands/Generate.swift +++ b/Sources/CodegenCLI/Commands/Generate.swift @@ -38,49 +38,47 @@ public struct Generate: ParsableCommand { with: inputs ) - switch (inputs.string, inputs.path) { - case let (.some(string), _): - try generate( - data: try string.asData(), - codegenProvider: codegenProvider, - schemaDownloadProvider: schemaDownloadProvider - ) - - case let (nil, path): - let data = try fileManager.unwrappedContents(atPath: path) - try generate( - data: data, - codegenProvider: codegenProvider, - schemaDownloadProvider: schemaDownloadProvider - ) - } + try generate( + configuration: inputs.getCodegenConfiguration(fileManager: fileManager), + codegenProvider: codegenProvider, + schemaDownloadProvider: schemaDownloadProvider + ) } private func generate( - data: Data, + configuration: ApolloCodegenConfiguration, codegenProvider: CodegenProvider.Type, schemaDownloadProvider: SchemaDownloadProvider.Type ) throws { - let configuration = try JSONDecoder().decode(ApolloCodegenConfiguration.self, from: data) - if fetchSchema { guard - let schemaDownloadConfiguration = configuration.schemaDownloadConfiguration + let schemaDownload = configuration.schemaDownload else { throw Error(errorDescription: """ - Missing schema download configuration. Hint: check the `schemaDownloadConfiguration` \ + Missing schema download configuration. Hint: check the `schemaDownload` \ property of your configuration. """ ) } try fetchSchema( - configuration: schemaDownloadConfiguration, + configuration: schemaDownload, schemaDownloadProvider: schemaDownloadProvider ) } + + var itemsToGenerate: ApolloCodegen.ItemsToGenerate = .code + + if let operationManifest = configuration.operationManifest, + operationManifest.generateManifestOnCodeGeneration { + itemsToGenerate.insert(.operationManifest) + } - try codegenProvider.build(with: configuration, withRootURL: rootOutputURL(for: inputs)) + try codegenProvider.build( + with: configuration, + withRootURL: rootOutputURL(for: inputs), + itemsToGenerate: itemsToGenerate + ) } private func fetchSchema( diff --git a/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift b/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift index faef37325f..2b695766cc 100644 --- a/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift +++ b/Sources/CodegenCLI/Commands/GenerateOperationManifest.swift @@ -9,9 +9,9 @@ public struct GenerateOperationManifest: ParsableCommand { 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() { } @@ -26,36 +26,39 @@ public struct GenerateOperationManifest: ParsableCommand { logger: LogLevelSetter.Type = CodegenLogger.self ) throws { logger.SetLoggingLevel(verbose: inputs.verbose) - - try checkForCLIVersionMismatch( - with: inputs + + let configuration = try inputs.getCodegenConfiguration(fileManager: fileManager) + + try validate(configuration: configuration) + + try generateManifest( + configuration: configuration, + codegenProvider: codegenProvider ) - - 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, + configuration: ApolloCodegenConfiguration, codegenProvider: CodegenProvider.Type ) throws { - let configuration = try JSONDecoder().decode(ApolloCodegenConfiguration.self, from: data) - - try codegenProvider.generateOperationManifest( + try codegenProvider.build( with: configuration, withRootURL: rootOutputURL(for: inputs), - fileManager: .default + itemsToGenerate: [.operationManifest] ) } + + // MARK: - Validation + + func validate(configuration: ApolloCodegenConfiguration) throws { + try checkForCLIVersionMismatch(with: inputs) + + guard configuration.operationManifest != nil else { + throw ValidationError(""" + `operationManifest` section must be set in the codegen configuration JSON in order + to generate an operation manifest. + """) + } + } } diff --git a/Sources/CodegenCLI/OptionGroups/InputOptions.swift b/Sources/CodegenCLI/OptionGroups/InputOptions.swift index d076b466b3..107d54d99b 100644 --- a/Sources/CodegenCLI/OptionGroups/InputOptions.swift +++ b/Sources/CodegenCLI/OptionGroups/InputOptions.swift @@ -1,4 +1,6 @@ +import Foundation import ArgumentParser +import ApolloCodegenLib /// Shared group of common arguments used in commands for input parameters. struct InputOptions: ParsableArguments { @@ -28,4 +30,16 @@ struct InputOptions: ParsableArguments { help: "Ignore Apollo version mismatch errors. Warning: This may lead to incompatible generated objects." ) var ignoreVersionMismatch: Bool = false + + func getCodegenConfiguration(fileManager: FileManager) throws -> ApolloCodegenConfiguration { + var data: Data + switch (string, path) { + case let (.some(string), _): + data = try string.asData() + + case let (nil, path): + data = try fileManager.unwrappedContents(atPath: path) + } + return try JSONDecoder().decode(ApolloCodegenConfiguration.self, from: data) + } } diff --git a/Sources/CodegenCLI/Protocols/CodegenProvider.swift b/Sources/CodegenCLI/Protocols/CodegenProvider.swift index 9ed7051781..96ad36a103 100644 --- a/Sources/CodegenCLI/Protocols/CodegenProvider.swift +++ b/Sources/CodegenCLI/Protocols/CodegenProvider.swift @@ -4,14 +4,9 @@ import ApolloCodegenLib /// Generic representation of a code generation provider. public protocol CodegenProvider { static func build( - with configuration: ApolloCodegenConfiguration, - withRootURL rootURL: URL? - ) throws - - static func generateOperationManifest( with configuration: ApolloCodegenConfiguration, withRootURL rootURL: URL?, - fileManager: ApolloFileManager + itemsToGenerate: ApolloCodegen.ItemsToGenerate ) throws } diff --git a/Tests/ApolloCodegenInternalTestHelpers/MockApolloCodegenConfiguration.swift b/Tests/ApolloCodegenInternalTestHelpers/MockApolloCodegenConfiguration.swift index 7291d577ac..393a7d60ed 100644 --- a/Tests/ApolloCodegenInternalTestHelpers/MockApolloCodegenConfiguration.swift +++ b/Tests/ApolloCodegenInternalTestHelpers/MockApolloCodegenConfiguration.swift @@ -14,14 +14,16 @@ extension ApolloCodegenConfiguration { ) ), options: OutputOptions = .init(schemaDocumentation: .exclude), - experimentalFeatures: ExperimentalFeatures = .init() + experimentalFeatures: ExperimentalFeatures = .init(), + operationManifest: OperationManifestConfiguration? = nil ) -> Self { .init( schemaNamespace: schemaNamespace, input: input, output: output, options: options, - experimentalFeatures: experimentalFeatures + experimentalFeatures: experimentalFeatures, + operationManifest: operationManifest ) } diff --git a/Tests/ApolloCodegenTests/ApolloCodegenConfigurationCodableTests.swift b/Tests/ApolloCodegenTests/ApolloCodegenConfigurationCodableTests.swift index 50d8d18153..4545a7459a 100644 --- a/Tests/ApolloCodegenTests/ApolloCodegenConfigurationCodableTests.swift +++ b/Tests/ApolloCodegenTests/ApolloCodegenConfigurationCodableTests.swift @@ -35,8 +35,7 @@ class ApolloCodegenConfigurationCodableTests: XCTestCase { moduleType: .embeddedInTarget(name: "SomeTarget", accessModifier: .public) ), operations: .absolute(path: "/absolute/path", accessModifier: .internal), - testMocks: .swiftPackage(targetName: "SchemaTestMocks"), - operationManifest: .init(path: "/operation/identifiers/path") + testMocks: .swiftPackage(targetName: "SchemaTestMocks") ), options: .init( additionalInflectionRules: [ @@ -44,7 +43,6 @@ class ApolloCodegenConfigurationCodableTests: XCTestCase { ], deprecatedEnumCases: .exclude, schemaDocumentation: .exclude, - operationDocumentFormat: .definition, cocoapodsCompatibleImportStatements: true, warningsOnDeprecatedUsage: .exclude, conversionStrategies:.init(enumCases: .none), @@ -53,6 +51,11 @@ class ApolloCodegenConfigurationCodableTests: XCTestCase { experimentalFeatures: .init( clientControlledNullability: true, legacySafelistingCompatibleOperations: true + ), + operationManifest: .init( + path: "/operation/identifiers/path", + version: .persistedQueries, + generateManifestOnCodeGeneration: false ) ) } @@ -72,6 +75,11 @@ class ApolloCodegenConfigurationCodableTests: XCTestCase { "/path/to/schema.graphqls" ] }, + "operationManifest" : { + "generateManifestOnCodeGeneration" : false, + "path" : "/operation/identifiers/path", + "version" : "persistedQueries" + }, "options" : { "additionalInflectionRules" : [ { @@ -97,10 +105,6 @@ class ApolloCodegenConfigurationCodableTests: XCTestCase { "warningsOnDeprecatedUsage" : "exclude" }, "output" : { - "operationManifest" : { - "path" : "/operation/identifiers/path", - "version" : "persistedQueries" - }, "operations" : { "absolute" : { "accessModifier" : "internal", diff --git a/Tests/ApolloCodegenTests/ApolloCodegenConfigurationTests.swift b/Tests/ApolloCodegenTests/ApolloCodegenConfigurationTests.swift index 0472fc75ab..9229f2231b 100644 --- a/Tests/ApolloCodegenTests/ApolloCodegenConfigurationTests.swift +++ b/Tests/ApolloCodegenTests/ApolloCodegenConfigurationTests.swift @@ -69,7 +69,6 @@ class ApolloCodegenConfigurationTests: XCTestCase { ) // then - expect(output.operationManifest).to(beNil()) expect(output.operations).to(equal(.inSchemaModule)) } @@ -85,6 +84,5 @@ class ApolloCodegenConfigurationTests: XCTestCase { expect(config.options.additionalInflectionRules).to(beEmpty()) expect(config.options.deprecatedEnumCases).to(equal(.include)) expect(config.options.schemaDocumentation).to(equal(.include)) - expect(config.options.operationDocumentFormat).to(equal([.definition])) } } diff --git a/Tests/ApolloCodegenTests/ApolloCodegenTests.swift b/Tests/ApolloCodegenTests/ApolloCodegenTests.swift index 09633f3f9a..8fe421e4e6 100644 --- a/Tests/ApolloCodegenTests/ApolloCodegenTests.swift +++ b/Tests/ApolloCodegenTests/ApolloCodegenTests.swift @@ -671,7 +671,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -768,7 +769,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -871,7 +873,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -935,7 +938,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -1037,7 +1041,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -1137,7 +1142,8 @@ class ApolloCodegenTests: XCTestCase { compilationResult: compilationResult, ir: ir, config: config, - fileManager: fileManager + fileManager: fileManager, + itemsToGenerate: [.code] ) // then @@ -1184,7 +1190,7 @@ class ApolloCodegenTests: XCTestCase { options: .init(pruneGeneratedFiles: false) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testFile)).to(beTrue()) @@ -1241,7 +1247,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testFile)).to(beFalse()) @@ -1295,7 +1301,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testFile)).to(beFalse()) @@ -1364,7 +1370,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testGeneratedFileInRootPath)).to(beFalse()) @@ -1437,7 +1443,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testGeneratedFileInRootPath)).to(beFalse()) @@ -1495,13 +1501,13 @@ class ApolloCodegenTests: XCTestCase { // then // running codegen multiple times to validate symlink related file creation/deletion bug - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) expect(ApolloFileManager.default.doesFileExist(atPath: fileValidationPath)).to(beTrue()) } @@ -1586,7 +1592,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testGeneratedFileInRootPath)).to(beTrue()) @@ -1694,7 +1700,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testGeneratedFileInRootPath)).to(beTrue()) @@ -1796,7 +1802,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testGeneratedFileInRootPath)).to(beTrue()) @@ -1861,7 +1867,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testFile)).to(beFalse()) @@ -1917,7 +1923,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testInTestMocksFolderFile)).to(beFalse()) @@ -1975,7 +1981,7 @@ class ApolloCodegenTests: XCTestCase { ) ) - try ApolloCodegen.build(with: config, rootURL: directoryURL) + try ApolloCodegen.build(with: config, withRootURL: directoryURL) // then expect(ApolloFileManager.default.doesFileExist(atPath: testInTestMocksFolderFile)).to(beFalse()) diff --git a/Tests/ApolloCodegenTests/CodeGeneration/FileGenerators/OperationManifestFileGeneratorTests.swift b/Tests/ApolloCodegenTests/CodeGeneration/FileGenerators/OperationManifestFileGeneratorTests.swift index f9bc46aff0..5237fc2fcf 100644 --- a/Tests/ApolloCodegenTests/CodeGeneration/FileGenerators/OperationManifestFileGeneratorTests.swift +++ b/Tests/ApolloCodegenTests/CodeGeneration/FileGenerators/OperationManifestFileGeneratorTests.swift @@ -22,9 +22,9 @@ class OperationManifestFileGeneratorTests: XCTestCase { private func buildSubject( path: String? = nil, - version: ApolloCodegenConfiguration.OperationManifestFileOutput.Version = .legacyAPQ + version: ApolloCodegenConfiguration.OperationManifestConfiguration.Version = .legacy ) throws { - let manifest: ApolloCodegenConfiguration.OperationManifestFileOutput? = { + let manifest: ApolloCodegenConfiguration.OperationManifestConfiguration? = { guard let path else { return nil } return .init(path: path, version: version) }() @@ -32,9 +32,9 @@ class OperationManifestFileGeneratorTests: XCTestCase { subject = try OperationManifestFileGenerator( config: ApolloCodegen.ConfigurationContext(config: ApolloCodegenConfiguration.mock( output: .init( - schemaTypes: .init(path: "", moduleType: .swiftPackageManager), - operationManifest: manifest - ) + schemaTypes: .init(path: "", moduleType: .swiftPackageManager) + ), + operationManifest: manifest )) ).xctUnwrapped() } @@ -43,10 +43,14 @@ class OperationManifestFileGeneratorTests: XCTestCase { func test__initializer__givenPath_shouldReturnInstance() { // given - let config = ApolloCodegenConfiguration.mock(output: .init( - schemaTypes: .init(path: "", moduleType: .swiftPackageManager), - operationManifest: .init(path: "a/file/path") - )) + let config = ApolloCodegenConfiguration.mock( + output: .init( + schemaTypes: .init(path: "", moduleType: .swiftPackageManager) + ), + operationManifest: .init( + path: "a/file/path" + ) + ) // when let instance = OperationManifestFileGenerator(config: .init(config: config)) @@ -57,10 +61,12 @@ class OperationManifestFileGeneratorTests: XCTestCase { func test__initializer__givenNilPath_shouldReturnNil() { // given - let config = ApolloCodegenConfiguration.mock(output: .init( - schemaTypes: .init(path: "", moduleType: .swiftPackageManager), + let config = ApolloCodegenConfiguration.mock( + output: .init( + schemaTypes: .init(path: "", moduleType: .swiftPackageManager) + ), operationManifest: nil - )) + ) // when let instance = OperationManifestFileGenerator(config: .init(config: config)) @@ -267,9 +273,9 @@ class OperationManifestFileGeneratorTests: XCTestCase { // MARK: - Template Type Selection Tests - func test__template__givenOperationManifestVersion_apqLegacy__isLegacyAPQTemplate() throws { + func test__template__givenOperationManifestVersion_legacy__isLegacyTemplate() throws { // given - try buildSubject(path: "a/path", version: .legacyAPQ) + try buildSubject(path: "a/path", version: .legacy) // when let actual = subject.template diff --git a/Tests/CodegenCLITests/Commands/FetchSchemaTests.swift b/Tests/CodegenCLITests/Commands/FetchSchemaTests.swift index eee003df56..d2d52921fe 100644 --- a/Tests/CodegenCLITests/Commands/FetchSchemaTests.swift +++ b/Tests/CodegenCLITests/Commands/FetchSchemaTests.swift @@ -48,7 +48,7 @@ class FetchSchemaTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } @@ -80,7 +80,7 @@ class FetchSchemaTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } @@ -110,7 +110,7 @@ class FetchSchemaTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } diff --git a/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift b/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift index a035cd5933..20fdff8b0d 100644 --- a/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift +++ b/Tests/CodegenCLITests/Commands/GenerateOperationManifestTests.swift @@ -3,7 +3,6 @@ import Nimble import ApolloInternalTestHelpers @testable import CodegenCLI import ApolloCodegenLib -import ArgumentParser class GenerateOperationManifestTests: XCTestCase { diff --git a/Tests/CodegenCLITests/Commands/GenerateTests.swift b/Tests/CodegenCLITests/Commands/GenerateTests.swift index 773589ef62..b5230a328e 100644 --- a/Tests/CodegenCLITests/Commands/GenerateTests.swift +++ b/Tests/CodegenCLITests/Commands/GenerateTests.swift @@ -144,7 +144,7 @@ class GenerateTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } @@ -185,7 +185,7 @@ class GenerateTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } @@ -234,7 +234,7 @@ class GenerateTests: XCTestCase { var didCallFetch = false MockApolloSchemaDownloader.fetchHandler = { configuration in - expect(configuration).to(equal(mockConfiguration.schemaDownloadConfiguration)) + expect(configuration).to(equal(mockConfiguration.schemaDownload)) didCallFetch = true } diff --git a/Tests/CodegenCLITests/Support/MockApolloCodegen.swift b/Tests/CodegenCLITests/Support/MockApolloCodegen.swift index d1ca6ac2f3..24592d7ba6 100644 --- a/Tests/CodegenCLITests/Support/MockApolloCodegen.swift +++ b/Tests/CodegenCLITests/Support/MockApolloCodegen.swift @@ -7,7 +7,8 @@ class MockApolloCodegen: CodegenProvider { static func build( with configuration: ApolloCodegenConfiguration, - withRootURL rootURL: URL? + withRootURL rootURL: URL?, + itemsToGenerate: ApolloCodegen.ItemsToGenerate ) throws { guard let handler = buildHandler else { fatalError("You must set buildHandler before calling \(#function)!") @@ -20,19 +21,4 @@ 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 a337ab2a65..047adff770 100644 --- a/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift +++ b/Tests/CodegenCLITests/Support/MockApolloCodegenConfiguration.swift @@ -9,15 +9,19 @@ extension ApolloCodegenConfiguration { schemaPath: "./schema.graphqls" ), output: .init( - schemaTypes: .init(path: ".", moduleType: .swiftPackageManager), - operationManifest: .init(path: "./manifest", version: .persistedQueries) + schemaTypes: .init(path: ".", moduleType: .swiftPackageManager) ), options: .init( operationDocumentFormat: [.definition, .operationId] ), - schemaDownloadConfiguration: .init( + schemaDownload: .init( using: .introspection(endpointURL: URL(string: "http://some.server")!), outputPath: "./schema.graphqls" + ), + operationManifest: .init( + path: "./manifest", + version: .persistedQueries, + generateManifestOnCodeGeneration: false ) ) } diff --git a/docs/source/code-generation/codegen-configuration.mdx b/docs/source/code-generation/codegen-configuration.mdx index a2afde1273..76972f2863 100644 --- a/docs/source/code-generation/codegen-configuration.mdx +++ b/docs/source/code-generation/codegen-configuration.mdx @@ -21,7 +21,8 @@ There are a number of base configuration properties, each representing a specifi | [`output`](#file-output) | Location and structure of the generated files and modules. | | [`options`](#output-options) | Rules and options to customize the generated code. | | [`experimentalFeatures`](#experimental-features) | Used to enable experimental features.
*Note: These features could change at any time and are not guaranteed to always be available.* | -| [`schemaDownloadConfiguration`](#schema-download-configuration) | Configuration to fetch a GraphQL schema before generation. | +| [`schemaDownload`](#schema-download-configuration) | Configuration to fetch a GraphQL schema before generation. | +| [`operationManifest`](#operation-manifest-configuration) | Configuration to generate operation manifests for persisted queries | ## Schema namespace @@ -98,7 +99,6 @@ The properties to configure `output` are: | [`schemaTypes`](#schema-types) | Location and structure of the generated schema types files. | | [`operations`](#operations) | Location and structure of the generated operation files such as queries, mutations, subscriptions, and fragments. | | [`testMocks`](#test-mocks) | Location and structure of the test mock operation object files.

If `.none`, test mocks will not be generated. | -| [`operationManifest`](#operation-manifest) | Configures the generation of an operation manifest JSON file for use with persisted queries or [Automatic Persisted Queries](../fetching/apqs). | @@ -395,42 +395,6 @@ Specify the directory for your test mocks using the `path` parameter. This is re > > Test mocks generated this way may also be manually embedded in a test utility module that is imported by your test target. -### Operation Manifest - -Providing a value for this property will generate a JSON document with all your operations and their computed identifier hashes. This document can be used to pre-register the identifiers for your operations with a server that supports [persisted queries or Automatic Persisted Queries](./../fetching/apqs). - -The properties of the Operation Manifest configuration object are: - -| Property Name | Description | -| ----- | ----------- | -| `path` | Local path where the generated operation manifest file should be written. | -| [`version`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/operationmanifestfileoutput/version) | The version format to use when generating the operation manifest. | - - - -```json title="CLI Configuration JSON" -"output": { - "operationManifest" : { - "path" : "./generated/operationIdentifiers.json", - "version" : "persistedQueries" - } -} -``` - -```swift title="Swift Codegen Setup" -let configuration = ApolloCodegenConfiguration( - // Other properties not shown - output: ApolloCodegenConfiguration.FileOutput( - operationManifest: .init( - path: "./generated/operationIdentifiers.json", - version: .persistedQueries - ) - ) -) -``` - - - ## Output options The code generation engine supports a number of configuration options to change the behaviour of the generator and tailor the generated Swift code to your specific needs. @@ -444,7 +408,7 @@ The top-level properties are: | [`deprecatedEnumCases`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/deprecatedenumcases) | Annotate generated Swift enums with the Swift `@available` attribute for GraphQL enum cases annotated with the built-in [`@deprecated` directive](https://spec.graphql.org/draft/#sec--deprecated). | | [`schemaDocumentation`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/schemadocumentation) | Include or exclude [schema documentation](https://spec.graphql.org/draft/#sec-Descriptions) in the generated files. | | [`selectionSetInitializers`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/selectionsetinitializers) | Generate initializers for your generated selection set models. | -| [`operationDocumentFormat`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/operationdocumentformat) | How to generate the operation documents for your generated operations. This can be used to generate operation identifiers for use with a server that supports [persisted queries or Automatic Persisted Queries](./../fetching/apqs) | +| [`operationDocumentFormat`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/operationdocumentformat) | How to generate the operation documents for your generated operations. This can be used to generate operation identifiers for use with a server that supports [Persisted Queries or Automatic Persisted Queries](./../fetching/persisted-queries) | | [`cocoapodsCompatibleImportStatements`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/cocoapodscompatibleimportstatements) | Generate import statements that are compatible with including `Apollo` via Cocoapods. | | [`warningsOnDeprecatedUsage`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/warningsondeprecatedusage) | Annotate generated Swift code with the Swift `@available` attribute and `@deprecated` argument for parts of the GraphQL schema annotated with the built-in [`@deprecated` directive](https://spec.graphql.org/draft/#sec--deprecated). | | [`conversionStrategies`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/outputoptions/conversionstrategies) | Rules for how to convert the names of values from the schema in generated code. | @@ -579,7 +543,7 @@ The properties you will need to configure are: ```json title="CLI Configuration JSON" -"schemaDownloadConfiguration": { +"schemaDownload": { "downloadMethod": { "apolloRegistry": { "_0": { @@ -601,7 +565,7 @@ The properties you will need to configure are: ```swift title="Swift Codegen Setup" let configuration = ApolloCodegenConfiguration( // Other properties not shown - schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration( + schemaDownload: ApolloSchemaDownloadConfiguration( using: .apolloRegistry(.init( apiKey: "your-api-key", graphID: "your-graphid", @@ -635,7 +599,7 @@ The properties you will need to configure are: ```json title="CLI Configuration JSON" -"schemaDownloadConfiguration": { +"schemaDownload": { "downloadMethod": { "introspection": { "endpointURL": "https://server.com", @@ -655,7 +619,7 @@ The properties you will need to configure are: ```swift title="Swift Codegen Setup" let configuration = ApolloCodegenConfiguration( // Other properties not shown - schemaDownloadConfiguration: ApolloSchemaDownloadConfiguration( + schemaDownload: ApolloSchemaDownloadConfiguration( using: .introspection( endpointURL: URL(string: "https://server.com")!), timeout: 60.0, @@ -667,3 +631,36 @@ let configuration = ApolloCodegenConfiguration( For more details, see the section on [downloading a schema](./downloading-schema). + +## Operation Manifest Configuration + +Optional settings used to configure generation of the operation identifier manifest for use with [Persisted Queries](./../fetching/persisted-queries). + +| Property Name | Description | +| ------------- | ----------- | +| `path` | Local path where the generated operation manifest file should be written. | +| [`version`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/operationmanifestfileoutput/version) | The version format to use when generating the operation manifest. | +| [`generateManifestOnCodeGeneration`](https://www.apollographql.com/docs/ios/docc/documentation/apollocodegenlib/apollocodegenconfiguration/operationmanifestconfiguration/generateManifestOnCodeGeneration) | Whether or not the operation manifest should be generated every time code generation is run. Defaults to false. | + + + +```json title="CLI Configuration JSON" +"operationManifest" : { + "generateManifestOnCodeGeneration" : false, + "path" : "/operation/identifiers/path", + "version" : "persistedQueries" +} +``` + +```swift title="Swift Codegen Setup" +let configuration = ApolloCodegenConfiguration( + // Other properties not shown + operationManifest: .init( + path: "./manifest/operationManifest.json", + version: .persistedQueries, + generateManifestOnCodeGeneration: false + ) +) +``` + +