ruby_wasm_ui 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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +0 -2
  3. data/Makefile +53 -0
  4. data/README.md +22 -4
  5. data/examples/.gitignore +3 -0
  6. data/examples/Gemfile.lock +15 -17
  7. data/examples/src/index.html +21 -0
  8. data/examples/src/index.rb +26 -0
  9. data/exe/ruby-wasm-ui +6 -0
  10. data/lib/ruby_wasm_ui/cli/command/base.rb +174 -0
  11. data/lib/ruby_wasm_ui/cli/command/dev.rb +206 -0
  12. data/lib/ruby_wasm_ui/cli/command/pack.rb +36 -0
  13. data/lib/ruby_wasm_ui/cli/command/rebuild.rb +38 -0
  14. data/lib/ruby_wasm_ui/cli/command/setup.rb +130 -0
  15. data/lib/ruby_wasm_ui/cli/command.rb +48 -0
  16. data/lib/ruby_wasm_ui/version.rb +1 -1
  17. data/lib/ruby_wasm_ui.rb +8 -8
  18. data/package-lock.json +2 -2
  19. data/package.json +1 -1
  20. data/packages/npm-packages/runtime/package-lock.json +2 -2
  21. data/packages/npm-packages/runtime/package.json +3 -3
  22. data/packages/npm-packages/runtime/rollup.config.mjs +68 -10
  23. data/spec/ruby_wasm_ui/cli/command/base_spec.rb +358 -0
  24. data/spec/ruby_wasm_ui/cli/command/dev_spec.rb +412 -0
  25. data/spec/ruby_wasm_ui/cli/command/pack_spec.rb +127 -0
  26. data/spec/ruby_wasm_ui/cli/command/rebuild_spec.rb +95 -0
  27. data/spec/ruby_wasm_ui/cli/command/setup_spec.rb +186 -0
  28. data/spec/ruby_wasm_ui/cli/command_spec.rb +118 -0
  29. data/{packages/npm-packages/runtime/spec → spec}/spec_helper.rb +1 -1
  30. metadata +96 -38
  31. data/packages/npm-packages/runtime/Gemfile +0 -3
  32. data/packages/npm-packages/runtime/Gemfile.lock +0 -26
  33. /data/lib/ruby_wasm_ui/{app.rb → runtime/app.rb} +0 -0
  34. /data/lib/ruby_wasm_ui/{component.rb → runtime/component.rb} +0 -0
  35. /data/lib/ruby_wasm_ui/{dispatcher.rb → runtime/dispatcher.rb} +0 -0
  36. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/attributes.rb +0 -0
  37. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/destroy_dom.rb +0 -0
  38. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/events.rb +0 -0
  39. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/mount_dom.rb +0 -0
  40. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/patch_dom.rb +0 -0
  41. /data/lib/ruby_wasm_ui/{dom → runtime/dom}/scheduler.rb +0 -0
  42. /data/lib/ruby_wasm_ui/{dom.rb → runtime/dom.rb} +0 -0
  43. /data/lib/ruby_wasm_ui/{nodes_equal.rb → runtime/nodes_equal.rb} +0 -0
  44. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_conditional_group.rb +0 -0
  45. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_for_group.rb +0 -0
  46. /data/lib/ruby_wasm_ui/{template → runtime/template}/build_vdom.rb +0 -0
  47. /data/lib/ruby_wasm_ui/{template → runtime/template}/parser.rb +0 -0
  48. /data/lib/ruby_wasm_ui/{template.rb → runtime/template.rb} +0 -0
  49. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/arrays.rb +0 -0
  50. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/objects.rb +0 -0
  51. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/props.rb +0 -0
  52. /data/lib/ruby_wasm_ui/{utils → runtime/utils}/strings.rb +0 -0
  53. /data/lib/ruby_wasm_ui/{utils.rb → runtime/utils.rb} +0 -0
  54. /data/lib/ruby_wasm_ui/{vdom.rb → runtime/vdom.rb} +0 -0
  55. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/component_spec.rb +0 -0
  56. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/dom/scheduler_spec.rb +0 -0
  57. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/nodes_equal_spec.rb +0 -0
  58. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_conditional_group_spec.rb +0 -0
  59. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_for_group_spec.rb +0 -0
  60. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/build_vdom_spec.rb +0 -0
  61. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/template/parser_spec.rb +0 -0
  62. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/arrays_spec.rb +0 -0
  63. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/objects_spec.rb +0 -0
  64. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/props_spec.rb +0 -0
  65. /data/{packages/npm-packages/runtime/spec/ruby_wasm_ui → spec/ruby_wasm_ui/runtime}/utils/strings_spec.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a999410f2d0e3c789843f58022405b1f9af0bd5640efb042ccc41464beb9e70d
