ukiryu 0.1.1 → 0.1.3
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/.github/workflows/release.yml +58 -14
- data/.gitignore +3 -0
- data/.rubocop_todo.yml +170 -79
- data/Gemfile +1 -1
- data/README.adoc +1603 -576
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +10 -0
- data/docs/INDEX.adoc +261 -0
- data/docs/_config.yml +180 -0
- data/docs/advanced/custom-tool-classes.adoc +581 -0
- data/docs/advanced/index.adoc +20 -0
- data/docs/features/configuration.adoc +657 -0
- data/docs/features/index.adoc +31 -0
- data/docs/features/platform-support.adoc +488 -0
- data/docs/getting-started/core-concepts.adoc +666 -0
- data/docs/getting-started/index.adoc +36 -0
- data/docs/getting-started/installation.adoc +216 -0
- data/docs/getting-started/quick-start.adoc +258 -0
- data/docs/guides/env-var-sets.adoc +388 -0
- data/docs/guides/index.adoc +20 -0
- data/docs/interfaces/cli.adoc +609 -0
- data/docs/interfaces/index.adoc +153 -0
- data/docs/interfaces/ruby-api.adoc +538 -0
- data/docs/lychee.toml +49 -0
- data/docs/reference/configuration-options.adoc +720 -0
- data/docs/reference/error-codes.adoc +634 -0
- data/docs/reference/index.adoc +20 -0
- data/docs/reference/ruby-api.adoc +1217 -0
- data/docs/understanding/index.adoc +20 -0
- data/lib/ukiryu/cli.rb +43 -58
- data/lib/ukiryu/cli_commands/base_command.rb +16 -27
- data/lib/ukiryu/cli_commands/cache_command.rb +100 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +8 -8
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/config_command.rb +49 -7
- data/lib/ukiryu/cli_commands/definitions_command.rb +254 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +13 -7
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/docs_command.rb +148 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/extract_command.rb +2 -2
- data/lib/ukiryu/cli_commands/info_command.rb +7 -7
- data/lib/ukiryu/cli_commands/lint_command.rb +167 -0
- data/lib/ukiryu/cli_commands/list_command.rb +6 -6
- data/lib/ukiryu/cli_commands/opts_command.rb +2 -2
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +1 -1
- data/lib/ukiryu/cli_commands/register_command.rb +144 -0
- data/lib/ukiryu/cli_commands/resolve_command.rb +124 -0
- data/lib/ukiryu/cli_commands/run_command.rb +38 -14
- data/lib/ukiryu/cli_commands/run_file_command.rb +2 -2
- data/lib/ukiryu/cli_commands/system_command.rb +50 -32
- data/lib/ukiryu/cli_commands/validate_command.rb +452 -51
- data/lib/ukiryu/cli_commands/which_command.rb +5 -5
- data/lib/ukiryu/command_builder.rb +81 -23
- data/lib/ukiryu/config/env_provider.rb +3 -3
- data/lib/ukiryu/config/env_schema.rb +6 -6
- data/lib/ukiryu/config.rb +11 -11
- data/lib/ukiryu/definition/definition_cache.rb +238 -0
- data/lib/ukiryu/definition/definition_composer.rb +257 -0
- data/lib/ukiryu/definition/definition_linter.rb +460 -0
- data/lib/ukiryu/definition/definition_validator.rb +320 -0
- data/lib/ukiryu/definition/discovery.rb +239 -0
- data/lib/ukiryu/definition/documentation_generator.rb +429 -0
- data/lib/ukiryu/definition/lint_issue.rb +168 -0
- data/lib/ukiryu/definition/loader.rb +139 -0
- data/lib/ukiryu/definition/metadata.rb +159 -0
- data/lib/ukiryu/definition/source.rb +87 -0
- data/lib/ukiryu/definition/sources/file.rb +138 -0
- data/lib/ukiryu/definition/sources/string.rb +88 -0
- data/lib/ukiryu/definition/validation_result.rb +158 -0
- data/lib/ukiryu/definition/version_resolver.rb +194 -0
- data/lib/ukiryu/definition.rb +40 -0
- data/lib/ukiryu/errors.rb +6 -0
- data/lib/ukiryu/execution_context.rb +11 -11
- data/lib/ukiryu/executor.rb +6 -0
- data/lib/ukiryu/extractors/extractor.rb +6 -5
- data/lib/ukiryu/extractors/help_parser.rb +13 -19
- data/lib/ukiryu/logger.rb +3 -1
- data/lib/ukiryu/models/command_definition.rb +3 -3
- data/lib/ukiryu/models/command_info.rb +1 -1
- data/lib/ukiryu/models/components.rb +1 -3
- data/lib/ukiryu/models/env_var_definition.rb +11 -3
- data/lib/ukiryu/models/flag_definition.rb +15 -0
- data/lib/ukiryu/models/option_definition.rb +7 -7
- data/lib/ukiryu/models/platform_profile.rb +6 -3
- data/lib/ukiryu/models/routing.rb +1 -1
- data/lib/ukiryu/models/tool_definition.rb +2 -4
- data/lib/ukiryu/models/tool_metadata.rb +6 -6
- data/lib/ukiryu/models/validation_result.rb +1 -1
- data/lib/ukiryu/models/version_compatibility.rb +6 -3
- data/lib/ukiryu/models/version_detection.rb +10 -1
- data/lib/ukiryu/{registry.rb → register.rb} +54 -38
- data/lib/ukiryu/register_auto_manager.rb +268 -0
- data/lib/ukiryu/schema_validator.rb +31 -10
- data/lib/ukiryu/shell/base.rb +18 -0
- data/lib/ukiryu/shell/bash.rb +19 -1
- data/lib/ukiryu/shell/cmd.rb +11 -1
- data/lib/ukiryu/shell/powershell.rb +11 -1
- data/lib/ukiryu/shell.rb +1 -1
- data/lib/ukiryu/tool.rb +107 -95
- data/lib/ukiryu/tool_index.rb +22 -22
- data/lib/ukiryu/tools/base.rb +12 -25
- data/lib/ukiryu/tools/generator.rb +7 -7
- data/lib/ukiryu/tools.rb +3 -3
- data/lib/ukiryu/type.rb +20 -5
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +21 -2
- data/lib/ukiryu.rb +6 -3
- data/ukiryu-proposal.md +41 -41
- data/ukiryu.gemspec +1 -0
- metadata +64 -8
- data/.gitmodules +0 -3
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lint_issue'
|
|
4
|
+
|
|
5
|
+
module Ukiryu
|
|
6
|
+
module Definition
|
|
7
|
+
# Lint tool definitions for best practices
|
|
8
|
+
#
|
|
9
|
+
# This class checks tool definitions for best practices,
|
|
10
|
+
# deprecated patterns, naming conventions, and security issues.
|
|
11
|
+
class DefinitionLinter
|
|
12
|
+
# Linting result
|
|
13
|
+
class LintResult
|
|
14
|
+
attr_reader :issues
|
|
15
|
+
|
|
16
|
+
def initialize(issues = [])
|
|
17
|
+
@issues = issues
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get issues by severity
|
|
21
|
+
#
|
|
22
|
+
# @param severity [Symbol] severity level
|
|
23
|
+
# @return [Array<LintIssue>] issues with the specified severity
|
|
24
|
+
def by_severity(severity)
|
|
25
|
+
@issues.select { |i| i.severity == severity }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get errors
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<LintIssue>] error issues
|
|
31
|
+
def errors
|
|
32
|
+
by_severity(LintIssue::SEVERITY_ERROR)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get warnings
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<LintIssue>] warning issues
|
|
38
|
+
def warnings
|
|
39
|
+
by_severity(LintIssue::SEVERITY_WARNING)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get info issues
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<LintIssue>] info issues
|
|
45
|
+
def infos
|
|
46
|
+
by_severity(LintIssue::SEVERITY_INFO)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Get style issues
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<LintIssue>] style issues
|
|
52
|
+
def styles
|
|
53
|
+
by_severity(LintIssue::SEVERITY_STYLE)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check if there are any issues
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean] true if there are issues
|
|
59
|
+
def has_issues?
|
|
60
|
+
!@issues.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check if there are any errors
|
|
64
|
+
#
|
|
65
|
+
# @return [Boolean] true if there are errors
|
|
66
|
+
def has_errors?
|
|
67
|
+
!errors.empty?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get total issue count
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer] total number of issues
|
|
73
|
+
def count
|
|
74
|
+
@issues.length
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get count by severity
|
|
78
|
+
#
|
|
79
|
+
# @param severity [Symbol] severity level
|
|
80
|
+
# @return [Integer] count of issues with specified severity
|
|
81
|
+
def count_by_severity(severity)
|
|
82
|
+
by_severity(severity).length
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Convert to hash
|
|
86
|
+
#
|
|
87
|
+
# @return [Hash] hash representation
|
|
88
|
+
def to_h
|
|
89
|
+
{
|
|
90
|
+
issues: @issues.map(&:to_h),
|
|
91
|
+
total_count: count,
|
|
92
|
+
error_count: errors.length,
|
|
93
|
+
warning_count: warnings.length,
|
|
94
|
+
info_count: infos.length,
|
|
95
|
+
style_count: styles.length
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Format as string
|
|
100
|
+
#
|
|
101
|
+
# @return [String] formatted result
|
|
102
|
+
def to_s
|
|
103
|
+
return 'No issues found' unless has_issues?
|
|
104
|
+
|
|
105
|
+
output = []
|
|
106
|
+
output << "Found #{count} issue(s):"
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
LintIssue::SEVERITY_ERROR => errors,
|
|
110
|
+
LintIssue::SEVERITY_WARNING => warnings,
|
|
111
|
+
LintIssue::SEVERITY_INFO => infos,
|
|
112
|
+
LintIssue::SEVERITY_STYLE => styles
|
|
113
|
+
}.each do |severity, issues|
|
|
114
|
+
next if issues.empty?
|
|
115
|
+
|
|
116
|
+
output << ''
|
|
117
|
+
output << "#{severity.to_s.upcase}:"
|
|
118
|
+
issues.each { |issue| output << " #{issue}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
output.join("\n")
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Linting rules configuration
|
|
126
|
+
class Rules
|
|
127
|
+
# Naming convention rules
|
|
128
|
+
NAMING_RULES = {
|
|
129
|
+
tool_name_format: {
|
|
130
|
+
rule_id: 'naming_tool_name_format',
|
|
131
|
+
pattern: /^[a-z][a-z0-9_-]*$/,
|
|
132
|
+
message: 'Tool name should start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores',
|
|
133
|
+
suggestion: 'Use lowercase with hyphens for multi-word names (e.g., "my-tool")'
|
|
134
|
+
},
|
|
135
|
+
command_name_format: {
|
|
136
|
+
rule_id: 'naming_command_name_format',
|
|
137
|
+
pattern: /^[a-z][a-z0-9_]*$/,
|
|
138
|
+
message: 'Command names should be lowercase with underscores',
|
|
139
|
+
suggestion: 'Use snake_case for command names (e.g., "build_command")'
|
|
140
|
+
}
|
|
141
|
+
}.freeze
|
|
142
|
+
|
|
143
|
+
# Completeness rules
|
|
144
|
+
COMPLETENESS_RULES = {
|
|
145
|
+
missing_description: {
|
|
146
|
+
rule_id: 'complete_missing_description',
|
|
147
|
+
message: 'Tool is missing a description',
|
|
148
|
+
suggestion: 'Add a "description" field to help users understand what this tool does'
|
|
149
|
+
},
|
|
150
|
+
missing_homepage: {
|
|
151
|
+
rule_id: 'complete_missing_homepage',
|
|
152
|
+
message: 'Tool is missing a homepage URL',
|
|
153
|
+
suggestion: 'Add a "homepage" field linking to the tool\'s website'
|
|
154
|
+
},
|
|
155
|
+
missing_version_detection: {
|
|
156
|
+
rule_id: 'complete_missing_version_detection',
|
|
157
|
+
message: 'Tool is missing version detection',
|
|
158
|
+
suggestion: 'Add "version_detection" to auto-detect installed versions'
|
|
159
|
+
}
|
|
160
|
+
}.freeze
|
|
161
|
+
|
|
162
|
+
# Security rules
|
|
163
|
+
SECURITY_RULES = {
|
|
164
|
+
suspicious_subcommand: {
|
|
165
|
+
rule_id: 'security_suspicious_subcommand',
|
|
166
|
+
pattern: /(^|\s|;)\s*(rm\s+-rf|del|format|mkfs)/,
|
|
167
|
+
message: 'Subcommand contains potentially dangerous shell commands',
|
|
168
|
+
suggestion: 'Avoid using destructive commands in subcommands'
|
|
169
|
+
},
|
|
170
|
+
unvalidated_user_input: {
|
|
171
|
+
rule_id: 'security_unvalidated_input',
|
|
172
|
+
message: 'Arguments should specify type validation',
|
|
173
|
+
suggestion: 'Add "type" field to all arguments for validation'
|
|
174
|
+
}
|
|
175
|
+
}.freeze
|
|
176
|
+
|
|
177
|
+
# Deprecated patterns
|
|
178
|
+
DEPRECATED_RULES = {
|
|
179
|
+
old_schema_version: {
|
|
180
|
+
rule_id: 'deprecated_old_schema',
|
|
181
|
+
threshold: '1.0',
|
|
182
|
+
message: 'Using old schema version',
|
|
183
|
+
suggestion: 'Update to the latest schema version (1.2)'
|
|
184
|
+
}
|
|
185
|
+
}.freeze
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
class << self
|
|
189
|
+
# Lint a definition
|
|
190
|
+
#
|
|
191
|
+
# @param definition [Hash] the definition to lint
|
|
192
|
+
# @param rules [Hash, nil] optional rule overrides
|
|
193
|
+
# @return [LintResult] linting result
|
|
194
|
+
def lint(definition, rules: nil)
|
|
195
|
+
issues = []
|
|
196
|
+
|
|
197
|
+
# Check if definition is a hash
|
|
198
|
+
return LintResult.new([LintIssue.error('Definition must be a hash/object')]) unless definition.is_a?(Hash)
|
|
199
|
+
|
|
200
|
+
# Run all lint checks
|
|
201
|
+
issues.concat(check_naming_conventions(definition))
|
|
202
|
+
issues.concat(check_completeness(definition))
|
|
203
|
+
issues.concat(check_security(definition))
|
|
204
|
+
issues.concat(check_deprecated_patterns(definition))
|
|
205
|
+
issues.concat(check_best_practices(definition))
|
|
206
|
+
|
|
207
|
+
# Filter by rules if provided
|
|
208
|
+
if rules
|
|
209
|
+
enabled_rules = rules[:enabled] || []
|
|
210
|
+
disabled_rules = rules[:disabled] || []
|
|
211
|
+
|
|
212
|
+
issues = issues.select do |issue|
|
|
213
|
+
if disabled_rules.any?
|
|
214
|
+
!disabled_rules.include?(issue.rule_id)
|
|
215
|
+
elsif enabled_rules.any?
|
|
216
|
+
enabled_rules.include?(issue.rule_id)
|
|
217
|
+
else
|
|
218
|
+
true
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
LintResult.new(issues)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Lint a definition file
|
|
227
|
+
#
|
|
228
|
+
# @param file_path [String] path to definition file
|
|
229
|
+
# @param rules [Hash, nil] optional rule overrides
|
|
230
|
+
# @return [LintResult] linting result
|
|
231
|
+
def lint_file(file_path, rules: nil)
|
|
232
|
+
# Load raw YAML hash for linting
|
|
233
|
+
require 'yaml'
|
|
234
|
+
definition = YAML.safe_load(File.read(file_path), permitted_classes: [Symbol, Date, Time],
|
|
235
|
+
symbolize_names: true)
|
|
236
|
+
lint(definition, rules: rules)
|
|
237
|
+
rescue Ukiryu::DefinitionNotFoundError
|
|
238
|
+
LintResult.new([LintIssue.error("File not found: #{file_path}")])
|
|
239
|
+
rescue Ukiryu::DefinitionLoadError, Ukiryu::DefinitionValidationError => e
|
|
240
|
+
LintResult.new([LintIssue.error(e.message)])
|
|
241
|
+
rescue Errno::ENOENT
|
|
242
|
+
LintResult.new([LintIssue.error("File not found: #{file_path}")])
|
|
243
|
+
rescue Psych::SyntaxError => e
|
|
244
|
+
LintResult.new([LintIssue.error("Invalid YAML: #{e.message}")])
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Lint a YAML string
|
|
248
|
+
#
|
|
249
|
+
# @param yaml_string [String] YAML content
|
|
250
|
+
# @param rules [Hash, nil] optional rule overrides
|
|
251
|
+
# @return [LintResult] linting result
|
|
252
|
+
def lint_string(yaml_string, rules: nil)
|
|
253
|
+
require 'yaml'
|
|
254
|
+
definition = YAML.safe_load(yaml_string, permitted_classes: [Symbol, Date, Time])
|
|
255
|
+
lint(definition, rules: rules)
|
|
256
|
+
rescue Psych::SyntaxError => e
|
|
257
|
+
LintResult.new([LintIssue.error("Invalid YAML: #{e.message}")])
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
private
|
|
261
|
+
|
|
262
|
+
# Check naming conventions
|
|
263
|
+
#
|
|
264
|
+
# @param definition [Hash] the definition
|
|
265
|
+
# @return [Array<LintIssue>] naming issues
|
|
266
|
+
def check_naming_conventions(definition)
|
|
267
|
+
issues = []
|
|
268
|
+
|
|
269
|
+
# Check tool name format
|
|
270
|
+
if definition[:name]
|
|
271
|
+
rule = Rules::NAMING_RULES[:tool_name_format]
|
|
272
|
+
unless definition[:name].match?(rule[:pattern])
|
|
273
|
+
issues << LintIssue.warning(
|
|
274
|
+
rule[:message],
|
|
275
|
+
location: 'name',
|
|
276
|
+
suggestion: rule[:suggestion],
|
|
277
|
+
rule_id: rule[:rule_id]
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Check command names
|
|
283
|
+
definition[:profiles]&.each_with_index do |profile, p_idx|
|
|
284
|
+
next unless profile[:commands]
|
|
285
|
+
|
|
286
|
+
profile[:commands].each_key do |cmd_name|
|
|
287
|
+
rule = Rules::NAMING_RULES[:command_name_format]
|
|
288
|
+
next if cmd_name.to_s.match?(rule[:pattern])
|
|
289
|
+
|
|
290
|
+
issues << LintIssue.warning(
|
|
291
|
+
rule[:message],
|
|
292
|
+
location: "profiles[#{p_idx}].commands.#{cmd_name}",
|
|
293
|
+
suggestion: rule[:suggestion],
|
|
294
|
+
rule_id: rule[:rule_id]
|
|
295
|
+
)
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
issues
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Check completeness
|
|
303
|
+
#
|
|
304
|
+
# @param definition [Hash] the definition
|
|
305
|
+
# @return [Array<LintIssue>] completeness issues
|
|
306
|
+
def check_completeness(definition)
|
|
307
|
+
issues = []
|
|
308
|
+
|
|
309
|
+
# Check for description
|
|
310
|
+
unless definition[:description]
|
|
311
|
+
rule = Rules::COMPLETENESS_RULES[:missing_description]
|
|
312
|
+
issues << LintIssue.info(
|
|
313
|
+
rule[:message],
|
|
314
|
+
location: 'definition',
|
|
315
|
+
suggestion: rule[:suggestion],
|
|
316
|
+
rule_id: rule[:rule_id]
|
|
317
|
+
)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# Check for homepage
|
|
321
|
+
unless definition[:homepage]
|
|
322
|
+
rule = Rules::COMPLETENESS_RULES[:missing_homepage]
|
|
323
|
+
issues << LintIssue.info(
|
|
324
|
+
rule[:message],
|
|
325
|
+
location: 'definition',
|
|
326
|
+
suggestion: rule[:suggestion],
|
|
327
|
+
rule_id: rule[:rule_id]
|
|
328
|
+
)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# Check for version detection
|
|
332
|
+
unless definition[:version_detection]
|
|
333
|
+
rule = Rules::COMPLETENESS_RULES[:missing_version_detection]
|
|
334
|
+
issues << LintIssue.warning(
|
|
335
|
+
rule[:message],
|
|
336
|
+
location: 'definition',
|
|
337
|
+
suggestion: rule[:suggestion],
|
|
338
|
+
rule_id: rule[:rule_id]
|
|
339
|
+
)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
issues
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Check security issues
|
|
346
|
+
#
|
|
347
|
+
# @param definition [Hash] the definition
|
|
348
|
+
# @return [Array<LintIssue>] security issues
|
|
349
|
+
def check_security(definition)
|
|
350
|
+
issues = []
|
|
351
|
+
|
|
352
|
+
# Check for suspicious subcommands
|
|
353
|
+
definition[:profiles]&.each_with_index do |profile, p_idx|
|
|
354
|
+
next unless profile[:commands]
|
|
355
|
+
|
|
356
|
+
profile[:commands].each do |cmd_name, cmd_def|
|
|
357
|
+
next unless cmd_def[:subcommand]
|
|
358
|
+
|
|
359
|
+
subcommand = cmd_def[:subcommand].to_s
|
|
360
|
+
rule = Rules::SECURITY_RULES[:suspicious_subcommand]
|
|
361
|
+
next unless subcommand.match?(rule[:pattern])
|
|
362
|
+
|
|
363
|
+
issues << LintIssue.error(
|
|
364
|
+
rule[:message],
|
|
365
|
+
location: "profiles[#{p_idx}].commands.#{cmd_name}.subcommand",
|
|
366
|
+
suggestion: rule[:suggestion],
|
|
367
|
+
rule_id: rule[:rule_id]
|
|
368
|
+
)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Check for unvalidated arguments
|
|
372
|
+
next unless profile[:commands]
|
|
373
|
+
|
|
374
|
+
profile[:commands].each do |cmd_name, cmd_def|
|
|
375
|
+
next unless cmd_def[:arguments]
|
|
376
|
+
|
|
377
|
+
cmd_def[:arguments].each_with_index do |arg, a_idx|
|
|
378
|
+
next if arg[:type]
|
|
379
|
+
|
|
380
|
+
rule = Rules::SECURITY_RULES[:unvalidated_user_input]
|
|
381
|
+
issues << LintIssue.warning(
|
|
382
|
+
rule[:message],
|
|
383
|
+
location: "profiles[#{p_idx}].commands.#{cmd_name}.arguments[#{a_idx}]",
|
|
384
|
+
suggestion: rule[:suggestion],
|
|
385
|
+
rule_id: rule[:rule_id]
|
|
386
|
+
)
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
issues
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# Check for deprecated patterns
|
|
395
|
+
#
|
|
396
|
+
# @param definition [Hash] the definition
|
|
397
|
+
# @return [Array<LintIssue>] deprecation issues
|
|
398
|
+
def check_deprecated_patterns(definition)
|
|
399
|
+
issues = []
|
|
400
|
+
|
|
401
|
+
# Check schema version
|
|
402
|
+
if definition[:schema] || definition['$schema']
|
|
403
|
+
schema = definition[:schema] || definition['$schema']
|
|
404
|
+
version = schema.to_s.split('/').last.gsub('v', '')
|
|
405
|
+
|
|
406
|
+
require 'ukiryu/definition/version_resolver'
|
|
407
|
+
rule = Rules::DEPRECATED_RULES[:old_schema_version]
|
|
408
|
+
|
|
409
|
+
begin
|
|
410
|
+
if VersionResolver.compare_versions(version, rule[:threshold]).negative?
|
|
411
|
+
issues << LintIssue.warning(
|
|
412
|
+
rule[:message],
|
|
413
|
+
location: 'schema',
|
|
414
|
+
suggestion: rule[:suggestion],
|
|
415
|
+
rule_id: rule[:rule_id]
|
|
416
|
+
)
|
|
417
|
+
end
|
|
418
|
+
rescue StandardError
|
|
419
|
+
# Skip if version comparison fails
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
issues
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
# Check best practices
|
|
427
|
+
#
|
|
428
|
+
# @param definition [Hash] the definition
|
|
429
|
+
# @return [Array<LintIssue>] best practice issues
|
|
430
|
+
def check_best_practices(definition)
|
|
431
|
+
issues = []
|
|
432
|
+
|
|
433
|
+
# Check for redundant default profile name
|
|
434
|
+
definition[:profiles]&.each_with_index do |profile, p_idx|
|
|
435
|
+
next unless profile[:name] == 'default' && definition[:profiles].length == 1
|
|
436
|
+
|
|
437
|
+
issues << LintIssue.style(
|
|
438
|
+
'Single profile named "default" is redundant',
|
|
439
|
+
location: "profiles[#{p_idx}]",
|
|
440
|
+
suggestion: 'For single-profile definitions, omit the profiles array and define commands directly',
|
|
441
|
+
rule_id: 'style_redundant_default_profile'
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Check for missing platforms
|
|
445
|
+
next if profile[:platforms]
|
|
446
|
+
|
|
447
|
+
issues << LintIssue.warning(
|
|
448
|
+
'Profile missing platforms specification',
|
|
449
|
+
location: "profiles[#{p_idx}]",
|
|
450
|
+
suggestion: 'Specify supported platforms (macos, linux, windows)',
|
|
451
|
+
rule_id: 'complete_missing_platforms'
|
|
452
|
+
)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
issues
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
end
|