tidewave 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +0 -8
- data/lib/tidewave/database_adapter.rb +0 -4
- data/lib/tidewave/database_adapters/active_record.rb +0 -4
- data/lib/tidewave/database_adapters/sequel.rb +0 -17
- data/lib/tidewave/railtie.rb +4 -0
- data/lib/tidewave/tools/create_design_canvas.rb +124 -0
- data/lib/tidewave/version.rb +1 -1
- metadata +3 -3
- data/lib/tidewave/tools/get_models.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1267db74de119b4df61724a8842bf73cf8808ca502a36595a7180806c172731
|
|
4
|
+
data.tar.gz: 9a0661b32af7c7f0932930d0948fa7842b9da7037aa8c936707cf7349c043342
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b10d2635c19388de314ffeb62f27bfd3fbcde4f3ec3cdbb07476ebd598bc455423e003d04a222d81fd7231b5cee2bf778683fb67589d1cc1278ebeef1e51b29
|
|
7
|
+
data.tar.gz: dc487bfc541140331ca32e28ad7986b8d9265ef73c696a31d8014c7766fc32f8eb1972594b301652937c07d2684ae4df2494cc4c819ab4a63458fd8965044f35
|
data/README.md
CHANGED
|
@@ -82,14 +82,6 @@ Returns output from your running server's log.
|
|
|
82
82
|
|
|
83
83
|
Your agent can use it to see what happened after a request. For example, reading the request log and backtrace when something misbehaves, or checking the log after an action to confirm the request came in with the expected params.
|
|
84
84
|
|
|
85
|
-
### `get_models`
|
|
86
|
-
|
|
87
|
-
Lists all of your app's models and where each one is defined, by file and line.
|
|
88
|
-
|
|
89
|
-
[](https://asciinema.org/a/qeELC7wEgMEI5T7n)
|
|
90
|
-
|
|
91
|
-
Your agent can use it to map the data domain and find where each model lives before opening files, rather than grepping around for class definitions.
|
|
92
|
-
|
|
93
85
|
### `get_source_location`
|
|
94
86
|
|
|
95
87
|
Returns the file and line where a class, module, or method is defined, across both your app and its dependencies.
|
|
@@ -28,23 +28,6 @@ class Tidewave
|
|
|
28
28
|
database: db.opts[:database]
|
|
29
29
|
}
|
|
30
30
|
end
|
|
31
|
-
|
|
32
|
-
def get_models
|
|
33
|
-
# Filter out anonymous Sequel models that can't be resolved as constants
|
|
34
|
-
descendants_of(::Sequel::Model).reject { |model| model.name&.start_with?("Sequel::_Model(") }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def descendants_of(base)
|
|
40
|
-
if base.respond_to?(:descendants)
|
|
41
|
-
base.descendants
|
|
42
|
-
elsif base.respond_to?(:subclasses)
|
|
43
|
-
base.subclasses.flat_map { |subclass| [ subclass ] + descendants_of(subclass) }
|
|
44
|
-
else
|
|
45
|
-
[]
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
31
|
end
|
|
49
32
|
end
|
|
50
33
|
end
|
data/lib/tidewave/railtie.rb
CHANGED
|
@@ -68,6 +68,10 @@ class Tidewave
|
|
|
68
68
|
script_src_elem << client_origin unless script_src_elem.include?(client_origin)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
directives["connect-src"].try do |connect_src|
|
|
72
|
+
connect_src << client_origin unless connect_src.include?(client_origin)
|
|
73
|
+
end
|
|
74
|
+
|
|
71
75
|
directives.delete("frame-ancestors")
|
|
72
76
|
end
|
|
73
77
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "pathname"
|
|
7
|
+
require "uri"
|
|
8
|
+
|
|
9
|
+
class Tidewave::Tools::CreateDesignCanvas < Tidewave::Tool
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
DESCRIPTION = <<~DESCRIPTION.freeze
|
|
13
|
+
Creates a new design canvas, an HTML file for presenting design explorations.
|
|
14
|
+
|
|
15
|
+
The tool returns the absolute path of the HTML file. The file includes usage
|
|
16
|
+
instructions, read it, then edit it to author the actual design.
|
|
17
|
+
DESCRIPTION
|
|
18
|
+
|
|
19
|
+
FETCH_TIMEOUT_SECONDS = 15
|
|
20
|
+
|
|
21
|
+
def initialize(options = {})
|
|
22
|
+
super
|
|
23
|
+
@client_url = options[:client_url]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def browser_tool?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def definition
|
|
31
|
+
{
|
|
32
|
+
"name" => "create_design_canvas",
|
|
33
|
+
"description" => DESCRIPTION,
|
|
34
|
+
"inputSchema" => {
|
|
35
|
+
"type" => "object",
|
|
36
|
+
"properties" => {
|
|
37
|
+
"path" => {
|
|
38
|
+
"type" => "string",
|
|
39
|
+
"description" =>
|
|
40
|
+
"The absolute path for the canvas file, with .html file extension. The file must not exist yet."
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"required" => [ "path" ]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def call(arguments)
|
|
49
|
+
path = arguments.fetch("path")
|
|
50
|
+
|
|
51
|
+
unless Pathname.new(path).absolute? && path.end_with?(".html")
|
|
52
|
+
return error_result("Invalid path #{path.inspect}. It must be an absolute path with the .html file extension.")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
html = fetch_canvas_html
|
|
56
|
+
write_canvas(path, html)
|
|
57
|
+
|
|
58
|
+
"Design canvas created at: <path>#{path}</path>. Read the file for usage instructions."
|
|
59
|
+
rescue Error => error
|
|
60
|
+
error_result(error.message)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def write_canvas(path, html)
|
|
66
|
+
begin
|
|
67
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
68
|
+
rescue SystemCallError => error
|
|
69
|
+
raise Error, "Failed to create the design canvas directory: #{error.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
File.write(path, html, mode: "wx")
|
|
73
|
+
rescue Errno::EEXIST
|
|
74
|
+
raise Error, "Failed to create the design canvas file, the file already exists."
|
|
75
|
+
rescue SystemCallError => error
|
|
76
|
+
raise Error, "Failed to create the design canvas file: #{error.message}"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def fetch_canvas_html
|
|
80
|
+
url = @client_url.to_s.sub(%r{/\z}, "") + "/tc/data/canvas.json"
|
|
81
|
+
uri = URI.parse(url)
|
|
82
|
+
|
|
83
|
+
response =
|
|
84
|
+
Net::HTTP.start(
|
|
85
|
+
uri.host,
|
|
86
|
+
uri.port,
|
|
87
|
+
use_ssl: uri.scheme == "https",
|
|
88
|
+
open_timeout: FETCH_TIMEOUT_SECONDS,
|
|
89
|
+
read_timeout: FETCH_TIMEOUT_SECONDS
|
|
90
|
+
) { |http| http.request(Net::HTTP::Get.new(uri)) }
|
|
91
|
+
|
|
92
|
+
unless response.is_a?(Net::HTTPOK)
|
|
93
|
+
raise Error, "Failed to fetch the design canvas template, request to #{url} failed with status #{response.code}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
parse_canvas_html(response.body, url)
|
|
97
|
+
rescue SystemCallError, SocketError, IOError, Timeout::Error, OpenSSL::SSL::SSLError => error
|
|
98
|
+
raise Error, "Failed to fetch the design canvas template, request to #{url} failed: #{error.message}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_canvas_html(body, url)
|
|
102
|
+
payload =
|
|
103
|
+
begin
|
|
104
|
+
JSON.parse(body)
|
|
105
|
+
rescue JSON::ParserError
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
html = payload["html"] if payload.is_a?(Hash)
|
|
110
|
+
|
|
111
|
+
unless html.is_a?(String)
|
|
112
|
+
raise Error, "Failed to fetch the design canvas template, unexpected response from #{url}"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
html
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def error_result(text)
|
|
119
|
+
{
|
|
120
|
+
"content" => [ { "type" => "text", "text" => text } ],
|
|
121
|
+
"isError" => true
|
|
122
|
+
}
|
|
123
|
+
end
|
|
124
|
+
end
|
data/lib/tidewave/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tidewave
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yorick Jacquin
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-08-
|
|
12
|
+
date: 2026-08-18 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: rack
|
|
@@ -47,10 +47,10 @@ files:
|
|
|
47
47
|
- lib/tidewave/railtie.rb
|
|
48
48
|
- lib/tidewave/tool.rb
|
|
49
49
|
- lib/tidewave/tools/browser_eval.rb
|
|
50
|
+
- lib/tidewave/tools/create_design_canvas.rb
|
|
50
51
|
- lib/tidewave/tools/execute_sql_query.rb
|
|
51
52
|
- lib/tidewave/tools/get_docs.rb
|
|
52
53
|
- lib/tidewave/tools/get_logs.rb
|
|
53
|
-
- lib/tidewave/tools/get_models.rb
|
|
54
54
|
- lib/tidewave/tools/get_source_location.rb
|
|
55
55
|
- lib/tidewave/tools/project_eval.rb
|
|
56
56
|
- lib/tidewave/version.rb
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "pathname"
|
|
4
|
-
|
|
5
|
-
class Tidewave::Tools::GetModels < Tidewave::Tool
|
|
6
|
-
DESCRIPTION = <<~DESCRIPTION.freeze
|
|
7
|
-
Returns a list of all database-backed models in the application.
|
|
8
|
-
DESCRIPTION
|
|
9
|
-
|
|
10
|
-
def initialize(options = {})
|
|
11
|
-
@root = options[:root] ? Pathname.new(options[:root].to_s) : Pathname.pwd
|
|
12
|
-
@database_adapter = Tidewave::DatabaseAdapter.for(options[:orm_adapter]) if options[:orm_adapter]
|
|
13
|
-
@before_reload = options[:before_reload]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def definition
|
|
17
|
-
return nil unless @database_adapter
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
"name" => "get_models",
|
|
21
|
-
"description" => DESCRIPTION,
|
|
22
|
-
"inputSchema" => {
|
|
23
|
-
"type" => "object",
|
|
24
|
-
"properties" => {}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def call(_arguments)
|
|
30
|
-
@before_reload&.call
|
|
31
|
-
|
|
32
|
-
models = @database_adapter.get_models
|
|
33
|
-
|
|
34
|
-
models.map do |model|
|
|
35
|
-
display_name = model.name || model.to_s
|
|
36
|
-
|
|
37
|
-
if location = get_relative_source_location(model.name)
|
|
38
|
-
"* #{display_name} at #{location}"
|
|
39
|
-
else
|
|
40
|
-
"* #{display_name}"
|
|
41
|
-
end
|
|
42
|
-
end.join("\n")
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def get_relative_source_location(model_name)
|
|
48
|
-
return nil if model_name.nil? || model_name.empty?
|
|
49
|
-
|
|
50
|
-
source_location = Object.const_source_location(model_name)
|
|
51
|
-
return nil unless source_location
|
|
52
|
-
|
|
53
|
-
file_path, line_number = source_location
|
|
54
|
-
relative_path = Pathname.new(file_path).relative_path_from(@root)
|
|
55
|
-
"#{relative_path}:#{line_number}"
|
|
56
|
-
rescue ArgumentError
|
|
57
|
-
"#{file_path}:#{line_number}"
|
|
58
|
-
end
|
|
59
|
-
end
|