4
- data.tar.gz: 92733ff997682478437f0df8f21fa3a4d0903ebf20fb1dd17c4923686b4e19df
3
+ metadata.gz: ba3eb2a44a8b73913fb511fe95407173c865a57890f3622915b8e6eb6ef42c87
4
+ data.tar.gz: '00526093db65a601dc375cd3c9944dde4d9c04bee00cf33701e656914e89e2c1'
5
5
  SHA512:
6
- metadata.gz: 35e4dcd436516375d2dc7e993580b1dd0cb73f3e8f6c55fbf4729ec6923f1b5655c724422e9356d65c6386c941838f6479281e002923f9f09071264e57681943
7
- data.tar.gz: 62b4a4bb92dc563ce77c6ad0ee761acffd7c39b32c438bc1ca2732b1e729bd25062c8681553768f1061963962337825bad2de8674d902363eff78f5e52456572
6
+ metadata.gz: 3529c934f5bef222cdcbb85b3b83e8ca323b36da9ea0d150c5977c4ebe1dfe282d8e97e7933eb8ae2bf90e632373d731928e14e48e105bede36239f782ab7fd5
7
+ data.tar.gz: e883ae7b71653376ccd06de8fe760aa909116614c1b0ad7a8357e9427b0bd9709dfa5de43be241c0ba1630a37a95ed02f099eb5515880a07b6acf4afde3f8cf5
@@ -24,10 +24,8 @@ jobs:
24
24
 
25
25
  - name: Install dependencies
26
26
  run: |
27
- cd packages/npm-packages/runtime
28
27
  bundle install
29
28
 
30
29
  - name: Run RSpec
31
30
  run: |
32
- cd packages/npm-packages/runtime
33
31
  bundle exec rspec
