graphql-docs 5.2.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +9 -0
  3. data/CHANGELOG.md +76 -0
  4. data/README.md +404 -8
  5. data/bin/console +6 -7
  6. data/bin/setup +8 -5
  7. data/exe/graphql-docs +9 -9
  8. data/graphql-docs.gemspec +50 -38
  9. data/lib/graphql-docs/app.rb +488 -0
  10. data/lib/graphql-docs/configuration.rb +28 -11
  11. data/lib/graphql-docs/generator.rb +147 -42
  12. data/lib/graphql-docs/helpers.rb +132 -12
  13. data/lib/graphql-docs/layouts/assets/_sass/_api-box.scss +2 -2
  14. data/lib/graphql-docs/layouts/assets/_sass/_content.scss +20 -19
  15. data/lib/graphql-docs/layouts/assets/_sass/_deprecations.scss +7 -0
  16. data/lib/graphql-docs/layouts/assets/_sass/_fonts.scss +6 -21
  17. data/lib/graphql-docs/layouts/assets/_sass/_header.scss +8 -2
  18. data/lib/graphql-docs/layouts/assets/_sass/_mobile.scss +10 -3
  19. data/lib/graphql-docs/layouts/assets/_sass/_search.scss +32 -10
  20. data/lib/graphql-docs/layouts/assets/_sass/_sidebar.scss +22 -15
  21. data/lib/graphql-docs/layouts/assets/_sass/_syntax.scss +1 -1
  22. data/lib/graphql-docs/layouts/assets/css/screen.scss +54 -5
  23. data/lib/graphql-docs/layouts/default.html +75 -1
  24. data/lib/graphql-docs/layouts/includes/sidebar.html +4 -2
  25. data/lib/graphql-docs/parser.rb +89 -33
  26. data/lib/graphql-docs/renderer.rb +98 -31
  27. data/lib/graphql-docs/version.rb +2 -1
  28. data/lib/graphql-docs.rb +78 -12
  29. data/lib/tasks/graphql-docs.rake +57 -0
  30. metadata +74 -78
  31. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
  32. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  33. data/.github/workflows/tests.yml +0 -21
  34. data/.gitignore +0 -15
  35. data/.rubocop.yml +0 -11
  36. data/CODE_OF_CONDUCT.md +0 -84
  37. data/CONTRIBUTING.md +0 -18
  38. data/Gemfile +0 -6
  39. data/Rakefile +0 -86
  40. data/lib/graphql-docs/layouts/assets/images/graphiql-headers.png +0 -0
  41. data/lib/graphql-docs/layouts/assets/images/graphiql-variables.png +0 -0
  42. data/lib/graphql-docs/layouts/assets/images/graphiql.png +0 -0
  43. data/lib/graphql-docs/layouts/assets/images/search.svg +0 -3
  44. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.eot +0 -0
  45. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.ttf +0 -0
  46. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.woff +0 -0
  47. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.woff2 +0 -0
  48. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.eot +0 -0
  49. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.ttf +0 -0
  50. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.woff +0 -0
  51. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.woff2 +0 -0
  52. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.eot +0 -0
  53. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.ttf +0 -0
  54. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.woff +0 -0
  55. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.woff2 +0 -0
  56. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.eot +0 -0
  57. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.ttf +0 -0
  58. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.woff +0 -0
  59. data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.woff2 +0 -0
@@ -1,23 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql'
3
+ require "graphql"
4
4
 
5
5
  module GraphQLDocs
6
+ # Parses GraphQL schemas into a structured format for documentation generation.
7
+ #
8
+ # The Parser takes a GraphQL schema (as a string or schema class) and transforms it
9
+ # into a normalized hash structure that's easier to work with during HTML generation.
10
+ # It extracts all types, fields, arguments, and metadata from the schema.
11
+ #
12
+ # @example Basic usage
13
+ # parser = GraphQLDocs::Parser.new(schema_string, options)
14
+ # parsed = parser.parse
15
+ #
16
+ # @example Accessing parsed data
17
+ # parsed[:object_types] # => Array of object type hashes
18
+ # parsed[:query_types] # => Array of query hashes
6
19
  class Parser
