wabi 0.4.0 → 0.5.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/CHANGELOG.md +39 -0
- data/lib/wabi/generators/add_generator.rb +4 -1
- data/lib/wabi/generators/update_generator.rb +184 -0
- data/lib/wabi/lockfile.rb +5 -2
- data/lib/wabi/version.rb +1 -1
- data/lib/wabi.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0660623d8f6e54a685e9793a3e1ca16e9ac56fcf7150e061c0e80835b8fbe9bd
|
|
4
|
+
data.tar.gz: ecbbce636fe38adf58c7931c208dbbe7000cc6b737ce516c286396c483f8e2e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7c197e08b3f8cdea3f5587fc265495af390b12a185a030daf7add62fcf17dafd156d7d0466c39f0c943fff8400b486b46a09ce20a85c34fa89bd463056897ff3
|
|
7
|
+
data.tar.gz: f8f0d96bad57dc8889221364e06e594bec7f8762ad1373715a308f5a312751e3335347d9b54895dae4c6c29da441900220e318e9d389c65bbd65295efd4eb924
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Wabi land here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
4
4
|
|
|
5
|
+
## 0.5.0 - 2026-05-27
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- **Overlays portal to `document.body` by default**. Dialog, Drawer,
|
|
10
|
+
Tooltip, Popover, DropdownMenu, and Select all move their content (and
|
|
11
|
+
backdrop/positioner where applicable) to `<body>` on connect. Pass
|
|
12
|
+
`portal: false` to keep v0.4 in-tree behavior. The shared
|
|
13
|
+
`WabiPortalRegistry` JS module is now part of every overlay's wabi:add
|
|
14
|
+
install — it lands at
|
|
15
|
+
`app/javascript/controllers/wabi/_shared/portal_registry.js`.
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- `wabi:update` generator. Per-component diff-aware updates with
|
|
20
|
+
per-file conflict detection. Flags: `--force`, `--dry-run`. Reads
|
|
21
|
+
and writes per-file SHA256 hashes in `wabi.lock.json` (additive
|
|
22
|
+
schema; legacy lockfiles fall back to prompting on every file).
|
|
23
|
+
- Real portal pattern for the 6 overlays. Resolves the v0.1 carryover
|
|
24
|
+
documented in `docs/V01-CARRYOVER.md` (#3). Overlays inside
|
|
25
|
+
transformed/scrollable ancestors now position relative to the
|
|
26
|
+
viewport.
|
|
27
|
+
|
|
28
|
+
### Docs
|
|
29
|
+
|
|
30
|
+
- `+esm` jsdelivr footnote added to 5 component pages
|
|
31
|
+
(checkbox/select/switch/dialog/tabs) for parity with the other 5
|
|
32
|
+
Zag-backed components.
|
|
33
|
+
|
|
34
|
+
### Deferred to v0.6
|
|
35
|
+
|
|
36
|
+
- Toast `@zag-js/toast` group machine. Attempted in v0.5 but reverted — the group machine's interaction with Phlex+Stimulus+Turbo caused an infinite render loop. v0.4 vanilla setTimeout controller restored; group features (max/gap/pause-on-group-hover/swipe) targeted for v0.6 with a different implementation strategy.
|
|
37
|
+
- Forms wave (RadioGroup/Toggle/ToggleGroup/Slider/Combobox/Command/Form).
|
|
38
|
+
- Nav wave (Sheet/ContextMenu/Pagination/NavigationMenu).
|
|
39
|
+
- Data wave (Calendar/DatePicker/DataTable).
|
|
40
|
+
- Three-way merge in `wabi:update`.
|
|
41
|
+
- Multi-level DropdownMenu nesting (sub-inside-sub).
|
|
42
|
+
- Phlex 2.4 Ruby 4 warnings (upstream).
|
|
43
|
+
|
|
5
44
|
## [0.4.0] — 2026-05-27
|
|
6
45
|
|
|
7
46
|
Sprint 8 — docs completeness + theme polish. The docs site gains the
|
|
@@ -37,17 +37,20 @@ module Wabi
|
|
|
37
37
|
|
|
38
38
|
Array(data["registry_dependencies"]).each { |dep| install_component(dep) }
|
|
39
39
|
|
|
40
|
+
files_map = {}
|
|
40
41
|
data["files"].each do |file|
|
|
41
42
|
target = File.join(destination_root, file["path"])
|
|
42
43
|
FileUtils.mkdir_p(File.dirname(target))
|
|
43
44
|
File.write(target, file["content"])
|
|
45
|
+
files_map[file["path"]] = Digest::SHA256.hexdigest(file["content"])
|
|
44
46
|
say " create #{file["path"]}", :green
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
(data["js_dependencies"] || {}).each { |pkg, ver| @js_deps_to_pin[pkg] = ver }
|
|
48
50
|
|
|
49
51
|
hash = Digest::SHA256.hexdigest(JSON.generate(data["files"]))
|
|
50
|
-
lockfile.record(name, version: data["version"], hash: hash
|
|
52
|
+
lockfile.record(name, version: data["version"], hash: hash, files: files_map,
|
|
53
|
+
js_dependencies: data["js_dependencies"])
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
def print_js_pin_instructions
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "json"
|
|
6
|
+
require "wabi/registry_client"
|
|
7
|
+
require "wabi/lockfile"
|
|
8
|
+
|
|
9
|
+
module Wabi
|
|
10
|
+
module Generators
|
|
11
|
+
class UpdateGenerator < Rails::Generators::Base
|
|
12
|
+
argument :components, type: :array, default: [],
|
|
13
|
+
banner: "[component_name ...]"
|
|
14
|
+
|
|
15
|
+
class_option :force, type: :boolean, default: false,
|
|
16
|
+
desc: "Overwrite locally-edited files without prompting."
|
|
17
|
+
class_option :dry_run, type: :boolean, default: false,
|
|
18
|
+
desc: "Report planned changes, write nothing."
|
|
19
|
+
|
|
20
|
+
desc "Update installed Wabi components to the latest registry versions."
|
|
21
|
+
|
|
22
|
+
class UpdateAborted < StandardError; end
|
|
23
|
+
|
|
24
|
+
def update_components
|
|
25
|
+
names = target_names
|
|
26
|
+
if names.empty?
|
|
27
|
+
say " no components to update", :yellow
|
|
28
|
+
return
|
|
29
|
+
end
|
|
30
|
+
names.each { |name| update_component(name) }
|
|
31
|
+
lockfile.save unless options[:dry_run]
|
|
32
|
+
rescue UpdateAborted => e
|
|
33
|
+
say " aborted: #{e.message}", :red
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def target_names
|
|
39
|
+
components.any? ? components : lockfile.components.keys
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def lockfile
|
|
43
|
+
@lockfile ||= Wabi::Lockfile.load(File.join(destination_root, "config/wabi.lock.json"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def client
|
|
47
|
+
@client ||= Wabi::RegistryClient.new(base_url: lockfile.registry)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update_component(name)
|
|
51
|
+
unless lockfile.components.key?(name)
|
|
52
|
+
say " not installed: #{name} (use `wabi:add #{name}`)", :yellow
|
|
53
|
+
return
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
installed_version = lockfile.components[name]["version"]
|
|
57
|
+
data = client.fetch(name)
|
|
58
|
+
remote_version = data["version"]
|
|
59
|
+
|
|
60
|
+
if Gem::Version.new(remote_version) <= Gem::Version.new(installed_version)
|
|
61
|
+
say " up-to-date #{name} (#{installed_version})", :cyan
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
say " updating #{name} (#{installed_version} -> #{remote_version})", :green
|
|
66
|
+
|
|
67
|
+
files_map = {}
|
|
68
|
+
installed_map = lockfile.components[name]["files"] || {}
|
|
69
|
+
|
|
70
|
+
data["files"].each do |file|
|
|
71
|
+
path = file["path"]
|
|
72
|
+
new_hash = Digest::SHA256.hexdigest(file["content"])
|
|
73
|
+
target = File.join(destination_root, path)
|
|
74
|
+
|
|
75
|
+
files_map[path] = new_hash
|
|
76
|
+
|
|
77
|
+
if !File.exist?(target)
|
|
78
|
+
write_file(target, file["content"], reason: "create")
|
|
79
|
+
next
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
on_disk_hash = Digest::SHA256.hexdigest(File.read(target))
|
|
83
|
+
installed_hash = installed_map[path]
|
|
84
|
+
|
|
85
|
+
if installed_hash.nil?
|
|
86
|
+
handle_conflict(path, target, file["content"], reason: "legacy lockfile has no per-file hash")
|
|
87
|
+
next
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if on_disk_hash == installed_hash
|
|
91
|
+
write_file(target, file["content"], reason: "update")
|
|
92
|
+
else
|
|
93
|
+
handle_conflict(path, target, file["content"], reason: "edited locally")
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
print_js_deps_diff(name, data["js_dependencies"])
|
|
98
|
+
|
|
99
|
+
lockfile.record(
|
|
100
|
+
name,
|
|
101
|
+
version: remote_version,
|
|
102
|
+
hash: Digest::SHA256.hexdigest(JSON.generate(data["files"])),
|
|
103
|
+
files: files_map,
|
|
104
|
+
js_dependencies: data["js_dependencies"],
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def write_file(target, content, reason:)
|
|
109
|
+
if options[:dry_run]
|
|
110
|
+
say " would write #{target} (#{reason})", :cyan
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
114
|
+
File.write(target, content)
|
|
115
|
+
say " #{reason.ljust(10)} #{target}", :green
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def handle_conflict(path, target, new_content, reason:)
|
|
119
|
+
if options[:force]
|
|
120
|
+
write_file(target, new_content, reason: "force-overwrite")
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
if options[:dry_run]
|
|
124
|
+
say " CONFLICT #{path} (#{reason}) - would prompt", :yellow
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
loop do
|
|
129
|
+
say ""
|
|
130
|
+
say " conflict #{path}", :yellow
|
|
131
|
+
say " #{reason}. Overwrite with new version?", :yellow
|
|
132
|
+
answer = prompt_conflict(path)
|
|
133
|
+
case answer
|
|
134
|
+
when "y"
|
|
135
|
+
write_file(target, new_content, reason: "overwrite")
|
|
136
|
+
return
|
|
137
|
+
when "n"
|
|
138
|
+
say " skipped #{path}", :cyan
|
|
139
|
+
return
|
|
140
|
+
when "d"
|
|
141
|
+
print_diff(target, new_content)
|
|
142
|
+
next
|
|
143
|
+
when "q"
|
|
144
|
+
raise UpdateAborted, "user aborted update"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def prompt_conflict(_path)
|
|
150
|
+
ask(" (y)es / (n)o / (d)iff / (q)uit?", limited_to: %w[y n d q])
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def print_diff(target, new_content)
|
|
154
|
+
on_disk = File.exist?(target) ? File.read(target) : ""
|
|
155
|
+
on_disk_lines = on_disk.split("\n", -1)
|
|
156
|
+
new_lines = new_content.split("\n", -1)
|
|
157
|
+
say ""
|
|
158
|
+
say " --- on-disk", :red
|
|
159
|
+
on_disk_lines.each { |l| say " - #{l}", :red }
|
|
160
|
+
say " +++ new", :green
|
|
161
|
+
new_lines.each { |l| say " + #{l}", :green }
|
|
162
|
+
say ""
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def print_js_deps_diff(name, new_deps)
|
|
166
|
+
new_deps ||= {}
|
|
167
|
+
old_deps = lockfile.components.dig(name, "js_dependencies") || {}
|
|
168
|
+
|
|
169
|
+
added = new_deps.reject { |pkg, _| old_deps.key?(pkg) }
|
|
170
|
+
changed = new_deps.select { |pkg, ver| old_deps.key?(pkg) && old_deps[pkg] != ver }
|
|
171
|
+
|
|
172
|
+
return if added.empty? && changed.empty?
|
|
173
|
+
|
|
174
|
+
say ""
|
|
175
|
+
say " JS pin changes for #{name}:", :yellow
|
|
176
|
+
(added.merge(changed)).each do |pkg, version|
|
|
177
|
+
v = version.to_s.sub(/\A[~^]/, "")
|
|
178
|
+
v = "1.0.0" if v.empty?
|
|
179
|
+
say %( pin "#{pkg}", to: "https://cdn.jsdelivr.net/npm/#{pkg}@#{v}/+esm")
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
data/lib/wabi/lockfile.rb
CHANGED
|
@@ -27,8 +27,11 @@ module Wabi
|
|
|
27
27
|
@components = data["components"] || {}
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def record(name, version:, hash:)
|
|
31
|
-
|
|
30
|
+
def record(name, version:, hash:, files: nil, js_dependencies: nil)
|
|
31
|
+
entry = { "version" => version, "hash" => hash }
|
|
32
|
+
entry["files"] = files if files
|
|
33
|
+
entry["js_dependencies"] = js_dependencies if js_dependencies
|
|
34
|
+
@components[name] = entry
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
def save
|
data/lib/wabi/version.rb
CHANGED
data/lib/wabi.rb
CHANGED
|
@@ -16,6 +16,7 @@ end
|
|
|
16
16
|
if defined?(Rails::Generators)
|
|
17
17
|
require_relative "wabi/generators/install_generator"
|
|
18
18
|
require_relative "wabi/generators/add_generator"
|
|
19
|
+
require_relative "wabi/generators/update_generator"
|
|
19
20
|
require_relative "wabi/generators/list_generator"
|
|
20
21
|
require_relative "wabi/generators/registry_generator"
|
|
21
22
|
require_relative "wabi/generators/theme_generator"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wabi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oscar Ortega
|
|
@@ -126,6 +126,7 @@ files:
|
|
|
126
126
|
- lib/wabi/generators/list_generator.rb
|
|
127
127
|
- lib/wabi/generators/registry_generator.rb
|
|
128
128
|
- lib/wabi/generators/theme_generator.rb
|
|
129
|
+
- lib/wabi/generators/update_generator.rb
|
|
129
130
|
- lib/wabi/lockfile.rb
|
|
130
131
|
- lib/wabi/registry_client.rb
|
|
131
132
|
- lib/wabi/turbo_stream_extensions.rb
|