data/Makefile ADDED
@@ -0,0 +1,53 @@
1
+ .PHONY: bump help
2
+
3
+ help:
4
+ @echo "Usage: make bump <version>"
5
+ @echo " make bump VERSION=<version>"
6
+ @echo "Example: make bump 0.9.0"
7
+ @echo ""
8
+ @echo "This command updates the version number in the following files:"
9
+ @echo " - lib/ruby_wasm_ui/version.rb"
10
+ @echo " - package.json"
11
+ @echo " - packages/npm-packages/runtime/package.json"
12
+ @echo " - README.md"
13
+ @echo " - package-lock.json (via npm install)"
14
+ @echo " - packages/npm-packages/runtime/package-lock.json (via npm install)"
15
+
16
+ # Get version from position argument (if VERSION is not already set)
17
+ VERSION_ARG := $(word 2,$(MAKECMDGOALS))
18
+ ifndef VERSION
19
+ ifneq ($(VERSION_ARG),)
20
+ VERSION := $(VERSION_ARG)
21
+ endif
22
+ endif
23
+
24
+ bump:
25
+ @if [ -z "$(VERSION)" ]; then \
26
+ echo "Error: VERSION is required. Usage: make bump 0.9.0"; \
27
+ exit 1; \
28
+ fi
29
+ @echo "Bumping version to $(VERSION)..."
30
+ @# Update lib/ruby_wasm_ui/version.rb
31
+ @sed -i '' 's/VERSION = ".*"/VERSION = "$(VERSION)"/' lib/ruby_wasm_ui/version.rb
32
+ @echo "✓ Updated lib/ruby_wasm_ui/version.rb"
33
+ @# Update root package.json
34
+ @sed -i '' 's/"version": ".*"/"version": "$(VERSION)"/' package.json
35
+ @echo "✓ Updated package.json"
36
+ @# Update packages/npm-packages/runtime/package.json
37
+ @sed -i '' 's/"version": ".*"/"version": "$(VERSION)"/' packages/npm-packages/runtime/package.json
38
+ @echo "✓ Updated packages/npm-packages/runtime/package.json"
39
+ @# Update README.md (unpkg.com URL)
40
+ @sed -i '' 's|unpkg.com/ruby-wasm-ui@[0-9.]*|unpkg.com/ruby-wasm-ui@$(VERSION)|' README.md
41
+ @echo "✓ Updated README.md"
42
+ @echo ""
43
+ @echo "Updating package-lock.json files..."
44
+ @npm install --package-lock-only
45
+ @echo "✓ Updated root package-lock.json"
46
+ @cd packages/npm-packages/runtime && npm install --package-lock-only && cd ../../..
47
+ @echo "✓ Updated packages/npm-packages/runtime/package-lock.json"
48
+ @echo ""
49
+ @echo "Version bumped to $(VERSION) successfully!"
50
+
51
+ # Prevent make from treating the version argument as a target
52
+ %:
53
+ @:
data/README.md CHANGED
@@ -21,7 +21,7 @@ Create an HTML file:
21
21
  <!DOCTYPE html>
22
22
  <html>
23
23
  <head>
24
- <script src="https://unpkg.com/ruby-wasm-ui@0.8.2"></script>
24
+ <script src="https://unpkg.com/ruby-wasm-ui@0.9.0"></script>
25
25
  <script defer type="text/ruby" src="app.rb"></script>
26
26
  </head>
27
27
  <body>
@@ -112,20 +112,38 @@ bundle install
112
112
 
113
113
  ### Building Your Application
114
114
 
115
- 1. Build Ruby WASM:
115
+ 1. Set up your project (first time only):
116
116
 
117
117
  ```bash
118
- bundle exec rbwasm build --ruby-version 3.4 -o ruby.wasm
118
+ bundle exec ruby-wasm-ui setup
119
119
  ```
120
120
 
121
+ This command will:
122
+ - Configure excluded gems for WASM build
123
+ - Build Ruby WASM file
124
+ - Update `.gitignore`
125
+ - Create initial `src/index.html` and `src/index.rb` files
126
+
121
127
  2. Pack your application files:
122
128
 
123
129
  ```bash
124
- bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasm
130
+ bundle exec ruby-wasm-ui pack
125
131
  ```
126
132
 
127
133
  This command packs your Ruby files from the `./src` directory into the WASM file.
128
134
 
135
+ **Additional Commands:**
136
+
137
+ - **Development server**: Start a development server with file watching and auto-build:
138
+ ```bash
139
+ bundle exec ruby-wasm-ui dev
140
+ ```
141
+
142
+ - **Rebuild Ruby WASM**: Rebuild the Ruby WASM file when you add new gems:
143
+ ```bash
144
+ bundle exec ruby-wasm-ui rebuild
145
+ ```
146
+
129
147
  ### Creating Your HTML File
130
148
 
131
149
  Create an HTML file in the `src` directory that loads the WASM file:
@@ -0,0 +1,3 @@
1
+ *.wasm
2
+ /rubies
3
+ /build
@@ -1,38 +1,36 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- ruby_wasm_ui (0.8.1)
4
+ ruby_wasm_ui (0.8.3)
5
5
  js (~> 2.7)
6
+ listen (~> 3.8)
7
+ puma (~> 6.0)
8
+ rack (~> 3.0)
6
9
  ruby_wasm (~> 2.7)
7
10
 
8
11
  GEM
9
12
  remote: https://rubygems.org/