7
20
  include Helpers
8
21
 
22
+ # @!attribute [r] processed_schema
23
+ # @return [Hash] The parsed schema structure containing all GraphQL types
9
24
  attr_reader :processed_schema
10
25
 
26
+ # Initializes a new Parser instance.
27
+ #
28
+ # @param schema [String, GraphQL::Schema] GraphQL schema as IDL string or schema class
29
+ # @param options [Hash] Configuration options including the notices proc
30
+ # @option options [Proc] :notices Proc for adding custom notices to schema members
11
31
  def initialize(schema, options)
12
32
  @options = options
13
33
 
14
34
  @options[:notices] ||= ->(_schema_member_path) { [] }
15
35
 
16
36
  @schema = if schema.is_a?(String)
17
- GraphQL::Schema.from_definition(schema)
18
- elsif schema < GraphQL::Schema
19
- schema
20
- end
37
+ GraphQL::Schema.from_definition(schema)
38
+ elsif schema < GraphQL::Schema
39
+ schema
40
+ end
21
41
 
22
42
  @processed_schema = {
23
43
  operation_types: [],
@@ -33,6 +53,34 @@ module GraphQLDocs
33
53
  }
34
54
  end
35
55
 
56
+ # Parses the GraphQL schema into a structured hash.
57
+ #
58
+ # This method processes the entire schema and extracts all types, including:
59
+ # - Operation types (Query, Mutation)
60
+ # - Object types
61
+ # - Interface types
62
+ # - Enum types
63
+ # - Union types
64
+ # - Input object types
65
+ # - Scalar types
66
+ # - Directive types
67
+ #
68
+ # Each type includes its fields, arguments, deprecation notices, and custom notices.
69
+ #
70
+ # @return [Hash] Structured hash containing all parsed schema data with keys:
71
+ # - :root_types - Query and Mutation root type names
72
+ # - :operation_types - Array of operation type hashes
73
+ # - :query_types - Array of query field hashes
74
+ # - :mutation_types - Array of mutation field hashes
75
+ # - :object_types - Array of object type hashes
76
+ # - :interface_types - Array of interface type hashes
77
+ # - :enum_types - Array of enum type hashes
78
+ # - :union_types - Array of union type hashes
79
+ # - :input_object_types - Array of input object type hashes
80
+ # - :scalar_types - Array of scalar type hashes
81
+ # - :directive_types - Array of directive hashes
82
+ #
83
+ # @raise [TypeError] If an unknown GraphQL type is encountered
36
84
  def parse
37
85
  root_types = {}
38
86
  %w[query mutation].each do |operation|
@@ -49,7 +97,7 @@ module GraphQLDocs
49
97
  data[:name] = object.graphql_name
50
98
  data[:description] = object.description
51
99
 
52
- if data[:name] == root_types['query']
100
+ if data[:name] == root_types["query"]
53
101
  data[:interfaces] = object.interfaces.map(&:graphql_name).sort
54
102
  data[:fields], data[:connections] = fetch_fields(object.fields, object.graphql_name)
55
103
  @processed_schema[:operation_types] << data
@@ -57,36 +105,44 @@ module GraphQLDocs
57
105
  object.fields.each_value do |query|
58
106
  h = {}
59
107
 
60
- h[:notices] = @options[:notices].call([object.graphql_name, query.graphql_name].join('.'))
108
+ h[:notices] = @options[:notices].call([object.graphql_name, query.graphql_name].join("."))
61
109
  h[:name] = query.graphql_name
62
110
  h[:description] = query.description
63
- h[:arguments], = fetch_fields(query.arguments, [object.graphql_name, query.graphql_name].join('.'))
111
+ if query.respond_to?(:deprecation_reason) && !query.deprecation_reason.nil?
112
+ h[:is_deprecated] = true
113
+ h[:deprecation_reason] = query.deprecation_reason
114
+ end
115
+ h[:arguments], = fetch_fields(query.arguments, [object.graphql_name, query.graphql_name].join("."))
64
116
 
65
117
  return_type = query.type
66
118
  if return_type.unwrap.respond_to?(:fields)
