importmap-rails 2.2.1 → 2.2.2
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 +9 -5
- data/lib/importmap/commands.rb +23 -10
- data/lib/importmap/map.rb +46 -0
- data/lib/importmap/npm.rb +4 -4
- data/lib/importmap/packager.rb +50 -4
- data/lib/importmap/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 91837e96107f59efa73a314dec3955a488f9f40826c27e116da43c5d9ef0a44a
|
4
|
+
data.tar.gz: ce9d44539cd6bb86b1e027c9c714dc8274b4a7b918ee0a67493b79b9eb58103f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95a4d846c8a808b8037e2425ad307e1cecd736051053e86f5c9739f9118375d81e0e967f64be3f6cee916d0ce7ca763fd7b236684f9b0889679b62f5718643a0
|
7
|
+
data.tar.gz: 5e20ed5b2d79945a603db1cf6e0e5d694a8a7ad7b96f5bb13e6e1083225034fcbf7ba2019acf397b004345bd4d54827895b9e4ffd5c831eb1fdfc2c051dcb063
|
data/README.md
CHANGED
@@ -81,12 +81,13 @@ If you want to import local js module files from `app/javascript/src` or other s
|
|
81
81
|
pin_all_from 'app/javascript/src', under: 'src', to: 'src'
|
82
82
|
|
83
83
|
# With automatic integrity calculation for enhanced security
|
84
|
+
enable_integrity!
|
84
85
|
pin_all_from 'app/javascript/controllers', under: 'controllers', integrity: true
|
85
86
|
```
|
86
87
|
|
87
88
|
The `:to` parameter is only required if you want to change the destination logical import name. If you drop the :to option, you must place the :under option directly after the first parameter.
|
88
89
|
|
89
|
-
The `integrity: true`
|
90
|
+
The `enable_integrity!` call enables integrity calculation globally, and `integrity: true` automatically calculates integrity hashes for all files in the directory, providing security benefits without manual hash management.
|
90
91
|
|
91
92
|
Allows you to:
|
92
93
|
|
@@ -142,12 +143,15 @@ For enhanced security, importmap-rails supports [Subresource Integrity (SRI)](ht
|
|
142
143
|
|
143
144
|
### Automatic integrity for local assets
|
144
145
|
|
145
|
-
|
146
|
+
To enable automatic integrity calculation for local assets served by the Rails asset pipeline, you must first call `enable_integrity!` in your importmap configuration:
|
146
147
|
|
147
148
|
```ruby
|
148
149
|
# config/importmap.rb
|
149
150
|
|
150
|
-
#
|
151
|
+
# Enable integrity calculation globally
|
152
|
+
enable_integrity!
|
153
|
+
|
154
|
+
# With integrity enabled, these will auto-calculate integrity hashes
|
151
155
|
pin "application" # Auto-calculated integrity
|
152
156
|
pin "admin", to: "admin.js" # Auto-calculated integrity
|
153
157
|
pin_all_from "app/javascript/controllers", under: "controllers" # Auto-calculated integrity
|
@@ -163,7 +167,7 @@ This is particularly useful for:
|
|
163
167
|
* **Bulk operations** with `pin_all_from` where calculating hashes manually would be tedious
|
164
168
|
* **Development workflow** where asset contents change frequently
|
165
169
|
|
166
|
-
This behavior can be
|
170
|
+
**Note:** Integrity calculation is opt-in and must be enabled with `enable_integrity!`. This behavior can be further controlled by setting `integrity: false` or `integrity: nil` on individual pins.
|
167
171
|
|
168
172
|
**Important for Propshaft users:** SRI support requires Propshaft 1.2+ and you must configure the integrity hash algorithm in your application:
|
169
173
|
|
@@ -174,7 +178,7 @@ config.assets.integrity_hash_algorithm = 'sha256' # or 'sha384', 'sha512'
|
|
174
178
|
|
175
179
|
Without this configuration, integrity will be disabled by default when using Propshaft. Sprockets includes integrity support out of the box.
|
176
180
|
|
177
|
-
**Example output with `integrity: true`:**
|
181
|
+
**Example output with `enable_integrity!` and `integrity: true`:**
|
178
182
|
```json
|
179
183
|
{
|
180
184
|
"imports": {
|
data/lib/importmap/commands.rb
CHANGED
@@ -15,13 +15,7 @@ class Importmap::Commands < Thor
|
|
15
15
|
option :preload, type: :string, repeatable: true, desc: "Can be used multiple times"
|
16
16
|
def pin(*packages)
|
17
17
|
for_each_import(packages, env: options[:env], from: options[:from]) do |package, url|
|
18
|
-
|
19
|
-
|
20
|
-
packager.download(package, url)
|
21
|
-
|
22
|
-
pin = packager.vendored_pin_for(package, url, options[:preload])
|
23
|
-
|
24
|
-
update_importmap_with_pin(package, pin)
|
18
|
+
pin_package(package, url, options[:preload])
|
25
19
|
end
|
26
20
|
end
|
27
21
|
|
@@ -96,7 +90,14 @@ class Importmap::Commands < Thor
|
|
96
90
|
desc "update", "Update outdated package pins"
|
97
91
|
def update
|
98
92
|
if (outdated_packages = npm.outdated_packages).any?
|
99
|
-
|
93
|
+
package_names = outdated_packages.map(&:name)
|
94
|
+
packages_with_options = packager.extract_existing_pin_options(package_names)
|
95
|
+
|
96
|
+
for_each_import(package_names, env: "production", from: "jspm") do |package, url|
|
97
|
+
options = packages_with_options[package] || {}
|
98
|
+
|
99
|
+
pin_package(package, url, options[:preload])
|
100
|
+
end
|
100
101
|
else
|
101
102
|
puts "No outdated packages found"
|
102
103
|
end
|
@@ -116,11 +117,23 @@ class Importmap::Commands < Thor
|
|
116
117
|
@npm ||= Importmap::Npm.new
|
117
118
|
end
|
118
119
|
|
120
|
+
def pin_package(package, url, preload)
|
121
|
+
puts %(Pinning "#{package}" to #{packager.vendor_path}/#{package}.js via download from #{url})
|
122
|
+
|
123
|
+
packager.download(package, url)
|
124
|
+
|
125
|
+
pin = packager.vendored_pin_for(package, url, preload)
|
126
|
+
|
127
|
+
update_importmap_with_pin(package, pin)
|
128
|
+
end
|
129
|
+
|
119
130
|
def update_importmap_with_pin(package, pin)
|
131
|
+
new_pin = "#{pin}\n"
|
132
|
+
|
120
133
|
if packager.packaged?(package)
|
121
|
-
gsub_file("config/importmap.rb",
|
134
|
+
gsub_file("config/importmap.rb", Importmap::Map.pin_line_regexp_for(package), pin, verbose: false)
|
122
135
|
else
|
123
|
-
append_to_file("config/importmap.rb",
|
136
|
+
append_to_file("config/importmap.rb", new_pin, verbose: false)
|
124
137
|
end
|
125
138
|
end
|
126
139
|
|
data/lib/importmap/map.rb
CHANGED
@@ -3,9 +3,16 @@ require "pathname"
|
|
3
3
|
class Importmap::Map
|
4
4
|
attr_reader :packages, :directories
|
5
5
|
|
6
|
+
PIN_REGEX = /^pin\s+["']([^"']+)["']/.freeze # :nodoc:
|
7
|
+
|
8
|
+
def self.pin_line_regexp_for(package) # :nodoc:
|
9
|
+
/^.*pin\s+["']#{Regexp.escape(package)}["'].*$/.freeze
|
10
|
+
end
|
11
|
+
|
6
12
|
class InvalidFile < StandardError; end
|
7
13
|
|
8
14
|
def initialize
|
15
|
+
@integrity = false
|
9
16
|
@packages, @directories = {}, {}
|
10
17
|
@cache = {}
|
11
18
|
end
|
@@ -25,6 +32,43 @@ class Importmap::Map
|
|
25
32
|
self
|
26
33
|
end
|
27
34
|
|
35
|
+
# Enables automatic integrity hash calculation for all pinned modules.
|
36
|
+
#
|
37
|
+
# When enabled, integrity values are included in the importmap JSON for all
|
38
|
+
# pinned modules. For local assets served by the Rails asset pipeline,
|
39
|
+
# integrity hashes are automatically calculated when +integrity: true+ is
|
40
|
+
# specified. For modules with explicit integrity values, those values are
|
41
|
+
# included as provided. This provides Subresource Integrity (SRI) protection
|
42
|
+
# to ensure JavaScript modules haven't been tampered with.
|
43
|
+
#
|
44
|
+
# Clears the importmap cache when called to ensure fresh integrity hashes
|
45
|
+
# are generated.
|
46
|
+
#
|
47
|
+
# ==== Examples
|
48
|
+
#
|
49
|
+
# # config/importmap.rb
|
50
|
+
# enable_integrity!
|
51
|
+
#
|
52
|
+
# # These will now auto-calculate integrity hashes
|
53
|
+
# pin "application" # integrity: true by default
|
54
|
+
# pin "admin", to: "admin.js" # integrity: true by default
|
55
|
+
# pin_all_from "app/javascript/lib" # integrity: true by default
|
56
|
+
#
|
57
|
+
# # Manual control still works
|
58
|
+
# pin "no_integrity", integrity: false
|
59
|
+
# pin "custom_hash", integrity: "sha384-abc123..."
|
60
|
+
#
|
61
|
+
# ==== Notes
|
62
|
+
#
|
63
|
+
# * Integrity calculation is disabled by default and must be explicitly enabled
|
64
|
+
# * Requires asset pipeline support for integrity calculation (Sprockets or Propshaft 1.2+)
|
65
|
+
# * For Propshaft, you must configure +config.assets.integrity_hash_algorithm+
|
66
|
+
# * External CDN packages should provide their own integrity hashes
|
67
|
+
def enable_integrity!
|
68
|
+
clear_cache
|
69
|
+
@integrity = true
|
70
|
+
end
|
71
|
+
|
28
72
|
def pin(name, to: nil, preload: true, integrity: true)
|
29
73
|
clear_cache
|
30
74
|
@packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload, integrity: integrity)
|
@@ -210,6 +254,8 @@ class Importmap::Map
|
|
210
254
|
end
|
211
255
|
|
212
256
|
def resolve_integrity_value(integrity, path, resolver:)
|
257
|
+
return unless @integrity
|
258
|
+
|
213
259
|
case integrity
|
214
260
|
when true
|
215
261
|
resolver.asset_integrity(path) if resolver.respond_to?(:asset_integrity)
|
data/lib/importmap/npm.rb
CHANGED
@@ -3,7 +3,7 @@ require "uri"
|
|
3
3
|
require "json"
|
4
4
|
|
5
5
|
class Importmap::Npm
|
6
|
-
PIN_REGEX =
|
6
|
+
PIN_REGEX = /#{Importmap::Map::PIN_REGEX}.*/.freeze # :nodoc:
|
7
7
|
|
8
8
|
Error = Class.new(StandardError)
|
9
9
|
HTTPError = Class.new(Error)
|
@@ -17,7 +17,7 @@ class Importmap::Npm
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def outdated_packages
|
20
|
-
packages_with_versions.
|
20
|
+
packages_with_versions.each_with_object([]) do |(package, current_version), outdated_packages|
|
21
21
|
outdated_package = OutdatedPackage.new(name: package, current_version: current_version)
|
22
22
|
|
23
23
|
if !(response = get_package(package))
|
@@ -51,7 +51,7 @@ class Importmap::Npm
|
|
51
51
|
def packages_with_versions
|
52
52
|
# We cannot use the name after "pin" because some dependencies are loaded from inside packages
|
53
53
|
# Eg. pin "buffer", to: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.19/nodelibs/browser/buffer.js"
|
54
|
-
with_versions = importmap.scan(/^pin .*(?<=npm:|npm\/|skypack\.dev\/|unpkg\.com\/)(
|
54
|
+
with_versions = importmap.scan(/^pin .*(?<=npm:|npm\/|skypack\.dev\/|unpkg\.com\/)([^@\/]+)@(\d+\.\d+\.\d+(?:[^\/\s"']*))/) |
|
55
55
|
importmap.scan(/#{PIN_REGEX} #.*@(\d+\.\d+\.\d+(?:[^\s]*)).*$/)
|
56
56
|
|
57
57
|
vendored_packages_without_version(with_versions).each do |package, path|
|
@@ -147,7 +147,7 @@ class Importmap::Npm
|
|
147
147
|
end
|
148
148
|
|
149
149
|
def find_unversioned_vendored_package(line, versioned_packages)
|
150
|
-
regexp = line.include?("to:")? /#{PIN_REGEX}to: ["']([^
|
150
|
+
regexp = line.include?("to:")? /#{PIN_REGEX}to: ["']([^"']*)["'].*/ : PIN_REGEX
|
151
151
|
match = line.match(regexp)
|
152
152
|
|
153
153
|
return unless match
|
data/lib/importmap/packager.rb
CHANGED
@@ -3,6 +3,9 @@ require "uri"
|
|
3
3
|
require "json"
|
4
4
|
|
5
5
|
class Importmap::Packager
|
6
|
+
PIN_REGEX = /#{Importmap::Map::PIN_REGEX}(.*)/.freeze # :nodoc:
|
7
|
+
PRELOAD_OPTION_REGEXP = /preload:\s*(\[[^\]]+\]|true|false|["'][^"']*["'])/.freeze # :nodoc:
|
8
|
+
|
6
9
|
Error = Class.new(StandardError)
|
7
10
|
HTTPError = Class.new(Error)
|
8
11
|
ServiceError = Error.new(Error)
|
@@ -51,7 +54,7 @@ class Importmap::Packager
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def packaged?(package)
|
54
|
-
importmap.match(
|
57
|
+
importmap.match(Importmap::Map.pin_line_regexp_for(package))
|
55
58
|
end
|
56
59
|
|
57
60
|
def download(package, url)
|
@@ -65,14 +68,57 @@ class Importmap::Packager
|
|
65
68
|
remove_package_from_importmap(package)
|
66
69
|
end
|
67
70
|
|
71
|
+
def extract_existing_pin_options(packages)
|
72
|
+
return {} unless @importmap_path.exist?
|
73
|
+
|
74
|
+
packages = Array(packages)
|
75
|
+
|
76
|
+
all_package_options = build_package_options_lookup(importmap.lines)
|
77
|
+
|
78
|
+
packages.to_h do |package|
|
79
|
+
[package, all_package_options[package] || {}]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
68
83
|
private
|
84
|
+
def build_package_options_lookup(lines)
|
85
|
+
lines.each_with_object({}) do |line, package_options|
|
86
|
+
match = line.strip.match(PIN_REGEX)
|
87
|
+
|
88
|
+
if match
|
89
|
+
package_name = match[1]
|
90
|
+
options_part = match[2]
|
91
|
+
|
92
|
+
preload_match = options_part.match(PRELOAD_OPTION_REGEXP)
|
93
|
+
|
94
|
+
if preload_match
|
95
|
+
preload = preload_from_string(preload_match[1])
|
96
|
+
package_options[package_name] = { preload: preload }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def preload_from_string(value)
|
103
|
+
case value
|
104
|
+
when "true"
|
105
|
+
true
|
106
|
+
when "false"
|
107
|
+
false
|
108
|
+
when /^\[.*\]$/
|
109
|
+
JSON.parse(value)
|
110
|
+
else
|
111
|
+
value.gsub(/["']/, "")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
69
115
|
def preload(preloads)
|
70
116
|
case Array(preloads)
|
71
117
|
in []
|
72
118
|
""
|
73
|
-
in ["true"]
|
119
|
+
in ["true"] | [true]
|
74
120
|
%(, preload: true)
|
75
|
-
in ["false"]
|
121
|
+
in ["false"] | [false]
|
76
122
|
%(, preload: false)
|
77
123
|
in [string]
|
78
124
|
%(, preload: "#{string}")
|
@@ -129,7 +175,7 @@ class Importmap::Packager
|
|
129
175
|
|
130
176
|
def remove_package_from_importmap(package)
|
131
177
|
all_lines = File.readlines(@importmap_path)
|
132
|
-
with_lines_removed = all_lines.grep_v(
|
178
|
+
with_lines_removed = all_lines.grep_v(Importmap::Map.pin_line_regexp_for(package))
|
133
179
|
|
134
180
|
File.open(@importmap_path, "w") do |file|
|
135
181
|
with_lines_removed.each { |line| file.write(line) }
|
data/lib/importmap/version.rb
CHANGED