10
13
  specs:
14
+ ffi (1.17.2-arm64-darwin)
11
15
  js (2.7.2)
16
+ listen (3.9.0)
17
+ rb-fsevent (~> 0.10, >= 0.10.3)
18
+ rb-inotify (~> 0.9, >= 0.9.10)
12
19
  logger (1.7.0)
20
+ nio4r (2.7.5)
21
+ puma (6.6.1)
22
+ nio4r (~> 2.0)
23
+ rack (3.2.4)
24
+ rb-fsevent (0.11.2)
25
+ rb-inotify (0.11.1)
26
+ ffi (~> 1.0)
13
27
  ruby_wasm (2.7.2)
14
28
  logger
15
- ruby_wasm (2.7.2-aarch64-linux)
16
- logger
17
- ruby_wasm (2.7.2-aarch64-linux-musl)
18
- logger
19
29
  ruby_wasm (2.7.2-arm64-darwin)
20
30
  logger
21
- ruby_wasm (2.7.2-x86_64-darwin)
22
- logger
23
- ruby_wasm (2.7.2-x86_64-linux)
24
- logger
25
- ruby_wasm (2.7.2-x86_64-linux-musl)
26
- logger
27
31
 
28
32
  PLATFORMS
29
- aarch64-linux
30
- aarch64-linux-musl
31
33
  arm64-darwin
32
- ruby
33
- x86_64-darwin
34
- x86_64-linux
35
- x86_64-linux-musl
36
34
 
37
35
  DEPENDENCIES