67
119
  h[:return_fields], = fetch_fields(return_type.unwrap.fields, return_type.graphql_name)
68
120
  else # it is a scalar return type
69
- h[:return_fields], = fetch_fields({ return_type.graphql_name => query }, return_type.graphql_name)
121
+ h[:return_fields], = fetch_fields({return_type.graphql_name => query}, return_type.graphql_name)
70
122
  end
71
123
 
72
124
  @processed_schema[:query_types] << h
73
125
  end
74
- elsif data[:name] == root_types['mutation']
126
+ elsif data[:name] == root_types["mutation"]
75
127
  @processed_schema[:operation_types] << data
76
128
 
77
129
  object.fields.each_value do |mutation|
78
130
  h = {}
79
131
 
80
- h[:notices] = @options[:notices].call([object.graphql_name, mutation.graphql_name].join('.'))
132
+ h[:notices] = @options[:notices].call([object.graphql_name, mutation.graphql_name].join("."))
81
133
  h[:name] = mutation.graphql_name
82
134
  h[:description] = mutation.description
83
- h[:input_fields], = fetch_fields(mutation.arguments, [object.graphql_name, mutation.graphql_name].join('.'))
135
+ if mutation.respond_to?(:deprecation_reason) && !mutation.deprecation_reason.nil?
136
+ h[:is_deprecated] = true
137
+ h[:deprecation_reason] = mutation.deprecation_reason
138
+ end
139
+ h[:input_fields], = fetch_fields(mutation.arguments, [object.graphql_name, mutation.graphql_name].join("."))
84
140
 
85
141
  return_type = mutation.type
86
142
  if return_type.unwrap.respond_to?(:fields)
87
143
  h[:return_fields], = fetch_fields(return_type.unwrap.fields, return_type.graphql_name)
88
144
  else # it is a scalar return type
89
- h[:return_fields], = fetch_fields({ return_type.graphql_name => mutation }, return_type.graphql_name)
145
+ h[:return_fields], = fetch_fields({return_type.graphql_name => mutation}, return_type.graphql_name)
90
146
  end
91
147
 
92
148
  @processed_schema[:mutation_types] << h
@@ -109,7 +165,7 @@ module GraphQLDocs
109
165
 
110
166
  data[:values] = object.values.values.map do |val|
111
167
  h = {}
112
- h[:notices] = @options[:notices].call([object.graphql_name, val.graphql_name].join('.'))
168
+ h[:notices] = @options[:notices].call([object.graphql_name, val.graphql_name].join("."))
113
169
  h[:name] = val.graphql_name
114
170
  h[:description] = val.description
115
171
  unless val.deprecation_reason.nil?
@@ -177,7 +233,7 @@ module GraphQLDocs
177
233
  object_fields.each_value do |field|
178
234
  hash = {}
179
235
 
180
- hash[:notices] = @options[:notices].call([parent_path, field.graphql_name].join('.'))
236
+ hash[:notices] = @options[:notices].call([parent_path, field.graphql_name].join("."))
181
237
  hash[:name] = field.graphql_name
182
238
  hash[:description] = field.description
183
239
  if field.respond_to?(:deprecation_reason) && !field.deprecation_reason.nil?
@@ -217,24 +273,24 @@ module GraphQLDocs
217
273
  name = type.unwrap.graphql_name
218
274
 
219
275
  path = if type.unwrap < GraphQL::Schema::Object
220
- if name == 'Query'
221
- 'operation'
222
- else
223
- 'object'
224
- end
225
- elsif type.unwrap < GraphQL::Schema::Scalar
226
- 'scalar'
227
- elsif type.unwrap < GraphQL::Schema::Interface
228
- 'interface'
229
- elsif type.unwrap < GraphQL::Schema::Enum
230
- 'enum'
231
- elsif type.unwrap < GraphQL::Schema::InputObject
232
- 'input_object'
233
- elsif type.unwrap < GraphQL::Schema::Union
234
- 'union'
235
- else
236
- raise TypeError, "Unknown type for `#{name}`: `#{type.unwrap.class}`"
237
- end
276
+ if name == "Query"
277
+ "operation"
278
+ else
279
+ "object"
280
+ end
281
+ elsif type.unwrap < GraphQL::Schema::Scalar
282
+ "scalar"
283
+ elsif type.unwrap < GraphQL::Schema::Interface
284
+ "interface"
285
+ elsif type.unwrap < GraphQL::Schema::Enum
286
+ "enum"
287
+ elsif type.unwrap < GraphQL::Schema::InputObject
288
+ "input_object"
289
+ elsif type.unwrap < GraphQL::Schema::Union
290
+ "union"
291
+ else
292
+ raise TypeError, "Unknown type for `#{name}`: `#{type.unwrap.class}`"
293
+ end
238
294
 
