docopslab-dev 0.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 +7 -0
- data/LICENSE +21 -0
- data/README.adoc +904 -0
- data/assets/config-packs/actionlint/base.yml +13 -0
- data/assets/config-packs/actionlint/project.yml +13 -0
- data/assets/config-packs/htmlproofer/base.yml +27 -0
- data/assets/config-packs/htmlproofer/project.yml +25 -0
- data/assets/config-packs/rubocop/base.yml +130 -0
- data/assets/config-packs/rubocop/project.yml +8 -0
- data/assets/config-packs/shellcheck/base.shellcheckrc +14 -0
- data/assets/config-packs/subtxt/ai-asciidoc-antipatterns.sub.txt +11 -0
- data/assets/config-packs/vale/asciidoc/ExplicitSectionIDs.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ExtraLineBeforeLevel1.yml +7 -0
- data/assets/config-packs/vale/asciidoc/OneSentencePerLine.yml +8 -0
- data/assets/config-packs/vale/asciidoc/PreferSourceBlocks.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ProperAdmonitions.yml +8 -0
- data/assets/config-packs/vale/asciidoc/ProperDLs.yml +7 -0
- data/assets/config-packs/vale/asciidoc/UncleanListStart.yml +8 -0
- data/assets/config-packs/vale/authoring/ButParagraph.yml +8 -0
- data/assets/config-packs/vale/authoring/ExNotEg.yml +8 -0
- data/assets/config-packs/vale/authoring/LiteralTerms.yml +20 -0
- data/assets/config-packs/vale/authoring/Spelling.yml +679 -0
- data/assets/config-packs/vale/base.ini +38 -0
- data/assets/config-packs/vale/config/scripts/ExplicitSectionIDs.tengo +56 -0
- data/assets/config-packs/vale/config/scripts/ExtraLineBeforeLevel1.tengo +121 -0
- data/assets/config-packs/vale/config/scripts/OneSentencePerLine.tengo +53 -0
- data/assets/config-packs/vale/project.ini +5 -0
- data/assets/hooks/pre-commit +63 -0
- data/assets/hooks/pre-push +72 -0
- data/assets/scripts/adoc_section_ids.rb +50 -0
- data/assets/scripts/build-common.sh +193 -0
- data/assets/scripts/build-docker.sh +64 -0
- data/assets/scripts/build.sh +56 -0
- data/assets/scripts/parse_jekyll_asciidoc_logs.rb +467 -0
- data/assets/templates/Gemfile +7 -0
- data/assets/templates/Rakefile +3 -0
- data/assets/templates/gitignore +69 -0
- data/assets/templates/jekyll-asciidoc-fix.prompt.yml +17 -0
- data/assets/templates/spellcheck.prompt.yml +16 -0
- data/docopslab-dev.gemspec +56 -0
- data/docs/agent/AGENTS.md +229 -0
- data/docs/agent/index.md +80 -0
- data/docs/agent/missions/conduct-release.md +224 -0
- data/docs/agent/missions/setup-new-project.md +250 -0
- data/docs/agent/roles/devops-release-engineer.md +152 -0
- data/docs/agent/roles/docops-engineer.md +193 -0
- data/docs/agent/roles/planner-architect.md +74 -0
- data/docs/agent/roles/product-engineer.md +153 -0
- data/docs/agent/roles/product-manager.md +130 -0
- data/docs/agent/roles/project-manager.md +139 -0
- data/docs/agent/roles/qa-testing-engineer.md +115 -0
- data/docs/agent/roles/tech-docs-manager.md +143 -0
- data/docs/agent/roles/tech-writer.md +163 -0
- data/docs/agent/skills/asciidoc.md +609 -0
- data/docs/agent/skills/code-commenting.md +347 -0
- data/docs/agent/skills/fix-broken-links.md +309 -0
- data/docs/agent/skills/fix-jekyll-asciidoc-build-errors.md +23 -0
- data/docs/agent/skills/fix-spelling-issues.md +13 -0
- data/docs/agent/skills/git.md +170 -0
- data/docs/agent/skills/github-issues.md +135 -0
- data/docs/agent/skills/product-release-rollback-and-patching.md +71 -0
- data/docs/agent/skills/rake-cli-dev.md +57 -0
- data/docs/agent/skills/readme-driven-dev.md +13 -0
- data/docs/agent/skills/release-history.md +29 -0
- data/docs/agent/skills/ruby.md +192 -0
- data/docs/agent/skills/schemagraphy-sgyml.md +18 -0
- data/docs/agent/skills/tests-running.md +25 -0
- data/docs/agent/skills/tests-writing.md +45 -0
- data/docs/agent/skills/write-the-docs.md +54 -0
- data/docs/agent/topics/common-project-paths.md +117 -0
- data/docs/agent/topics/dev-tooling-usage.md +202 -0
- data/docs/agent/topics/devops-ci-cd.md +55 -0
- data/docs/agent/topics/product-docs-deployment.md +25 -0
- data/lib/docopslab/dev/auto_fix_asciidoc.rb +46 -0
- data/lib/docopslab/dev/checkers.rb +108 -0
- data/lib/docopslab/dev/config_manager.rb +241 -0
- data/lib/docopslab/dev/file_utils.rb +140 -0
- data/lib/docopslab/dev/git_hooks.rb +140 -0
- data/lib/docopslab/dev/help.rb +121 -0
- data/lib/docopslab/dev/initializer.rb +95 -0
- data/lib/docopslab/dev/linters.rb +451 -0
- data/lib/docopslab/dev/log_parser.rb +31 -0
- data/lib/docopslab/dev/paths.rb +46 -0
- data/lib/docopslab/dev/script_manager.rb +136 -0
- data/lib/docopslab/dev/spell_check.rb +194 -0
- data/lib/docopslab/dev/sync_ops.rb +468 -0
- data/lib/docopslab/dev/tasks.rb +440 -0
- data/lib/docopslab/dev/tool_execution.rb +68 -0
- data/lib/docopslab/dev/version.rb +8 -0
- data/lib/docopslab/dev.rb +392 -0
- data/specs/data/default-manifest.yml +64 -0
- data/specs/data/manifest-schema.yaml +63 -0
- data/specs/data/tasks-def.yml +321 -0
- data/specs/data/tools.yml +60 -0
- metadata +362 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module DocOpsLab
|
|
6
|
+
module Dev
|
|
7
|
+
module Initializer
|
|
8
|
+
class << self
|
|
9
|
+
def create_project_manifest
|
|
10
|
+
return if File.exist?(MANIFEST_PATH)
|
|
11
|
+
|
|
12
|
+
puts '📋 Creating docopslab-dev.yml...'
|
|
13
|
+
|
|
14
|
+
FileUtils.mkdir_p('.config')
|
|
15
|
+
|
|
16
|
+
# Copy template from gem
|
|
17
|
+
FileUtils.cp(MANIFEST_DEF_PATH, MANIFEST_PATH)
|
|
18
|
+
puts "✅ Created #{MANIFEST_PATH}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def create_gitignore_stub
|
|
22
|
+
if File.exist?('.gitignore')
|
|
23
|
+
puts '⏭️ .gitignore already exists, skipping'
|
|
24
|
+
return false
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
FileUtils.cp(GITIGNORE_STUB_SOURCE_PATH, '.gitignore')
|
|
28
|
+
puts '✅ Created .gitignore file'
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create_gemfile_stub
|
|
33
|
+
if File.exist?('Gemfile')
|
|
34
|
+
puts '⏭️ Gemfile already exists, skipping'
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
FileUtils.cp(GEMFILE_STUB_SOURCE_PATH, 'Gemfile')
|
|
39
|
+
puts '✅ Created Gemfile'
|
|
40
|
+
true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def create_rakefile_stub
|
|
44
|
+
if File.exist?('Rakefile')
|
|
45
|
+
puts '⏭️ Rakefile already exists, skipping'
|
|
46
|
+
return false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
FileUtils.cp(RAKEFILE_STUB_SOURCE_PATH, 'Rakefile')
|
|
50
|
+
puts '✅ Created Rakefile'
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def init_git_repository
|
|
55
|
+
if Dir.exist?('.git')
|
|
56
|
+
puts '⏭️ Git repository already initialized, skipping'
|
|
57
|
+
return false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
system('git', 'init')
|
|
61
|
+
puts '✅ Initialized Git repository'
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def bootstrap_project
|
|
66
|
+
puts '� Bootstrapping DocOps Lab project...'
|
|
67
|
+
puts ''
|
|
68
|
+
|
|
69
|
+
created = []
|
|
70
|
+
|
|
71
|
+
# Core project files
|
|
72
|
+
created << 'Git repository' if init_git_repository
|
|
73
|
+
created << '.gitignore' if create_gitignore_stub
|
|
74
|
+
# Skip Gemfile; not needed for Docker workflow, template available if needed
|
|
75
|
+
created << 'Rakefile' if create_rakefile_stub
|
|
76
|
+
|
|
77
|
+
# DocOpsLab-specific
|
|
78
|
+
create_project_manifest
|
|
79
|
+
created << '.config/docopslab-dev.yml' unless File.exist?(MANIFEST_PATH)
|
|
80
|
+
|
|
81
|
+
puts ''
|
|
82
|
+
if created.any?
|
|
83
|
+
puts "✅ Bootstrap complete! Created: #{created.join(', ')}"
|
|
84
|
+
puts ''
|
|
85
|
+
puts 'Next steps:'
|
|
86
|
+
puts ' 1. bundle exec rake labdev:sync:all # or: docker run ... labdev:sync:all'
|
|
87
|
+
puts ' 2. Start using labdev tasks!'
|
|
88
|
+
else
|
|
89
|
+
puts '✅ Project already initialized, nothing to create'
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module DocOpsLab
|
|
6
|
+
module Dev
|
|
7
|
+
module Linters
|
|
8
|
+
class << self
|
|
9
|
+
def run_rubocop context, file_path=nil, opts_string=''
|
|
10
|
+
context.generate_rubocop_config if context.respond_to?(:generate_rubocop_config)
|
|
11
|
+
|
|
12
|
+
rubocop_config_file = CONFIG_PATHS[:rubocop]
|
|
13
|
+
unless File.exist?(rubocop_config_file)
|
|
14
|
+
rubocop_config_file = RUBOCOP_CONFIG_PATH # Fallback to vendor config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
unless File.exist?(rubocop_config_file)
|
|
18
|
+
puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
|
|
19
|
+
return false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
puts "📄 Using config: #{rubocop_config_file}"
|
|
23
|
+
|
|
24
|
+
path_config = context.get_path_config('rubocop')
|
|
25
|
+
|
|
26
|
+
if path_config[:skip] && !path_config[:skip].empty?
|
|
27
|
+
puts "⚠️ RuboCop does not support command-line exclusion. Use the 'Exclude' " \
|
|
28
|
+
"property in '.config/rubocop.yml' to ignore files or directories."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
paths_to_check = if file_path
|
|
32
|
+
[file_path]
|
|
33
|
+
else
|
|
34
|
+
path_config[:lint]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
cmd = "bundle exec rubocop --config #{rubocop_config_file}"
|
|
38
|
+
if paths_to_check.nil? || paths_to_check.empty?
|
|
39
|
+
puts '📄 No paths configured to check for RuboCop, running on entire project.'
|
|
40
|
+
else
|
|
41
|
+
puts "👮 Running RuboCop on paths: #{paths_to_check.join(' ')}"
|
|
42
|
+
cmd += " #{paths_to_check.join(' ')}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Append additional options if provided
|
|
46
|
+
cmd += " #{opts_string}" unless opts_string.empty?
|
|
47
|
+
|
|
48
|
+
success = system(cmd)
|
|
49
|
+
|
|
50
|
+
if success
|
|
51
|
+
puts '✅ RuboCop passed'
|
|
52
|
+
else
|
|
53
|
+
puts '❌ RuboCop found issues'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
success
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def run_rubocop_with_filter _context, filter_name
|
|
60
|
+
rubocop_config_file = CONFIG_PATHS[:rubocop]
|
|
61
|
+
unless File.exist?(rubocop_config_file)
|
|
62
|
+
rubocop_config_file = RUBOCOP_CONFIG_PATH # Fallback to vendor config
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless File.exist?(rubocop_config_file)
|
|
66
|
+
puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
|
|
67
|
+
return false
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts "📄 Using config: #{rubocop_config_file}"
|
|
71
|
+
puts "🔍 Running RuboCop with filter: #{filter_name}"
|
|
72
|
+
|
|
73
|
+
cmd = "bundle exec rubocop --config #{rubocop_config_file} --only #{filter_name}"
|
|
74
|
+
success = system(cmd)
|
|
75
|
+
|
|
76
|
+
if success
|
|
77
|
+
puts '✅ RuboCop passed'
|
|
78
|
+
else
|
|
79
|
+
puts '❌ RuboCop found issues'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
success
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def run_shellcheck context, file_path=nil, opts_string=''
|
|
86
|
+
scope = file_path ? :file : :project
|
|
87
|
+
running_on = file_path || 'entire project'
|
|
88
|
+
puts "🐚 Running ShellCheck on #{running_on}"
|
|
89
|
+
|
|
90
|
+
shell_scripts = if scope == :file
|
|
91
|
+
File.exist?(file_path) ? [file_path] : []
|
|
92
|
+
else
|
|
93
|
+
context.find_shell_scripts
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
if shell_scripts.empty?
|
|
97
|
+
puts '📄 No shell scripts found to check'
|
|
98
|
+
return true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
puts "📄 Found #{shell_scripts.length} shell script(s) to check" if scope == :project
|
|
102
|
+
success = true
|
|
103
|
+
shell_scripts.each do |script|
|
|
104
|
+
puts "🔍 Checking #{script}..."
|
|
105
|
+
passed = true
|
|
106
|
+
shebang_status = check_shebang(script)
|
|
107
|
+
unless shebang_status
|
|
108
|
+
puts "❌ Faulty shebang in #{script}; must be: #!/usr/bin/env bash"
|
|
109
|
+
success = false
|
|
110
|
+
passed = false
|
|
111
|
+
end
|
|
112
|
+
cmd = "shellcheck --severity=warning #{opts_string} --rcfile=.config/shellcheckrc #{script}".strip
|
|
113
|
+
shellcheck = context.run_with_fallback('shellcheck', cmd)
|
|
114
|
+
unless shellcheck
|
|
115
|
+
success = false
|
|
116
|
+
passed = false
|
|
117
|
+
puts "❌ ShellCheck found issues in #{script}"
|
|
118
|
+
end
|
|
119
|
+
puts "✅ ShellCheck passed for #{script}" if passed
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if success
|
|
123
|
+
puts '✅ ShellCheck passed'
|
|
124
|
+
else
|
|
125
|
+
puts '❌ ShellCheck found issues'
|
|
126
|
+
end
|
|
127
|
+
success
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def run_actionlint context, opts_string=''
|
|
131
|
+
puts '⚙️ Running actionlint...'
|
|
132
|
+
workflows_dir = '.github/workflows'
|
|
133
|
+
unless Dir.exist?(workflows_dir)
|
|
134
|
+
puts '📄 No GitHub Actions workflows found (.github/workflows/ not present)'
|
|
135
|
+
return true
|
|
136
|
+
end
|
|
137
|
+
workflow_files = Dir.glob("#{workflows_dir}/**/*.{yml,yaml}")
|
|
138
|
+
if workflow_files.empty?
|
|
139
|
+
puts '📄 No workflow files found in .github/workflows/'
|
|
140
|
+
return true
|
|
141
|
+
end
|
|
142
|
+
puts "📄 Found #{workflow_files.length} workflow file(s) to check"
|
|
143
|
+
config_file = '.config/actionlint.yml'
|
|
144
|
+
cmd = if File.exist?(config_file)
|
|
145
|
+
puts "📄 Using config: #{config_file}"
|
|
146
|
+
[
|
|
147
|
+
'actionlint',
|
|
148
|
+
'-config-file', config_file,
|
|
149
|
+
'-shellcheck=', # Disable shellcheck integration (empty value)
|
|
150
|
+
opts_string,
|
|
151
|
+
*workflow_files
|
|
152
|
+
].reject(&:empty?)
|
|
153
|
+
else
|
|
154
|
+
[
|
|
155
|
+
'actionlint',
|
|
156
|
+
'-shellcheck=', # Disable shellcheck integration (empty value)
|
|
157
|
+
opts_string,
|
|
158
|
+
*workflow_files
|
|
159
|
+
].reject(&:empty?)
|
|
160
|
+
end
|
|
161
|
+
success = context.run_with_fallback('actionlint', cmd)
|
|
162
|
+
if success
|
|
163
|
+
puts '✅ actionlint passed'
|
|
164
|
+
else
|
|
165
|
+
puts '❌ actionlint found issues'
|
|
166
|
+
end
|
|
167
|
+
success
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def run_vale context, file_path=nil, opts_string='', output_format: :cli, filter: nil, style_override: nil
|
|
171
|
+
scope = file_path ? :file : :project
|
|
172
|
+
running_on = file_path ? "file: #{file_path}" : scope.to_s
|
|
173
|
+
|
|
174
|
+
override_label = case style_override
|
|
175
|
+
when :adoc then ' (AsciiDoc syntax)'
|
|
176
|
+
when :text then ' (prose/text)'
|
|
177
|
+
else ''
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
puts "📝 Running Vale on #{running_on}#{override_label}"
|
|
181
|
+
|
|
182
|
+
# Generate runtime config from base + local with optional style override
|
|
183
|
+
puts ' ✅ Vale config up to date' unless context.generate_vale_config(style_override: style_override)
|
|
184
|
+
|
|
185
|
+
# Use the generated config file
|
|
186
|
+
config_file = CONFIG_PATHS[:vale]
|
|
187
|
+
|
|
188
|
+
unless File.exist?(config_file)
|
|
189
|
+
puts "❌ No Vale config found. Run 'labdev:sync:all' to generate one."
|
|
190
|
+
return false
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
puts "📄 Using config: #{config_file}"
|
|
194
|
+
|
|
195
|
+
# Check if Vale is available natively or via Docker
|
|
196
|
+
unless context.tool_available?('vale')
|
|
197
|
+
if context.docker_available?
|
|
198
|
+
puts '⚠️ Vale not found natively, using Docker fallback'
|
|
199
|
+
else
|
|
200
|
+
puts '⚠️ Vale not found. Install options:'
|
|
201
|
+
puts ' • macOS: brew install vale'
|
|
202
|
+
puts ' • Linux: https://vale.sh/docs/vale-cli/installation/'
|
|
203
|
+
puts ' • Docker: docker pull docopslab/dev'
|
|
204
|
+
return false
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Find AsciiDoc files to check, excluding vendor/ignored directories
|
|
209
|
+
if scope == :file
|
|
210
|
+
asciidoc_files = [file_path]
|
|
211
|
+
else
|
|
212
|
+
asciidoc_files = context.find_asciidoc_files
|
|
213
|
+
if asciidoc_files.empty?
|
|
214
|
+
puts '📄 No AsciiDoc files found to check'
|
|
215
|
+
return true
|
|
216
|
+
end
|
|
217
|
+
puts "📄 Found #{asciidoc_files.length} AsciiDoc file(s) to check"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Run Vale on specific files instead of scanning everything
|
|
221
|
+
cmd = ['vale', '--config', config_file]
|
|
222
|
+
|
|
223
|
+
# Add output format if not default CLI
|
|
224
|
+
cmd << "--output=#{output_format.to_s.upcase}" unless output_format == :cli
|
|
225
|
+
|
|
226
|
+
# Add filter if specified; Vale requires: --filter='.Name=="RuleName"'
|
|
227
|
+
if filter
|
|
228
|
+
# Accept either 'RuleName' or '.Name==RuleName' or '.Name=="RuleName"'
|
|
229
|
+
# Vale filter syntax: .Name=="RuleName" (expr-lang syntax, needs double quotes)
|
|
230
|
+
|
|
231
|
+
# Extract just the rule name, stripping any existing .Name== prefix and quotes
|
|
232
|
+
filter_expr = if filter.start_with?('.Name==')
|
|
233
|
+
# Strip .Name== prefix and any surrounding quotes
|
|
234
|
+
filter.sub(/^\.Name==/, '').gsub(/^["']|["']$/, '')
|
|
235
|
+
else
|
|
236
|
+
# Just the rule name, remove any quotes if present
|
|
237
|
+
filter.gsub(/^["']|["']$/, '')
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Pass as two separate args to avoid shell quoting issues
|
|
241
|
+
cmd << '--filter'
|
|
242
|
+
cmd << ".Name==\"#{filter_expr}\""
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Add additional options if provided
|
|
246
|
+
cmd += opts_string.split unless opts_string.empty?
|
|
247
|
+
|
|
248
|
+
# Add files to check
|
|
249
|
+
cmd += asciidoc_files
|
|
250
|
+
|
|
251
|
+
if output_format == :json
|
|
252
|
+
# For JSON output, capture stdout
|
|
253
|
+
# Use array form to preserve argument boundaries (esp. for --filter)
|
|
254
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
|
255
|
+
|
|
256
|
+
# Vale returns 1 for found issues, >1 for actual problems
|
|
257
|
+
if status.exitstatus > 1
|
|
258
|
+
puts "❌ Vale command failed: #{stderr}"
|
|
259
|
+
return nil
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
stdout
|
|
263
|
+
else
|
|
264
|
+
# Standard execution for CLI output
|
|
265
|
+
success = context.run_with_fallback('vale', cmd)
|
|
266
|
+
|
|
267
|
+
if success
|
|
268
|
+
puts '✅ Vale passed'
|
|
269
|
+
else
|
|
270
|
+
puts '❌ Vale found issues'
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
success
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def run_htmlproofer context
|
|
278
|
+
require 'html-proofer'
|
|
279
|
+
|
|
280
|
+
context.generate_htmlproofer_config
|
|
281
|
+
|
|
282
|
+
config_options = context.load_htmlproofer_config
|
|
283
|
+
return (puts '⚠️ No HTMLProofer config found; skipping') && true unless config_options.is_a?(Hash)
|
|
284
|
+
|
|
285
|
+
path_config = context.get_path_config('htmlproofer')
|
|
286
|
+
lint_path = path_config[:lint]
|
|
287
|
+
site_dir = lint_path.is_a?(Array) ? lint_path.first : lint_path
|
|
288
|
+
|
|
289
|
+
# Fallback to old check_directory for backward compatibility
|
|
290
|
+
site_dir ||= config_options.delete(:check_directory)
|
|
291
|
+
|
|
292
|
+
unless site_dir
|
|
293
|
+
msg = '⚠️ No directory to check for HTMLProofer specified in manifest or config file; skipping'
|
|
294
|
+
return (puts msg) && true
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
unless Dir.exist?(site_dir)
|
|
298
|
+
return (puts "⚠️ Directory '#{site_dir}' does not exist; skipping HTMLProofer") && true
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
puts "📂 Checking #{site_dir} directory..."
|
|
302
|
+
|
|
303
|
+
# Add ignored files from path config
|
|
304
|
+
ignore_files = path_config[:skip] || []
|
|
305
|
+
if ignore_files.any?
|
|
306
|
+
config_options[:ignore_files] ||= []
|
|
307
|
+
config_options[:ignore_files].concat(ignore_files.map { |p| /#{p}/ })
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
puts "🐛 [DEBUG] Final config_options: #{config_options.inspect}" if ENV['LABDEV_DEBUG'] == 'true'
|
|
311
|
+
|
|
312
|
+
begin
|
|
313
|
+
HTMLProofer.check_directory(site_dir, config_options).run
|
|
314
|
+
puts '✅ HTMLProofer passed'
|
|
315
|
+
true
|
|
316
|
+
rescue StandardError => e
|
|
317
|
+
puts "❌ HTMLProofer failed: #{e.message}"
|
|
318
|
+
false
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def run_auto_fix context
|
|
323
|
+
puts '🔧 Auto-fixing safe linting issues...'
|
|
324
|
+
|
|
325
|
+
success = true
|
|
326
|
+
|
|
327
|
+
# Auto-fix RuboCop issues
|
|
328
|
+
success &= run_rubocop_auto_fix(context)
|
|
329
|
+
|
|
330
|
+
if success
|
|
331
|
+
puts '✅ Auto-fix complete'
|
|
332
|
+
else
|
|
333
|
+
puts '❌ Some auto-fixes failed'
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
success
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def run_all_linters context
|
|
340
|
+
puts '🧹 Running all linters...'
|
|
341
|
+
|
|
342
|
+
results = {}
|
|
343
|
+
|
|
344
|
+
results[:rubocop] = run_rubocop(context)
|
|
345
|
+
results[:vale] = run_vale(context)
|
|
346
|
+
results[:shellcheck] = run_shellcheck(context)
|
|
347
|
+
results[:actionlint] = run_actionlint(context)
|
|
348
|
+
results[:htmlproofer] = run_htmlproofer(context)
|
|
349
|
+
|
|
350
|
+
# Summary
|
|
351
|
+
passed = results.values.count(true)
|
|
352
|
+
total = results.size
|
|
353
|
+
|
|
354
|
+
if passed == total
|
|
355
|
+
puts '✅ All linting complete'
|
|
356
|
+
else
|
|
357
|
+
puts "⚠️ #{passed}/#{total} linters passed"
|
|
358
|
+
results.each do |linter, result|
|
|
359
|
+
status = result ? '✅' : '❌'
|
|
360
|
+
puts " #{status} #{linter}"
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
results.values.all?
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def run_rubocop_auto_fix _context, path: nil
|
|
368
|
+
puts '👮 Running RuboCop auto-correction...'
|
|
369
|
+
|
|
370
|
+
unless File.exist?(RUBOCOP_CONFIG_PATH)
|
|
371
|
+
puts "❌ No RuboCop config found. Run 'labdev:init' to create one."
|
|
372
|
+
return false
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
puts "📄 Using config: #{RUBOCOP_CONFIG_PATH}"
|
|
376
|
+
|
|
377
|
+
# Build command with optional path
|
|
378
|
+
cmd = "bundle exec rubocop --config #{RUBOCOP_CONFIG_PATH} --autocorrect-all"
|
|
379
|
+
if path
|
|
380
|
+
cmd += " #{path}"
|
|
381
|
+
puts "📄 Targeting path: #{path}"
|
|
382
|
+
end
|
|
383
|
+
puts "🔧 Running: #{cmd}"
|
|
384
|
+
|
|
385
|
+
success = system(cmd)
|
|
386
|
+
|
|
387
|
+
if success
|
|
388
|
+
puts '✅ RuboCop auto-correction completed'
|
|
389
|
+
else
|
|
390
|
+
puts '❌ RuboCop auto-correction encountered issues'
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
success
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def run_linter_group context, group_name, linters
|
|
397
|
+
puts "Running #{group_name} linting..."
|
|
398
|
+
|
|
399
|
+
results = {}
|
|
400
|
+
linters.each do |linter|
|
|
401
|
+
method_name = "run_#{linter}"
|
|
402
|
+
if respond_to?(method_name, true)
|
|
403
|
+
results[linter.to_sym] = send(method_name, context)
|
|
404
|
+
else
|
|
405
|
+
puts "⚠️ Unknown linter: #{linter}"
|
|
406
|
+
results[linter.to_sym] = false
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
passed = results.values.count(true)
|
|
411
|
+
total = results.size
|
|
412
|
+
|
|
413
|
+
if passed == total
|
|
414
|
+
puts "✅ #{group_name} linting complete"
|
|
415
|
+
else
|
|
416
|
+
puts "❌ #{passed}/#{total} #{group_name} linters passed"
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
results.values.all?
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def lint_file context, file_path
|
|
423
|
+
ext = File.extname(file_path).downcase
|
|
424
|
+
case ext
|
|
425
|
+
when '.adoc', '.asciidoc', '.asc'
|
|
426
|
+
run_vale(context, file_path)
|
|
427
|
+
when '.rb', '.gemspec', ''
|
|
428
|
+
run_rubocop(context, file_path)
|
|
429
|
+
when '.sh'
|
|
430
|
+
run_shellcheck(context, file_path)
|
|
431
|
+
else
|
|
432
|
+
puts "⚠️ No linter configured for file type: #{ext}"
|
|
433
|
+
false
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def check_shebang file_path
|
|
438
|
+
return false unless File.exist?(file_path)
|
|
439
|
+
|
|
440
|
+
first_line = File.open(file_path, &:readline).strip
|
|
441
|
+
first_line == '#!/usr/bin/env bash'
|
|
442
|
+
rescue EOFError
|
|
443
|
+
false
|
|
444
|
+
rescue StandardError => e
|
|
445
|
+
puts "⚠️ Error checking shebang for #{file_path}: #{e.message}"
|
|
446
|
+
false
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'shellwords'
|
|
5
|
+
|
|
6
|
+
module DocOpsLab
|
|
7
|
+
module Dev
|
|
8
|
+
# Log parsing functionality for Jekyll AsciiDoctor build logs
|
|
9
|
+
module LogParser
|
|
10
|
+
class << self
|
|
11
|
+
def parse_jekyll_asciidoc_log log_file, output_dir=nil
|
|
12
|
+
output_dir ||= default_output_dir
|
|
13
|
+
|
|
14
|
+
script_name = 'parse_jekyll_asciidoc_logs.rb'
|
|
15
|
+
|
|
16
|
+
# Execute the parsing script using ScriptManager.run_script
|
|
17
|
+
Dev.run_script(script_name, [log_file, output_dir])
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def default_output_dir
|
|
23
|
+
manifest = DocOpsLab::Dev.load_manifest
|
|
24
|
+
log_config = manifest.dig('logs', 'output_dir') || '.agent/reports'
|
|
25
|
+
FileUtils.mkdir_p(log_config)
|
|
26
|
+
log_config
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DocOpsLab
|
|
4
|
+
module Dev
|
|
5
|
+
# Centralized path constants for DocOps Lab Dev
|
|
6
|
+
module Paths
|
|
7
|
+
# Gem root directory (gems/docopslab-dev/)
|
|
8
|
+
def self.gem_root
|
|
9
|
+
File.expand_path('../../..', __dir__)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Asset directories in gem
|
|
13
|
+
def self.gem_assets
|
|
14
|
+
File.join(gem_root, 'assets')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.gem_config_packs
|
|
18
|
+
File.join(gem_assets, 'config-packs')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.gem_hooks
|
|
22
|
+
File.join(gem_assets, 'hooks')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.gem_scripts
|
|
26
|
+
File.join(gem_assets, 'scripts')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Config vendor directory (where config packs are synced to)
|
|
30
|
+
def self.config_vendor_dir
|
|
31
|
+
'.config/.vendor/docopslab'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Generated/managed config files
|
|
35
|
+
CONFIG_FILES = {
|
|
36
|
+
vale: '.config/vale.ini',
|
|
37
|
+
htmlproofer: '.config/htmlproofer.yml',
|
|
38
|
+
rubocop: '.config/rubocop.yml'
|
|
39
|
+
}.freeze
|
|
40
|
+
|
|
41
|
+
def self.config_file name
|
|
42
|
+
CONFIG_FILES[name]
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|