consolle 0.3.6 → 0.3.7
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/.version +1 -1
- data/Gemfile.lock +1 -1
- data/lib/consolle/cli.rb +1 -1
- data/lib/consolle/config.rb +78 -0
- data/lib/consolle/errors.rb +52 -1
- data/lib/consolle/server/console_supervisor.rb +24 -10
- data/lib/consolle.rb +1 -0
- data/rule.ko.md +33 -2
- data/rule.md +33 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a88f6019a32aaae300dee3a3e39b1dd1084ea1e2900171d6ee02f880923e071e
|
|
4
|
+
data.tar.gz: e9c468bc8095d8197e8f70cc4c0b75d0cc8da9ba3a7b0af45573300b755e1970
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5c0615769edda8936547380360ed5a7beeaaeda7569ccf91322daa4658923a82cad4ad87e5ffabeca4c230e89f3d371ebcaa5eec58663b25172ee0ab8c2ba07
|
|
7
|
+
data.tar.gz: 83dd02825015e9f9e73ff48075436f459722cdc045ee4b0b8d90509c5ca91274c27af682549ef114598efcf21d90628fc460360f33a069134b0da63eeab94c81
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.7
|
data/Gemfile.lock
CHANGED
data/lib/consolle/cli.rb
CHANGED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Consolle
|
|
6
|
+
# Configuration loader for .consolle.yml
|
|
7
|
+
class Config
|
|
8
|
+
CONFIG_FILENAME = '.consolle.yml'
|
|
9
|
+
|
|
10
|
+
# Default prompt pattern that matches various console prompts
|
|
11
|
+
# - Custom sentinel: \u001E\u001F<CONSOLLE>\u001F\u001E
|
|
12
|
+
# - Rails app prompts: app(env)> or app(env):001>
|
|
13
|
+
# - IRB prompts: irb(main):001:0> or irb(main):001>
|
|
14
|
+
# - Generic prompts: >> or >
|
|
15
|
+
DEFAULT_PROMPT_PATTERN = /^[^\w]*(\u001E\u001F<CONSOLLE>\u001F\u001E|\w+[-_]?\w*\([^)]*\)(:\d+)?>|irb\([^)]+\):\d+:?\d*[>*]|>>|>)\s*$/
|
|
16
|
+
|
|
17
|
+
attr_reader :rails_root, :prompt_pattern, :raw_prompt_pattern
|
|
18
|
+
|
|
19
|
+
def initialize(rails_root)
|
|
20
|
+
@rails_root = rails_root
|
|
21
|
+
@config = load_config
|
|
22
|
+
@raw_prompt_pattern = @config['prompt_pattern']
|
|
23
|
+
@prompt_pattern = parse_prompt_pattern(@raw_prompt_pattern)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.load(rails_root)
|
|
27
|
+
new(rails_root)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Check if a custom prompt pattern is configured
|
|
31
|
+
def custom_prompt_pattern?
|
|
32
|
+
!@raw_prompt_pattern.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get human-readable description of expected prompt patterns
|
|
36
|
+
def prompt_pattern_description
|
|
37
|
+
if custom_prompt_pattern?
|
|
38
|
+
"Custom pattern: #{@raw_prompt_pattern}"
|
|
39
|
+
else
|
|
40
|
+
<<~DESC.strip
|
|
41
|
+
Default patterns:
|
|
42
|
+
- app(env)> or app(env):001> (Rails console)
|
|
43
|
+
- irb(main):001:0> (IRB)
|
|
44
|
+
- >> or > (Generic)
|
|
45
|
+
DESC
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def config_path
|
|
52
|
+
File.join(@rails_root, CONFIG_FILENAME)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_config
|
|
56
|
+
return {} unless File.exist?(config_path)
|
|
57
|
+
|
|
58
|
+
begin
|
|
59
|
+
YAML.safe_load(File.read(config_path)) || {}
|
|
60
|
+
rescue Psych::SyntaxError => e
|
|
61
|
+
warn "[Consolle] Warning: Failed to parse #{CONFIG_FILENAME}: #{e.message}"
|
|
62
|
+
{}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_prompt_pattern(pattern_string)
|
|
67
|
+
return DEFAULT_PROMPT_PATTERN if pattern_string.nil?
|
|
68
|
+
|
|
69
|
+
begin
|
|
70
|
+
Regexp.new(pattern_string)
|
|
71
|
+
rescue RegexpError => e
|
|
72
|
+
warn "[Consolle] Warning: Invalid prompt_pattern '#{pattern_string}': #{e.message}"
|
|
73
|
+
warn "[Consolle] Using default pattern instead."
|
|
74
|
+
DEFAULT_PROMPT_PATTERN
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/consolle/errors.rb
CHANGED
|
@@ -39,6 +39,56 @@ module Consolle
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Prompt detection failure with diagnostic information
|
|
43
|
+
class PromptDetectionError < Error
|
|
44
|
+
attr_reader :received_output, :expected_patterns, :config_path
|
|
45
|
+
|
|
46
|
+
def initialize(timeout:, received_output:, expected_patterns:, config_path:)
|
|
47
|
+
@received_output = received_output
|
|
48
|
+
@expected_patterns = expected_patterns
|
|
49
|
+
@config_path = config_path
|
|
50
|
+
|
|
51
|
+
super(build_message(timeout))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def build_message(timeout)
|
|
57
|
+
# Extract last line that looks like a prompt (ends with > or similar)
|
|
58
|
+
lines = @received_output.to_s.lines.map(&:strip).reject(&:empty?)
|
|
59
|
+
potential_prompt = lines.reverse.find { |l| l.match?(/[>*]\s*$/) } || lines.last
|
|
60
|
+
|
|
61
|
+
<<~MSG.strip
|
|
62
|
+
Prompt not detected after #{timeout} seconds
|
|
63
|
+
|
|
64
|
+
Received output:
|
|
65
|
+
#{@received_output.to_s.lines.last(5).map { |l| l.strip }.join("\n ")}
|
|
66
|
+
|
|
67
|
+
Potential prompt found:
|
|
68
|
+
#{potential_prompt.inspect}
|
|
69
|
+
|
|
70
|
+
#{@expected_patterns}
|
|
71
|
+
|
|
72
|
+
To fix this, add to #{@config_path}:
|
|
73
|
+
prompt_pattern: '#{escape_for_yaml(potential_prompt)}'
|
|
74
|
+
|
|
75
|
+
Or set environment variable:
|
|
76
|
+
CONSOLLE_PROMPT_PATTERN='#{escape_for_yaml(potential_prompt)}' cone start
|
|
77
|
+
MSG
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def escape_for_yaml(str)
|
|
81
|
+
return '' if str.nil?
|
|
82
|
+
# Escape special regex characters and create a simple pattern
|
|
83
|
+
str.to_s
|
|
84
|
+
.gsub(/\e\[[\d;]*[a-zA-Z]/, '') # Remove ANSI codes
|
|
85
|
+
.gsub(/[\x00-\x1F]/, '') # Remove control characters
|
|
86
|
+
.strip
|
|
87
|
+
.gsub(/(\d+)/, '\d+') # Replace numbers with \d+
|
|
88
|
+
.gsub(/([().\[\]{}|*+?^$\\])/, '\\\\\1') # Escape regex special chars
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
42
92
|
# Syntax error in executed code
|
|
43
93
|
class SyntaxError < ExecutionError
|
|
44
94
|
def initialize(message)
|
|
@@ -80,7 +130,8 @@ module Consolle
|
|
|
80
130
|
'::RuntimeError' => 'RUNTIME_ERROR',
|
|
81
131
|
'StandardError' => 'STANDARD_ERROR',
|
|
82
132
|
'Exception' => 'EXCEPTION',
|
|
83
|
-
'Consolle::Errors::ServerUnhealthy' => 'SERVER_UNHEALTHY'
|
|
133
|
+
'Consolle::Errors::ServerUnhealthy' => 'SERVER_UNHEALTHY',
|
|
134
|
+
'Consolle::Errors::PromptDetectionError' => 'PROMPT_DETECTION_ERROR'
|
|
84
135
|
}.freeze
|
|
85
136
|
|
|
86
137
|
def self.to_code(exception)
|
|
@@ -5,6 +5,7 @@ require 'timeout'
|
|
|
5
5
|
require 'fcntl'
|
|
6
6
|
require 'logger'
|
|
7
7
|
require_relative '../constants'
|
|
8
|
+
require_relative '../config'
|
|
8
9
|
require_relative '../errors'
|
|
9
10
|
|
|
10
11
|
# Ruby 3.4.0+ extracts base64 as a default gem
|
|
@@ -17,15 +18,13 @@ $VERBOSE = original_verbose
|
|
|
17
18
|
module Consolle
|
|
18
19
|
module Server
|
|
19
20
|
class ConsoleSupervisor
|
|
20
|
-
attr_reader :pid, :reader, :writer, :rails_root, :rails_env, :logger
|
|
21
|
+
attr_reader :pid, :reader, :writer, :rails_root, :rails_env, :logger, :config
|
|
21
22
|
|
|
22
23
|
RESTART_DELAY = 1 # seconds
|
|
23
24
|
MAX_RESTARTS = 5 # within 5 minutes
|
|
24
25
|
RESTART_WINDOW = 300 # 5 minutes
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
# Allow optional non-word characters before the prompt (e.g., Unicode symbols like ▽)
|
|
28
|
-
PROMPT_PATTERN = /^[^\w]*(\u001E\u001F<CONSOLLE>\u001F\u001E|\w+[-_]?\w*\([^)]*\)>|irb\([^)]+\):\d+:?\d*[>*]|>>|>)\s*$/
|
|
26
|
+
# Legacy constant for backward compatibility - use config.prompt_pattern instead
|
|
27
|
+
PROMPT_PATTERN = Consolle::Config::DEFAULT_PROMPT_PATTERN
|
|
29
28
|
CTRL_C = "\x03"
|
|
30
29
|
|
|
31
30
|
def initialize(rails_root:, rails_env: 'development', logger: nil, command: nil, wait_timeout: nil)
|
|
@@ -34,6 +33,7 @@ module Consolle
|
|
|
34
33
|
@command = command || 'bin/rails console'
|
|
35
34
|
@logger = logger || Logger.new(STDOUT)
|
|
36
35
|
@wait_timeout = wait_timeout || Consolle::DEFAULT_WAIT_TIMEOUT
|
|
36
|
+
@config = Consolle::Config.load(rails_root)
|
|
37
37
|
@pid = nil
|
|
38
38
|
@reader = nil
|
|
39
39
|
@writer = nil
|
|
@@ -227,7 +227,7 @@ module Consolle
|
|
|
227
227
|
|
|
228
228
|
# Check if we got prompt back
|
|
229
229
|
clean = strip_ansi(output)
|
|
230
|
-
if clean.match?(
|
|
230
|
+
if clean.match?(prompt_pattern)
|
|
231
231
|
# Wait a bit for any trailing output
|
|
232
232
|
sleep 0.1
|
|
233
233
|
begin
|
|
@@ -300,6 +300,11 @@ module Consolle
|
|
|
300
300
|
end
|
|
301
301
|
end
|
|
302
302
|
|
|
303
|
+
# Returns the prompt pattern to use (custom from config or default)
|
|
304
|
+
def prompt_pattern
|
|
305
|
+
@config&.prompt_pattern || Consolle::Config::DEFAULT_PROMPT_PATTERN
|
|
306
|
+
end
|
|
307
|
+
|
|
303
308
|
def stop
|
|
304
309
|
@running = false
|
|
305
310
|
|
|
@@ -531,7 +536,16 @@ module Consolle
|
|
|
531
536
|
logger.error "[ConsoleSupervisor] Timeout reached. Current: #{current_time}, Deadline: #{deadline}, Elapsed: #{current_time - start_time}s"
|
|
532
537
|
logger.error "[ConsoleSupervisor] Output so far: #{output.inspect}"
|
|
533
538
|
logger.error "[ConsoleSupervisor] Stripped: #{strip_ansi(output).inspect}"
|
|
534
|
-
|
|
539
|
+
|
|
540
|
+
# Raise PromptDetectionError with diagnostic information
|
|
541
|
+
config_path = File.join(@rails_root || '.', Consolle::Config::CONFIG_FILENAME)
|
|
542
|
+
expected_desc = @config&.prompt_pattern_description || "Default prompt patterns"
|
|
543
|
+
raise Consolle::Errors::PromptDetectionError.new(
|
|
544
|
+
timeout: timeout,
|
|
545
|
+
received_output: strip_ansi(output),
|
|
546
|
+
expected_patterns: expected_desc,
|
|
547
|
+
config_path: config_path
|
|
548
|
+
)
|
|
535
549
|
end
|
|
536
550
|
|
|
537
551
|
# If we found prompt and consume_all is true, continue reading for a bit more
|
|
@@ -560,7 +574,7 @@ module Consolle
|
|
|
560
574
|
clean = strip_ansi(output)
|
|
561
575
|
# Check each line for prompt pattern
|
|
562
576
|
clean.lines.each do |line|
|
|
563
|
-
if line.match?(
|
|
577
|
+
if line.match?(prompt_pattern)
|
|
564
578
|
logger.info '[ConsoleSupervisor] Found prompt!'
|
|
565
579
|
prompt_found = true
|
|
566
580
|
end
|
|
@@ -647,7 +661,7 @@ module Consolle
|
|
|
647
661
|
# Wait for prompt after configuration with reasonable timeout
|
|
648
662
|
begin
|
|
649
663
|
wait_for_prompt(timeout: 2, consume_all: false)
|
|
650
|
-
rescue Timeout::Error
|
|
664
|
+
rescue Timeout::Error, Consolle::Errors::PromptDetectionError
|
|
651
665
|
# This can fail with some console types, but that's okay
|
|
652
666
|
logger.debug '[ConsoleSupervisor] No prompt after IRB configuration, continuing'
|
|
653
667
|
end
|
|
@@ -686,7 +700,7 @@ module Consolle
|
|
|
686
700
|
end
|
|
687
701
|
|
|
688
702
|
# Skip prompts (but not return values that start with =>)
|
|
689
|
-
next if line.match?(
|
|
703
|
+
next if line.match?(prompt_pattern) && !line.start_with?('=>')
|
|
690
704
|
|
|
691
705
|
# Skip common IRB configuration output patterns
|
|
692
706
|
if line.match?(/^(IRB\.conf|DISABLE_PRY_RAILS|Switch to inspect mode|Loading .*\.rb|nil)$/) ||
|
data/lib/consolle.rb
CHANGED
data/rule.ko.md
CHANGED
|
@@ -20,10 +20,10 @@ Cone은 디버깅, 데이터 탐색, 그리고 개발 보조 도구로 사용됩
|
|
|
20
20
|
|
|
21
21
|
## Cone 서버 시작과 중지
|
|
22
22
|
|
|
23
|
-
`start` 명령어로 cone을 시작할 수
|
|
23
|
+
`start` 명령어로 cone을 시작할 수 있습니다. 실행 환경 지정은 `RAILS_ENV` 환경변수를 사용합니다.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
$ cone start # 서버 시작
|
|
26
|
+
$ cone start # 서버 시작 (RAILS_ENV가 없으면 development)
|
|
27
27
|
$ RAILS_ENV=test cone start # test 환경에서 console 시작
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -115,3 +115,34 @@ $ cone exec -f complex_task.rb
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
모든 방법은 세션 상태를 유지하므로 변수와 객체가 실행 간에 지속됩니다.
|
|
118
|
+
|
|
119
|
+
## 실행 안전장치 & 타임아웃
|
|
120
|
+
|
|
121
|
+
- 기본 타임아웃: 60초
|
|
122
|
+
- 타임아웃 우선순위: `CONSOLLE_TIMEOUT`(설정되고 0보다 클 때) > CLI `--timeout` > 기본값(60초)
|
|
123
|
+
- 사전 Ctrl‑C(프롬프트 분리):
|
|
124
|
+
- 매 `exec` 전에 Ctrl‑C를 보내고 IRB 프롬프트를 최대 3초 대기해 깨끗한 상태를 보장합니다.
|
|
125
|
+
- 3초 내 프롬프트가 돌아오지 않으면 콘솔 하위 프로세스를 재시작하고 요청은 `SERVER_UNHEALTHY`로 실패합니다.
|
|
126
|
+
- 서버 전역 비활성화: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
127
|
+
- 호출 단위 제어: `--pre-sigint` / `--no-pre-sigint`
|
|
128
|
+
|
|
129
|
+
### 예시
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# CLI로 타임아웃 지정(환경변수 미설정 시 유효)
|
|
133
|
+
cone exec 'heavy_task' --timeout 120
|
|
134
|
+
|
|
135
|
+
# 최우선 타임아웃(클라이언트·서버 모두 적용)
|
|
136
|
+
CONSOLLE_TIMEOUT=90 cone exec 'heavy_task'
|
|
137
|
+
|
|
138
|
+
# 타임아웃 이후 복구 확인
|
|
139
|
+
cone exec 'sleep 999' --timeout 2 # -> EXECUTION_TIMEOUT로 실패
|
|
140
|
+
cone exec "puts :after_timeout; :ok" # -> 정상 동작(프롬프트 복구)
|
|
141
|
+
|
|
142
|
+
# 호출 단위로 사전 Ctrl‑C 비활성화
|
|
143
|
+
cone exec --no-pre-sigint 'code'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 에러 코드
|
|
147
|
+
- `EXECUTION_TIMEOUT`: 실행한 코드가 타임아웃을 초과함
|
|
148
|
+
- `SERVER_UNHEALTHY`: 사전 프롬프트 확인(3초) 실패로 콘솔 재시작, 요청 실패
|
data/rule.md
CHANGED
|
@@ -20,10 +20,10 @@ Existing objects also reference old code, so you need to create new ones to use
|
|
|
20
20
|
|
|
21
21
|
## Starting and Stopping Cone Server
|
|
22
22
|
|
|
23
|
-
You can start cone with the `start` command
|
|
23
|
+
You can start cone with the `start` command. To select the Rails environment, set the `RAILS_ENV` environment variable.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
$ cone start # Start server
|
|
26
|
+
$ cone start # Start server (uses RAILS_ENV or defaults to development)
|
|
27
27
|
$ RAILS_ENV=test cone start # Start console in test environment
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -115,3 +115,34 @@ $ cone exec -f complex_task.rb
|
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
All methods maintain the session state, so variables and objects persist between executions.
|
|
118
|
+
|
|
119
|
+
## Execution Safety & Timeouts
|
|
120
|
+
|
|
121
|
+
- Default timeout: 60s
|
|
122
|
+
- Timeout precedence: `CONSOLLE_TIMEOUT` (if set and > 0) > CLI `--timeout` > default (60s)
|
|
123
|
+
- Pre-exec Ctrl-C (prompt separation):
|
|
124
|
+
- Before each `exec`, cone sends Ctrl-C and waits up to 3 seconds for the IRB prompt to ensure a clean state.
|
|
125
|
+
- If the prompt does not return in 3 seconds, the console subprocess is restarted and the request fails with `SERVER_UNHEALTHY`.
|
|
126
|
+
- Disable globally for the server: `CONSOLLE_DISABLE_PRE_SIGINT=1 cone start`
|
|
127
|
+
- Per-call control: `--pre-sigint` / `--no-pre-sigint`
|
|
128
|
+
|
|
129
|
+
### Examples
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# Set timeout via CLI (fallback when CONSOLLE_TIMEOUT is not set)
|
|
133
|
+
cone exec 'heavy_task' --timeout 120
|
|
134
|
+
|
|
135
|
+
# Highest priority timeout (applies on client and server)
|
|
136
|
+
CONSOLLE_TIMEOUT=90 cone exec 'heavy_task'
|
|
137
|
+
|
|
138
|
+
# Verify recovery after a timeout
|
|
139
|
+
cone exec 'sleep 999' --timeout 2 # -> fails with EXECUTION_TIMEOUT
|
|
140
|
+
cone exec "puts :after_timeout; :ok" # -> should succeed (prompt recovered)
|
|
141
|
+
|
|
142
|
+
# Disable pre-exec Ctrl-C for a single call
|
|
143
|
+
cone exec --no-pre-sigint 'code'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Error Codes
|
|
147
|
+
- `EXECUTION_TIMEOUT`: The executed code exceeded its timeout.
|
|
148
|
+
- `SERVER_UNHEALTHY`: The pre-exec prompt did not return within 3 seconds; the console subprocess was restarted and the request failed.
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: consolle
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- nacyot
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-12-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: logger
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- lib/consolle.rb
|
|
80
80
|
- lib/consolle/adapters/rails_console.rb
|
|
81
81
|
- lib/consolle/cli.rb
|
|
82
|
+
- lib/consolle/config.rb
|
|
82
83
|
- lib/consolle/constants.rb
|
|
83
84
|
- lib/consolle/errors.rb
|
|
84
85
|
- lib/consolle/server/console_socket_server.rb
|