239
295
  {
240
296
  name: name,
@@ -1,16 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'html/pipeline'
4
- require 'yaml'
5
- require 'extended-markdown-filter'
6
- require 'ostruct'
3
+ require "html_pipeline"
4
+ require "gemoji"
5
+ require "yaml"
6
+ require "ostruct"
7
7
 
8
8
  module GraphQLDocs
9
+ # Renders documentation content into HTML.
10
+ #
11
+ # The Renderer takes parsed schema content and converts it to HTML using html-pipeline.
12
+ # It applies markdown processing, emoji support, and other filters, then wraps the
13
+ # result in the default layout template.
14
+ #
15
+ # @example Basic usage
16
+ # renderer = GraphQLDocs::Renderer.new(parsed_schema, options)
17
+ # html = renderer.render(markdown_content, type: 'object', name: 'User')
18
+ #
19
+ # @example Custom renderer
20
+ # class MyRenderer < GraphQLDocs::Renderer
21
+ # def render(contents, type: nil, name: nil, filename: nil)
22
+ # # Custom rendering logic
23
+ # super
24
+ # end
25
+ # end
9
26
  class Renderer
10
27
  include Helpers
11
28
 
29
+ # @!attribute [r] options
30
+ # @return [Hash] Configuration options for the renderer
12
31
  attr_reader :options
13
32
 
33
+ # Initializes a new Renderer instance.
34
+ #
35
+ # @param parsed_schema [Hash] The parsed schema from {Parser#parse}
36
+ # @param options [Hash] Configuration options
37
+ # @option options [Hash] :templates Template file paths
38
+ # @option options [Hash] :pipeline_config html-pipeline configuration
14
39
  def initialize(parsed_schema, options)
15
40
  @parsed_schema = parsed_schema
16
41
  @options = options
@@ -18,51 +43,93 @@ module GraphQLDocs
18
43
  @graphql_default_layout = ERB.new(File.read(@options[:templates][:default])) unless @options[:templates][:default].nil?
19
44
 
20
45
  @pipeline_config = @options[:pipeline_config] || {}
21
- pipeline = @pipeline_config[:pipeline] || {}
22
46
  context = @pipeline_config[:context] || {}
23
47
 
24
- filters = pipeline.map do |f|
25
- if filter?(f)
26
- f
27
- else
28
- key = filter_key(f)
29
- filter = HTML::Pipeline.constants.find { |c| c.downcase == key }
30
- # possibly a custom filter
31
- if filter.nil?
32
- Kernel.const_get(f)
33
- else
34
- HTML::Pipeline.const_get(filter)
35
- end
36
- end
37
- end
48
+ # Convert context for html-pipeline 3
49
+ @pipeline_context = {}
50
+ @pipeline_context[:unsafe] = context[:unsafe] if context.key?(:unsafe)
51
+ @pipeline_context[:asset_root] = context[:asset_root] if context.key?(:asset_root)
38
52
 
39
- @pipeline = HTML::Pipeline.new(filters, context)
53
+ # html-pipeline 3 uses a simplified API - we'll just use text-to-text processing
54
+ # since markdown conversion is handled by commonmarker directly
55
+ @pipeline = nil # We'll handle markdown conversion directly in to_html
40
56
  end
41
57
 
58
+ # Renders content into complete HTML with layout.
59
+ #
60
+ # This method converts the content through the html-pipeline filters and wraps it
61
+ # in the default layout template. If the method returns nil, no file will be written.
62
+ #
63
+ # @param contents [String] Content to render (typically Markdown)
64
+ # @param type [String, nil] GraphQL type category (e.g., 'object', 'interface')
65
+ # @param name [String, nil] Name of the GraphQL type being rendered
66
+ # @param filename [String, nil] Output filename path
67
+ # @return [String, nil] Rendered HTML content, or nil to skip file generation
68
+ #
69
+ # @example
70
+ # html = renderer.render(markdown, type: 'object', name: 'User')
42
71
  def render(contents, type: nil, name: nil, filename: nil)
43
- opts = { base_url: @options[:base_url], output_dir: @options[:output_dir] }.merge({ type: type, name: name, filename: filename }).merge(helper_methods)
72
+ # Include all options (like Generator does) to support YAML frontmatter variables like title
73
+ opts = @options.merge({type: type, name: name, filename: filename}).merge(helper_methods)
44
74
 
45
- contents = to_html(contents, context: { filename: filename })
75
+ contents = to_html(contents, context: {filename: filename})
46
76
  return contents if @graphql_default_layout.nil?
47
77
 
48
78
  opts[:content] = contents
49
79
  @graphql_default_layout.result(OpenStruct.new(opts).instance_eval { binding })
50
80
  end
51
81
 
82
+ # Converts a string to HTML using commonmarker with emoji support.
83
+ #
84
+ # @param string [String] Content to convert
85
+ # @param context [Hash] Additional context (unused, kept for compatibility)
86
+ # @return [String] HTML output
87
+ #
88
+ # @api private
52
89
  def to_html(string, context: {})
53
- @pipeline.to_html(string, context)
54
- end
90
+ return "" if string.nil?
91
+ return "" if string.empty?
55
92
 
56
- private
93
+ begin
94
+ # Replace emoji shortcodes before markdown processing
95
+ string_with_emoji = emojify(string)
96
+
97
+ # Commonmarker 2.x uses parse/render API
98
+ # Parse with GitHub Flavored Markdown extensions enabled by default
99
+ doc = ::Commonmarker.parse(string_with_emoji)
57
100
 
58
- def filter_key(str)
59
- str.downcase
101
+ # Convert to HTML - commonmarker 2.x automatically includes:
102
+ # - GitHub Flavored Markdown (tables, strikethrough, etc.)
103
+ # - Header anchors with IDs
104
+ # - Safe HTML by default (unless unsafe mode is enabled)
105
+ html = if @pipeline_context[:unsafe]
106
+ doc.to_html(options: {render: {unsafe: true}})
107
+ else
108
+ doc.to_html
109
+ end
110
+
111
+ # Strip trailing newline that commonmarker adds
112
+ html.chomp
113
+ rescue => e
114
+ # Log error and return safe fallback
115
+ warn "Failed to parse markdown: #{e.message}"
116
+ require "cgi" unless defined?(CGI)
117
+ CGI.escapeHTML(string.to_s)
118
+ end
60
119
  end
61
120
 
62
- def filter?(filter)
63
- filter < HTML::Pipeline::Filter
64
- rescue LoadError, ArgumentError
65
- false
121
+ # Converts emoji shortcodes like :smile: to emoji characters
122
+ #
123
+ # @param string [String] Text containing emoji shortcodes
124
+ # @return [String] Text with shortcodes replaced by emoji
125
+ # @api private
126
+ def emojify(string)
127
+ string.gsub(/:([a-z0-9_+-]+):/) do |match|
128
+ emoji = Emoji.find_by_alias(Regexp.last_match(1))
129
+ emoji ? emoji.raw : match
130
+ end
66
131
  end
132
+
133
+ private
67
134
  end
68
135
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphQLDocs
4
- VERSION = '5.2.0'
4
+ # Current version of the GraphQLDocs gem
5
+ VERSION = "6.0.0"
5
6
  end
data/lib/graphql-docs.rb CHANGED
@@ -1,14 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'graphql-docs/helpers'
4
- require 'graphql-docs/renderer'
5
- require 'graphql-docs/configuration'
6
- require 'graphql-docs/generator'
7
- require 'graphql-docs/parser'
8
- require 'graphql-docs/version'
3
+ require "graphql-docs/helpers"
4
+ require "graphql-docs/renderer"
5
+ require "graphql-docs/configuration"
6
+ require "graphql-docs/generator"
7
+ require "graphql-docs/parser"
8
+ require "graphql-docs/version"
9
9
 
10
+ # Lazy-load the Rack app - only loads if Rack is available
11
+ begin
12
+ require "graphql-docs/app" if defined?(Rack)
13
+ rescue LoadError
14
+ # Rack not available, App class won't be loaded
15
+ end
16
+
17
+ # GraphQLDocs is a library for generating beautiful HTML documentation from GraphQL schemas.
18
+ # It parses GraphQL schema files or schema objects and generates a complete documentation website
19
+ # with customizable templates and styling.
20
+ #
21
+ # @example Generate docs from a file
22
+ # GraphQLDocs.build(filename: 'schema.graphql')
23
+ #
24
+ # @example Generate docs from a schema string
25
+ # GraphQLDocs.build(schema: schema_string)
26
+ #
27
+ # @example Generate docs from a schema class
28
+ # schema = GraphQL::Schema.define { query query_type }
29
+ # GraphQLDocs.build(schema: schema)
30
+ #
31
+ # @see Configuration For available configuration options
10
32
  module GraphQLDocs
11
33
  class << self
34
+ # Builds HTML documentation from a GraphQL schema.
35
+ #
36
+ # This is the main entry point for generating documentation. It accepts either a schema file path
37
+ # or a schema string/object, parses it, and generates a complete HTML documentation website.
38
+ #
39
+ # @param options [Hash] Configuration options for generating the documentation
40
+ # @option options [String] :filename Path to GraphQL schema IDL file
41
+ # @option options [String, GraphQL::Schema] :schema GraphQL schema as string or schema class
42
+ # @option options [String] :output_dir ('./output/') Directory where HTML will be generated
43
+ # @option options [Boolean] :use_default_styles (true) Whether to include default CSS styles
44
+ # @option options [String] :base_url ('') Base URL to prepend for assets and links
45
+ # @option options [Boolean] :delete_output (false) Delete output directory before generating
46
+ # @option options [Hash] :pipeline_config Configuration for html-pipeline rendering
47
+ # @option options [Class] :renderer (GraphQLDocs::Renderer) Custom renderer class
48
+ # @option options [Hash] :templates Custom template file paths
49
+ # @option options [Hash] :landing_pages Custom landing page file paths
50
+ # @option options [Hash] :classes Additional CSS class names for elements
51
+ # @option options [Proc] :notices Proc for adding notices to schema members
52
+ #
53
+ # @return [Boolean] Returns true on successful generation
54
+ #
55
+ # @raise [ArgumentError] If both filename and schema are provided, or if neither is provided
56
+ # @raise [ArgumentError] If the specified filename does not exist
57
+ # @raise [TypeError] If filename is not a String
58
+ # @raise [TypeError] If schema is not a String or GraphQL::Schema
59
+ #
60
+ # @example Basic usage with file
61
+ # GraphQLDocs.build(filename: 'schema.graphql')
62
+ #
63
+ # @example With custom options
64
+ # GraphQLDocs.build(
65
+ # filename: 'schema.graphql',
66
+ # output_dir: './docs',
67
+ # base_url: '/api-docs',
68
+ # delete_output: true
69
+ # )
70
+ #
71
+ # @example With custom renderer
72
+ # class MyRenderer < GraphQLDocs::Renderer
73
+ # def render(contents, type: nil, name: nil)
74
+ # # Custom rendering logic
75
+ # end
76
+ # end
77
+ # GraphQLDocs.build(filename: 'schema.graphql', renderer: MyRenderer)
78
+ #
79
+ # @see Configuration::GRAPHQLDOCS_DEFAULTS For all available options
12
80
  def build(options)
13
81
  # do not let user provided values overwrite every single value
14
82
  %i[templates landing_pages].each do |opt|
@@ -24,9 +92,9 @@ module GraphQLDocs
24
92
  filename = options[:filename]
25
93
  schema = options[:schema]
26
94
 
27
- raise ArgumentError, 'Pass in `filename` or `schema`, but not both!' if !filename.nil? && !schema.nil?
95
+ raise ArgumentError, "Pass in `filename` or `schema`, but not both!" if !filename.nil? && !schema.nil?
28
96
 
29
- raise ArgumentError, 'Pass in either `filename` or `schema`' if filename.nil? && schema.nil?
97
+ raise ArgumentError, "Pass in either `filename` or `schema`" if filename.nil? && schema.nil?
30
98
 
31
99
  if filename
32
100
  raise TypeError, "Expected `String`, got `#{filename.class}`" unless filename.is_a?(String)
@@ -34,10 +102,8 @@ module GraphQLDocs
34
102
  raise ArgumentError, "#{filename} does not exist!" unless File.exist?(filename)
35
103
 
36
104
  schema = File.read(filename)
37
- else
38
- raise TypeError, "Expected `String` or `GraphQL::Schema`, got `#{schema.class}`" if !schema.is_a?(String) && !schema_type?(schema)
39
-
40
- schema = schema
105
+ elsif !schema.is_a?(String) && !schema_type?(schema)
106
+ raise TypeError, "Expected `String` or `GraphQL::Schema`, got `#{schema.class}`"
41
107
  end
42
108
 
43
109
  parser = GraphQLDocs::Parser.new(schema, options)
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "graphql-docs"
5
+
6
+ namespace :"graphql-docs" do
7
+ desc "Generate GraphQL documentation from schema"
8
+ task :generate, [:schema_file, :output_dir, :base_url, :delete_output] do |_t, args|
9
+ options = {}
10
+
11
+ # Prefer task arguments over environment variables
12
+ options[:filename] = args[:schema_file] || ENV["GRAPHQL_SCHEMA_FILE"]
13
+ options[:output_dir] = args[:output_dir] || ENV["GRAPHQL_OUTPUT_DIR"]
14
+ options[:base_url] = args[:base_url] || ENV["GRAPHQL_BASE_URL"]
15
+
16
+ # Handle delete_output as a boolean
17
+ delete_output_arg = args[:delete_output] || ENV["GRAPHQL_DELETE_OUTPUT"]
18
+ options[:delete_output] = delete_output_arg == "true" if delete_output_arg
19
+
20
+ # Check if a schema file is specified
21
+ if options[:filename].nil?
22
+ puts "Please specify a GraphQL schema file:"
23
+ puts ""
24
+ puts "Using task arguments:"
25
+ puts " rake graphql-docs:generate[path/to/schema.graphql]"
26
+ puts " rake graphql-docs:generate[schema.graphql,./docs]"
27
+ puts " rake graphql-docs:generate[schema.graphql,./docs,/api-docs,true]"
28
+ puts ""
29
+ puts "Or using environment variables:"
30
+ puts " GRAPHQL_SCHEMA_FILE=path/to/schema.graphql rake graphql-docs:generate"
31
+ puts ""
32
+ puts "Available arguments (in order):"
33
+ puts " 1. schema_file - Path to GraphQL schema file (required)"
34
+ puts " 2. output_dir - Output directory (default: ./output/)"
35
+ puts " 3. base_url - Base URL for assets and links (default: '')"
36
+ puts " 4. delete_output - Delete output directory before generating (true/false, default: false)"
37
+ puts ""
38
+ puts "Available environment variables:"
39
+ puts " GRAPHQL_SCHEMA_FILE - Path to GraphQL schema file (required)"
40
+ puts " GRAPHQL_OUTPUT_DIR - Output directory (default: ./output/)"
41
+ puts " GRAPHQL_BASE_URL - Base URL for assets and links (default: '')"
42
+ puts " GRAPHQL_DELETE_OUTPUT - Delete output directory before generating (true/false)"
43
+ exit 1
44
+ end
45
+
46
+ puts "Generating GraphQL documentation..."
47
+ puts " Schema: #{options[:filename]}"
48
+ puts " Output: #{options[:output_dir] || "./output/"}"
49
+
50
+ GraphQLDocs.build(options)
51
+
52
+ puts "Documentation generated successfully!"
53
+ end
54
+ end
55
+ rescue LoadError
56
+ # graphql-docs not available, skip task definition
57
+ end