importmap-node 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5c5646881534b9345ec18f89096877bdf2997a54eff2ca7d398a11e7031c6cac
4
+ data.tar.gz: b3fb276ee1463143206de177a6b3913377980be5c4bde3e26e9da7f92b07aadb
5
+ SHA512:
6
+ metadata.gz: 63a5f385846268bb9a4c059b2cd35371e9bd87d28f599993da8976c9de9971506e4591fda0f2680e87671e0602771b88d6d5141faa967cab8529eb1f0c8fbe60
7
+ data.tar.gz: fa5b2dacbd647fa39e5860ac179ecc9e92eb2b1a06c225c3bd2e3709230ac1e5ecb4cba87733797c2adb25ed40931a831471f5fb80afaf4842ef67b530cf57c2
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'json'
5
+
6
+ module Importmap
7
+ module Node
8
+ class Installer
9
+ JS_DEST = 'vendor/javascript'
10
+ CONFIG_FILE = 'config/importmap_node.json'
11
+
12
+ def initialize(root: Rails.root)
13
+ @root = Pathname.new(root)
14
+ @node_modules = @root.join('node_modules')
15
+ @importmap = @root.join('config', 'importmap.rb')
16
+ @config_file = @root.join(CONFIG_FILE)
17
+ end
18
+
19
+ def install(package)
20
+ yarn_add(package)
21
+
22
+ pkg_name = resolve_package_name(package)
23
+ pkg_meta = read_package_meta(pkg_name)
24
+ js_files = vendor_js(pkg_name, pkg_meta)
25
+ pin_importmap(pkg_name, js_files)
26
+ record_package(package)
27
+
28
+ summarize(pkg_name, js_files)
29
+ end
30
+
31
+ def uninstall(package)
32
+ pkg_name = resolve_package_name(package)
33
+ pkg_meta = read_package_meta(pkg_name)
34
+ js_files = vendored_js_files(pkg_name, pkg_meta)
35
+
36
+ yarn_remove(pkg_name)
37
+ delete_files(JS_DEST, js_files)
38
+ unpin_importmap(pkg_name)
39
+ unrecord_package(package)
40
+
41
+ summarize_removal(pkg_name, js_files)
42
+ end
43
+
44
+ def update
45
+ packages = load_packages
46
+ if packages.empty?
47
+ puts "No packages tracked in #{CONFIG_FILE}."
48
+ return
49
+ end
50
+ yarn_up_dependencies
51
+ puts "Re-vendoring #{packages.size} package(s)..."
52
+ packages.each { |pkg| revendor(pkg) }
53
+ end
54
+
55
+ private
56
+
57
+ def yarn_add(package)
58
+ yarn_arg = if package.start_with?('file:')
59
+ path = package.sub(/^file:/, '')
60
+ local_pkg_json = File.expand_path(File.join(path, 'package.json'), @root.to_s)
61
+ name = JSON.parse(File.read(local_pkg_json))['name']
62
+ "#{name}@#{package}"
63
+ else
64
+ package
65
+ end
66
+
67
+ puts "Running: yarn add #{yarn_arg}"
68
+ system("yarn add #{yarn_arg}", chdir: @root.to_s) or raise "yarn add failed for #{package}"
69
+ end
70
+
71
+ def yarn_remove(pkg_name)
72
+ puts "Running: yarn remove #{pkg_name}"
73
+ system("yarn remove #{pkg_name}", chdir: @root.to_s) or raise "yarn remove failed for #{pkg_name}"
74
+ end
75
+
76
+ def yarn_up_dependencies
77
+ pkg_json = @root.join('package.json')
78
+ deps = JSON.parse(pkg_json.read).fetch('dependencies', {}).keys
79
+ return if deps.empty?
80
+
81
+ puts "Running: yarn up #{deps.join(' ')}"
82
+ system("yarn up #{deps.join(' ')}", chdir: @root.to_s) or raise 'yarn up failed'
83
+ end
84
+
85
+ def revendor(package)
86
+ pkg_name = resolve_package_name(package)
87
+ pkg_meta = read_package_meta(pkg_name)
88
+ js_files = vendor_js(pkg_name, pkg_meta)
89
+ pin_importmap(pkg_name, js_files)
90
+ summarize(pkg_name, js_files)
91
+ end
92
+
93
+ def resolve_package_name(package)
94
+ if package.start_with?('file:')
95
+ path = package.sub(/^file:/, '')
96
+ local_pkg_json = File.expand_path(File.join(path, 'package.json'), @root.to_s)
97
+ JSON.parse(File.read(local_pkg_json))['name']
98
+ else
99
+ package
100
+ end
101
+ end
102
+
103
+ def read_package_meta(pkg_name)
104
+ path = @node_modules.join(pkg_name, 'package.json')
105
+ raise "Cannot find #{path}. Did yarn add succeed?" unless path.exist?
106
+
107
+ JSON.parse(path.read)
108
+ end
109
+
110
+ def vendor_js(pkg_name, meta)
111
+ file = meta['module'] || meta['main'] || 'index.js'
112
+ src = @node_modules.join(pkg_name, file)
113
+ return [] unless src.exist?
114
+
115
+ dest_dir = @root.join(JS_DEST)
116
+ FileUtils.mkdir_p(dest_dir)
117
+ dest = dest_dir.join(src.basename)
118
+ FileUtils.cp(src, dest)
119
+ [dest.basename.to_s]
120
+ end
121
+
122
+ def vendored_js_files(pkg_name, meta)
123
+ file = meta['module'] || meta['main'] || 'index.js'
124
+ src = @node_modules.join(pkg_name, file)
125
+ src.exist? ? [src.basename.to_s] : []
126
+ end
127
+
128
+ def delete_files(dest, filenames)
129
+ filenames.each do |name|
130
+ path = @root.join(dest, name)
131
+ if path.exist?
132
+ path.delete
133
+ puts " Deleted: #{dest}/#{name}"
134
+ end
135
+ end
136
+ end
137
+
138
+ def pin_importmap(pkg_name, js_files)
139
+ return if js_files.empty? || !@importmap.exist?
140
+
141
+ content = @importmap.read
142
+ pin_line = "pin \"#{pkg_name}\", to: \"#{js_files.first}\""
143
+
144
+ if content.match?(/pin ['"]#{Regexp.escape(pkg_name)}['"]/)
145
+ new_content = content.gsub(/pin ['"]#{Regexp.escape(pkg_name)}['"][^\n]*/, pin_line)
146
+ @importmap.write(new_content)
147
+ puts " Updated pin: #{pin_line}"
148
+ else
149
+ @importmap.open('a') { |f| f.puts pin_line }
150
+ puts " Added pin: #{pin_line}"
151
+ end
152
+ end
153
+
154
+ def unpin_importmap(pkg_name)
155
+ return unless @importmap.exist?
156
+
157
+ content = @importmap.read
158
+ new_content = content.gsub(/\n?pin ['"]#{Regexp.escape(pkg_name)}['"][^\n]*/, '')
159
+ return unless content != new_content
160
+
161
+ @importmap.write(new_content)
162
+ puts " Removed pin for #{pkg_name} from config/importmap.rb"
163
+ end
164
+
165
+ def summarize(pkg_name, js_files)
166
+ puts "\n#{pkg_name} installed:"
167
+ js_files.each { |f| puts " JS: vendor/javascript/#{f}" }
168
+ puts ' Pinned in config/importmap.rb' unless js_files.empty?
169
+ end
170
+
171
+ def summarize_removal(pkg_name, js_files)
172
+ puts "\n#{pkg_name} removed:"
173
+ js_files.each { |f| puts " JS: #{JS_DEST}/#{f}" }
174
+ end
175
+
176
+ def load_packages
177
+ return [] unless @config_file.exist?
178
+
179
+ JSON.parse(@config_file.read)['packages'] || []
180
+ end
181
+
182
+ def record_package(package)
183
+ packages = load_packages
184
+ return if packages.include?(package)
185
+
186
+ packages << package
187
+ @config_file.write(JSON.pretty_generate('packages' => packages))
188
+ end
189
+
190
+ def unrecord_package(package)
191
+ packages = load_packages
192
+ return unless packages.delete(package)
193
+
194
+ @config_file.write(JSON.pretty_generate('packages' => packages))
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+
5
+ module Importmap
6
+ module Node
7
+ class Railtie < Rails::Railtie
8
+ railtie_name :importmap_node
9
+
10
+ rake_tasks do
11
+ load File.join(__dir__, '..', '..', 'tasks', 'importmap_node.rake')
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Importmap
4
+ module Node
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'importmap/node/version'
4
+ require 'importmap/node/installer'
5
+ require 'importmap/node/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :importmap do
4
+ namespace :node do
5
+ desc 'Install a node package and vendor its JS/CSS files. Usage: rails importmap:node:install[package-name-or-url]'
6
+ task :install, [:package] => :environment do |_t, args|
7
+ package = args[:package]
8
+ abort 'Usage: rails importmap:node:install[package-name-or-url]' if package.blank?
9
+
10
+ Importmap::Node::Installer.new.install(package)
11
+ end
12
+
13
+ desc 'Remove a node package and delete its vendored JS files. Usage: rails importmap:node:remove[package-name]'
14
+ task :remove, [:package] => :environment do |_t, args|
15
+ package = args[:package]
16
+ abort 'Usage: rails importmap:node:remove[package-name]' if package.blank?
17
+
18
+ Importmap::Node::Installer.new.uninstall(package)
19
+ end
20
+
21
+ desc 'Re-vendor all packages tracked in config/importmap_node.json'
22
+ task update: :environment do
23
+ Importmap::Node::Installer.new.update
24
+ end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: importmap-node
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tien
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: railties
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ email:
27
+ - tieeeeen1994@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - lib/importmap-node.rb
33
+ - lib/importmap/node/installer.rb
34
+ - lib/importmap/node/railtie.rb
35
+ - lib/importmap/node/version.rb
36
+ - lib/tasks/importmap_node.rake
37
+ homepage: https://github.com/tieeeeen1994/importmap-node
38
+ licenses:
39
+ - MIT
40
+ metadata:
41
+ rubygems_mfa_required: 'true'
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '3.1'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 4.0.7
57
+ specification_version: 4
58
+ summary: Install node packages as vendored assets in Rails via yarn
59
+ test_files: []