utopia 3.0.2 → 3.0.4
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
- checksums.yaml.gz.sig +0 -0
- data/bake/utopia/components.rb +44 -0
- data/context/integrating-with-javascript.md +1 -1
- data/lib/utopia/components.rb +179 -0
- data/lib/utopia/import_map.rb +6 -10
- data/lib/utopia/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
- data/bake/utopia/node.rb +0 -87
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3e9f5a6eb41a075cebcde2daf0e8811e1af414776f7807a14e07e9ed228c7657
|
|
4
|
+
data.tar.gz: 1d566c0e5b68123699f6f33185857ce00e6eb3b1659eae5c0f0b896443a36527
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc90dd50e914c27b28463992bbe33b0ef52eb077bd666519b876575202cee39babfed39720e2c22b66f3c456460bdcd16446876bb2827714b9665ed72c655ce0
|
|
7
|
+
data.tar.gz: 18b31ba92269e3340cc385e2a8b953bbe7b75b0db1f7323dd4e00d55faa03ebf71a097a940de69c84183a5115d2e982ba0a8aa2f480ca495a8d2119a93d2aa4f
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
NPM = ENV["NPM"] || "npm"
|
|
7
|
+
|
|
8
|
+
# Update public components from production JavaScript packages.
|
|
9
|
+
#
|
|
10
|
+
# Packages are copied from their `dist` directory when present, or otherwise
|
|
11
|
+
# from the package root. The `utopia.components` section of `package.json` can
|
|
12
|
+
# specify per-package `include` patterns to select only required files.
|
|
13
|
+
#
|
|
14
|
+
# @parameter root [String] The project root directory.
|
|
15
|
+
def update(root: context.root)
|
|
16
|
+
require "json"
|
|
17
|
+
require "open3"
|
|
18
|
+
require "utopia/components"
|
|
19
|
+
|
|
20
|
+
components = Utopia::Components.new(root)
|
|
21
|
+
production_packages = fetch_production_packages(components.package_root)
|
|
22
|
+
|
|
23
|
+
components.update(production_packages)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def fetch_production_packages(package_root)
|
|
29
|
+
stdout, _status = Open3.capture2(NPM, "ls", "--production", "--json", chdir: package_root.to_s)
|
|
30
|
+
json = JSON.parse(stdout)
|
|
31
|
+
|
|
32
|
+
flatten_package_dependencies(json).sort.uniq
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def flatten_package_dependencies(json, into = [])
|
|
36
|
+
if json["dependencies"]
|
|
37
|
+
json["dependencies"].each do |name, details|
|
|
38
|
+
into << name
|
|
39
|
+
flatten_package_dependencies(details, into)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return into
|
|
44
|
+
end
|
|
@@ -17,7 +17,7 @@ $ npm install jquery
|
|
|
17
17
|
Copy the distribution files to `public/_components`:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
$ bundle exec bake utopia:
|
|
20
|
+
$ bundle exec bake utopia:components:update
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
This will copy the library's distribution files (typically from `node_modules/*/dist/`) to your `public/_components/` directory, making them available for local serving.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require "json"
|
|
8
|
+
require "pathname"
|
|
9
|
+
|
|
10
|
+
module Utopia
|
|
11
|
+
# Installs JavaScript packages into the public components directory. Package contents are copied from `dist` when it exists, otherwise from the package root.
|
|
12
|
+
#
|
|
13
|
+
# By default, the complete source directory is installed. Projects can limit an individual package to a set of files using `utopia.components` in their `package.json` file.
|
|
14
|
+
class Components
|
|
15
|
+
# Initialize a component installer for the given project root.
|
|
16
|
+
#
|
|
17
|
+
# @parameter root [String | Pathname] The project root directory.
|
|
18
|
+
def initialize(root)
|
|
19
|
+
@root = Pathname.new(root)
|
|
20
|
+
@package_root = @root + "node_modules"
|
|
21
|
+
|
|
22
|
+
# This is a legacy path:
|
|
23
|
+
unless @package_root.directory?
|
|
24
|
+
@package_root = @root + "lib/components"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@install_root = @root + "public/_components"
|
|
28
|
+
@configuration = load_configuration
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @attribute [Pathname] The directory containing the installed JavaScript packages.
|
|
32
|
+
attr :package_root
|
|
33
|
+
|
|
34
|
+
# Update the specified packages in the public components directory.
|
|
35
|
+
#
|
|
36
|
+
# @parameter package_names [Array(String)] The production package names to install.
|
|
37
|
+
def update(package_names)
|
|
38
|
+
expand_package_paths(@package_root).each do |package_path|
|
|
39
|
+
package_name = package_path.relative_path_from(@package_root).to_s
|
|
40
|
+
|
|
41
|
+
if package_names.include?(package_name)
|
|
42
|
+
install(package_name, package_path)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Load the optional per-package installation rules. A missing `package.json`, or a file without `utopia.components`, preserves the default behaviour of copying complete packages.
|
|
50
|
+
# @returns [Hash] The per-package installation rules.
|
|
51
|
+
def load_configuration
|
|
52
|
+
package_path = @root + "package.json"
|
|
53
|
+
|
|
54
|
+
unless package_path.file?
|
|
55
|
+
return {}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
configuration = JSON.parse(package_path.read).dig("utopia", "components") || {}
|
|
59
|
+
|
|
60
|
+
unless configuration.is_a?(Hash)
|
|
61
|
+
raise ArgumentError, "utopia.components must be an object!"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
return configuration
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Install one package. Distribution directories are preferred because they generally contain the browser-ready form of a package.
|
|
68
|
+
# @parameter package_name [String] The package name relative to `node_modules`.
|
|
69
|
+
# @parameter package_path [Pathname] The package source directory.
|
|
70
|
+
def install(package_name, package_path)
|
|
71
|
+
install_path = @install_root + package_name
|
|
72
|
+
dist_path = package_path + "dist"
|
|
73
|
+
|
|
74
|
+
if dist_path.directory?
|
|
75
|
+
source_path = dist_path
|
|
76
|
+
else
|
|
77
|
+
source_path = package_path
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
configuration = @configuration[package_name]
|
|
81
|
+
|
|
82
|
+
if configuration
|
|
83
|
+
install_selected(package_name, source_path, install_path, configuration)
|
|
84
|
+
else
|
|
85
|
+
FileUtils::Verbose.rm_rf(install_path)
|
|
86
|
+
FileUtils::Verbose.mkpath(install_path.dirname)
|
|
87
|
+
FileUtils::Verbose.cp_r(source_path, install_path)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Install only the files matched by the configured include patterns. Every pattern is resolved before removing the existing installation, so an invalid configuration cannot leave a package partially installed or remove a previously working copy.
|
|
92
|
+
# @parameter package_name [String] The package name relative to `node_modules`.
|
|
93
|
+
# @parameter source_path [Pathname] The package source directory.
|
|
94
|
+
# @parameter install_path [Pathname] The destination directory.
|
|
95
|
+
# @parameter configuration [Hash] The package installation rules.
|
|
96
|
+
def install_selected(package_name, source_path, install_path, configuration)
|
|
97
|
+
unless configuration.is_a?(Hash)
|
|
98
|
+
raise ArgumentError, "utopia.components.#{package_name}.include must be a non-empty array!"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
include_patterns = configuration["include"]
|
|
102
|
+
|
|
103
|
+
unless include_patterns.is_a?(Array) && include_patterns.any?
|
|
104
|
+
raise ArgumentError, "utopia.components.#{package_name}.include must be a non-empty array!"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
paths = include_patterns.flat_map do |pattern|
|
|
108
|
+
included_paths(package_name, source_path, pattern)
|
|
109
|
+
end.uniq.sort
|
|
110
|
+
|
|
111
|
+
FileUtils::Verbose.rm_rf(install_path)
|
|
112
|
+
|
|
113
|
+
paths.each do |relative_path|
|
|
114
|
+
source_file = source_path + relative_path
|
|
115
|
+
install_file = install_path + relative_path
|
|
116
|
+
|
|
117
|
+
FileUtils::Verbose.mkpath(install_file.dirname)
|
|
118
|
+
FileUtils::Verbose.cp(source_file, install_file)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Expand one include pattern into files relative to the package source. Directories are excluded so each result can be copied independently.
|
|
123
|
+
# @parameter package_name [String] The package name used in validation errors.
|
|
124
|
+
# @parameter source_path [Pathname] The package source directory.
|
|
125
|
+
# @parameter pattern [String] The include pattern to expand.
|
|
126
|
+
# @returns [Array(String)] The matching file paths relative to the package source.
|
|
127
|
+
def included_paths(package_name, source_path, pattern)
|
|
128
|
+
unless pattern.is_a?(String) && relative_pattern?(pattern)
|
|
129
|
+
raise ArgumentError, "Invalid include pattern for #{package_name}: #{pattern.inspect}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
paths = Dir.glob(pattern, base: source_path.to_s).select do |relative_path|
|
|
133
|
+
(source_path + relative_path).file?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
if paths.empty?
|
|
137
|
+
raise ArgumentError, "Include pattern for #{package_name} matched no files: #{pattern.inspect}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
return paths
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Determine whether the pattern is contained within the package source. Absolute paths and parent traversal are rejected because they could otherwise copy arbitrary files from outside the package.
|
|
144
|
+
# @parameter pattern [String] The include pattern to validate.
|
|
145
|
+
# @returns [Boolean] Whether the pattern is relative and does not contain parent traversal.
|
|
146
|
+
def relative_pattern?(pattern)
|
|
147
|
+
path = Pathname.new(pattern)
|
|
148
|
+
|
|
149
|
+
if path.absolute?
|
|
150
|
+
return false
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
if path.each_filename.any?{|component| component == ".."}
|
|
154
|
+
return false
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
return true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Enumerate packages in `node_modules`, descending through scoped package directories such as `@socketry` while preserving their scoped names.
|
|
161
|
+
# @parameter root [Pathname] The directory to enumerate.
|
|
162
|
+
# @parameter into [Array(Pathname)] The array into which package paths are appended.
|
|
163
|
+
# @returns [Array(Pathname)] The discovered package directories.
|
|
164
|
+
def expand_package_paths(root, into = [])
|
|
165
|
+
root.children.select(&:directory?).each do |path|
|
|
166
|
+
basename = path.basename.to_s
|
|
167
|
+
|
|
168
|
+
# Handle organisation sub-directories which start with an '@' symbol:
|
|
169
|
+
if basename.start_with?("@")
|
|
170
|
+
expand_package_paths(path, into)
|
|
171
|
+
else
|
|
172
|
+
into << path
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
return into
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
data/lib/utopia/import_map.rb
CHANGED
|
@@ -268,13 +268,7 @@ module Utopia
|
|
|
268
268
|
# page_map = import_map.relative_to("/foo/bar/")
|
|
269
269
|
# # Base becomes: "../../_components/"
|
|
270
270
|
def relative_to(path)
|
|
271
|
-
|
|
272
|
-
# Calculate the relative path from the page to the base
|
|
273
|
-
relative_path = @base.path.relative(path)
|
|
274
|
-
resolved_base = Protocol::URL::Relative.new(relative_path)
|
|
275
|
-
else
|
|
276
|
-
resolved_base = nil
|
|
277
|
-
end
|
|
271
|
+
resolved_base = @base&.relative_to(path)
|
|
278
272
|
|
|
279
273
|
instance = self.class.new(@imports.dup, @integrity.dup, @scopes.dup, base: resolved_base)
|
|
280
274
|
|
|
@@ -285,10 +279,12 @@ module Utopia
|
|
|
285
279
|
#
|
|
286
280
|
# @parameter value [String] The import URL or path value.
|
|
287
281
|
# @parameter base [Protocol::URL | Nil] The base URL context for resolving relative paths.
|
|
288
|
-
# @returns [Protocol::URL
|
|
282
|
+
# @returns [Protocol::URL] The resolved URL.
|
|
289
283
|
private def resolve_value(value, base)
|
|
284
|
+
value = Protocol::URL[value]
|
|
285
|
+
|
|
290
286
|
if base
|
|
291
|
-
base +
|
|
287
|
+
base + value
|
|
292
288
|
else
|
|
293
289
|
value
|
|
294
290
|
end
|
|
@@ -303,7 +299,7 @@ module Utopia
|
|
|
303
299
|
result = {}
|
|
304
300
|
|
|
305
301
|
imports.each do |specifier, value|
|
|
306
|
-
result[specifier] = resolve_value(value, base).to_s
|
|
302
|
+
result[specifier] = resolve_value(value, base).to_s(explicit: true)
|
|
307
303
|
end
|
|
308
304
|
|
|
309
305
|
result
|
data/lib/utopia/version.rb
CHANGED
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: utopia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -203,14 +203,14 @@ dependencies:
|
|
|
203
203
|
requirements:
|
|
204
204
|
- - "~>"
|
|
205
205
|
- !ruby/object:Gem::Version
|
|
206
|
-
version: '0.
|
|
206
|
+
version: '0.18'
|
|
207
207
|
type: :runtime
|
|
208
208
|
prerelease: false
|
|
209
209
|
version_requirements: !ruby/object:Gem::Requirement
|
|
210
210
|
requirements:
|
|
211
211
|
- - "~>"
|
|
212
212
|
- !ruby/object:Gem::Version
|
|
213
|
-
version: '0.
|
|
213
|
+
version: '0.18'
|
|
214
214
|
- !ruby/object:Gem::Dependency
|
|
215
215
|
name: samovar
|
|
216
216
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -272,8 +272,8 @@ extensions: []
|
|
|
272
272
|
extra_rdoc_files: []
|
|
273
273
|
files:
|
|
274
274
|
- bake/utopia.rb
|
|
275
|
+
- bake/utopia/components.rb
|
|
275
276
|
- bake/utopia/environment.rb
|
|
276
|
-
- bake/utopia/node.rb
|
|
277
277
|
- bake/utopia/server.rb
|
|
278
278
|
- bake/utopia/shell.rb
|
|
279
279
|
- bake/utopia/site.rb
|
|
@@ -287,6 +287,7 @@ files:
|
|
|
287
287
|
- context/what-is-xnode.md
|
|
288
288
|
- lib/utopia.rb
|
|
289
289
|
- lib/utopia/application.rb
|
|
290
|
+
- lib/utopia/components.rb
|
|
290
291
|
- lib/utopia/content.rb
|
|
291
292
|
- lib/utopia/content/builder.rb
|
|
292
293
|
- lib/utopia/content/document.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|
data/bake/utopia/node.rb
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Released under the MIT License.
|
|
4
|
-
# Copyright, 2016-2025, by Samuel Williams.
|
|
5
|
-
|
|
6
|
-
NPM = ENV["NPM"] || "npm"
|
|
7
|
-
|
|
8
|
-
def update
|
|
9
|
-
require "fileutils"
|
|
10
|
-
require "utopia/path"
|
|
11
|
-
|
|
12
|
-
root = Pathname.new(context.root)
|
|
13
|
-
package_root = root + "node_modules"
|
|
14
|
-
|
|
15
|
-
# This is a legacy path:
|
|
16
|
-
unless package_root.directory?
|
|
17
|
-
package_root = root + "lib/components"
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
install_root = root + "public/_components"
|
|
21
|
-
|
|
22
|
-
# Fetch only production dependencies using `npm ls --production`
|
|
23
|
-
production_packages = fetch_production_packages(package_root)
|
|
24
|
-
package_paths = expand_package_paths(package_root).select do |path|
|
|
25
|
-
package_name = path.relative_path_from(package_root).to_s
|
|
26
|
-
production_packages.include?(package_name)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
package_paths.each do |package_path|
|
|
30
|
-
package_directory = package_path.relative_path_from(package_root)
|
|
31
|
-
install_path = install_root + package_directory
|
|
32
|
-
|
|
33
|
-
dist_path = package_path + "dist"
|
|
34
|
-
|
|
35
|
-
FileUtils::Verbose.rm_rf(install_path)
|
|
36
|
-
FileUtils::Verbose.mkpath(install_path.dirname)
|
|
37
|
-
|
|
38
|
-
# If a package has a dist directory, we only symlink that... otherwise we have to do the entire package, and hope that bower's ignore was setup correctly:
|
|
39
|
-
if dist_path.exist?
|
|
40
|
-
link_path = Utopia::Path.shortest_path(dist_path, install_path)
|
|
41
|
-
else
|
|
42
|
-
link_path = Utopia::Path.shortest_path(package_path, install_path)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
FileUtils::Verbose.cp_r File.expand_path(link_path, install_path), install_path
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
51
|
-
def fetch_production_packages(package_root)
|
|
52
|
-
require "json"
|
|
53
|
-
require "open3"
|
|
54
|
-
|
|
55
|
-
stdout, _status = Open3.capture2(NPM, "ls", "--production", "--json", chdir: package_root.to_s)
|
|
56
|
-
|
|
57
|
-
json = JSON.parse(stdout)
|
|
58
|
-
|
|
59
|
-
flatten_package_dependencies(json).sort.uniq
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def flatten_package_dependencies(json, into = [])
|
|
63
|
-
if json["dependencies"]
|
|
64
|
-
json["dependencies"].each do |name, details|
|
|
65
|
-
into << name
|
|
66
|
-
flatten_package_dependencies(details, into)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
return into
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def expand_package_paths(root, into = [])
|
|
74
|
-
paths = root.children.select(&:directory?)
|
|
75
|
-
|
|
76
|
-
paths.each do |path|
|
|
77
|
-
basename = path.basename.to_s
|
|
78
|
-
# Handle organisation sub-directories which start with an '@' symbol:
|
|
79
|
-
if basename.start_with?("@")
|
|
80
|
-
expand_package_paths(path, into)
|
|
81
|
-
else
|
|
82
|
-
into << path
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
return into
|
|
87
|
-
end
|