docyard 1.0.2 → 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 +27 -1
- data/lib/docyard/build/asset_bundler.rb +7 -33
- 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 +30 -32
- 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/sidebar/local_config_loader.rb +44 -21
- data/lib/docyard/rendering/icon_helpers.rb +1 -3
- 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 +17 -13
- data/lib/docyard/server/rack_application.rb +64 -3
- data/lib/docyard/server/resolution_result.rb +0 -4
- 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 +47 -25
- 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,31 @@ 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
|
+
|
|
10
35
|
## [1.0.2] - 2026-01-23
|
|
11
36
|
|
|
12
37
|
### Fixed
|
|
@@ -224,7 +249,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
224
249
|
- Initial gem structure
|
|
225
250
|
- Project scaffolding
|
|
226
251
|
|
|
227
|
-
[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
|
|
228
254
|
[1.0.2]: https://github.com/sanifhimani/docyard/compare/v1.0.1...v1.0.2
|
|
229
255
|
[1.0.1]: https://github.com/sanifhimani/docyard/compare/v1.0.0...v1.0.1
|
|
230
256
|
[1.0.0]: https://github.com/sanifhimani/docyard/compare/v0.9.0...v1.0.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)
|
|
@@ -154,24 +146,6 @@ module Docyard
|
|
|
154
146
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
155
147
|
File.write(output_path, content)
|
|
156
148
|
end
|
|
157
|
-
|
|
158
|
-
def log_compression_stats(original, minified, label)
|
|
159
|
-
original_size = (original.bytesize / 1024.0).round(1)
|
|
160
|
-
minified_size = (minified.bytesize / 1024.0).round(1)
|
|
161
|
-
reduction = (((original_size - minified_size) / original_size) * 100).round(0)
|
|
162
|
-
log " [✓] #{label}: #{original_size} KB -> #{minified_size} KB (-#{reduction}%)"
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def normalize_base_url(url)
|
|
166
|
-
return "/" if url.nil? || url.empty? || url == "/"
|
|
167
|
-
|
|
168
|
-
url = "/#{url}" unless url.start_with?("/")
|
|
169
|
-
url.end_with?("/") ? url : "#{url}/"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def log(message)
|
|
173
|
-
Docyard.logger.info(message)
|
|
174
|
-
end
|
|
175
149
|
end
|
|
176
150
|
end
|
|
177
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)
|
|
@@ -165,7 +161,6 @@ module Docyard
|
|
|
165
161
|
FileUtils.mkdir_p(File.dirname(output_path))
|
|
166
162
|
File.write(output_path, html_content)
|
|
167
163
|
end
|
|
168
|
-
log "Generated: #{output_path}" if verbose
|
|
169
164
|
end
|
|
170
165
|
|
|
171
166
|
def build_sidebar_cache
|
|
@@ -184,8 +179,13 @@ module Docyard
|
|
|
184
179
|
config.source
|
|
185
180
|
end
|
|
186
181
|
|
|
187
|
-
def
|
|
188
|
-
|
|
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
|
|
189
189
|
end
|
|
190
190
|
|
|
191
191
|
def generate_error_page
|
|
@@ -194,7 +194,6 @@ module Docyard
|
|
|
194
194
|
docs_path: docs_path,
|
|
195
195
|
renderer: build_renderer
|
|
196
196
|
).generate
|
|
197
|
-
log "[✓] Generated 404.html"
|
|
198
197
|
end
|
|
199
198
|
|
|
200
199
|
def generate_root_fallback_if_needed
|
|
@@ -204,8 +203,7 @@ module Docyard
|
|
|
204
203
|
sidebar_cache: sidebar_cache,
|
|
205
204
|
renderer: build_renderer
|
|
206
205
|
)
|
|
207
|
-
|
|
208
|
-
log "[✓] Generated root redirect to #{target}" if target
|
|
206
|
+
generator.generate_if_needed
|
|
209
207
|
end
|
|
210
208
|
end
|
|
211
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
|