importmap-rails 2.2.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '079361e5c1e2b84206422fb3f2a5de6f6969c07a29e73d40843986d1549410ce'
4
- data.tar.gz: 61e335f52eacd06ffb635e4400ac68701e58cb57a9edfd7d38cc53a1a173c7a9
3
+ metadata.gz: 91837e96107f59efa73a314dec3955a488f9f40826c27e116da43c5d9ef0a44a
4
+ data.tar.gz: ce9d44539cd6bb86b1e027c9c714dc8274b4a7b918ee0a67493b79b9eb58103f
5
5
  SHA512:
6
- metadata.gz: 1c92850ba94cc450f18b1554675737f94eff235e5b51d2922cbb1433a9cd6f3be065a839a72b93661feb7b1f346177b43fbf558a6b9f30445e232f7446e2071a
7
- data.tar.gz: 0be5aa11b95db77526ef6d933eeca0197346ba69ce5ef6a5148fb871eb7f986c050399c177cce0ccd8178dcb05ccd69a6d673b8efabadc382fea79284df7105c
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` option automatically calculates integrity hashes for all files in the directory, providing security benefits without manual hash management.
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
 
@@ -138,73 +139,27 @@ Unpinning and removing "react"
138
139
 
139
140
  ## Subresource Integrity (SRI)
140
141
 
141
- For enhanced security, importmap-rails automatically includes [Subresource Integrity (SRI)](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hashes by default when pinning packages. This ensures that JavaScript files loaded from CDNs haven't been tampered with.
142
-
143
- ### Default behavior with integrity
144
-
145
- When you pin a package, integrity hashes are automatically included:
146
-
147
- ```bash
148
- ./bin/importmap pin lodash
149
- Pinning "lodash" to vendor/javascript/lodash.js via download from https://ga.jspm.io/npm:lodash@4.17.21/lodash.js
150
- Using integrity: sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF
151
- ```
152
-
153
- This generates a pin in your `config/importmap.rb` with the integrity hash:
154
-
155
- ```ruby
156
- pin "lodash", integrity: "sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF" # @4.17.21
157
- ```
158
-
159
- ### Opting out of integrity
160
-
161
- If you need to disable integrity checking (not recommended for security reasons), you can use the `--no-integrity` flag:
162
-
163
- ```bash
164
- ./bin/importmap pin lodash --no-integrity
165
- Pinning "lodash" to vendor/javascript/lodash.js via download from https://ga.jspm.io/npm:lodash@4.17.21/lodash.js
166
- ```
167
-
168
- This generates a pin without integrity:
169
-
170
- ```ruby
171
- pin "lodash" # @4.17.21
172
- ```
173
-
174
- ### Adding integrity to existing pins
175
-
176
- If you have existing pins without integrity hashes, you can add them using the `integrity` command:
177
-
178
- ```bash
179
- # Add integrity to specific packages
180
- ./bin/importmap integrity lodash react
181
-
182
- # Add integrity to all pinned packages
183
- ./bin/importmap integrity
184
-
185
- # Update your importmap.rb file with integrity hashes
186
- ./bin/importmap integrity --update
187
- ```
142
+ For enhanced security, importmap-rails supports [Subresource Integrity (SRI)](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) hashes for packages loaded from external CDNs.
188
143
 
189
144
  ### Automatic integrity for local assets
190
145
 
191
- For local assets served by the Rails asset pipeline (like those created with `pin` or `pin_all_from`), you can use `integrity: true` to automatically calculate integrity hashes from the compiled assets:
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:
192
147
 
193
148
  ```ruby
194
149
  # config/importmap.rb
195
150
 
196
- # Automatically calculate integrity from asset pipeline
197
- pin "application", integrity: true
198
- pin "admin", to: "admin.js", integrity: true
151
+ # Enable integrity calculation globally
152
+ enable_integrity!
199
153
 
200
- # Works with pin_all_from too
201
- pin_all_from "app/javascript/controllers", under: "controllers", integrity: true
202
- pin_all_from "app/javascript/lib", under: "lib", integrity: true
154
+ # With integrity enabled, these will auto-calculate integrity hashes
155
+ pin "application" # Auto-calculated integrity
156
+ pin "admin", to: "admin.js" # Auto-calculated integrity
157
+ pin_all_from "app/javascript/controllers", under: "controllers" # Auto-calculated integrity
203
158
 
204
- # Mixed usage
205
- pin "local_module", integrity: true # Auto-calculated
206
- pin "cdn_package", integrity: "sha384-abc123..." # Pre-calculated
207
- pin "no_integrity_package" # No integrity (default)
159
+ # Mixed usage - explicitly controlling integrity
160
+ pin "cdn_package", integrity: "sha384-abc123..." # Pre-calculated hash
161
+ pin "no_integrity_package", integrity: false # Explicitly disable integrity
162
+ pin "nil_integrity_package", integrity: nil # Explicitly disable integrity
208
163
  ```
209
164
 
210
165
  This is particularly useful for:
@@ -212,13 +167,18 @@ This is particularly useful for:
212
167
  * **Bulk operations** with `pin_all_from` where calculating hashes manually would be tedious
213
168
  * **Development workflow** where asset contents change frequently
214
169
 
215
- The `integrity: true` option:
216
- * Uses the Rails asset pipeline's built-in integrity calculation
217
- * Works with both Sprockets and Propshaft
218
- * Automatically updates when assets are recompiled
219
- * Gracefully handles missing assets (returns `nil` for non-existent files)
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.
171
+
172
+ **Important for Propshaft users:** SRI support requires Propshaft 1.2+ and you must configure the integrity hash algorithm in your application:
173
+
174
+ ```ruby
175
+ # config/application.rb or config/environments/*.rb
176
+ config.assets.integrity_hash_algorithm = 'sha256' # or 'sha384', 'sha512'
177
+ ```
178
+
179
+ Without this configuration, integrity will be disabled by default when using Propshaft. Sprockets includes integrity support out of the box.
220
180
 
221
- **Example output with `integrity: true`:**
181
+ **Example output with `enable_integrity!` and `integrity: true`:**
222
182
  ```json
223
183
  {
224
184
  "imports": {
@@ -240,10 +200,14 @@ The integrity hashes are automatically included in your import map and module pr
240
200
  ```json
241
201
  {
242
202
  "imports": {
243
- "lodash": "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js"
203
+ "lodash": "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js",
204
+ "application": "/assets/application-abc123.js",
205
+ "controllers/hello_controller": "/assets/controllers/hello_controller-def456.js"
244
206
  },
245
207
  "integrity": {
246
208
  "https://ga.jspm.io/npm:lodash@4.17.21/lodash.js": "sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF"
209
+ "/assets/application-abc123.js": "sha256-xyz789...",
210
+ "/assets/controllers/hello_controller-def456.js": "sha256-uvw012..."
247
211
  }
248
212
  }
249
213
  ```
@@ -251,22 +215,12 @@ The integrity hashes are automatically included in your import map and module pr
251
215
  **Module preload tags:**
252
216
  ```html
253
217
  <link rel="modulepreload" href="https://ga.jspm.io/npm:lodash@4.17.21/lodash.js" integrity="sha384-PkIkha4kVPRlGtFantHjuv+Y9mRefUHpLFQbgOYUjzy247kvi16kLR7wWnsAmqZF">
218
+ <link rel="modulepreload" href="/assets/application-abc123.js" integrity="sha256-xyz789...">
219
+ <link rel="modulepreload" href="/assets/controllers/hello_controller-def456.js" integrity="sha256-uvw012...">
254
220
  ```
255
221
 
256
222
  Modern browsers will automatically validate these integrity hashes when loading the JavaScript modules, ensuring the files haven't been modified.
257
223
 
258
- ### Redownloading packages with integrity
259
-
260
- The `pristine` command also includes integrity by default:
261
-
262
- ```bash
263
- # Redownload all packages with integrity (default)
264
- ./bin/importmap pristine
265
-
266
- # Redownload packages without integrity
267
- ./bin/importmap pristine --no-integrity
268
- ```
269
-
270
224
  ## Preloading pinned modules
271
225
 
272
226
  To avoid the waterfall effect where the browser has to load one file after another before it can get to the deepest nested import, importmap-rails uses [modulepreload links](https://developers.google.com/web/updates/2017/12/modulepreload) by default. If you don't want to preload a dependency, because you want to load it on-demand for efficiency, append `preload: false` to the pin.
@@ -13,19 +13,9 @@ class Importmap::Commands < Thor
13
13
  option :env, type: :string, aliases: :e, default: "production"
14
14
  option :from, type: :string, aliases: :f, default: "jspm"
15
15
  option :preload, type: :string, repeatable: true, desc: "Can be used multiple times"
16
- option :integrity, type: :boolean, aliases: :i, default: true, desc: "Include integrity hash from JSPM"
17
16
  def pin(*packages)
18
- with_import_response(packages, env: options[:env], from: options[:from], integrity: options[:integrity]) do |imports, integrity_hashes|
19
- process_imports(imports, integrity_hashes) do |package, url, integrity_hash|
20
- puts %(Pinning "#{package}" to #{packager.vendor_path}/#{package}.js via download from #{url})
21
-
22
- packager.download(package, url)
23
-
24
- pin = packager.vendored_pin_for(package, url, options[:preload], integrity: integrity_hash)
25
-
26
- log_integrity_usage(integrity_hash)
27
- update_importmap_with_pin(package, pin)
28
- end
17
+ for_each_import(packages, env: options[:env], from: options[:from]) do |package, url|
18
+ pin_package(package, url, options[:preload])
29
19
  end
30
20
  end
31
21
 
@@ -33,12 +23,10 @@ class Importmap::Commands < Thor
33
23
  option :env, type: :string, aliases: :e, default: "production"
34
24
  option :from, type: :string, aliases: :f, default: "jspm"
35
25
  def unpin(*packages)
36
- with_import_response(packages, env: options[:env], from: options[:from]) do |imports, _integrity_hashes|
37
- imports.each do |package, url|
38
- if packager.packaged?(package)
39
- puts %(Unpinning and removing "#{package}")
40
- packager.remove(package)
41
- end
26
+ for_each_import(packages, env: options[:env], from: options[:from]) do |package, url|
27
+ if packager.packaged?(package)
28
+ puts %(Unpinning and removing "#{package}")
29
+ packager.remove(package)
42
30
  end
43
31
  end
44
32
  end
@@ -46,18 +34,13 @@ class Importmap::Commands < Thor
46
34
  desc "pristine", "Redownload all pinned packages"
47
35
  option :env, type: :string, aliases: :e, default: "production"
48
36
  option :from, type: :string, aliases: :f, default: "jspm"
49
- option :integrity, type: :boolean, aliases: :i, default: true, desc: "Include integrity hash from JSPM"
50
37
  def pristine
51
38
  packages = prepare_packages_with_versions
52
39
 
53
- with_import_response(packages, env: options[:env], from: options[:from], integrity: options[:integrity]) do |imports, integrity_hashes|
54
- process_imports(imports, integrity_hashes) do |package, url, integrity_hash|
55
- puts %(Downloading "#{package}" to #{packager.vendor_path}/#{package}.js from #{url})
56
-
57
- packager.download(package, url)
40
+ for_each_import(packages, env: options[:env], from: options[:from]) do |package, url|
41
+ puts %(Downloading "#{package}" to #{packager.vendor_path}/#{package}.js from #{url})
58
42
 
59
- log_integrity_usage(integrity_hash)
60
- end
43
+ packager.download(package, url)
61
44
  end
62
45
  end
63
46
 
@@ -107,7 +90,14 @@ class Importmap::Commands < Thor
107
90
  desc "update", "Update outdated package pins"
108
91
  def update
109
92
  if (outdated_packages = npm.outdated_packages).any?
110
- pin(*outdated_packages.map(&:name))
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
111
101
  else
112
102
  puts "No outdated packages found"
113
103
  end
@@ -118,33 +108,6 @@ class Importmap::Commands < Thor
118
108
  puts npm.packages_with_versions.map { |x| x.join(' ') }
119
109
  end
120
110
 
121
- desc "integrity [*PACKAGES]", "Download and add integrity hashes for packages"
122
- option :env, type: :string, aliases: :e, default: "production"
123
- option :from, type: :string, aliases: :f, default: "jspm"
124
- option :update, type: :boolean, aliases: :u, default: false, desc: "Update importmap.rb with integrity hashes"
125
- def integrity(*packages)
126
- packages = prepare_packages_with_versions(packages)
127
-
128
- with_import_response(packages, env: options[:env], from: options[:from], integrity: true) do |imports, integrity_hashes|
129
- process_imports(imports, integrity_hashes) do |package, url, integrity_hash|
130
- puts %(Getting integrity for "#{package}" from #{url})
131
-
132
- if integrity_hash
133
- puts %( #{package}: #{integrity_hash})
134
-
135
- if options[:update]
136
- pin_with_integrity = packager.pin_for(package, url, integrity: integrity_hash)
137
-
138
- update_importmap_with_pin(package, pin_with_integrity)
139
- puts %( Updated importmap.rb with integrity for "#{package}")
140
- end
141
- else
142
- puts %( No integrity hash available for "#{package}")
143
- end
144
- end
145
- end
146
- end
147
-
148
111
  private
149
112
  def packager
150
113
  @packager ||= Importmap::Packager.new
@@ -154,18 +117,26 @@ class Importmap::Commands < Thor
154
117
  @npm ||= Importmap::Npm.new
155
118
  end
156
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
+
157
130
  def update_importmap_with_pin(package, pin)
131
+ new_pin = "#{pin}\n"
132
+
158
133
  if packager.packaged?(package)
159
- gsub_file("config/importmap.rb", /^pin "#{package}".*$/, pin, verbose: false)
134
+ gsub_file("config/importmap.rb", Importmap::Map.pin_line_regexp_for(package), pin, verbose: false)
160
135
  else
161
- append_to_file("config/importmap.rb", "#{pin}\n", verbose: false)
136
+ append_to_file("config/importmap.rb", new_pin, verbose: false)
162
137
  end
163
138
  end
164
139
 
165
- def log_integrity_usage(integrity_hash)
166
- puts %( Using integrity: #{integrity_hash}) if integrity_hash
167
- end
168
-
169
140
  def handle_package_not_found(packages, from)
170
141
  puts "Couldn't find any packages in #{packages.inspect} on #{from}"
171
142
  end
@@ -205,18 +176,11 @@ class Importmap::Commands < Thor
205
176
  end
206
177
  end
207
178
 
208
- def process_imports(imports, integrity_hashes, &block)
209
- imports.each do |package, url|
210
- integrity_hash = integrity_hashes[url]
211
- block.call(package, url, integrity_hash)
212
- end
213
- end
214
-
215
- def with_import_response(packages, **options)
179
+ def for_each_import(packages, **options, &block)
216
180
  response = packager.import(*packages, **options)
217
181
 
218
182
  if response
219
- yield response[:imports], response[:integrity]
183
+ response[:imports].each(&block)
220
184
  else
221
185
  handle_package_not_found(packages, options[:from])
222
186
  end
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,12 +32,49 @@ class Importmap::Map
25
32
  self
26
33
  end
27
34
 
28
- def pin(name, to: nil, preload: true, integrity: nil)
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
+
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)
31
75
  end
32
76
 
33
- def pin_all_from(dir, under: nil, to: nil, preload: true, integrity: nil)
77
+ def pin_all_from(dir, under: nil, to: nil, preload: true, integrity: true)
34
78
  clear_cache
35
79
  @directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload, integrity: integrity)
36
80
  end
@@ -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 = /^pin ["']([^["']]*)["'].*/
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.each.with_object([]) do |(package, current_version), outdated_packages|
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\/)(.*)(?=@\d+\.\d+\.\d+)@(\d+\.\d+\.\d+(?:[^\/\s["']]*)).*$/) |
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: ["']([^["']]*)["'].*/ : PIN_REGEX
150
+ regexp = line.include?("to:")? /#{PIN_REGEX}to: ["']([^"']*)["'].*/ : PIN_REGEX
151
151
  match = line.match(regexp)
152
152
 
153
153
  return unless match
@@ -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)
@@ -17,13 +20,12 @@ class Importmap::Packager
17
20
  @vendor_path = Pathname.new(vendor_path)
18
21
  end
19
22
 
20
- def import(*packages, env: "production", from: "jspm", integrity: false)
23
+ def import(*packages, env: "production", from: "jspm")
21
24
  response = post_json({
22
25
  "install" => Array(packages),
23
26
  "flattenScope" => true,
24
27
  "env" => [ "browser", "module", env ],
25
28
  "provider" => normalize_provider(from),
26
- "integrity" => integrity
27
29
  })
28
30
 
29
31
  case response.code
@@ -36,24 +38,23 @@ class Importmap::Packager
36
38
  end
37
39
  end
38
40
 
39
- def pin_for(package, url = nil, preloads: nil, integrity: nil)
41
+ def pin_for(package, url = nil, preloads: nil)
40
42
  to = url ? %(, to: "#{url}") : ""
41
43
  preload_param = preload(preloads)
42
- integrity_param = integrity ? %(, integrity: "#{integrity}") : ""
43
44
 
44
- %(pin "#{package}") + to + preload_param + integrity_param
45
+ %(pin "#{package}") + to + preload_param
45
46
  end
46
47
 
47
- def vendored_pin_for(package, url, preloads = nil, integrity: nil)
48
+ def vendored_pin_for(package, url, preloads = nil)
48
49
  filename = package_filename(package)
49
50
  version = extract_package_version_from(url)
50
51
  to = "#{package}.js" != filename ? filename : nil
51
52
 
52
- pin_for(package, to, preloads: preloads, integrity: integrity) + %( # #{version})
53
+ pin_for(package, to, preloads: preloads) + %( # #{version})
53
54
  end
54
55
 
55
56
  def packaged?(package)
56
- importmap.match(/^pin ["']#{package}["'].*$/)
57
+ importmap.match(Importmap::Map.pin_line_regexp_for(package))
57
58
  end
58
59
 
59
60
  def download(package, url)
@@ -67,14 +68,57 @@ class Importmap::Packager
67
68
  remove_package_from_importmap(package)
68
69
  end
69
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
+
70
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
+
71
115
  def preload(preloads)
72
116
  case Array(preloads)
73
117
  in []
74
118
  ""
75
- in ["true"]
119
+ in ["true"] | [true]
76
120
  %(, preload: true)
77
- in ["false"]
121
+ in ["false"] | [false]
78
122
  %(, preload: false)
79
123
  in [string]
80
124
  %(, preload: "#{string}")
@@ -96,11 +140,9 @@ class Importmap::Packager
96
140
  def extract_parsed_response(response)
97
141
  parsed = JSON.parse(response.body)
98
142
  imports = parsed.dig("map", "imports")
99
- integrity = parsed.dig("map", "integrity") || {}
100
143
 
101
144
  {
102
145
  imports: imports,
103
- integrity: integrity
104
146
  }
105
147
  end
106
148
 
@@ -133,7 +175,7 @@ class Importmap::Packager
133
175
 
134
176
  def remove_package_from_importmap(package)
135
177
  all_lines = File.readlines(@importmap_path)
136
- with_lines_removed = all_lines.grep_v(/pin ["']#{package}["']/)
178
+ with_lines_removed = all_lines.grep_v(Importmap::Map.pin_line_regexp_for(package))
137
179
 
138
180
  File.open(@importmap_path, "w") do |file|
139
181
  with_lines_removed.each { |line| file.write(line) }
@@ -1,3 +1,3 @@
1
1
  module Importmap
2
- VERSION = "2.2.0"
2
+ VERSION = "2.2.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importmap-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson