ruflet_cli 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c2afb6fb7d347d71d74263f24813988c2b1dbd8f9fa686483983ac5e9a304035
4
+ data.tar.gz: 8ca131cf5f28652f03fe6b78ede64c114e2aa9f54bef456889b3ab548c4d09d2
5
+ SHA512:
6
+ metadata.gz: c425acd4231c1b8ee4b9b8528420d77de3142d935af203675493da4ca2848dc8fea8a2c3e7f6e109feab106546b7c93a8ac027934f1675e0a9dd5260489f5a64
7
+ data.tar.gz: 1ebf882ccd5fb64c1a6741cf48e78adb85f95e579a42b85938556961e8b0c21ad2fdb983518eda64b28a9a31a272f5d5a99a1752db56196831a432ee92e6dcf6
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # ruflet_cli
2
+
3
+ Part of Ruflet monorepo.
data/bin/ruflet ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/ruflet_cli"
5
+
6
+ exit Ruflet::CLI.run(ARGV)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module CLI
5
+ module BuildCommand
6
+ def command_build(args)
7
+ platform = (args.shift || "").downcase
8
+ if platform.empty?
9
+ warn "Usage: ruflet build <apk|ios|aab|web|macos|windows|linux>"
10
+ return 1
11
+ end
12
+
13
+ flutter_cmd = flutter_build_command(platform)
14
+ unless flutter_cmd
15
+ warn "Unsupported build target: #{platform}"
16
+ return 1
17
+ end
18
+
19
+ client_dir = detect_flutter_client_dir
20
+ unless client_dir
21
+ warn "Could not find Flutter client directory."
22
+ warn "Set RUFLET_CLIENT_DIR or place client at ./ruflet_client"
23
+ return 1
24
+ end
25
+
26
+ ok = system(*flutter_cmd, chdir: client_dir)
27
+ ok ? 0 : 1
28
+ end
29
+
30
+ private
31
+
32
+ def detect_flutter_client_dir
33
+ env_dir = ENV["RUFLET_CLIENT_DIR"]
34
+ return env_dir if env_dir && Dir.exist?(env_dir)
35
+
36
+ local = File.expand_path("ruflet_client", Dir.pwd)
37
+ return local if Dir.exist?(local)
38
+
39
+ nil
40
+ end
41
+
42
+ def flutter_build_command(platform)
43
+ case platform
44
+ when "apk", "android"
45
+ ["flutter", "build", "apk"]
46
+ when "aab", "appbundle"
47
+ ["flutter", "build", "appbundle"]
48
+ when "ios"
49
+ ["flutter", "build", "ios", "--no-codesign"]
50
+ when "web"
51
+ ["flutter", "build", "web"]
52
+ when "macos"
53
+ ["flutter", "build", "macos"]
54
+ when "windows"
55
+ ["flutter", "build", "windows"]
56
+ when "linux"
57
+ ["flutter", "build", "linux"]
58
+ else
59
+ nil
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Ruflet
6
+ module CLI
7
+ module NewCommand
8
+ def command_new(args)
9
+ app_name = args.shift
10
+ if app_name.nil? || app_name.strip.empty?
11
+ warn "Usage: ruflet new <appname>"
12
+ return 1
13
+ end
14
+
15
+ root = File.expand_path(app_name, Dir.pwd)
16
+ if Dir.exist?(root)
17
+ warn "Directory already exists: #{root}"
18
+ return 1
19
+ end
20
+
21
+ FileUtils.mkdir_p(root)
22
+ FileUtils.mkdir_p(File.join(root, ".bundle"))
23
+ File.write(File.join(root, "main.rb"), format(Ruflet::CLI::MAIN_TEMPLATE, app_title: humanize_name(File.basename(root))))
24
+ File.write(File.join(root, "Gemfile"), Ruflet::CLI::GEMFILE_TEMPLATE)
25
+ File.write(File.join(root, ".bundle", "config"), Ruflet::CLI::BUNDLE_CONFIG_TEMPLATE)
26
+ File.write(File.join(root, "README.md"), format(Ruflet::CLI::README_TEMPLATE, app_name: File.basename(root)))
27
+
28
+ project_name = File.basename(root)
29
+ puts "Ruflet app created: #{project_name}"
30
+ puts "Run:"
31
+ puts " cd #{project_name}"
32
+ puts " bundle install"
33
+ puts " bundle exec ruflet run main"
34
+ 0
35
+ end
36
+
37
+ private
38
+
39
+ def humanize_name(name)
40
+ name.to_s.gsub(/[_-]+/, " ").split.map(&:capitalize).join(" ")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "rbconfig"
5
+ require "socket"
6
+
7
+ module Ruflet
8
+ module CLI
9
+ module RunCommand
10
+ def command_run(args)
11
+ options = { target: "mobile" }
12
+ parser = OptionParser.new do |o|
13
+ o.on("--web") { options[:target] = "web" }
14
+ o.on("--mobile") { options[:target] = "mobile" }
15
+ o.on("--desktop") { options[:target] = "desktop" }
16
+ end
17
+ parser.parse!(args)
18
+
19
+ script_token = args.shift || "main"
20
+ script_path = resolve_script(script_token)
21
+ unless script_path
22
+ warn "Script not found: #{script_token}"
23
+ warn "Expected: ./#{script_token}.rb, ./#{script_token}, or explicit file path."
24
+ return 1
25
+ end
26
+
27
+ selected_port = find_available_port(8550)
28
+ env = {
29
+ "RUFLET_TARGET" => options[:target],
30
+ "RUFLET_SUPPRESS_SERVER_BANNER" => "1",
31
+ "RUFLET_PORT" => selected_port.to_s
32
+ }
33
+
34
+ puts "Requested port 8550 is busy; bound to #{selected_port}" if selected_port != 8550
35
+ print_mobile_qr_hint(port: selected_port) if options[:target] == "mobile"
36
+
37
+ cmd =
38
+ if File.file?(File.expand_path("Gemfile", Dir.pwd))
39
+ env["BUNDLE_PATH"] = "vendor/bundle"
40
+ env["BUNDLE_DISABLE_SHARED_GEMS"] = "true"
41
+ bundle_ready = system(env, "bundle", "check", out: File::NULL, err: File::NULL)
42
+ return 1 unless bundle_ready || system(env, "bundle", "install")
43
+
44
+ ["bundle", "exec", RbConfig.ruby, script_path]
45
+ else
46
+ [RbConfig.ruby, script_path]
47
+ end
48
+
49
+ child_pid = Process.spawn(env, *cmd, pgroup: true)
50
+ forward_signal = lambda do |signal|
51
+ begin
52
+ Process.kill(signal, -child_pid)
53
+ rescue Errno::ESRCH
54
+ nil
55
+ end
56
+ end
57
+
58
+ previous_int = Signal.trap("INT") { forward_signal.call("INT") }
59
+ previous_term = Signal.trap("TERM") { forward_signal.call("TERM") }
60
+
61
+ _pid, status = Process.wait2(child_pid)
62
+ status.success? ? 0 : (status.exitstatus || 1)
63
+ ensure
64
+ Signal.trap("INT", previous_int) if defined?(previous_int) && previous_int
65
+ Signal.trap("TERM", previous_term) if defined?(previous_term) && previous_term
66
+
67
+ if defined?(child_pid) && child_pid
68
+ begin
69
+ Process.kill("TERM", -child_pid)
70
+ rescue Errno::ESRCH
71
+ nil
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def resolve_script(token)
79
+ path = File.expand_path(token, Dir.pwd)
80
+ return path if File.file?(path)
81
+
82
+ candidate = File.expand_path("#{token}.rb", Dir.pwd)
83
+ return candidate if File.file?(candidate)
84
+
85
+ nil
86
+ end
87
+
88
+ def print_mobile_qr_hint(port: 8550)
89
+ host = best_lan_host
90
+ payload = "http://#{host}:#{port}"
91
+
92
+ puts
93
+ puts "Ruflet mobile connect URL:"
94
+ puts " #{payload}"
95
+ puts "Ruflet server ws URL:"
96
+ puts " ws://0.0.0.0:#{port}/ws"
97
+ puts "Scan this QR from ruflet_client (Connect -> Scan QR):"
98
+ print_ascii_qr(payload)
99
+ puts
100
+ rescue StandardError => e
101
+ warn "QR setup failed: #{e.class}: #{e.message}"
102
+ end
103
+
104
+ def find_available_port(start_port, max_attempts: 100)
105
+ port = start_port.to_i
106
+
107
+ max_attempts.times do
108
+ begin
109
+ begin
110
+ probe = TCPServer.new("0.0.0.0", port)
111
+ rescue Errno::EACCES, Errno::EPERM
112
+ probe = TCPServer.new("127.0.0.1", port)
113
+ end
114
+ probe.close
115
+ return port
116
+ rescue Errno::EADDRINUSE
117
+ port += 1
118
+ end
119
+ end
120
+
121
+ start_port
122
+ end
123
+
124
+ def best_lan_host
125
+ ips = Socket.ip_address_list
126
+ addr = ips.find { |ip| ip.ipv4_private? && !ip.ipv4_loopback? }
127
+ return addr.ip_address if addr
128
+
129
+ "127.0.0.1"
130
+ end
131
+
132
+ def print_ascii_qr(payload)
133
+ begin
134
+ require "rqrcode"
135
+ rescue LoadError
136
+ puts "(Install 'rqrcode' gem in CLI package for terminal QR rendering.)"
137
+ return
138
+ end
139
+
140
+ q = RQRCode::QRCode.new(payload)
141
+ border = 1
142
+ core = q.modules
143
+ size = core.length + (2 * border)
144
+
145
+ matrix = Array.new(size) do |y|
146
+ Array.new(size) do |x|
147
+ cy = y - border
148
+ cx = x - border
149
+ cy >= 0 && cx >= 0 && cy < core.length && cx < core.length && core[cy][cx]
150
+ end
151
+ end
152
+
153
+ y = 0
154
+ while y < size
155
+ line = +""
156
+ (0...size).each do |x|
157
+ top = matrix[y][x]
158
+ bottom = (y + 1 < size) ? matrix[y + 1][x] : false
159
+ line << if top && bottom
160
+ "\u2588"
161
+ elsif top
162
+ "\u2580"
163
+ elsif bottom
164
+ "\u2584"
165
+ else
166
+ " "
167
+ end
168
+ end
169
+ puts line
170
+ y += 2
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ module CLI
5
+ MAIN_TEMPLATE = <<~RUBY
6
+ require "ruflet"
7
+
8
+ class MainApp < Ruflet::App
9
+ def initialize
10
+ super
11
+ @count = 0
12
+ end
13
+
14
+ def view(page)
15
+ page.title = "Counter Demo"
16
+ page.vertical_alignment = Ruflet::MainAxisAlignment::CENTER
17
+ page.horizontal_alignment = Ruflet::CrossAxisAlignment::CENTER
18
+ count_text = page.text(value: @count.to_s, size: 40)
19
+
20
+ page.add(
21
+ page.container(
22
+ expand: true,
23
+ padding: 24,
24
+ content: page.column(
25
+ expand: true,
26
+ alignment: Ruflet::MainAxisAlignment::CENTER,
27
+ horizontal_alignment: Ruflet::CrossAxisAlignment::CENTER,
28
+ spacing: 12,
29
+ controls: [
30
+ page.text(value: "You have pushed the button this many times:"),
31
+ count_text
32
+ ]
33
+ )
34
+ ),
35
+ appbar: page.app_bar(
36
+ title: page.text(value: "Counter Demo")
37
+ ),
38
+ floating_action_button: page.fab(
39
+ page.icon(icon: Ruflet::MaterialIcons::ADD),
40
+ on_click: ->(_e) {
41
+ @count += 1
42
+ page.update(count_text, value: @count.to_s)
43
+ }
44
+ )
45
+ )
46
+ end
47
+ end
48
+
49
+ MainApp.new.run
50
+
51
+ RUBY
52
+
53
+ GEMFILE_TEMPLATE = <<~GEMFILE
54
+ source "https://rubygems.org"
55
+
56
+ RUFLET_GIT = "https://github.com/AdamMusa/Ruflet.git"
57
+ RUFLET_BRANCH = "main"
58
+
59
+ gem "ruflet_server", git: RUFLET_GIT, branch: RUFLET_BRANCH, glob: "packages/ruflet_server/*.gemspec"
60
+ gem "ruflet", git: RUFLET_GIT, branch: RUFLET_BRANCH, glob: "packages/ruflet/*.gemspec"
61
+ GEMFILE
62
+
63
+ BUNDLE_CONFIG_TEMPLATE = <<~YAML
64
+ ---
65
+ BUNDLE_PATH: "vendor/bundle"
66
+ BUNDLE_DISABLE_SHARED_GEMS: "true"
67
+ YAML
68
+
69
+ README_TEMPLATE = <<~MD
70
+ # %<app_name>s
71
+
72
+ Ruflet app.
73
+
74
+ ## Setup
75
+
76
+ ```bash
77
+ bundle install
78
+ ```
79
+
80
+ ## Run
81
+
82
+ ```bash
83
+ bundle exec ruflet run main
84
+ ```
85
+
86
+ ## Build
87
+
88
+ ```bash
89
+ bundle exec ruflet build apk
90
+ bundle exec ruflet build ios
91
+ ```
92
+ MD
93
+ end
94
+ end
data/lib/ruflet/cli.rb ADDED
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ require_relative "cli/templates"
6
+ require_relative "cli/new_command"
7
+ require_relative "cli/run_command"
8
+ require_relative "cli/build_command"
9
+
10
+ module Ruflet
11
+ module CLI
12
+ extend self
13
+ extend NewCommand
14
+ extend RunCommand
15
+ extend BuildCommand
16
+
17
+ def run(argv = ARGV)
18
+ command = (argv.shift || "help").downcase
19
+
20
+ case command
21
+ when "new", "bootstrap", "init"
22
+ command_new(argv)
23
+ when "run"
24
+ command_run(argv)
25
+ when "build"
26
+ command_build(argv)
27
+ when "help", "-h", "--help"
28
+ print_help
29
+ 0
30
+ else
31
+ warn "Unknown command: #{command}"
32
+ print_help
33
+ 1
34
+ end
35
+ end
36
+
37
+ def print_help
38
+ puts <<~HELP
39
+ Ruflet CLI
40
+
41
+ Commands:
42
+ ruflet new <appname>
43
+ ruflet run [scriptname|path] [--web|--mobile|--desktop]
44
+ ruflet build <apk|ios|aab|web|macos|windows|linux>
45
+ HELP
46
+ end
47
+
48
+ def bootstrap(path)
49
+ command_new([path || Dir.pwd])
50
+ 0
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ruflet
4
+ VERSION = "0.0.1" unless const_defined?(:VERSION)
5
+ end
data/lib/ruflet_cli.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruflet/cli"
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruflet_cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - AdamMusa
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rqrcode
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.2'
26
+ description: Ruflet command line interface for creating and running Ruflet apps.
27
+ email:
28
+ - adammusa2222@gmail.com
29
+ executables:
30
+ - ruflet
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - bin/ruflet
36
+ - lib/ruflet/cli.rb
37
+ - lib/ruflet/cli/build_command.rb
38
+ - lib/ruflet/cli/new_command.rb
39
+ - lib/ruflet/cli/run_command.rb
40
+ - lib/ruflet/cli/templates.rb
41
+ - lib/ruflet/version.rb
42
+ - lib/ruflet_cli.rb
43
+ homepage: https://github.com/AdamMusa/Ruflet
44
+ licenses: []
45
+ metadata: {}
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.1'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubygems_version: 3.7.2
61
+ specification_version: 4
62
+ summary: Ruflet CLI package.
63
+ test_files: []