scriptor 0.0.1 → 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 +4 -4
- data/README.md +31 -17
- data/app/controllers/scriptor/scripts_controller.rb +4 -2
- data/app/models/scriptor/execution.rb +18 -0
- data/app/models/scriptor/script.rb +19 -5
- data/app/views/layouts/scriptor/application.html.erb +47 -4
- data/app/views/scriptor/scripts/index.html.erb +7 -2
- data/app/views/scriptor/scripts/show.html.erb +73 -1
- data/db/migrate/20241219110922_create_scriptor_executions.rb +14 -0
- data/lib/generators/scriptor/install_generator.rb +16 -0
- data/lib/scriptor/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9cbfe438c0b163aed13ee3f067785c77719f9d4dc8632c7054e9e249722b530c
|
4
|
+
data.tar.gz: 24112730551af6d90ef3c0f4ac3fe557a3d4e0adece1913a49fb7663eb892e99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74bc62088328ecf150c2cb8c02617167aa7392576edb955eea7f98785e6b9467082ab861433edb99e72fdc6ac92240f7211f67b0aab394a2348a62be82fc5e82
|
7
|
+
data.tar.gz: a861ce4689eadf74a6d9c814bb1a1691401953c60207ac824e64d8497f1c335dcdae2c1c1aa929bc559306db3bb2355266b3aa38e7c151da9b4ecde59ae44e69
|
data/README.md
CHANGED
@@ -1,28 +1,42 @@
|
|
1
1
|
# Scriptor
|
2
|
-
Short description and motivation.
|
3
2
|
|
4
|
-
|
5
|
-
|
3
|
+
Scriptorはscriptフォルダにあるrubyファイルを、ブラウザから実行できるようにしたRails Engineです。
|
4
|
+
`Rails 8.0.0 >= 0` の場合 `bin/rails generate scriot my_script` を実行すると `script/myscript.rb` が作成されます。
|
5
|
+
(Rails 8.0.0 以上でなくても、scriptフォルダにファイルを置いておけば利用可能です。)
|
6
6
|
|
7
|
-
|
8
|
-
Add this line to your application's Gemfile:
|
7
|
+
このscriptをブラウザから一覧で見たり、詳細ページではコードを閲覧したり、実行できます。
|
9
8
|
|
10
|
-
|
11
|
-
gem "scriptor"
|
12
|
-
```
|
9
|
+
https://github.com/user-attachments/assets/057c75ce-7c8e-4c89-a238-13546b5797f1
|
13
10
|
|
14
|
-
And then execute:
|
15
|
-
```bash
|
16
|
-
$ bundle
|
17
|
-
```
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
12
|
+
|
13
|
+
## 使い方
|
14
|
+
|
15
|
+
1. `bundle add scriptor`
|
16
|
+
2. `bin/rails generate maintenance_tasks:install` を実行
|
17
|
+
- `config/routes.rb` に `mount Scriptor::Engine => "/scriptor"` が記載されます
|
18
|
+
- `db/migrations/****_create_scriptor_executions.scriptor.rb` ファイルが作成されます
|
19
|
+
3. scriptフォルダにスクリプトを作ると、/scriptor ページにスクリプト一覧が並びます
|
20
|
+
|
21
|
+
で完成です。
|
22
|
+
|
23
|
+
## リリース手順
|
24
|
+
|
25
|
+
0. version.rb と Gemfile.lockのscriptorのバージョンが適切か確認しましょう
|
26
|
+
|
27
|
+
|
28
|
+
1. gemのbuild
|
29
|
+
```shell
|
30
|
+
gem build scriptor.gemspec
|
31
|
+
```
|
32
|
+
|
33
|
+
2. gemのpush
|
34
|
+
```shll
|
35
|
+
gem push scriptor-*.*.*.gem
|
36
|
+
```
|
23
37
|
|
24
38
|
## Contributing
|
25
|
-
|
39
|
+
- 色々足りていません。大歓迎です。
|
26
40
|
|
27
41
|
## License
|
28
42
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -6,14 +6,16 @@ module Scriptor
|
|
6
6
|
|
7
7
|
def show
|
8
8
|
@script = Scriptor::Script.find(params[:filename])
|
9
|
+
@executions = Execution.where(script_filename: @script.filename).order(id: :desc)
|
9
10
|
end
|
10
11
|
|
11
12
|
def run
|
12
13
|
script = Scriptor::Script.find(params[:filename])
|
13
14
|
args = params[:args].to_s.strip.split
|
14
15
|
script.run(*args)
|
15
|
-
|
16
|
-
|
16
|
+
redirect_to script_path(script.filename), notice: "Script executed successfully."
|
17
|
+
rescue StandardError => e
|
18
|
+
redirect_to script_path(script.filename), alert: "Error executing script: #{e.message}"
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Scriptor
|
2
|
+
class Execution < ApplicationRecord
|
3
|
+
validates :script_filename, presence: true
|
4
|
+
validates :executed_command, presence: true
|
5
|
+
validates :started_at, presence: true
|
6
|
+
|
7
|
+
enum :status, { running: "running", success: "success", error: "error" }
|
8
|
+
|
9
|
+
#
|
10
|
+
# Rails.rootパスを.に置換。想定として、"ruby /Users/hatsu/..."のように
|
11
|
+
# ruby [space]の直後にRails.root.to_sが現れることを想定しているため、
|
12
|
+
# /^ruby\s+#{Rails.root}/ のような正規表現で置き換える。
|
13
|
+
#
|
14
|
+
def truncated_command
|
15
|
+
executed_command.sub(/^ruby\s+#{Regexp.escape(Rails.root.to_s)}/, "ruby .")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
1
3
|
module Scriptor
|
2
4
|
class Script
|
3
5
|
attr_reader :filename, :content
|
@@ -31,14 +33,26 @@ module Scriptor
|
|
31
33
|
script_path = Rails.root.join("script", "#{filename}.rb")
|
32
34
|
raise "Script file does not exist: #{script_path}" unless File.exist?(script_path)
|
33
35
|
|
36
|
+
escaped_args = args.map(&:shellescape).join(" ")
|
37
|
+
command = "ruby #{script_path} #{escaped_args}"
|
34
38
|
# Rails 環境が正しくロードされるように Rails.root をカレントディレクトリに設定
|
39
|
+
execution = Execution.create!(
|
40
|
+
script_filename: filename,
|
41
|
+
status: :running,
|
42
|
+
executed_command: command,
|
43
|
+
started_at: Time.current
|
44
|
+
)
|
35
45
|
Dir.chdir(Rails.root) do
|
36
|
-
|
37
|
-
|
46
|
+
_, err, status = Open3.capture3(command)
|
47
|
+
if status.success?
|
48
|
+
# コマンド成功時の処理
|
49
|
+
execution.update!(status: :success, finished_at: Time.current)
|
50
|
+
else
|
51
|
+
error_message = err.presence || "Command failed with exit status #{status.exitstatus}"
|
52
|
+
execution.update!(status: :error, error_message: error_message.strip, finished_at: Time.current)
|
53
|
+
raise error_message
|
54
|
+
end
|
38
55
|
end
|
39
|
-
rescue StandardError => e
|
40
|
-
Rails.logger.error "Error running script #{filename}: #{e.message}"
|
41
|
-
raise e
|
42
56
|
end
|
43
57
|
|
44
58
|
private
|
@@ -7,12 +7,55 @@
|
|
7
7
|
|
8
8
|
<%= yield :head %>
|
9
9
|
|
10
|
-
<%= stylesheet_link_tag
|
10
|
+
<%= stylesheet_link_tag "scriptor/application", media: "all" %>
|
11
11
|
<script src="https://cdn.tailwindcss.com"></script>
|
12
12
|
</head>
|
13
|
-
<body>
|
14
|
-
|
15
|
-
|
13
|
+
<body class="bg-gray-900 text-gray-200">
|
14
|
+
<!-- Header -->
|
15
|
+
<header class="bg-gray-800 border-b border-gray-700">
|
16
|
+
<div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
|
17
|
+
<!-- Left: Title -->
|
18
|
+
<h1 class="text-xl font-bold text-blue-400">
|
19
|
+
<%= link_to "Scriptor", root_path, class: "hover:underline" %>
|
20
|
+
</h1>
|
21
|
+
<!-- Right: GitHub link -->
|
22
|
+
<a href="https://github.com/hatsu38/scriptor" target="_blank" rel="noopener noreferrer"
|
23
|
+
class="flex items-center space-x-2 text-gray-300 hover:text-gray-100 transition">
|
24
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
25
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 .296C5.372.296 0 5.668 0 12.296c0 5.3 3.438 9.773 8.205 11.368.6.111.82-.258.82-.578
|
26
|
+
0-.285-.01-1.04-.016-2.039-3.338.726-4.042-1.61-4.042-1.61-.545-1.385-1.333-1.755-1.333-1.755-1.091-.747.083-.732.083-.732
|
27
|
+
1.205.084 1.84 1.238 1.84 1.238 1.07 1.835 2.807 1.305 3.493.998.108-.776.418-1.305.76-1.606-2.665-.303-5.466-1.333-5.466-5.933
|
28
|
+
0-1.311.469-2.382 1.237-3.223-.124-.304-.536-1.523.118-3.176 0 0 1.01-.323 3.301 1.23.96-.267 1.98-.399 3-.404 1.02.005
|
29
|
+
2.04.137 3 .404 2.291-1.553 3.3-1.23 3.3-1.23.655 1.653.243 2.872.12 3.176.77.841 1.237 1.912 1.237 3.223
|
30
|
+
0 4.61-2.804 5.626-5.476 5.922.43.372.81 1.102.81 2.222 0 1.606-.014 2.903-.014 3.301 0 .321.22.694.825.576C20.565 22.068
|
31
|
+
24 17.595 24 12.296c0-6.628-5.372-12-12-12z"/>
|
32
|
+
</svg>
|
33
|
+
<span class="font-semibold">GitHub</span>
|
34
|
+
</a>
|
35
|
+
</div>
|
36
|
+
</header>
|
16
37
|
|
38
|
+
<section class="max-w-7xl mx-auto px-4 py-6">
|
39
|
+
<% if notice %>
|
40
|
+
<div class="mb-4 flex items-center space-x-3 bg-green-800 border border-green-500 rounded-md p-4 shadow-lg">
|
41
|
+
<svg class="h-5 w-5 text-green-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
42
|
+
xmlns="http://www.w3.org/2000/svg">
|
43
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
44
|
+
d="M9 11l3 3L22 4"></path>
|
45
|
+
</svg>
|
46
|
+
<span class="font-semibold"><%= notice %></span>
|
47
|
+
</div>
|
48
|
+
<% elsif alert %>
|
49
|
+
<div class="mb-4 flex items-center space-x-3 bg-red-800 border border-red-500 rounded-md p-4 shadow-lg">
|
50
|
+
<svg class="h-5 w-5 text-red-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
51
|
+
xmlns="http://www.w3.org/2000/svg">
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
53
|
+
d="M18.364 5.636L12 12m0 0L5.636 5.636M12 12l0 8"></path>
|
54
|
+
</svg>
|
55
|
+
<span class="font-semibold"><%= alert %></span>
|
56
|
+
</div>
|
57
|
+
<% end %>
|
58
|
+
<%= yield %>
|
59
|
+
</section>
|
17
60
|
</body>
|
18
61
|
</html>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="min-h-screen bg-gray-900 text-gray-200 py-8">
|
2
|
-
<div class="max-w-
|
2
|
+
<div class="max-w-7xl mx-auto px-4">
|
3
3
|
<!-- Header -->
|
4
4
|
<h1 class="text-4xl font-bold text-blue-400 mb-8 border-b border-gray-700 pb-2">
|
5
5
|
📜 Scripts Explorer
|
@@ -10,7 +10,12 @@
|
|
10
10
|
<% @scripts.each_with_index do |script, index| %>
|
11
11
|
<div class="bg-gray-800 hover:bg-gray-700 p-4 rounded-lg shadow-lg transition duration-200 group">
|
12
12
|
<h2 class="text-xl font-semibold mb-2 text-gray-300 group-hover:text-blue-300">
|
13
|
-
|
13
|
+
<!-- 親要素か、直接テキストを含む要素にtruncateクラスを適用 -->
|
14
|
+
<!-- inline-blockとmax-w-fullを組み合わせることで、最大幅内で省略可能に -->
|
15
|
+
<span class="inline-block max-w-full truncate">
|
16
|
+
<%= index + 1 %>.
|
17
|
+
<%= link_to script.filename, script_path(script.filename), class: "hover:underline" %>
|
18
|
+
</span>
|
14
19
|
</h2>
|
15
20
|
</div>
|
16
21
|
<% end %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<div class="min-h-screen bg-gray-900 text-gray-200 py-8">
|
2
|
-
<div class="max-w-
|
2
|
+
<div class="max-w-7xl mx-auto px-4">
|
3
3
|
<!-- Header -->
|
4
4
|
<h1 class="text-4xl font-bold text-blue-400 mb-8 border-b border-gray-700 pb-2">
|
5
5
|
Script Details
|
@@ -43,6 +43,60 @@
|
|
43
43
|
<% end %>
|
44
44
|
</div>
|
45
45
|
|
46
|
+
<!-- Execution History -->
|
47
|
+
<% if @executions.present? %>
|
48
|
+
<div class="bg-gray-800 shadow-lg rounded-lg p-6 mb-6">
|
49
|
+
<!-- タイトル部分をクリックすると開閉 -->
|
50
|
+
<h3 id="execution-history-toggle" class="text-xl font-semibold text-blue-300 mb-4 cursor-pointer flex items-center justify-between select-none">
|
51
|
+
<span>Execution History</span>
|
52
|
+
<svg id="toggle-icon" class="h-5 w-5 text-gray-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
53
|
+
<!-- 折り畳みアイコン(初期は閉じた状態に見えるように) -->
|
54
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
55
|
+
</svg>
|
56
|
+
</h3>
|
57
|
+
|
58
|
+
<!-- 初期状態ではhidden -->
|
59
|
+
<div id="execution-history-content" class="space-y-4 hidden">
|
60
|
+
<% @executions.each do |execution| %>
|
61
|
+
<div class="bg-gray-700 rounded-lg p-4 shadow-md flex flex-col sm:flex-row sm:justify-between sm:items-start space-y-4 sm:space-y-0 sm:space-x-4">
|
62
|
+
<div class="flex-1">
|
63
|
+
<div class="mb-6 text-gray-100 font-semibold">
|
64
|
+
Status:
|
65
|
+
<% if execution.status == "success" %>
|
66
|
+
<span class="text-green-400"><%= execution.status.capitalize %></span>
|
67
|
+
<% else %>
|
68
|
+
<span class="text-red-400"><%= execution.status.capitalize %></span>
|
69
|
+
<% end %>
|
70
|
+
</div>
|
71
|
+
<div class="text-sm text-gray-300 mt-1 break-words">
|
72
|
+
<span class="text-gray-400 font-medium">Command:</span>
|
73
|
+
<span class="font-mono text-gray-200 max-h-24 overflow-auto">
|
74
|
+
<%= execution.truncated_command %>
|
75
|
+
</span>
|
76
|
+
</div>
|
77
|
+
<% if execution.error_message.present? %>
|
78
|
+
<div class="text-sm text-red-400 mt-2 break-words">
|
79
|
+
<span class="text-gray-400 font-medium">Error:</span>
|
80
|
+
<div class="font-mono whitespace-pre-wrap break-words">
|
81
|
+
<%= execution.error_message %>
|
82
|
+
</div>
|
83
|
+
</div>
|
84
|
+
<% end %>
|
85
|
+
</div>
|
86
|
+
<div class="text-right text-sm text-gray-400">
|
87
|
+
<div>Started: <%= execution.started_at.strftime("%Y-%m-%d %H:%M:%S") %></div>
|
88
|
+
<% if execution.finished_at %>
|
89
|
+
<div>Finished: <%= execution.finished_at.strftime("%Y-%m-%d %H:%M:%S") %></div>
|
90
|
+
<% else %>
|
91
|
+
<div>Running...</div>
|
92
|
+
<% end %>
|
93
|
+
</div>
|
94
|
+
</div>
|
95
|
+
<% end %>
|
96
|
+
</div>
|
97
|
+
</div>
|
98
|
+
<% end %>
|
99
|
+
|
46
100
|
<!-- Back Link -->
|
47
101
|
<div class="flex justify-end">
|
48
102
|
<%= link_to '⬅️ Back to Index', scripts_path, class: "text-blue-500 hover:text-blue-400 font-medium hover:underline transition duration-200" %>
|
@@ -71,4 +125,22 @@
|
|
71
125
|
const args = argsInput.value.trim();
|
72
126
|
commandPreviewArgs.textContent = args;
|
73
127
|
});
|
128
|
+
|
129
|
+
// Toggle execution history
|
130
|
+
const toggleHeading = document.getElementById('execution-history-toggle');
|
131
|
+
const content = document.getElementById('execution-history-content');
|
132
|
+
const toggleIcon = document.getElementById('toggle-icon');
|
133
|
+
|
134
|
+
if (toggleHeading && content) {
|
135
|
+
toggleHeading.addEventListener('click', () => {
|
136
|
+
content.classList.toggle('hidden');
|
137
|
+
// アイコンの向きを切り替え
|
138
|
+
// 開いている場合は上向き矢印、閉じている場合は下向き矢印に
|
139
|
+
if (content.classList.contains('hidden')) {
|
140
|
+
toggleIcon.classList.remove('rotate-180');
|
141
|
+
} else {
|
142
|
+
toggleIcon.classList.add('rotate-180');
|
143
|
+
}
|
144
|
+
});
|
145
|
+
}
|
74
146
|
</script>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateScriptorExecutions < ActiveRecord::Migration[8.0]
|
2
|
+
def change
|
3
|
+
create_table :scriptor_executions do |t|
|
4
|
+
t.string :script_filename, null: false, index: true
|
5
|
+
t.string :status, null: false
|
6
|
+
t.text :executed_command, null: false
|
7
|
+
t.text :error_message, null: true
|
8
|
+
t.datetime :started_at, null: false
|
9
|
+
t.datetime :finished_at, null: true
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Scriptor
|
2
|
+
class InstallGenerator < Rails::Generators::Base
|
3
|
+
desc "Install Scriptor"
|
4
|
+
|
5
|
+
# Mounts the engine in the host application's config/routes.rb
|
6
|
+
def mount_engine
|
7
|
+
route("mount Scriptor::Engine, at: \"/scriptor\"")
|
8
|
+
end
|
9
|
+
|
10
|
+
# Copies engine migrations to host application and migrates the database
|
11
|
+
def install_migrations
|
12
|
+
rake("scriptor:install:migrations")
|
13
|
+
rake("db:migrate")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/scriptor/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scriptor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- hatsu38
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -51,11 +51,14 @@ files:
|
|
51
51
|
- app/jobs/scriptor/application_job.rb
|
52
52
|
- app/mailers/scriptor/application_mailer.rb
|
53
53
|
- app/models/scriptor/application_record.rb
|
54
|
+
- app/models/scriptor/execution.rb
|
54
55
|
- app/models/scriptor/script.rb
|
55
56
|
- app/views/layouts/scriptor/application.html.erb
|
56
57
|
- app/views/scriptor/scripts/index.html.erb
|
57
58
|
- app/views/scriptor/scripts/show.html.erb
|
58
59
|
- config/routes.rb
|
60
|
+
- db/migrate/20241219110922_create_scriptor_executions.rb
|
61
|
+
- lib/generators/scriptor/install_generator.rb
|
59
62
|
- lib/scriptor.rb
|
60
63
|
- lib/scriptor/engine.rb
|
61
64
|
- lib/scriptor/version.rb
|