importmap-plus 2.0.1 → 2.0.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: 1d0e4b656e7047b77938a8b92681f72ec7af73034c2c94517ef2ff37d725b76d
4
- data.tar.gz: 9a83c644e76fab4892e074ea8f254877b4351c55b071294f4edd3c965167054f
3
+ metadata.gz: '068b0fd351d34f6741037740731f5545ec5e98530ddd935abc45d18ac92b30e1'
4
+ data.tar.gz: 6482173bb6db3322fa43711fce8ccf0c9ac48d7b35c0b572e5b68d45f11d0445
5
5
  SHA512:
6
- metadata.gz: 39c36fbd0719746f4ca30e21632731f1d91ebc4e524132c2592b2d0a1432315cf8e2d3e7b70fffa935cbc4d3135a14d561dd86d52c6de3a18fd41030f91209a1
7
- data.tar.gz: c9eaf6a0f2b07be30f46487d07ffa17ebaa538efd443d23d677fd8e95509b4d50b193162a136dec8d9d6888b5414cbfe7b832d0929b0d0f9d6373de35ad2f0bf
6
+ metadata.gz: 22b1de80057a17f315ab8230d5537400985c9f67c1c50faa184336856de9e3e1d8f06ea815392c78a0fef30bffa3373bd25b206ab638cf056c8447faa674fe44
7
+ data.tar.gz: 444fca5f51ec92e43ab7163bb14ce92791727b3a93242298e776a753cc243e218f17fdf3acb6c873842012bf38867862fa37bf4eb99e0464ed8e72061254c33f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,40 @@
4
4
 
5
5
  ### Fixed
6
6
 
7
+ - **`doctor` leaves a bundle installed inside the app alone.** A file an engine
8
+ serves out of its gem is skipped, because no `bin/importmap` command can fix
9
+ it — but "out of its gem" was read as "outside the app root". With
10
+ `bundle config set path vendor/bundle`, as a CI caching its gems has, the
11
+ gems are under the root, so an engine's controller importing a package the
12
+ app never pinned failed `doctor` in CI and nowhere else. Files under
13
+ `Gem.path` or `Bundler.bundle_path` are now skipped wherever they sit.
14
+
15
+ - **A `preload: %w[...]` survives a rewrite.** `pin`, `update` and `pristine`
16
+ carried over `preload: ["admin", "app"]` but not the same list written as a
17
+ word array, which RuboCop and Standard prefer (`Style/WordArray`). The option
18
+ was dropped without a word, so an updated package quietly stopped being
19
+ preloaded on the pages that named it. It is now read — backslash escapes and
20
+ nested brackets included, so `%w[my\ app]` is the one entry point Ruby sees and
21
+ `%w[foo [bar]]` keeps its `[bar]` — and written back in the form the app chose:
22
+
23
+ ```ruby
24
+ pin "tailwindcss-stimulus-components", preload: %w[application webcad] # @6.1.3
25
+ # after bin/importmap update
26
+ pin "tailwindcss-stimulus-components", preload: %w[application webcad] # @6.1.4
27
+ ```
28
+
29
+ - **A quoted preload keeps a bracket or quote inside it.** `preload: ["a]", "b"]`
30
+ was cut at the first `]` and `preload: "it's"` at the `'`, so a rewrite wrote
31
+ back a shorter list or a different entry point. Quoted strings are now read
32
+ whole, and a quoted preload — one entry point or a bracketed list — is written
33
+ back exactly as the app wrote it, quotes included, so `"\u0061pp"` stays the
34
+ `app` entry point and `"#{prefix}app"` still interpolates (both used to come
35
+ back as a different name). `preload: 'say "hi"'` no longer becomes `"say "hi""`.
36
+
37
+ ## 2.0.1
38
+
39
+ ### Fixed
40
+
7
41
  - **`bin/importmap doctor` no longer reports comments and prose as unpinned
8
42
  imports.** The import scanner read `from "…"` and `import "…"` wherever the
9
43
  text appeared, so a line comment such as `// e.g. 'en' from 'en-US'` and
