devex 0.3.5
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/.obsidian/app.json +6 -0
- data/.obsidian/appearance.json +4 -0
- data/.obsidian/community-plugins.json +5 -0
- data/.obsidian/core-plugins.json +33 -0
- data/.obsidian/plugins/obsidian-minimal-settings/data.json +34 -0
- data/.obsidian/plugins/obsidian-minimal-settings/main.js +8 -0
- data/.obsidian/plugins/obsidian-minimal-settings/manifest.json +11 -0
- data/.obsidian/plugins/obsidian-style-settings/data.json +15 -0
- data/.obsidian/plugins/obsidian-style-settings/main.js +165 -0
- data/.obsidian/plugins/obsidian-style-settings/manifest.json +10 -0
- data/.obsidian/plugins/obsidian-style-settings/styles.css +243 -0
- data/.obsidian/plugins/table-editor-obsidian/data.json +6 -0
- data/.obsidian/plugins/table-editor-obsidian/main.js +236 -0
- data/.obsidian/plugins/table-editor-obsidian/manifest.json +17 -0
- data/.obsidian/plugins/table-editor-obsidian/styles.css +78 -0
- data/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
- data/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
- data/.obsidian/themes/Minimal/manifest.json +8 -0
- data/.obsidian/themes/Minimal/theme.css +2251 -0
- data/.rubocop.yml +231 -0
- data/CHANGELOG.md +97 -0
- data/LICENSE +21 -0
- data/README.md +314 -0
- data/Rakefile +13 -0
- data/devex-logo.jpg +0 -0
- data/docs/developing-tools.md +1000 -0
- data/docs/ref/agent-mode.md +46 -0
- data/docs/ref/cli-interface.md +60 -0
- data/docs/ref/configuration.md +46 -0
- data/docs/ref/design-philosophy.md +17 -0
- data/docs/ref/error-handling.md +38 -0
- data/docs/ref/io-handling.md +88 -0
- data/docs/ref/signals.md +141 -0
- data/docs/ref/temporal-software-theory.md +790 -0
- data/exe/dx +52 -0
- data/lib/devex/builtins/.index.rb +10 -0
- data/lib/devex/builtins/debug.rb +43 -0
- data/lib/devex/builtins/format.rb +44 -0
- data/lib/devex/builtins/gem.rb +77 -0
- data/lib/devex/builtins/lint.rb +61 -0
- data/lib/devex/builtins/test.rb +76 -0
- data/lib/devex/builtins/version.rb +156 -0
- data/lib/devex/cli.rb +340 -0
- data/lib/devex/context.rb +433 -0
- data/lib/devex/core/configuration.rb +136 -0
- data/lib/devex/core.rb +79 -0
- data/lib/devex/dirs.rb +210 -0
- data/lib/devex/dsl.rb +100 -0
- data/lib/devex/exec/controller.rb +245 -0
- data/lib/devex/exec/result.rb +229 -0
- data/lib/devex/exec.rb +662 -0
- data/lib/devex/loader.rb +136 -0
- data/lib/devex/output.rb +257 -0
- data/lib/devex/project_paths.rb +309 -0
- data/lib/devex/support/ansi.rb +437 -0
- data/lib/devex/support/core_ext.rb +560 -0
- data/lib/devex/support/global.rb +68 -0
- data/lib/devex/support/path.rb +357 -0
- data/lib/devex/support.rb +71 -0
- data/lib/devex/template_helpers.rb +136 -0
- data/lib/devex/templates/debug.erb +24 -0
- data/lib/devex/tool.rb +374 -0
- data/lib/devex/version.rb +5 -0
- data/lib/devex/working_dir.rb +99 -0
- data/lib/devex.rb +158 -0
- data/ruby-project-template/.gitignore +0 -0
- data/ruby-project-template/Gemfile +0 -0
- data/ruby-project-template/README.md +0 -0
- data/ruby-project-template/docs/README.md +0 -0
- data/sig/devex.rbs +4 -0
- metadata +122 -0
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Archema RuboCop Configuration
|
|
2
|
+
#
|
|
3
|
+
# Layout/alignment settings come from tablecop.
|
|
4
|
+
# This file contains archema-specific settings.
|
|
5
|
+
|
|
6
|
+
plugins:
|
|
7
|
+
- rubocop-minitest
|
|
8
|
+
- rubocop-tablecop
|
|
9
|
+
|
|
10
|
+
AllCops:
|
|
11
|
+
TargetRubyVersion: 3.3
|
|
12
|
+
NewCops: enable
|
|
13
|
+
SuggestExtensions: false
|
|
14
|
+
Exclude:
|
|
15
|
+
- "tmp/**/*"
|
|
16
|
+
- "vendor/**/*"
|
|
17
|
+
- "coverage/**/*"
|
|
18
|
+
- "examples/**/*"
|
|
19
|
+
- "docs/msc/**/*" # Experimental analysis scripts, not production code
|
|
20
|
+
|
|
21
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
+
# Style
|
|
23
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
# Allow both single and double quotes - use double by convention,
|
|
26
|
+
# single when semantically meaningful (e.g., JSON-style string keys)
|
|
27
|
+
Style/StringLiterals:
|
|
28
|
+
Enabled: false
|
|
29
|
+
|
|
30
|
+
# Prefer explicit bracket syntax for symbol arrays
|
|
31
|
+
Style/SymbolArray:
|
|
32
|
+
EnforcedStyle: brackets
|
|
33
|
+
|
|
34
|
+
# Prefer %w[] for word arrays
|
|
35
|
+
Style/WordArray:
|
|
36
|
+
EnforcedStyle: percent
|
|
37
|
+
|
|
38
|
+
# Require class/module documentation (except for internal modules)
|
|
39
|
+
Style/Documentation:
|
|
40
|
+
Enabled: true
|
|
41
|
+
AllowedConstants:
|
|
42
|
+
- ClassMethods # Internal mixin modules don't need separate docs
|
|
43
|
+
- DSL # DSL namespace modules documented at parent level
|
|
44
|
+
Exclude:
|
|
45
|
+
- "lib/archema/schema/differ.rb" # Operation classes are internal data structures
|
|
46
|
+
- "lib/archema/shared_types.rb" # Module reopening, documented in main file
|
|
47
|
+
- "lib/archema/stores.rb" # Module reopening, documented in main file
|
|
48
|
+
- "test/**/*" # Test classes don't need documentation
|
|
49
|
+
|
|
50
|
+
# Allow multi-line block chains when readable
|
|
51
|
+
Style/MultilineBlockChain:
|
|
52
|
+
Enabled: false
|
|
53
|
+
|
|
54
|
+
# DANGEROUS: Autocorrect converts !!value to !value.nil? which has
|
|
55
|
+
# different semantics for boolean values (!!false → false, !false.nil? → true)
|
|
56
|
+
Style/DoubleNegation:
|
|
57
|
+
Enabled: false
|
|
58
|
+
|
|
59
|
+
# DANGEROUS: Changes semantics with mixed key types.
|
|
60
|
+
# reject { |k, _| keys.include?(k) } handles symbol/string keys correctly,
|
|
61
|
+
# but hash.except(*keys) treats :key and "key" as different.
|
|
62
|
+
Style/HashExcept:
|
|
63
|
+
Enabled: false
|
|
64
|
+
|
|
65
|
+
# DANGEROUS: Converts `Module % [args]` to `format(Module, args)` which breaks
|
|
66
|
+
# custom % methods (like ANSI % ["text", :style] for nested color spans).
|
|
67
|
+
Style/FormatString:
|
|
68
|
+
Enabled: false
|
|
69
|
+
|
|
70
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
# Layout
|
|
72
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
Layout/LineLength:
|
|
75
|
+
Max: 140
|
|
76
|
+
|
|
77
|
+
# Allow RBS inline type comments like #: Type
|
|
78
|
+
Layout/LeadingCommentSpace:
|
|
79
|
+
AllowRBSInlineAnnotation: true
|
|
80
|
+
|
|
81
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
# Naming
|
|
83
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
# DISABLED: Renames @data_layer_instance to @data_layer, but tests
|
|
86
|
+
# use instance_variable_set(:@data_layer_instance, nil) to reset state.
|
|
87
|
+
Naming/MemoizedInstanceVariableName:
|
|
88
|
+
Enabled: false
|
|
89
|
+
|
|
90
|
+
# DISABLED: Too pedantic about underscores in numbers (last_5, chain_from_11, 1_0_0)
|
|
91
|
+
Naming/VariableNumber:
|
|
92
|
+
Enabled: false
|
|
93
|
+
|
|
94
|
+
# Allow common short parameter names
|
|
95
|
+
Naming/MethodParameterName:
|
|
96
|
+
AllowedNames:
|
|
97
|
+
- a
|
|
98
|
+
- b
|
|
99
|
+
- c
|
|
100
|
+
- e # exception, element, entry
|
|
101
|
+
- i # index
|
|
102
|
+
- j # secondary index
|
|
103
|
+
- k # key
|
|
104
|
+
- v # value
|
|
105
|
+
- h # hash
|
|
106
|
+
- n # number, count
|
|
107
|
+
- x # coordinate, generic
|
|
108
|
+
- y # coordinate
|
|
109
|
+
- id # identifier
|
|
110
|
+
- io # IO object
|
|
111
|
+
- op # operation
|
|
112
|
+
- db # database
|
|
113
|
+
- fn # function
|
|
114
|
+
- s1 # string comparison (levenshtein)
|
|
115
|
+
- s2 # string comparison (levenshtein)
|
|
116
|
+
- sc # sub_constraint in composition constraints
|
|
117
|
+
- ds # dataset (Sequel)
|
|
118
|
+
- to # destination (from:, to:)
|
|
119
|
+
- of # element type (array of:)
|
|
120
|
+
- _ # explicitly unused
|
|
121
|
+
|
|
122
|
+
# Allow has_* prefix for methods that genuinely "have" something (not just predicates)
|
|
123
|
+
# has_default?, has_role?, etc. are clearer than default?, role? in context
|
|
124
|
+
Naming/PredicatePrefix:
|
|
125
|
+
AllowedMethods:
|
|
126
|
+
- has_default?
|
|
127
|
+
- has_role?
|
|
128
|
+
- has_managed_data?
|
|
129
|
+
- has_cardinality_constraints?
|
|
130
|
+
- has_temporal_defaults?
|
|
131
|
+
- has_irreversible_operations?
|
|
132
|
+
- has_one # DSL method: has_one :profile, Profile
|
|
133
|
+
- has_many # DSL method: has_many :posts, Post
|
|
134
|
+
|
|
135
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
+
# Lint
|
|
137
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
Lint/UselessAssignment:
|
|
140
|
+
Enabled: true
|
|
141
|
+
|
|
142
|
+
# DISABLED: Forces combining case branches that happen to have the same result,
|
|
143
|
+
# but often they represent distinct semantic intents. With tablecop alignment,
|
|
144
|
+
# separate branches read as visual lookup tables which is clearer.
|
|
145
|
+
Lint/DuplicateBranch:
|
|
146
|
+
Enabled: false
|
|
147
|
+
|
|
148
|
+
Lint/UnusedMethodArgument:
|
|
149
|
+
AllowUnusedKeywordArguments: true
|
|
150
|
+
IgnoreEmptyMethods: true
|
|
151
|
+
|
|
152
|
+
Lint/MissingSuper:
|
|
153
|
+
Enabled: false
|
|
154
|
+
|
|
155
|
+
# DANGEROUS: Removes `require "set"` as "redundant" in Ruby 3.2+, but code
|
|
156
|
+
# using Set.new will fail if Set isn't explicitly required.
|
|
157
|
+
Lint/RedundantRequireStatement:
|
|
158
|
+
Enabled: false
|
|
159
|
+
|
|
160
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
161
|
+
# Metrics - Lenient thresholds for real-world code
|
|
162
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
Metrics/MethodLength:
|
|
165
|
+
Max: 40
|
|
166
|
+
CountAsOne:
|
|
167
|
+
- array
|
|
168
|
+
- hash
|
|
169
|
+
- heredoc
|
|
170
|
+
Exclude:
|
|
171
|
+
- "lib/chiridion/engine/inline_rbs_loader.rb"
|
|
172
|
+
|
|
173
|
+
Metrics/AbcSize:
|
|
174
|
+
Max: 50
|
|
175
|
+
Exclude:
|
|
176
|
+
- "lib/chiridion/engine/inline_rbs_loader.rb"
|
|
177
|
+
|
|
178
|
+
Metrics/CyclomaticComplexity:
|
|
179
|
+
Max: 20
|
|
180
|
+
|
|
181
|
+
Metrics/PerceivedComplexity:
|
|
182
|
+
Max: 15
|
|
183
|
+
Exclude:
|
|
184
|
+
- "lib/chiridion/engine/inline_rbs_loader.rb"
|
|
185
|
+
|
|
186
|
+
Metrics/ClassLength:
|
|
187
|
+
Max: 300
|
|
188
|
+
CountAsOne:
|
|
189
|
+
- array
|
|
190
|
+
- hash
|
|
191
|
+
- heredoc
|
|
192
|
+
|
|
193
|
+
Metrics/BlockLength:
|
|
194
|
+
Max: 40
|
|
195
|
+
CountAsOne:
|
|
196
|
+
- array
|
|
197
|
+
- hash
|
|
198
|
+
- heredoc
|
|
199
|
+
Exclude:
|
|
200
|
+
- "test/**/*"
|
|
201
|
+
- "spec/**/*"
|
|
202
|
+
- "features/**/*"
|
|
203
|
+
- "lib/chiridion/engine/inline_rbs_loader.rb"
|
|
204
|
+
AllowedMethods:
|
|
205
|
+
- describe
|
|
206
|
+
- context
|
|
207
|
+
- define
|
|
208
|
+
- configure
|
|
209
|
+
- namespace
|
|
210
|
+
|
|
211
|
+
Metrics/ParameterLists:
|
|
212
|
+
Max: 15
|
|
213
|
+
|
|
214
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
215
|
+
# Minitest
|
|
216
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
Minitest/EmptyLineBeforeAssertionMethods:
|
|
219
|
+
Enabled: false
|
|
220
|
+
|
|
221
|
+
Minitest/MultipleAssertions:
|
|
222
|
+
Max: 8
|
|
223
|
+
|
|
224
|
+
# DANGEROUS: Converts `assert obj.foo?` to `assert_predicate obj, :foo?`
|
|
225
|
+
# which breaks refinements since assert_predicate uses send() internally
|
|
226
|
+
# and refinements are lexically scoped (don't work with send).
|
|
227
|
+
Minitest/AssertPredicate:
|
|
228
|
+
Enabled: false
|
|
229
|
+
|
|
230
|
+
Minitest/RefutePredicate:
|
|
231
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to devex will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.3.5] - 2025-12-29
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- **Version detection fallback**: `dx version` now searches effective working directory when no project markers found, handling pre-git-init projects
|
|
9
|
+
- **`--dx-from-dir` flag**: Now correctly affects project root discovery (was being ignored)
|
|
10
|
+
- **Subtool helper methods**: Nested tools (`tool "name" do...end`) can now access helper methods defined in parent file (fixes `dx version bump` which was broken)
|
|
11
|
+
|
|
12
|
+
## [0.3.4] - 2025-12-13
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `prj.linter` convention in ProjectPaths - finds .standard.yml or .rubocop.yml
|
|
16
|
+
- `Path#rm`, `Path#delete`, `Path#unlink` - delete a file
|
|
17
|
+
- `Path#rm_rf` - delete directory recursively
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Built-ins now fully trust ProjectPaths fail-fast behavior - no more rescue/re-error patterns
|
|
21
|
+
- `dx lint` and `dx format` use `prj.linter` instead of manual detection
|
|
22
|
+
- `dx test` uses `prj.test`, `dx gem` uses `prj.gemspec` - all with clean fail-fast
|
|
23
|
+
|
|
24
|
+
## [0.3.3] - 2025-12-13
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **Built-in tools now run from project root** - `dx test`, `dx lint`, `dx format`, `dx gem` now properly discover project root via `Devex::Dirs.project_dir` and run commands from there, regardless of current directory
|
|
28
|
+
- Built-ins now use `ProjectPaths` (`prj.test`, `prj.gemspec`, etc.) for conventional path discovery instead of manual File.exist? checks
|
|
29
|
+
|
|
30
|
+
## [0.3.2] - 2025-12-13
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- ExecutionContext now includes Devex::Exec automatically, so nested tools (inside `tool "name" do ... end` blocks) have access to `cmd`, `capture`, etc. without explicit include
|
|
34
|
+
- Moved exec.rb require before tool.rb to ensure proper load order
|
|
35
|
+
|
|
36
|
+
## [0.3.1] - 2025-12-13
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
- `--no-verbose` flag to reset verbosity level (useful in subtools)
|
|
40
|
+
- `--no-quiet` flag to unset quiet mode
|
|
41
|
+
- `default:` option for flags to specify non-nil defaults
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Built-in tools (`test`, `lint`, `format`, `gem`) now use `Devex::Exec` module with `cmd` pattern
|
|
45
|
+
- `lint.rb` uses `capture()` with `.stdout_lines` for git diff (demonstrates capture API)
|
|
46
|
+
- `gem.rb` uses `.then { }` chaining for sequential build/install operations
|
|
47
|
+
- Documentation updated with comprehensive Exec examples showing result objects, chaining, capture
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
- Documentation examples now consistently use `cmd`/`cmd?` inside `def run` blocks
|
|
51
|
+
|
|
52
|
+
## [0.3.0] - 2025-12-13
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
- **Built-in `dx test`**: Auto-detects minitest or RSpec, runs with coverage flag
|
|
56
|
+
- **Built-in `dx lint`**: Auto-detects RuboCop or StandardRB, with `--fix` and `--diff` options
|
|
57
|
+
- **Built-in `dx format`**: Auto-formats code (equivalent to `dx lint --fix`)
|
|
58
|
+
- **Built-in `dx gem`**: Subcommands for `build`, `install`, and `clean`
|
|
59
|
+
- **`cmd` and `cmd?` aliases**: Use these in tools to avoid `def run` collision with `Devex::Exec.run`
|
|
60
|
+
- **Reserved flag validation**: Tools that define flags conflicting with global flags (`-v`, `-f`, `-q`, etc.) now fail fast with a helpful error message
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
- Boolean flags now default to `false` instead of `nil`, fixing method access in tools
|
|
64
|
+
- Fixed missing requires for `Devex::Exec`, `Devex::ProjectPaths`, and `Devex::WorkingDir` modules
|
|
65
|
+
- Tools can now use `run`, `capture`, `spawn`, and other Exec methods without errors
|
|
66
|
+
|
|
67
|
+
## [0.2.0] - 2025-12-13
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
- **Environment wrapper chain**: `[dotenv] [mise exec --] [bundle exec] command`
|
|
71
|
+
- `mise` auto-detected from `.mise.toml` or `.tool-versions`
|
|
72
|
+
- `bundle exec` auto-detected from `Gemfile` for gem commands
|
|
73
|
+
- `dotenv` requires explicit opt-in (`dotenv: true`)
|
|
74
|
+
- **Auto-require for project lib/**: Tools can `require "myproject/foo"` without `require_relative`
|
|
75
|
+
- **String case transforms**: `snake_case`, `kebab_case`, `camel_case`, `pascal_case`, `title_case`, `scream_case`, `up_case`, `down_case` with aliases
|
|
76
|
+
- `--dx-from-dir` flag for operating on projects remotely
|
|
77
|
+
- `.dx-use-local` delegation for version consistency
|
|
78
|
+
- `prj.hooks` and `prj.templates` path conventions
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- ADRs updated from Draft to Accepted status
|
|
82
|
+
|
|
83
|
+
## [0.1.0] - 2025-12-13
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
- Initial release
|
|
87
|
+
- CLI framework with tool routing and nested subcommands
|
|
88
|
+
- Help system (`dx help`, `dx tool --help`, `dx tool help`)
|
|
89
|
+
- DSL for defining tools: `desc`, `long_desc`, `flag`, `required_arg`, `optional_arg`, `remaining_args`, `tool`
|
|
90
|
+
- Agent mode detection (non-tty, CI, explicit env var)
|
|
91
|
+
- Environment detection (development, test, staging, production)
|
|
92
|
+
- Output helpers with color support
|
|
93
|
+
- Built-in `dx version` command with bump support
|
|
94
|
+
- Support library: Path class, ANSI colors, core extensions
|
|
95
|
+
- Directory context: `Dirs`, `ProjectPaths`, `WorkingDir`
|
|
96
|
+
- Command execution: `run`, `capture`, `spawn`, `shell`, `ruby`, `tool`
|
|
97
|
+
- Result monad with `then`, `map`, `exit_on_failure!`
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Joseph A Wecker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# Devex
|
|
4
|
+
|
|
5
|
+
A lightweight, zero-heavy-dependency Ruby CLI providing a unified `dx` command for common development tasks. Projects can extend with local tasks. Clean-room implementation inspired by toys-core patterns.
|
|
6
|
+
|
|
7
|
+
## Vision
|
|
8
|
+
|
|
9
|
+
- **Single entry point**: `dx` command for all dev tasks
|
|
10
|
+
- **Zero-dependency core**: Support library uses only Ruby stdlib
|
|
11
|
+
- **Project-local tools**: Override or extend built-ins with `tools/*.rb`
|
|
12
|
+
- **Agent-aware**: Automatically detects AI agent invocation and adapts output
|
|
13
|
+
- **Environment-aware**: Rails-style environment detection (dev/test/staging/prod)
|
|
14
|
+
- **Command execution**: Clean subprocess management with environment orchestration
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gem install devex
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or add to your Gemfile:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "devex"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Show available commands
|
|
32
|
+
dx help
|
|
33
|
+
|
|
34
|
+
# Show/manage version
|
|
35
|
+
dx version
|
|
36
|
+
dx version bump patch
|
|
37
|
+
dx version set 2.0.0
|
|
38
|
+
|
|
39
|
+
# With JSON output (auto-detected in agent mode)
|
|
40
|
+
dx version --format=json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Project-Local Tools
|
|
44
|
+
|
|
45
|
+
Create a `tools/` directory in your project root with Ruby files:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# tools/build.rb
|
|
49
|
+
desc "Build and test the project"
|
|
50
|
+
|
|
51
|
+
long_desc <<~DESC
|
|
52
|
+
Runs the full build pipeline: install dependencies, run tests,
|
|
53
|
+
and optionally lint the code.
|
|
54
|
+
DESC
|
|
55
|
+
|
|
56
|
+
flag :skip_lint, "-s", "--skip-lint", desc: "Skip linting step"
|
|
57
|
+
flag :coverage, "-c", "--coverage", desc: "Run with code coverage"
|
|
58
|
+
|
|
59
|
+
include Devex::Exec
|
|
60
|
+
|
|
61
|
+
def run
|
|
62
|
+
# Use `cmd` instead of `run` to avoid conflict with `def run`
|
|
63
|
+
cmd("bundle", "install").exit_on_failure!
|
|
64
|
+
|
|
65
|
+
# Access flags as methods (boolean flags default to false)
|
|
66
|
+
env = coverage ? { "COVERAGE" => "1" } : {}
|
|
67
|
+
cmd("bundle", "exec", "rake", "test", env: env).exit_on_failure!
|
|
68
|
+
|
|
69
|
+
unless skip_lint
|
|
70
|
+
cmd("bundle", "exec", "rubocop").exit_on_failure!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check global verbose flag
|
|
74
|
+
puts "Build complete!" if verbose?
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then run:
|
|
79
|
+
```bash
|
|
80
|
+
dx build # Full build
|
|
81
|
+
dx build --skip-lint # Skip linting
|
|
82
|
+
dx build --coverage # With coverage
|
|
83
|
+
dx -v build # Verbose output
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Nested Tools
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# tools/db.rb
|
|
90
|
+
desc "Database operations"
|
|
91
|
+
|
|
92
|
+
tool "migrate" do
|
|
93
|
+
desc "Run migrations"
|
|
94
|
+
def run
|
|
95
|
+
# ...
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
tool "seed" do
|
|
100
|
+
desc "Seed the database"
|
|
101
|
+
def run
|
|
102
|
+
# ...
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Access as: `dx db migrate`, `dx db seed`
|
|
108
|
+
|
|
109
|
+
## Command Execution
|
|
110
|
+
|
|
111
|
+
Tools have access to smart command execution that automatically handles your environment.
|
|
112
|
+
Use `cmd` (not `run`) inside tools to avoid shadowing the `def run` entry point:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# tools/deploy.rb
|
|
116
|
+
desc "Deploy the application"
|
|
117
|
+
|
|
118
|
+
flag :skip_tests, "--skip-tests", desc: "Skip test suite"
|
|
119
|
+
flag :dry_run, "-n", "--dry-run", desc: "Show what would be done"
|
|
120
|
+
|
|
121
|
+
include Devex::Exec
|
|
122
|
+
|
|
123
|
+
def run
|
|
124
|
+
# ─── Basic execution with exit_on_failure! ───
|
|
125
|
+
cmd("bundle", "install").exit_on_failure!
|
|
126
|
+
|
|
127
|
+
# ─── Boolean check: cmd? returns true/false ───
|
|
128
|
+
unless skip_tests || cmd?("which", "rspec")
|
|
129
|
+
cmd("rake", "test").exit_on_failure!
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# ─── Capture output into result object ───
|
|
133
|
+
result = capture("git", "rev-parse", "--short", "HEAD")
|
|
134
|
+
commit = result.stdout.strip
|
|
135
|
+
puts "Deploying commit #{commit}..."
|
|
136
|
+
|
|
137
|
+
# ─── Chain sequential operations with .then ───
|
|
138
|
+
cmd("docker", "build", "-t", "myapp:#{commit}", ".")
|
|
139
|
+
.then { cmd("docker", "push", "myapp:#{commit}") }
|
|
140
|
+
.exit_on_failure!
|
|
141
|
+
|
|
142
|
+
# ─── Transform captured output with .map ───
|
|
143
|
+
tag = capture("git", "describe", "--tags", "--abbrev=0")
|
|
144
|
+
.map { |stdout| stdout.strip }
|
|
145
|
+
|
|
146
|
+
# ─── Result object has rich info ───
|
|
147
|
+
result = cmd("kubectl", "apply", "-f", "k8s/")
|
|
148
|
+
if result.failed?
|
|
149
|
+
$stderr.puts "Deploy failed (exit #{result.exit_code})"
|
|
150
|
+
$stderr.puts result.stderr if result.stderr
|
|
151
|
+
exit 1
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
puts "Deployed #{tag || commit} successfully!" if verbose?
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Execution Methods
|
|
159
|
+
|
|
160
|
+
| Method | Purpose | Returns |
|
|
161
|
+
|--------|---------|---------|
|
|
162
|
+
| `cmd(*args)` | Run command, stream output | `Result` |
|
|
163
|
+
| `cmd?(*args)` | Test if command succeeds | `Boolean` |
|
|
164
|
+
| `capture(*args)` | Run command, capture output | `Result` with `.stdout`, `.stderr` |
|
|
165
|
+
| `shell(string)` | Run shell command (pipes, globs) | `Result` |
|
|
166
|
+
| `shell?(string)` | Test if shell command succeeds | `Boolean` |
|
|
167
|
+
| `spawn(*args)` | Start background process | `Controller` |
|
|
168
|
+
|
|
169
|
+
### Result Object
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
result = cmd("make", "test")
|
|
173
|
+
|
|
174
|
+
result.success? # => true if exit code is 0
|
|
175
|
+
result.failed? # => true if non-zero exit
|
|
176
|
+
result.exit_code # => Integer exit code
|
|
177
|
+
result.stdout # => captured stdout (if using capture)
|
|
178
|
+
result.stderr # => captured stderr
|
|
179
|
+
result.stdout_lines # => stdout split into lines
|
|
180
|
+
result.duration # => execution time in seconds
|
|
181
|
+
|
|
182
|
+
# Chaining
|
|
183
|
+
result.exit_on_failure! # Exit process if failed
|
|
184
|
+
result.then { cmd("next") } # Chain if successful
|
|
185
|
+
result.map { |out| out.strip } # Transform stdout
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Environment Wrappers
|
|
189
|
+
|
|
190
|
+
Commands are automatically wrapped based on your project:
|
|
191
|
+
|
|
192
|
+
| Wrapper | When Applied |
|
|
193
|
+
|---------|--------------|
|
|
194
|
+
| `mise exec --` | Auto if `.mise.toml` or `.tool-versions` exists |
|
|
195
|
+
| `bundle exec` | Auto if `Gemfile` exists and command looks like a gem |
|
|
196
|
+
| `dotenv` | Explicit opt-in only (`dotenv: true`) |
|
|
197
|
+
|
|
198
|
+
Control wrappers explicitly:
|
|
199
|
+
|
|
200
|
+
```ruby
|
|
201
|
+
cmd "rspec" # auto-detect mise + bundle
|
|
202
|
+
cmd "rspec", mise: false # skip mise wrapping
|
|
203
|
+
cmd "rspec", bundle: false # skip bundle exec
|
|
204
|
+
cmd "echo", "hi", raw: true # skip all wrappers
|
|
205
|
+
cmd "rails", "s", dotenv: true # enable dotenv loading
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Configuration
|
|
209
|
+
|
|
210
|
+
Create `.dx.yml` in your project root:
|
|
211
|
+
|
|
212
|
+
```yaml
|
|
213
|
+
# Custom tools directory (default: tools)
|
|
214
|
+
tools_dir: dev/tools
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Global Options
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
dx --help # Show help with global options
|
|
221
|
+
dx --dx-version # Show devex gem version
|
|
222
|
+
dx -f json version # Output in JSON format
|
|
223
|
+
dx --format=yaml version # Output in YAML format
|
|
224
|
+
dx -v version # Verbose mode
|
|
225
|
+
dx -q version # Quiet mode
|
|
226
|
+
dx --no-color version # Disable colors
|
|
227
|
+
dx --color=always version # Force colors
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Environment Variables
|
|
231
|
+
|
|
232
|
+
- `DX_ENV` / `DEVEX_ENV` - Set environment (development, test, staging, production)
|
|
233
|
+
- `DX_AGENT_MODE=1` - Force agent mode (structured output, no colors)
|
|
234
|
+
- `DX_INTERACTIVE=1` - Force interactive mode
|
|
235
|
+
- `NO_COLOR=1` - Disable colored output
|
|
236
|
+
- `FORCE_COLOR=1` - Force colored output
|
|
237
|
+
|
|
238
|
+
## Debug Flags
|
|
239
|
+
|
|
240
|
+
Hidden flags for testing and bug reproduction (not shown in `--help`):
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
dx --dx-agent-mode version # Force agent mode
|
|
244
|
+
dx --dx-no-agent-mode version # Force interactive mode
|
|
245
|
+
dx --dx-env=production version # Force environment
|
|
246
|
+
dx --dx-terminal version # Force terminal detection
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Built-in Commands
|
|
250
|
+
|
|
251
|
+
**Testing & Quality**
|
|
252
|
+
- `dx test` - Run tests (auto-detects minitest/RSpec)
|
|
253
|
+
- `dx lint` - Run linter (auto-detects RuboCop/StandardRB)
|
|
254
|
+
- `dx lint --fix` - Auto-fix linter issues
|
|
255
|
+
- `dx format` - Auto-format code (alias for `dx lint --fix`)
|
|
256
|
+
|
|
257
|
+
**Version Management**
|
|
258
|
+
- `dx version` - Show project version
|
|
259
|
+
- `dx version bump <major|minor|patch>` - Bump semantic version
|
|
260
|
+
- `dx version set <version>` - Set explicit version
|
|
261
|
+
|
|
262
|
+
**Gem Packaging**
|
|
263
|
+
- `dx gem build` - Build the gem
|
|
264
|
+
- `dx gem install` - Build and install locally
|
|
265
|
+
- `dx gem clean` - Remove built gem files
|
|
266
|
+
|
|
267
|
+
More built-ins planned: `types`, `pre-commit`, `init`
|
|
268
|
+
|
|
269
|
+
## Building Custom CLIs with Devex::Core
|
|
270
|
+
|
|
271
|
+
Devex exposes its CLI framework for building your own command-line tools:
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
require "devex/core"
|
|
275
|
+
|
|
276
|
+
config = Devex::Core::Configuration.new(
|
|
277
|
+
executable_name: "mycli",
|
|
278
|
+
flag_prefix: "mycli", # --mycli-version, --mycli-agent-mode
|
|
279
|
+
project_markers: %w[.mycli.yml .git Gemfile],
|
|
280
|
+
env_prefix: "MYCLI" # MYCLI_AGENT_MODE, MYCLI_ENV
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
cli = Devex::Core::CLI.new(config: config)
|
|
284
|
+
cli.load_tools("/path/to/tools")
|
|
285
|
+
exit cli.run(ARGV)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
This gives you:
|
|
289
|
+
- Tool routing with nested subcommands
|
|
290
|
+
- Automatic help generation
|
|
291
|
+
- Agent mode detection (adapts output for AI agents)
|
|
292
|
+
- Environment detection (dev/test/staging/prod)
|
|
293
|
+
- Command execution with environment wrappers
|
|
294
|
+
- Project path conventions
|
|
295
|
+
- Zero-dependency support library (Path, ANSI, CoreExt)
|
|
296
|
+
|
|
297
|
+
See [docs/developing-tools.md](docs/developing-tools.md) for the full API.
|
|
298
|
+
|
|
299
|
+
## Development
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
bundle install
|
|
303
|
+
bundle exec rake test
|
|
304
|
+
bundle exec exe/dx --help
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Documentation
|
|
308
|
+
|
|
309
|
+
- **[Developing Tools](docs/developing-tools.md)** - How to create tools, available interfaces, best practices
|
|
310
|
+
- **[CHANGELOG](CHANGELOG.md)** - Version history and release notes
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT - see [LICENSE](LICENSE)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.libs << "lib"
|
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
10
|
+
t.warning = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task default: :test
|
data/devex-logo.jpg
ADDED
|
Binary file
|