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.
- checksums.yaml +4 -4
- data/.yardopts +9 -0
- data/CHANGELOG.md +76 -0
- data/README.md +404 -8
- data/bin/console +6 -7
- data/bin/setup +8 -5
- data/exe/graphql-docs +9 -9
- data/graphql-docs.gemspec +50 -38
- data/lib/graphql-docs/app.rb +488 -0
- data/lib/graphql-docs/configuration.rb +28 -11
- data/lib/graphql-docs/generator.rb +147 -42
- data/lib/graphql-docs/helpers.rb +132 -12
- data/lib/graphql-docs/layouts/assets/_sass/_api-box.scss +2 -2
- data/lib/graphql-docs/layouts/assets/_sass/_content.scss +20 -19
- data/lib/graphql-docs/layouts/assets/_sass/_deprecations.scss +7 -0
- data/lib/graphql-docs/layouts/assets/_sass/_fonts.scss +6 -21
- data/lib/graphql-docs/layouts/assets/_sass/_header.scss +8 -2
- data/lib/graphql-docs/layouts/assets/_sass/_mobile.scss +10 -3
- data/lib/graphql-docs/layouts/assets/_sass/_search.scss +32 -10
- data/lib/graphql-docs/layouts/assets/_sass/_sidebar.scss +22 -15
- data/lib/graphql-docs/layouts/assets/_sass/_syntax.scss +1 -1
- data/lib/graphql-docs/layouts/assets/css/screen.scss +54 -5
- data/lib/graphql-docs/layouts/default.html +75 -1
- data/lib/graphql-docs/layouts/includes/sidebar.html +4 -2
- data/lib/graphql-docs/parser.rb +89 -33
- data/lib/graphql-docs/renderer.rb +98 -31
- data/lib/graphql-docs/version.rb +2 -1
- data/lib/graphql-docs.rb +78 -12
- data/lib/tasks/graphql-docs.rake +57 -0
- metadata +74 -78
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -31
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- data/.github/workflows/tests.yml +0 -21
- data/.gitignore +0 -15
- data/.rubocop.yml +0 -11
- data/CODE_OF_CONDUCT.md +0 -84
- data/CONTRIBUTING.md +0 -18
- data/Gemfile +0 -6
- data/Rakefile +0 -86
- data/lib/graphql-docs/layouts/assets/images/graphiql-headers.png +0 -0
- data/lib/graphql-docs/layouts/assets/images/graphiql-variables.png +0 -0
- data/lib/graphql-docs/layouts/assets/images/graphiql.png +0 -0
- data/lib/graphql-docs/layouts/assets/images/search.svg +0 -3
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.eot +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.ttf +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.woff +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_B_0.woff2 +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.eot +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.ttf +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.woff +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_C_0.woff2 +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.eot +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.ttf +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.woff +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_D_0.woff2 +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.eot +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.ttf +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.woff +0 -0
- data/lib/graphql-docs/layouts/assets/webfonts/2C4B9D_E_0.woff2 +0 -0
data/lib/graphql-docs/parser.rb
CHANGED
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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[
|
|
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
|
-
|
|
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({
|
|
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[
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
54
|
-
|
|
90
|
+
return "" if string.nil?
|
|
91
|
+
return "" if string.empty?
|
|
55
92
|
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
data/lib/graphql-docs/version.rb
CHANGED
data/lib/graphql-docs.rb
CHANGED
|
@@ -1,14 +1,82 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
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,
|
|
95
|
+
raise ArgumentError, "Pass in `filename` or `schema`, but not both!" if !filename.nil? && !schema.nil?
|
|
28
96
|
|
|
29
|
-
raise ArgumentError,
|
|
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
|
-
|
|
38
|
-
raise TypeError, "Expected `String` or `GraphQL::Schema`, got `#{schema.class}`"
|
|
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
|