@@ -59,11 +59,18 @@ class Importmap::Doctor
59
59
  # against the importing file's own URL, or fetches outright, is not one.
60
60
  SCHEME_REGEXP = %r{\A[a-zA-Z][a-zA-Z0-9+\-.]*:}.freeze # :nodoc:
61
61
 
62
- def initialize(importmap:, resolver:, root:, asset_paths: [], online: false)
62
+ # Where installed gems live: the default +gem_paths+. A bundle installed
63
+ # with `path: vendor/bundle` puts Bundler's under the app root.
64
+ def self.gem_paths
65
+ [ *Gem.path, (Bundler.bundle_path.to_s if defined?(Bundler)) ].compact.uniq
66
+ end
67
+
68
+ def initialize(importmap:, resolver:, root:, asset_paths: [], gem_paths: self.class.gem_paths, online: false)
63
69
  @importmap = importmap
64
70
  @resolver = resolver
65
71
  @root = Pathname.new(root)
66
72
  @asset_paths = Array(asset_paths).map { |path| Pathname.new(path) }
73
+ @gem_paths = Array(gem_paths).map { |path| Pathname.new(path) }
67
74
  @online = online
68
75
  end
69
76
 
@@ -188,9 +195,14 @@ class Importmap::Doctor
188
195
 
189
196
  # The files this gem may read: the app's own, not a file an engine serves
190
197
  # out of its gem, which no `bin/importmap` command can do anything about.
191
- # One file serving two keys is scanned once.
198
+ # Being under the root isn't enough to be the app's: a bundle installed
199
+ # into vendor/bundle is too. One file serving two keys is scanned once.
192
200
  def scannable
193
- @scannable ||= entries.select { |entry| entry.file && under?(entry.file, @root) }.uniq(&:file)
201
+ @scannable ||= entries.select { |entry| entry.file && app_file?(entry.file) }.uniq(&:file)
202
+ end
203
+
204
+ def app_file?(file)
205
+ under?(file, @root) && @gem_paths.none? { |gem_path| under?(file, gem_path) }
194
206
  end
195
207
 
196
208
  def vendored
@@ -13,10 +13,47 @@ class Importmap::Packager
13
13
  include Importmap::HttpRetries
14
14
 
15
15
  PIN_REGEX = /#{Importmap::Map::PIN_REGEX}(.*)/.freeze # :nodoc:
16
+ # A Ruby string literal, escapes included, so a quote or bracket inside one
17
+ # doesn't end the match early (`"it's"`, `["a]"]`).
18
+ QUOTED_STRING_REGEXP = /"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'/m.freeze # :nodoc:
16
19
  # The bracketed form matches an empty array too: `preload: []` is a pin an
17
20
  # app wrote, and a rewrite that dropped it would start preloading the package
