docyard 1.0.1 → 1.1.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/CHANGELOG.md +36 -1
- data/lib/docyard/build/asset_bundler.rb +9 -34
- data/lib/docyard/build/file_copier.rb +7 -15
- data/lib/docyard/build/llms_txt_generator.rb +0 -2
- data/lib/docyard/build/sitemap_generator.rb +1 -1
- data/lib/docyard/build/static_generator.rb +32 -33
- data/lib/docyard/build/step_runner.rb +88 -0
- data/lib/docyard/build/validator.rb +98 -0
- data/lib/docyard/builder.rb +82 -55
- data/lib/docyard/cli.rb +36 -4
- data/lib/docyard/components/aliases.rb +0 -4
- data/lib/docyard/components/processors/callout_processor.rb +1 -1
- data/lib/docyard/components/processors/code_block_diff_preprocessor.rb +1 -1
- data/lib/docyard/components/processors/code_block_focus_preprocessor.rb +1 -1
- data/lib/docyard/components/processors/code_block_options_preprocessor.rb +2 -2
- data/lib/docyard/components/processors/code_group_processor.rb +1 -1
- data/lib/docyard/components/processors/icon_processor.rb +2 -2
- data/lib/docyard/components/processors/tabs_processor.rb +1 -1
- data/lib/docyard/config/schema/definition.rb +29 -0
- data/lib/docyard/config/schema/sections.rb +63 -0
- data/lib/docyard/config/schema/simple_sections.rb +78 -0
- data/lib/docyard/config/schema.rb +28 -31
- data/lib/docyard/config/type_validators.rb +121 -0
- data/lib/docyard/config/validator.rb +136 -61
- data/lib/docyard/config.rb +1 -13
- data/lib/docyard/diagnostic.rb +89 -0
- data/lib/docyard/diagnostic_context.rb +48 -0
- data/lib/docyard/doctor/code_block_checker.rb +136 -0
- data/lib/docyard/doctor/component_checker.rb +49 -0
- data/lib/docyard/doctor/component_checkers/abbreviation_checker.rb +74 -0
- data/lib/docyard/doctor/component_checkers/badge_checker.rb +71 -0
- data/lib/docyard/doctor/component_checkers/base.rb +111 -0
- data/lib/docyard/doctor/component_checkers/callout_checker.rb +34 -0
- data/lib/docyard/doctor/component_checkers/cards_checker.rb +57 -0
- data/lib/docyard/doctor/component_checkers/code_group_checker.rb +47 -0
- data/lib/docyard/doctor/component_checkers/details_checker.rb +51 -0
- data/lib/docyard/doctor/component_checkers/icon_checker.rb +36 -0
- data/lib/docyard/doctor/component_checkers/image_attrs_checker.rb +46 -0
- data/lib/docyard/doctor/component_checkers/space_after_colons_checker.rb +45 -0
- data/lib/docyard/doctor/component_checkers/steps_checker.rb +35 -0
- data/lib/docyard/doctor/component_checkers/tabs_checker.rb +35 -0
- data/lib/docyard/doctor/component_checkers/tooltip_checker.rb +67 -0
- data/lib/docyard/doctor/component_checkers/unknown_type_checker.rb +34 -0
- data/lib/docyard/doctor/config_checker.rb +19 -0
- data/lib/docyard/doctor/config_fixer.rb +87 -0
- data/lib/docyard/doctor/content_checker.rb +164 -0
- data/lib/docyard/doctor/file_scanner.rb +113 -0
- data/lib/docyard/doctor/image_checker.rb +103 -0
- data/lib/docyard/doctor/link_checker.rb +91 -0
- data/lib/docyard/doctor/markdown_fixer.rb +62 -0
- data/lib/docyard/doctor/orphan_checker.rb +82 -0
- data/lib/docyard/doctor/reporter.rb +152 -0
- data/lib/docyard/doctor/sidebar_checker.rb +127 -0
- data/lib/docyard/doctor/sidebar_fixer.rb +47 -0
- data/lib/docyard/doctor.rb +178 -0
- data/lib/docyard/editor_launcher.rb +119 -0
- data/lib/docyard/errors.rb +0 -49
- data/lib/docyard/initializer.rb +32 -39
- data/lib/docyard/navigation/page_navigation_builder.rb +5 -3
- data/lib/docyard/navigation/prev_next_builder.rb +4 -3
- data/lib/docyard/navigation/sidebar/local_config_loader.rb +44 -21
- data/lib/docyard/rendering/icon_helpers.rb +1 -3
- data/lib/docyard/rendering/renderer.rb +17 -2
- data/lib/docyard/search/build_indexer.rb +39 -24
- data/lib/docyard/search/dev_indexer.rb +9 -23
- data/lib/docyard/server/dev_server.rb +55 -13
- data/lib/docyard/server/error_overlay.rb +73 -0
- data/lib/docyard/server/file_watcher.rb +0 -1
- data/lib/docyard/server/page_diagnostics.rb +27 -0
- data/lib/docyard/server/preview_server.rb +20 -8
- data/lib/docyard/server/rack_application.rb +64 -3
- data/lib/docyard/server/resolution_result.rb +0 -4
- data/lib/docyard/server/static_file_app.rb +20 -3
- data/lib/docyard/templates/assets/css/error-overlay.css +669 -0
- data/lib/docyard/templates/assets/css/variables.css +1 -1
- data/lib/docyard/templates/assets/fonts/Inter-Variable.woff2 +0 -0
- data/lib/docyard/templates/assets/js/components/relative-time.js +42 -0
- data/lib/docyard/templates/assets/js/error-overlay.js +547 -0
- data/lib/docyard/templates/assets/js/hot-reload.js +35 -7
- data/lib/docyard/templates/errors/404.html.erb +1 -1
- data/lib/docyard/templates/errors/500.html.erb +1 -1
- data/lib/docyard/templates/partials/_head.html.erb +1 -1
- data/lib/docyard/templates/partials/_page_actions.html.erb +1 -1
- data/lib/docyard/ui.rb +80 -0
- data/lib/docyard/utils/logging.rb +5 -1
- data/lib/docyard/utils/text_formatter.rb +0 -6
- data/lib/docyard/version.rb +1 -1
- data/lib/docyard.rb +4 -0
- metadata +54 -29
- data/lib/docyard/config/key_validator.rb +0 -30
- data/lib/docyard/config/validation_helpers.rb +0 -83
- data/lib/docyard/config/validators/navigation.rb +0 -43
- data/lib/docyard/config/validators/section.rb +0 -114
- data/lib/docyard/templates/assets/fonts/Inter-Variable.ttf +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a6ab36d72a5ae740f0ba203c29ab88b2698f0010bbc980ac0ef3a8b7d5f9f579
|
|
4
|
+
data.tar.gz: f4df0ae89a078729eb319733ac7dab36943fa9e228daaba9fb75e6323a2f6fab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c4e05cb41cf1259a74a73c24c45fa265da9fbaf6725905e9893f442e9e1a2dbf1979ba03c5aac030bafd9e2f574fa0fec50f1f11ee5fd583353e3fd7b8329ea
|
|
7
|
+
data.tar.gz: f53424206fd8224b815232c4c2babca237852829f4f43f8fe537f63136c65e7b25ae75d6276a4b07416b4c24cc8274b822946c6f03a2e6aa2c6f394cc5334327
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.1.0] - 2026-01-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Doctor Command** - Health check CLI with `docyard doctor` to find broken links, missing images, orphan pages, and config issues (#135)
|
|
14
|
+
- **Auto-fix** - Automatically fix common issues with `docyard doctor --fix` including typos, string booleans, and missing slashes (#135)
|
|
15
|
+
- **Config Validation** - Validate `docyard.yml` and `_sidebar.yml` with helpful error messages and suggestions (#138)
|
|
16
|
+
- **Dev Server Error Overlay** - Bottom sheet overlay showing errors with syntax-highlighted code snippets, line numbers, and clickable file paths (#143)
|
|
17
|
+
- **Strict Mode** - Fail builds on validation errors with `--strict` flag or `build.strict` config (#143)
|
|
18
|
+
- **Colored CLI Output** - ANSI colors for better readability with `--no-color` flag and `NO_COLOR` env support (#142)
|
|
19
|
+
- **CLI Progress Spinners** - Animated spinners and step-by-step progress display for build and serve commands (#136)
|
|
20
|
+
- **Verbose Timing Breakdown** - Per-page timing in `docyard build --verbose` output (#141)
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- "Last updated" timestamps now calculate relative time client-side for accurate display (#134)
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Build output format updated with progress indicators (#136, #141)
|
|
27
|
+
- Config validation now runs on every build (#138)
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
- Added `docyard doctor` command reference
|
|
31
|
+
- Added strict mode documentation to CLI and configuration references
|
|
32
|
+
- Added error overlay mention to dev server documentation
|
|
33
|
+
- Updated build output examples to match current format
|
|
34
|
+
|
|
35
|
+
## [1.0.2] - 2026-01-23
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- "Last updated" timestamps now show correct dates when deploying via CI (#129)
|
|
39
|
+
|
|
40
|
+
### Documentation
|
|
41
|
+
- Added git history requirements for accurate timestamps on GitHub Actions, Vercel, and Netlify
|
|
42
|
+
|
|
10
43
|
## [1.0.1] - 2026-01-22
|
|
11
44
|
|
|
12
45
|
### Added
|
|
@@ -216,7 +249,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
216
249
|
- Initial gem structure
|
|
217
250
|
- Project scaffolding
|
|
218
251
|
|
|
219
|
-
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.0
|
|
252
|
+
[Unreleased]: https://github.com/sanifhimani/docyard/compare/v1.1.0...HEAD
|
|
253
|
+
[1.1.0]: https://github.com/sanifhimani/docyard/compare/v1.0.2...v1.1.0
|
|
254
|
+
[1.0.2]: https://github.com/sanifhimani/docyard/compare/v1.0.1...v1.0.2
|
|
220
255
|
[1.0.1]: https://github.com/sanifhimani/docyard/compare/v1.0.0...v1.0.1
|
|
221
256
|
[1.0.0]: https://github.com/sanifhimani/docyard/compare/v0.9.0...v1.0.0
|
|
222
257
|
[0.9.0]: https://github.com/sanifhimani/docyard/compare/v0.8.0...v0.9.0
|
|
@@ -7,6 +7,8 @@ require "digest"
|
|
|
7
7
|
module Docyard
|
|
8
8
|
module Build
|
|
9
9
|
class AssetBundler
|
|
10
|
+
include Utils::UrlHelpers
|
|
11
|
+
|
|
10
12
|
ASSETS_PATH = File.join(__dir__, "..", "templates", "assets")
|
|
11
13
|
|
|
12
14
|
attr_reader :config, :verbose
|
|
@@ -17,21 +19,17 @@ module Docyard
|
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def bundle
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
css_hash = bundle_css
|
|
23
|
-
js_hash = bundle_js
|
|
22
|
+
css_hash, css_size = bundle_css
|
|
23
|
+
js_hash, js_size = bundle_js
|
|
24
24
|
|
|
25
25
|
update_html_references(css_hash, js_hash)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
[css_size, js_size]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
32
|
def bundle_css
|
|
33
|
-
log " Bundling CSS..."
|
|
34
|
-
|
|
35
33
|
main_css = File.read(File.join(ASSETS_PATH, "css", "main.css"))
|
|
36
34
|
css_content = resolve_css_imports(main_css)
|
|
37
35
|
minified = CSSminify.compress(css_content)
|
|
@@ -41,9 +39,8 @@ module Docyard
|
|
|
41
39
|
hash = generate_hash(minified)
|
|
42
40
|
|
|
43
41
|
write_bundled_asset(minified, hash, "css")
|
|
44
|
-
log_compression_stats(css_content, minified, "CSS")
|
|
45
42
|
|
|
46
|
-
hash
|
|
43
|
+
[hash, minified.bytesize]
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
def fix_calc_whitespace(css)
|
|
@@ -86,8 +83,6 @@ module Docyard
|
|
|
86
83
|
end
|
|
87
84
|
|
|
88
85
|
def bundle_js
|
|
89
|
-
log " Bundling JS..."
|
|
90
|
-
|
|
91
86
|
theme_js = File.read(File.join(ASSETS_PATH, "js", "theme.js"))
|
|
92
87
|
components_js = concatenate_component_js
|
|
93
88
|
js_content = [theme_js, components_js].join("\n")
|
|
@@ -96,9 +91,8 @@ module Docyard
|
|
|
96
91
|
hash = generate_hash(minified)
|
|
97
92
|
|
|
98
93
|
write_bundled_asset(minified, hash, "js")
|
|
99
|
-
log_compression_stats(js_content, minified, "JS")
|
|
100
94
|
|
|
101
|
-
hash
|
|
95
|
+
[hash, minified.bytesize]
|
|
102
96
|
end
|
|
103
97
|
|
|
104
98
|
def replace_js_asset_urls(js_content)
|
|
@@ -127,8 +121,6 @@ module Docyard
|
|
|
127
121
|
content = replace_asset_references(File.read(file), css_hash, js_hash, base_url)
|
|
128
122
|
File.write(file, content)
|
|
129
123
|
end
|
|
130
|
-
|
|
131
|
-
log " [✓] Updated asset references in #{html_files.size} HTML files"
|
|
132
124
|
end
|
|
133
125
|
|
|
134
126
|
def replace_asset_references(content, css_hash, js_hash, base_url)
|
|
@@ -142,7 +134,8 @@ module Docyard
|
|
|
142
134
|
def replace_content_image_paths(content, base_url)
|
|
143
135
|
return content if base_url == "/"
|
|
144
136
|
|
|
145
|
-
|
|
137
|
+
base_path_pattern = Regexp.escape(base_url.delete_prefix("/"))
|
|
138
|
+
content.gsub(%r{(<img[^>]*\ssrc=")/(?!_docyard/|#{base_path_pattern})([^"]*")}) do
|
|
146
139
|
"#{Regexp.last_match(1)}#{base_url}#{Regexp.last_match(2)}"
|
|
147
140
|
end
|
|
148
141
|
end
|
|
@@ -153,24 +146,6 @@ module Docyard
|
|
|
153
146
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
154
147
|
File.write(output_path, content)
|
|
155
148
|
end
|
|
156
|
-
|
|
157
|
-
def log_compression_stats(original, minified, label)
|
|
158
|
-
original_size = (original.bytesize / 1024.0).round(1)
|
|
159
|
-
minified_size = (minified.bytesize / 1024.0).round(1)
|
|
160
|
-
reduction = (((original_size - minified_size) / original_size) * 100).round(0)
|
|
161
|
-
log " [✓] #{label}: #{original_size} KB -> #{minified_size} KB (-#{reduction}%)"
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def normalize_base_url(url)
|
|
165
|
-
return "/" if url.nil? || url.empty? || url == "/"
|
|
166
|
-
|
|
167
|
-
url = "/#{url}" unless url.start_with?("/")
|
|
168
|
-
url.end_with?("/") ? url : "#{url}/"
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def log(message)
|
|
172
|
-
Docyard.logger.info(message)
|
|
173
|
-
end
|
|
174
149
|
end
|
|
175
150
|
end
|
|
176
151
|
end
|
|
@@ -13,14 +13,12 @@ module Docyard
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def copy
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
@copied_files = []
|
|
18
17
|
count = 0
|
|
19
18
|
count += copy_public_files
|
|
20
19
|
count += copy_branding_assets
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
count
|
|
20
|
+
details = verbose ? @copied_files : nil
|
|
21
|
+
[count, details]
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
private
|
|
@@ -32,7 +30,6 @@ module Docyard
|
|
|
32
30
|
files = find_files_in_dir(public_dir)
|
|
33
31
|
files.each { |file| copy_single_file(file, "#{public_dir}/", config.build.output) }
|
|
34
32
|
|
|
35
|
-
log "[✓] Copied #{files.size} public files from #{public_dir}/" if files.any?
|
|
36
33
|
files.size
|
|
37
34
|
end
|
|
38
35
|
|
|
@@ -47,14 +44,13 @@ module Docyard
|
|
|
47
44
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
48
45
|
FileUtils.cp(file, dest_path)
|
|
49
46
|
|
|
50
|
-
|
|
47
|
+
@copied_files << relative_path if verbose
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
def copy_branding_assets
|
|
54
51
|
count = 0
|
|
55
52
|
count += copy_default_branding_assets
|
|
56
53
|
count += copy_user_branding_assets
|
|
57
|
-
log "[✓] Copied #{count} branding assets" if count.positive?
|
|
58
54
|
count
|
|
59
55
|
end
|
|
60
56
|
|
|
@@ -80,7 +76,7 @@ module Docyard
|
|
|
80
76
|
dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, filename)
|
|
81
77
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
82
78
|
FileUtils.cp(source_path, dest_path)
|
|
83
|
-
|
|
79
|
+
@copied_files << "#{filename} (#{label})" if verbose
|
|
84
80
|
1
|
|
85
81
|
end
|
|
86
82
|
|
|
@@ -96,7 +92,7 @@ module Docyard
|
|
|
96
92
|
dest_path = File.join(config.build.output, DOCYARD_OUTPUT_DIR, "fonts", File.basename(font_file))
|
|
97
93
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
98
94
|
FileUtils.cp(font_file, dest_path)
|
|
99
|
-
|
|
95
|
+
@copied_files << "#{File.basename(font_file)} (font)" if verbose
|
|
100
96
|
1
|
|
101
97
|
end
|
|
102
98
|
|
|
@@ -122,13 +118,9 @@ module Docyard
|
|
|
122
118
|
FileUtils.mkdir_p(File.dirname(dest_path))
|
|
123
119
|
FileUtils.cp(full_path, dest_path)
|
|
124
120
|
|
|
125
|
-
|
|
121
|
+
@copied_files << "#{asset_path} (user branding)" if verbose
|
|
126
122
|
1
|
|
127
123
|
end
|
|
128
|
-
|
|
129
|
-
def log(message)
|
|
130
|
-
Docyard.logger.info(message)
|
|
131
|
-
end
|
|
132
124
|
end
|
|
133
125
|
end
|
|
134
126
|
end
|
|
@@ -50,7 +50,6 @@ module Docyard
|
|
|
50
50
|
def generate_llms_txt(pages)
|
|
51
51
|
output_path = File.join(config.build.output, "llms.txt")
|
|
52
52
|
File.write(output_path, build_llms_txt_content(pages))
|
|
53
|
-
Docyard.logger.info("[✓] Generated llms.txt (#{pages.size} pages)")
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def build_llms_txt_content(pages)
|
|
@@ -70,7 +69,6 @@ module Docyard
|
|
|
70
69
|
def generate_llms_full_txt(pages)
|
|
71
70
|
output_path = File.join(config.build.output, "llms-full.txt")
|
|
72
71
|
File.write(output_path, build_llms_full_txt_content(pages))
|
|
73
|
-
Docyard.logger.info("[✓] Generated llms-full.txt")
|
|
74
72
|
end
|
|
75
73
|
|
|
76
74
|
def build_llms_full_txt_content(pages)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "parallel"
|
|
4
|
-
require "tty-progressbar"
|
|
5
4
|
require_relative "../rendering/template_resolver"
|
|
6
5
|
require_relative "../navigation/page_navigation_builder"
|
|
7
6
|
require_relative "../navigation/sidebar/cache"
|
|
@@ -32,13 +31,11 @@ module Docyard
|
|
|
32
31
|
copy_custom_landing_page if custom_landing_page?
|
|
33
32
|
|
|
34
33
|
markdown_files = collect_markdown_files
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
generate_all_pages(markdown_files)
|
|
34
|
+
generated_pages = generate_all_pages(markdown_files)
|
|
38
35
|
generate_error_page
|
|
39
36
|
generate_root_fallback_if_needed
|
|
40
37
|
|
|
41
|
-
markdown_files.size
|
|
38
|
+
[markdown_files.size, build_verbose_details(generated_pages)]
|
|
42
39
|
ensure
|
|
43
40
|
Utils::GitInfo.clear_cache
|
|
44
41
|
end
|
|
@@ -46,20 +43,14 @@ module Docyard
|
|
|
46
43
|
private
|
|
47
44
|
|
|
48
45
|
def generate_all_pages(markdown_files)
|
|
49
|
-
progress = TTY::ProgressBar.new(
|
|
50
|
-
"Generating pages [:bar] :current/:total (:percent)",
|
|
51
|
-
total: markdown_files.size,
|
|
52
|
-
width: 50
|
|
53
|
-
)
|
|
54
|
-
mutex = Mutex.new
|
|
55
|
-
|
|
56
46
|
Logging.start_buffering
|
|
57
|
-
if markdown_files.size >= PARALLEL_THRESHOLD
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
pages = if markdown_files.size >= PARALLEL_THRESHOLD
|
|
48
|
+
generate_pages_in_parallel(markdown_files)
|
|
49
|
+
else
|
|
50
|
+
generate_pages_sequentially(markdown_files)
|
|
51
|
+
end
|
|
62
52
|
Logging.flush_warnings
|
|
53
|
+
pages
|
|
63
54
|
end
|
|
64
55
|
|
|
65
56
|
def custom_landing_page?
|
|
@@ -72,7 +63,6 @@ module Docyard
|
|
|
72
63
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
73
64
|
FileUtils.cp(File.join(docs_path, "index.html"), output_path)
|
|
74
65
|
end
|
|
75
|
-
log "[✓] Copied custom landing page (index.html)"
|
|
76
66
|
end
|
|
77
67
|
|
|
78
68
|
def collect_markdown_files
|
|
@@ -81,23 +71,28 @@ module Docyard
|
|
|
81
71
|
files
|
|
82
72
|
end
|
|
83
73
|
|
|
84
|
-
def generate_pages_in_parallel(markdown_files
|
|
85
|
-
Parallel.
|
|
86
|
-
|
|
87
|
-
mutex.synchronize { progress.advance }
|
|
74
|
+
def generate_pages_in_parallel(markdown_files)
|
|
75
|
+
Parallel.map(markdown_files, in_threads: Parallel.processor_count) do |file_path|
|
|
76
|
+
generate_page_with_timing(file_path, thread_local_renderer)
|
|
88
77
|
ensure
|
|
89
78
|
Thread.current[:docyard_build_renderer] = nil
|
|
90
79
|
end
|
|
91
80
|
end
|
|
92
81
|
|
|
93
|
-
def generate_pages_sequentially(markdown_files
|
|
82
|
+
def generate_pages_sequentially(markdown_files)
|
|
94
83
|
renderer = build_renderer
|
|
95
|
-
markdown_files.
|
|
96
|
-
|
|
97
|
-
progress.advance
|
|
84
|
+
markdown_files.map do |file_path|
|
|
85
|
+
generate_page_with_timing(file_path, renderer)
|
|
98
86
|
end
|
|
99
87
|
end
|
|
100
88
|
|
|
89
|
+
def generate_page_with_timing(file_path, renderer)
|
|
90
|
+
page_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
91
|
+
output_path = generate_page(file_path, renderer)
|
|
92
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - page_start
|
|
93
|
+
[output_path, elapsed]
|
|
94
|
+
end
|
|
95
|
+
|
|
101
96
|
def thread_local_renderer
|
|
102
97
|
Thread.current[:docyard_build_renderer] ||= build_renderer
|
|
103
98
|
end
|
|
@@ -114,6 +109,7 @@ module Docyard
|
|
|
114
109
|
html_content = render_markdown_file(markdown_file_path, current_path, renderer)
|
|
115
110
|
html_content = apply_search_exclusion(html_content, current_path)
|
|
116
111
|
write_output(output_path, html_content)
|
|
112
|
+
output_path
|
|
117
113
|
end
|
|
118
114
|
|
|
119
115
|
def apply_search_exclusion(html_content, current_path)
|
|
@@ -155,7 +151,8 @@ module Docyard
|
|
|
155
151
|
@navigation_builder ||= Navigation::PageNavigationBuilder.new(
|
|
156
152
|
docs_path: docs_path,
|
|
157
153
|
config: config,
|
|
158
|
-
sidebar_cache: sidebar_cache
|
|
154
|
+
sidebar_cache: sidebar_cache,
|
|
155
|
+
base_url: config.build.base
|
|
159
156
|
)
|
|
160
157
|
end
|
|
161
158
|
|
|
@@ -164,7 +161,6 @@ module Docyard
|
|
|
164
161
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
165
162
|
File.write(output_path, html_content)
|
|
166
163
|
end
|
|
167
|
-
log "Generated: #{output_path}" if verbose
|
|
168
164
|
end
|
|
169
165
|
|
|
170
166
|
def build_sidebar_cache
|
|
@@ -183,8 +179,13 @@ module Docyard
|
|
|
183
179
|
config.source
|
|
184
180
|
end
|
|
185
181
|
|
|
186
|
-
def
|
|
187
|
-
|
|
182
|
+
def build_verbose_details(generated_pages)
|
|
183
|
+
return nil unless verbose
|
|
184
|
+
|
|
185
|
+
generated_pages.map do |output_path, elapsed|
|
|
186
|
+
path = output_path.delete_prefix("#{config.build.output}/")
|
|
187
|
+
"#{path.ljust(30)} #{format('%<t>.2fs', t: elapsed)}"
|
|
188
|
+
end
|
|
188
189
|
end
|
|
189
190
|
|
|
190
191
|
def generate_error_page
|
|
@@ -193,7 +194,6 @@ module Docyard
|
|
|
193
194
|
docs_path: docs_path,
|
|
194
195
|
renderer: build_renderer
|
|
195
196
|
).generate
|
|
196
|
-
log "[✓] Generated 404.html"
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
def generate_root_fallback_if_needed
|
|
@@ -203,8 +203,7 @@ module Docyard
|
|
|
203
203
|
sidebar_cache: sidebar_cache,
|
|
204
204
|
renderer: build_renderer
|
|
205
205
|
)
|
|
206
|
-
|
|
207
|
-
log "[✓] Generated root redirect to #{target}" if target
|
|
206
|
+
generator.generate_if_needed
|
|
208
207
|
end
|
|
209
208
|
end
|
|
210
209
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
class StepRunner
|
|
6
|
+
STEP_SHORT_LABELS = {
|
|
7
|
+
"Generating pages" => "Pages",
|
|
8
|
+
"Bundling assets" => "Assets",
|
|
9
|
+
"Copying files" => "Files",
|
|
10
|
+
"Generating SEO" => "SEO",
|
|
11
|
+
"Indexing search" => "Search"
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :verbose, :step_timings
|
|
15
|
+
|
|
16
|
+
def initialize(verbose: false)
|
|
17
|
+
@verbose = verbose
|
|
18
|
+
@step_timings = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run(label, &)
|
|
22
|
+
print " #{label.ljust(20)}#{UI.dim('in progress')}"
|
|
23
|
+
$stdout.flush
|
|
24
|
+
result, details, elapsed = execute(label, &)
|
|
25
|
+
print_result(label, result, elapsed)
|
|
26
|
+
print_details(details) if verbose && details&.any?
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def print_timing_breakdown
|
|
31
|
+
total = step_timings.sum { |t| t[:elapsed] }
|
|
32
|
+
sorted = step_timings.sort_by { |t| -t[:elapsed] }
|
|
33
|
+
|
|
34
|
+
puts " #{UI.bold('Timing:')}"
|
|
35
|
+
sorted.each { |timing| puts " #{UI.dim(format_timing_line(timing, total))}" }
|
|
36
|
+
puts
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def execute(label)
|
|
42
|
+
step_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
result, details = yield
|
|
44
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start
|
|
45
|
+
@step_timings << { label: STEP_SHORT_LABELS.fetch(label, label), elapsed: elapsed }
|
|
46
|
+
[result, details, elapsed]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def print_result(label, result, elapsed)
|
|
50
|
+
timing_suffix = verbose ? UI.dim(" in #{format('%<t>.2fs', t: elapsed)}") : ""
|
|
51
|
+
print "\r #{label.ljust(20)}#{UI.green(format_result(label, result))}#{timing_suffix}\n"
|
|
52
|
+
$stdout.flush
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def print_details(details)
|
|
56
|
+
details.each { |detail| puts " #{UI.dim(detail)}" }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def format_result(label, result)
|
|
60
|
+
case label
|
|
61
|
+
when "Generating pages" then "done (#{result} pages)"
|
|
62
|
+
when "Bundling assets" then format_assets_result(result)
|
|
63
|
+
when "Copying files" then "done (#{result} files)"
|
|
64
|
+
when "Generating SEO" then "done (#{result.join(', ')})"
|
|
65
|
+
when "Indexing search" then "done (#{result} pages indexed)"
|
|
66
|
+
else "done"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def format_assets_result(result)
|
|
71
|
+
css, js = result
|
|
72
|
+
"done (#{format_size(css)} CSS, #{format_size(js)} JS)"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def format_size(bytes)
|
|
76
|
+
kb = bytes / 1024.0
|
|
77
|
+
kb >= 1000 ? format("%.1f MB", kb / 1024.0) : format("%.1f KB", kb)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def format_timing_line(timing, total)
|
|
81
|
+
label = timing[:label].ljust(12)
|
|
82
|
+
secs = format("%<t>5.2fs", t: timing[:elapsed])
|
|
83
|
+
pct = total.positive? ? (timing[:elapsed] / total * 100).round : 0
|
|
84
|
+
"#{label} #{secs} (#{format('%<p>2d', p: pct)}%)"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docyard
|
|
4
|
+
module Build
|
|
5
|
+
class Validator
|
|
6
|
+
attr_reader :config, :strict, :diagnostics
|
|
7
|
+
|
|
8
|
+
def initialize(config, strict: false)
|
|
9
|
+
@config = config
|
|
10
|
+
@strict = strict
|
|
11
|
+
@diagnostics = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid?
|
|
15
|
+
@diagnostics = collect_diagnostics
|
|
16
|
+
failing_diagnostics.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def errors
|
|
20
|
+
diagnostics.select(&:error?)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def failing_diagnostics
|
|
24
|
+
strict ? diagnostics : errors
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warnings
|
|
28
|
+
diagnostics.select(&:warning?)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def print_errors(context: "Build")
|
|
32
|
+
return if failing_diagnostics.empty?
|
|
33
|
+
|
|
34
|
+
print_header
|
|
35
|
+
puts " #{UI.red("#{context} failed due to validation errors:")}"
|
|
36
|
+
puts
|
|
37
|
+
failing_diagnostics.each { |d| puts format_diagnostic_line(d) }
|
|
38
|
+
puts
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def print_warnings
|
|
42
|
+
return if warnings.empty?
|
|
43
|
+
|
|
44
|
+
puts
|
|
45
|
+
puts " #{UI.yellow("#{warnings.size} warning(s) found:")}"
|
|
46
|
+
warnings.each { |d| puts " #{UI.yellow('warn ')} #{d.location.ljust(26)} #{d.message}" }
|
|
47
|
+
puts
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def print_header
|
|
53
|
+
puts
|
|
54
|
+
puts " #{UI.bold('Docyard')} v#{VERSION}"
|
|
55
|
+
puts
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_diagnostic_line(diagnostic)
|
|
59
|
+
if diagnostic.error?
|
|
60
|
+
" #{UI.red('error')} #{diagnostic.location.ljust(26)} #{diagnostic.message}"
|
|
61
|
+
else
|
|
62
|
+
" #{UI.yellow('warn ')} #{diagnostic.location.ljust(26)} #{diagnostic.message}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def collect_diagnostics
|
|
67
|
+
strict ? collect_strict_diagnostics : collect_essential_diagnostics
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def collect_essential_diagnostics
|
|
71
|
+
require_relative "../doctor/config_checker"
|
|
72
|
+
require_relative "../doctor/sidebar_checker"
|
|
73
|
+
|
|
74
|
+
[
|
|
75
|
+
Doctor::ConfigChecker.new(config).check,
|
|
76
|
+
Doctor::SidebarChecker.new(docs_path).check
|
|
77
|
+
].flatten
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def collect_strict_diagnostics
|
|
81
|
+
require_relative "../doctor"
|
|
82
|
+
|
|
83
|
+
file_scanner = Doctor::FileScanner.new(docs_path)
|
|
84
|
+
scanner_diagnostics = file_scanner.scan
|
|
85
|
+
|
|
86
|
+
[
|
|
87
|
+
collect_essential_diagnostics,
|
|
88
|
+
scanner_diagnostics,
|
|
89
|
+
Doctor::OrphanChecker.new(docs_path, config).check
|
|
90
|
+
].flatten
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def docs_path
|
|
94
|
+
config.source
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|