38
36
  ruby_wasm_ui!
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>My App</title>
6
+ <script type="module">
7
+ import { DefaultRubyVM } from "https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.2/dist/browser/+esm";
8
+ const response = await fetch("../src.wasm");
9
+ const module = await WebAssembly.compileStreaming(response);
10
+ const { vm } = await DefaultRubyVM(module);
11
+ vm.evalAsync(`
12
+ require "ruby_wasm_ui"
13
+ require_relative './src/index.rb'
14
+ `);
15
+ </script>
16
+ </head>
17
+ <body>
18
+ <h1>My App</h1>
19
+ <div id="app"></div>
20
+ </body>
21
+ </html>
@@ -0,0 +1,26 @@
1
+ # Simple Hello World component
2
+ HelloComponent = RubyWasmUi.define_component(
3
+ state: ->(props) {
4
+ { message: props[:message] || "Hello, Ruby WASM UI!" }
5
+ },
6
+ template: ->() {
7
+ RubyWasmUi::Template::Parser.parse_and_eval(<<~HTML, binding)
8
+ <div>
9
+ <h2>{state[:message]}</h2>
10
+ <button on="{ click: -> { update_message } }">
11
+ Click me!
12
+ </button>
13
+ </div>
14
+ HTML
15
+ },
16
+ methods: {
17
+ update_message: ->() {
18
+ update_state(message: "You clicked the button!")
19
+ }
20
+ }
21
+ )
22
+
23
+ # Create and mount the app
24
+ app = RubyWasmUi::App.create(HelloComponent, message: "Hello, Ruby WASM UI!")
25
+ app_element = JS.global[:document].getElementById("app")
26
+ app.mount(app_element)
data/exe/ruby-wasm-ui ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/ruby_wasm_ui/cli/command"
5
+
6
+ RubyWasmUi::Cli::Command.run(ARGV)
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "bundler/setup"
5
+ require "ruby_wasm"
6
+ require "ruby_wasm/cli"
7
+
8
+ module RubyWasmUi
9
+ module Cli
10
+ class Command
11
+ class Base
12
+ def self.description
13
+ nil
14
+ end
15
+
16
+ def run(_argv)
17
+ raise NotImplementedError, "Subclasses must implement #run"
18
+ end
19
+
20
+ protected
21
+
22
+ def run_command(command, exit_on_error: true)
23
+ log_debug("Executing command: #{command}")
24
+
25
+ Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
26
+ stdin.close
27
+
28
+ # Read stdout and stderr in separate threads for real-time output
29
+ stdout_thread = Thread.new do
30
+ stdout.each_line do |line|
31
+ print line
32
+ $stdout.flush
33
+ end
34
+ end
35
+
36
+ stderr_thread = Thread.new do
37
+ stderr.each_line do |line|
38
+ $stderr.print line
39
+ $stderr.flush
40
+ end
41
+ end
42
+
43
+ # Wait for both threads to finish reading
44
+ stdout_thread.join
45
+ stderr_thread.join
46
+
47
+ # Wait for the process to finish
48
+ exit_status = wait_thr.value
49
+
50
+ unless exit_status.success?
51
+ log_error("Command failed: #{command}")
52
+ if exit_on_error
53
+ raise SystemExit.new(exit_status.exitstatus)
54
+ else
55
+ return false
56
+ end
57
+ end
58
+
59
+ true
60
+ end
61
+ end
62
+
63
+ def ensure_src_directory
64
+ unless Dir.exist?("src")
65
+ log_error("src directory not found. Please run 'ruby-wasm-ui setup' first.")
66
+ raise SystemExit.new(1)
67
+ end
68
+ end
69
+
70
+ def ensure_ruby_wasm
71
+ unless File.exist?("ruby.wasm")
72
+ log_error("ruby.wasm not found. Please run 'ruby-wasm-ui setup' first.")
73
+ raise SystemExit.new(1)
74
+ end
75
+ end
76
+
77
+ def update_gitignore(entries_to_add)
78
+ gitignore_path = ".gitignore"
79
+
80
+ # Read existing .gitignore or create new content
81
+ if File.exist?(gitignore_path)
82
+ content = File.read(gitignore_path)
83
+ lines = content.lines.map(&:chomp)
84
+ else
85
+ lines = []
86
+ end
87
+
88
+ # Add entries that don't already exist
89
+ added_entries = []
90
+ entries_to_add.each do |entry|
91
+ unless lines.include?(entry)
92
+ lines << entry
93
+ added_entries << entry
94
+ end
95
+ end
96
+
97
+ # Write back to .gitignore
98
+ File.write(gitignore_path, lines.join("\n") + "\n")
99
+ if added_entries.any?
100
+ log_info("Added to .gitignore: #{added_entries.join(', ')}")
101
+ else
102
+ log_info("No new entries added to .gitignore (all entries already exist)")
103
+ end
104
+ end
105
+
106
+ protected
107
+
108
+ def log_info(message)
109
+ puts "[INFO] #{message}"
110
+ end
111
+
112
+ def log_success(message)
113
+ puts "[SUCCESS] #{message}"
114
+ end
115
+
116
+ def log_error(message)
117
+ puts "[ERROR] #{message}"
118
+ end
119
+
120
+ def log_debug(message)
121
+ puts "[DEBUG] #{message}"
122
+ end
123
+
124
+ def check_ruby_version
125
+ ruby_version = RUBY_VERSION.split('.').map(&:to_i)
126
+ major, minor = ruby_version[0], ruby_version[1]
127
+
128
+ if major < 3 || (major == 3 && minor < 2)
129
+ log_error("Ruby WASM requires Ruby 3.2 or higher. Current version: #{RUBY_VERSION}")
130
+ raise SystemExit.new(1)
131
+ end
132
+
133
+ "#{major}.#{minor}"
134
+ end
135
+
136
+ def configure_excluded_gems
137
+ # Get all gems from Bundler definition including all dependencies
138
+ definition = Bundler.definition
139
+ resolved = definition.resolve
140
+
141
+ # Get all resolved specs (including transitive dependencies)
142
+ all_specs = resolved.materialize(definition.requested_dependencies)
143
+ gem_names = all_specs.map(&:name).uniq
144
+
145
+ # Always exclude gems that cause WASM build errors
146
+ # These gems have native extensions that don't work in WASM environment
147
+ always_excluded = %w[nio4r puma rack listen ffi]
148
+
149
+ # Exclude gems that cause build errors or are unnecessary for WASM
150
+ # Keep essential gems like 'js' for WASM
151
+ excluded_gems = gem_names.select do |gem_name|
152
+ # Exclude gems that cause WASM build errors
153
+ always_excluded.include?(gem_name) ||
154
+ # Exclude development/test gems
155
+ gem_name.start_with?("rspec", "rubocop", "rake")
156
+ end
157
+
158
+ # Always add always_excluded gems to ensure they're excluded even if not in dependencies
159
+ excluded_gems.concat(always_excluded)
160
+ excluded_gems.uniq!
161
+
162
+ # Add to EXCLUDED_GEMS
163
+ RubyWasm::Packager::EXCLUDED_GEMS.concat(excluded_gems)
164
+ end
165
+
166
+ def build_ruby_wasm(ruby_version_str)
167
+ command = ["build", "--ruby-version", ruby_version_str, "-o", "ruby.wasm"]
168
+ cli = RubyWasm::CLI.new(stdout: $stdout, stderr: $stderr)
169
+ cli.run(command)
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+ require "listen"
5
+ require "rack"
6
+ require "thread"
7
+ require "rbconfig"
8
+
9
+ module RubyWasmUi
10
+ module Cli
11
+ class Command
12
+ class Dev < Base
13
+ def self.description
14
+ "Start development server with file watching and auto-build"
15
+ end
16
+
17
+ def run(_argv)
18
+ log_info("Starting development server...")
19
+ puts ""
20
+
21
+ ensure_src_directory
22
+
23
+ # Initial build
24
+ log_info("Performing initial build...")
25
+ build
26
+ log_success("✓ Initial build completed")
27
+ puts ""
28
+
29
+ # Start file watcher in a separate thread
30
+ @build_lock = Mutex.new
31
+ @build_queue = Queue.new
32
+ @listener = nil
33
+ @watcher_thread = nil
34
+
35
+ @watcher_thread = Thread.new do
36
+ start_file_watcher
37
+ end
38
+
39
+ # Register cleanup hook to ensure cleanup runs on exit
40
+ @cleanup_done = false
41
+ at_exit do
42
+ cleanup unless @cleanup_done
43
+ end
44
+
45
+ # Start development server
46
+ begin
47
+ start_server
48
+ ensure
49
+ cleanup
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def open_browser(url)
56
+ case RbConfig::CONFIG["host_os"]
57
+ when /darwin/
58
+ system("open", url)
59
+ when /linux/
60
+ system("xdg-open", url)
61
+ when /mswin|mingw|cygwin/
62
+ system("start", url)
63
+ else
64
+ log_info("Please open #{url} in your browser")
65
+ end
66
+ rescue => e
67
+ log_info("Could not open browser automatically: #{e.message}")
68
+ log_info("Please open #{url} in your browser")
69
+ end
70
+
71
+ def cleanup
72
+ return if @cleanup_done
73
+ @cleanup_done = true
74
+ @listener&.stop
75
+ @watcher_thread&.kill
76
+ end
77
+
78
+ def build
79
+ command = "bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasm"
80
+ log_info("Building: #{command}")
81
+
82
+ success = run_command(command, exit_on_error: false)
83
+ if success
84
+ log_success("✓ Build completed")
85
+ else
86
+ log_error("Build failed")
87
+ end
88
+ success
89
+ end
90
+
91
+ def start_file_watcher
92
+ @listener = Listen.to("src") do |modified, added, removed|
93
+ files_changed = (modified + added + removed).reject do |file|
94
+ # Ignore temporary files and common build artifacts
95
+ file.end_with?(".swp", ".tmp", "~", ".wasm")
96
+ end
97
+
98
+ if files_changed.any?
99
+ log_info("Files changed: #{files_changed.join(', ')}")
100
+
101
+ # Debounce: add to queue
102
+ @build_queue << :build
103
+
104
+ # Process queue with debouncing
105
+ Thread.new do
106
+ sleep 0.5 # Debounce delay
107
+ begin
108
+ if @build_queue.pop(true)
109
+ @build_queue.clear
110
+ @build_lock.synchronize do
111
+ build
112
+ end
113
+ end
114
+ rescue ThreadError
115
+ # Queue is empty, ignore
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ @listener.start
122
+ rescue => e
123
+ log_error("File watcher error: #{e.message}")
124
+ end
125
+
126
+ def start_server
127
+ port = 8080
128
+ log_info("Starting development server on http://localhost:#{port}")
129
+ log_info("Press Ctrl+C to stop")
130
+ puts ""
131
+
132
+ # Static file server application
133
+ static_app = lambda do |env|
134
+ path = env["PATH_INFO"]
135
+ if path == "/"
136
+ file_path = File.join(Dir.pwd, "src", "index.html")
137
+ else
138
+ file_path = File.join(Dir.pwd, path)
139
+ end
140
+
141
+ if File.exist?(file_path) && File.file?(file_path)
142
+ content_type = Rack::Mime.mime_type(File.extname(file_path), "text/html")
143
+ [200, { "Content-Type" => content_type }, [File.read(file_path)]]
144
+ else
145
+ [404, { "Content-Type" => "text/plain" }, ["File not found: #{path}"]]
146
+ end
147
+ end
148
+
149
+ # CORS middleware
150
+ cors_middleware = lambda do |env|
151
+ if env["REQUEST_METHOD"] == "OPTIONS"
152
+ [
153
+ 200,
154
+ {
155
+ "Access-Control-Allow-Origin" => "*",
156
+ "Access-Control-Allow-Methods" => "GET, POST, OPTIONS",
157
+ "Access-Control-Allow-Headers" => "Content-Type"
158
+ },
159
+ []
160
+ ]
161
+ else
162
+ status, headers, body = static_app.call(env)
163
+ headers["Access-Control-Allow-Origin"] = "*"
164
+ headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS"
165
+ headers["Access-Control-Allow-Headers"] = "Content-Type"
166
+ [status, headers, body]
167
+ end
168
+ end
169
+
170
+ # Create Rack application
171
+ app = Rack::Builder.new do
172
+ run cors_middleware
173
+ end
174
+
175
+ # Handle shutdown
176
+ trap("INT") do
177
+ log_info("\nShutting down server...")
178
+ cleanup
179
+ raise SystemExit.new(0)
180
+ end
181
+
182
+ # Use Puma handler (Rack 3.0 compatible)
183
+ begin
184
+ require "rack/handler/puma"
185
+ log_info("Using handler: puma")
186
+
187
+ # Open browser after a short delay to ensure server is ready
188
+ Thread.new do
189
+ sleep 1
190
+ open_browser("http://localhost:#{port}/src/index.html")
191
+ end
192
+
193
+ Rack::Handler::Puma.run(app, Port: port, Host: "0.0.0.0")
194
+ rescue LoadError => e
195
+ log_error("Puma handler not available: #{e.message}")
196
+ log_error("Please ensure puma gem is installed: gem install puma")
197
+ raise SystemExit.new(1)
198
+ end
199
+ rescue => e
200
+ log_error("Server error: #{e.message}")
201
+ raise SystemExit.new(1)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module RubyWasmUi
6
+ module Cli
7
+ class Command
8
+ class Pack < Base
9
+ def self.description
10
+ "Pack WASM file by packing Ruby source files"
11
+ end
12
+
13
+ def run(_argv)
14
+ log_info("Packing WASM file...")
15
+ puts ""
16
+
17
+ ensure_src_directory
18
+ ensure_ruby_wasm
19
+
20
+ # Pack WASM file
21
+ pack
22
+ end
23
+
24
+ private
25
+
26
+ def pack
27
+ command = "bundle exec rbwasm pack ruby.wasm --dir ./src::./src -o src.wasm"
28
+ log_info("Packing: #{command}")
29
+
30
+ run_command(command)
31
+ log_success("✓ Pack completed")
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end