18
- # on every page.
19
- PRELOAD_OPTION_REGEXP = /preload:\s*(\[[^\]]*\]|true|false|["'][^"']*["'])/.freeze # :nodoc:
21
+ # on every page. A word array nests its own delimiter the way Ruby does
22
+ # (`%w[foo [bar]]` is "foo" and "[bar]"), hence the recursive groups; with
23
+ # named groups in the pattern, the value has to be named too to stay [1].
24
+ PRELOAD_OPTION_REGEXP = /preload:\s*(?<value>\[(?:#{QUOTED_STRING_REGEXP}|[^\]"'])*\]|%w(?<brackets>\[(?:\\.|[^\[\]\\]|\g<brackets>)*\])|%w(?<parens>\((?:\\.|[^()\\]|\g<parens>)*\))|true|false|#{QUOTED_STRING_REGEXP})/.freeze # :nodoc:
25
+ # A `preload: %w[...]` read from a pin, so a rewrite writes it back in the
26
+ # form the app chose (RuboCop's Style/WordArray flags `["a", "b"]`). Read
27
+ # with Ruby's escapes rather than split on whitespace: `%w[my\ app]` is one
28
+ # word, and a backslash before anything but whitespace, a backslash or the
29
+ # literal's own delimiter stays in it.
30
+ class WordArray < Array # :nodoc:
31
+ LITERAL_REGEXP = /\A%w([\[(])(.*)[\])]\z/m.freeze
32
+
33
+ def self.parse(literal)
34
+ open, body = literal.match(LITERAL_REGEXP)&.captures
35
+ return unless open
36
+
37
+ escapable = /\\([\s\\#{Regexp.escape(open == "[" ? "[]" : "()")}])/
38
+ new(body.scan(/(?:\\.|[^\s\\])+/m).map { |word| word.gsub(escapable, '\1') })
39
+ end
40
+
41
+ def to_s
42
+ "%w[#{map { |word| word.gsub(/[\s\\\[\]]/) { "\\#{$&}" } }.join(" ")}]"
43
+ end
44
+ end
45
+ # A quoted preload read from a pin — a string or a bracketed array — keeps
46
+ # the literal it was read from, and a rewrite writes that back untouched.
47
+ # The names are read with only the simple escapes; the rest of Ruby's string
48
+ # semantics ("\u0061pp", "#{prefix}app") is Ruby's to evaluate, so writing
49
+ # the value read back through inspect would change the entry point.
50
+ module Literal # :nodoc:
51
+ attr_accessor :source
52
+
53
+ def self.of(value, source)
54
+ value.extend(self).tap { |literal| literal.source = source }
55
+ end
56
+ end
20
57
  TO_OPTION_REGEXP = /to:\s*["']([^"']*)["']/.freeze # :nodoc:
21
58
  # Only the booleans: a hash string is tied to the file it was computed for,
22
59
  # so a rewrite that changes the URL has to drop it.
@@ -537,21 +574,33 @@ class Importmap::Packager
537
574
  true
538
575
  when "false"
539
576
  false
577
+ when WordArray::LITERAL_REGEXP
578
+ WordArray.parse(value)
540
579
  when /^\[.*\]$/
541
580
  # config/importmap.rb is Ruby, not JSON, and a single-quoted pin is a
542
581
  # supported shape here, so the entry points are scanned out of the
543
582
  # literal rather than parsed. JSON.parse raised on every one of them.
544
- value.scan(/["']([^"']*)["']/).flatten
583
+ Literal.of(value.scan(QUOTED_STRING_REGEXP).map { |double, single| unquote(double, single) }, value)
545
584
  else
546
- value.gsub(/["']/, "")
585
+ Literal.of(unquote(*value.match(/\A#{QUOTED_STRING_REGEXP}\z/).captures), value)
547
586
  end
548
587
  end
549
588
 
589
+ # A single-quoted string unescapes only \\ and \'. In a double-quoted one
590
+ # a backslash before any character an entry point would hold is that
591
+ # character, which is all a preload name needs. The value is only read;
592
+ # a rewrite writes the Literal's source.
593
+ def unquote(double, single)
594
+ double ? double.gsub(/\\(.)/m, '\1') : single.gsub(/\\([\\'])/, '\1')
595
+ end
596
+
550
597
  def preload(preloads)
551
598
  # nil is "the pin carries no preload:"; [] is the pin carrying one that
552
599
  # names no entry point. Array() flattens both to [], so the difference
553
600
  # has to be read before it.
554
601
  return "" if preloads.nil?
602
+ return %(, preload: #{preloads}) if preloads.is_a?(WordArray)
603
+ return %(, preload: #{preloads.source}) if preloads.is_a?(Literal)
555
604
 
556
605
  case Array(preloads)
557
606
  in []
@@ -561,7 +610,7 @@ class Importmap::Packager
561
610
  in ["false"] | [false]
562
611
  %(, preload: false)
563
612
  in [string]
564
- %(, preload: "#{string}")
613
+ %(, preload: #{string.to_s.inspect})
565
614
  else
566
615
  %(, preload: #{preloads})
567
616
  end
@@ -1,6 +1,6 @@
1
1
  module Importmap
2
2
  # importmap-plus versions independently of the importmap-rails it forks.
3
3
  # UPSTREAM_VERSION is the importmap-rails release this tracks.
4
- VERSION = "2.0.1"
4
+ VERSION = "2.0.2"
5
5
  UPSTREAM_VERSION = "2.2.3"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importmap-plus
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson