importmap 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +6 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +20 -0
- data/Rakefile +6 -0
- data/exe/importmap +14 -0
- data/importmap.gemspec +32 -0
- data/lib/importmap/autoloader.rb +22 -0
- data/lib/importmap/cli/help/completion.md +20 -0
- data/lib/importmap/cli/help/completion_script.md +3 -0
- data/lib/importmap/cli/help/pin.md +5 -0
- data/lib/importmap/cli/help.rb +11 -0
- data/lib/importmap/cli.rb +156 -0
- data/lib/importmap/command.rb +89 -0
- data/lib/importmap/completer/script.rb +8 -0
- data/lib/importmap/completer/script.sh +10 -0
- data/lib/importmap/completer.rb +158 -0
- data/lib/importmap/framework/jets_framework.rb +11 -0
- data/lib/importmap/framework/rails_framework.rb +11 -0
- data/lib/importmap/framework.rb +49 -0
- data/lib/importmap/map.rb +158 -0
- data/lib/importmap/npm.rb +124 -0
- data/lib/importmap/packager.rb +145 -0
- data/lib/importmap/reloader.rb +20 -0
- data/lib/importmap/version.rb +3 -0
- data/lib/importmap.rb +24 -0
- data/spec/cli_spec.rb +26 -0
- data/spec/spec_helper.rb +29 -0
- metadata +217 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require "active_support/core_ext/string/inflections"
|
2
|
+
|
3
|
+
module Importmap
|
4
|
+
class Framework
|
5
|
+
|
6
|
+
@@instance = nil
|
7
|
+
def self.instance
|
8
|
+
@@instance ||= new
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
require framework_name
|
13
|
+
Importmap::Framework.send(:include, include_framework)
|
14
|
+
end
|
15
|
+
|
16
|
+
def framework_name
|
17
|
+
ENV['IMPORTMAP_FRAMEWORK'] || infer_from_gemfile || "jets"
|
18
|
+
end
|
19
|
+
|
20
|
+
# define at bottom so methods take precedence
|
21
|
+
def include_framework
|
22
|
+
if supported_frameworks.include?(framework_name)
|
23
|
+
"Importmap::Framework::#{framework_name.camelize}Framework".constantize
|
24
|
+
else
|
25
|
+
raise "Need to use a supported framework: #{supported_frameworks.join(', ')}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def infer_from_gemfile
|
30
|
+
return unless File.exist?("Gemfile")
|
31
|
+
lines = IO.readlines("Gemfile")
|
32
|
+
lines.each do |line|
|
33
|
+
next if line =~ /^\s*#/ # skip comments
|
34
|
+
gem_name = line.match(/gem ['"](\w+)['"]/)&.captures&.first
|
35
|
+
if supported_frameworks.include?(gem_name)
|
36
|
+
return gem_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
# To add another framework:
|
43
|
+
# 1. add to this list
|
44
|
+
# 2. define a module in importmap/framework/#{framework_name}_framework.rb
|
45
|
+
def supported_frameworks
|
46
|
+
%w[jets rails]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
class Importmap::Map
|
4
|
+
attr_reader :packages, :directories
|
5
|
+
|
6
|
+
class InvalidFile < StandardError; end
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@packages, @directories = {}, {}
|
10
|
+
@cache = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw(path = nil, &block)
|
14
|
+
if path && File.exist?(path)
|
15
|
+
begin
|
16
|
+
instance_eval(File.read(path), path.to_s)
|
17
|
+
rescue StandardError => e
|
18
|
+
Importmap.framework.logger.error "Unable to parse import map from #{path}: #{e.message}"
|
19
|
+
raise InvalidFile, "Unable to parse import map from #{path}: #{e.message}"
|
20
|
+
end
|
21
|
+
elsif block_given?
|
22
|
+
instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def pin(name, to: nil, preload: false)
|
29
|
+
clear_cache
|
30
|
+
@packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
|
31
|
+
end
|
32
|
+
|
33
|
+
def pin_all_from(dir, under: nil, to: nil, preload: false)
|
34
|
+
clear_cache
|
35
|
+
@directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an array of all the resolved module paths of the pinned packages. The `resolver` must respond to
|
39
|
+
# `path_to_asset`, such as `ActionController::Base.helpers` or `ApplicationController.helpers`. You'll want to use the
|
40
|
+
# resolver that has been configured for the `asset_host` you want these resolved paths to use. In case you need to
|
41
|
+
# resolve for different asset hosts, you can pass in a custom `cache_key` to vary the cache used by this method for
|
42
|
+
# the different cases.
|
43
|
+
def preloaded_module_paths(resolver:, cache_key: :preloaded_module_paths)
|
44
|
+
cache_as(cache_key) do
|
45
|
+
resolve_asset_paths(expanded_preloading_packages_and_directories, resolver: resolver).values
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a JSON hash (as a string) of all the resolved module paths of the pinned packages in the import map format.
|
50
|
+
# The `resolver` must respond to `path_to_asset`, such as `ActionController::Base.helpers` or
|
51
|
+
# `ApplicationController.helpers`. You'll want to use the resolver that has been configured for the `asset_host` you
|
52
|
+
# want these resolved paths to use. In case you need to resolve for different asset hosts, you can pass in a custom
|
53
|
+
# `cache_key` to vary the cache used by this method for the different cases.
|
54
|
+
def to_json(resolver:, cache_key: :json)
|
55
|
+
cache_as(cache_key) do
|
56
|
+
JSON.pretty_generate({ "imports" => resolve_asset_paths(expanded_packages_and_directories, resolver: resolver) })
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns a SHA1 digest of the import map json that can be used as a part of a page etag to
|
61
|
+
# ensure that a html cache is invalidated when the import map is changed.
|
62
|
+
#
|
63
|
+
# Example:
|
64
|
+
#
|
65
|
+
# class ApplicationController < ActionController::Base
|
66
|
+
# etag { Importmap.framework.application.importmap.digest(resolver: helpers) if request.format&.html? }
|
67
|
+
# end
|
68
|
+
def digest(resolver:)
|
69
|
+
Digest::SHA1.hexdigest(to_json(resolver: resolver).to_s)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an instance ActiveSupport::EventedFileUpdateChecker configured to clear the cache of the map
|
73
|
+
# when the directories passed on initialization via `watches:` have changes. This is used in development
|
74
|
+
# and test to ensure the map caches are reset when javascript files are changed.
|
75
|
+
def cache_sweeper(watches: nil)
|
76
|
+
if watches
|
77
|
+
@cache_sweeper =
|
78
|
+
Importmap.framework.application.config.file_watcher.new([], Array(watches).collect { |dir| [ dir.to_s, "js"] }.to_h) do
|
79
|
+
clear_cache
|
80
|
+
end
|
81
|
+
else
|
82
|
+
@cache_sweeper
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
MappedDir = Struct.new(:dir, :path, :under, :preload, keyword_init: true)
|
88
|
+
MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
|
89
|
+
|
90
|
+
def cache_as(name)
|
91
|
+
if result = @cache[name.to_s]
|
92
|
+
result
|
93
|
+
else
|
94
|
+
@cache[name.to_s] = yield
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clear_cache
|
99
|
+
@cache.clear
|
100
|
+
end
|
101
|
+
|
102
|
+
def rescuable_asset_error?(error)
|
103
|
+
Importmap.framework.application.config.importmap.rescuable_asset_errors.any? { |e| error.is_a?(e) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def resolve_asset_paths(paths, resolver:)
|
107
|
+
paths.transform_values do |mapping|
|
108
|
+
begin
|
109
|
+
resolver.path_to_asset(mapping.path)
|
110
|
+
rescue => e
|
111
|
+
if rescuable_asset_error?(e)
|
112
|
+
Importmap.framework.logger.warn "Importmap skipped missing path: #{mapping.path}"
|
113
|
+
nil
|
114
|
+
else
|
115
|
+
raise e
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end.compact
|
119
|
+
end
|
120
|
+
|
121
|
+
def expanded_preloading_packages_and_directories
|
122
|
+
expanded_packages_and_directories.select { |name, mapping| mapping.preload }
|
123
|
+
end
|
124
|
+
|
125
|
+
def expanded_packages_and_directories
|
126
|
+
@packages.dup.tap { |expanded| expand_directories_into expanded }
|
127
|
+
end
|
128
|
+
|
129
|
+
def expand_directories_into(paths)
|
130
|
+
@directories.values.each do |mapping|
|
131
|
+
if (absolute_path = absolute_root_of(mapping.dir)).exist?
|
132
|
+
find_javascript_files_in_tree(absolute_path).each do |filename|
|
133
|
+
module_filename = filename.relative_path_from(absolute_path)
|
134
|
+
module_name = module_name_from(module_filename, mapping)
|
135
|
+
module_path = module_path_from(module_filename, mapping)
|
136
|
+
|
137
|
+
paths[module_name] = MappedFile.new(name: module_name, path: module_path, preload: mapping.preload)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def module_name_from(filename, mapping)
|
144
|
+
[ mapping.under, filename.to_s.remove(filename.extname).remove(/\/?index$/).presence ].compact.join("/")
|
145
|
+
end
|
146
|
+
|
147
|
+
def module_path_from(filename, mapping)
|
148
|
+
[ mapping.path || mapping.under, filename.to_s ].compact.join("/")
|
149
|
+
end
|
150
|
+
|
151
|
+
def find_javascript_files_in_tree(path)
|
152
|
+
Dir[path.join("**/*.js{,m}")].collect { |file| Pathname.new(file) }.select(&:file?)
|
153
|
+
end
|
154
|
+
|
155
|
+
def absolute_root_of(path)
|
156
|
+
(pathname = Pathname.new(path)).absolute? ? pathname : Importmap.framework.root.join(path)
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Importmap
|
6
|
+
class Npm
|
7
|
+
Error = Class.new(StandardError)
|
8
|
+
HTTPError = Class.new(Error)
|
9
|
+
|
10
|
+
singleton_class.attr_accessor :base_uri
|
11
|
+
self.base_uri = URI("https://registry.npmjs.org")
|
12
|
+
|
13
|
+
def initialize(importmap_path = "config/importmap.rb")
|
14
|
+
@importmap_path = Pathname.new(importmap_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def outdated_packages
|
18
|
+
packages_with_versions.each.with_object([]) do |(package, current_version), outdated_packages|
|
19
|
+
outdated_package = OutdatedPackage.new(name: package,
|
20
|
+
current_version: current_version)
|
21
|
+
|
22
|
+
if !(response = get_package(package))
|
23
|
+
outdated_package.error = 'Response error'
|
24
|
+
elsif (error = response['error'])
|
25
|
+
outdated_package.error = error
|
26
|
+
else
|
27
|
+
latest_version = find_latest_version(response)
|
28
|
+
next unless outdated?(current_version, latest_version)
|
29
|
+
|
30
|
+
outdated_package.latest_version = latest_version
|
31
|
+
end
|
32
|
+
|
33
|
+
outdated_packages << outdated_package
|
34
|
+
end.sort_by(&:name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def vulnerable_packages
|
38
|
+
get_audit.flat_map do |package, vulnerabilities|
|
39
|
+
vulnerabilities.map do |vulnerability|
|
40
|
+
VulnerablePackage.new(name: package,
|
41
|
+
severity: vulnerability['severity'],
|
42
|
+
vulnerable_versions: vulnerability['vulnerable_versions'],
|
43
|
+
vulnerability: vulnerability['title'])
|
44
|
+
end
|
45
|
+
end.sort_by { |p| [p.name, p.severity] }
|
46
|
+
end
|
47
|
+
|
48
|
+
def packages_with_versions
|
49
|
+
# We cannot use the name after "pin" because some dependencies are loaded from inside packages
|
50
|
+
# Eg. pin "buffer", to: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.19/nodelibs/browser/buffer.js"
|
51
|
+
|
52
|
+
importmap.scan(/^pin .*(?<=npm:|npm\/|skypack\.dev\/|unpkg\.com\/)(.*)(?=@\d+\.\d+\.\d+)@(\d+\.\d+\.\d+(?:[^\/\s"]*)).*$/) |
|
53
|
+
importmap.scan(/^pin "([^"]*)".* #.*@(\d+\.\d+\.\d+(?:[^\s]*)).*$/)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
OutdatedPackage = Struct.new(:name, :current_version, :latest_version, :error, keyword_init: true)
|
58
|
+
VulnerablePackage = Struct.new(:name, :severity, :vulnerable_versions, :vulnerability, keyword_init: true)
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
def importmap
|
63
|
+
@importmap ||= File.read(@importmap_path)
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_package(package)
|
67
|
+
uri = self.class.base_uri.dup
|
68
|
+
uri.path = "/" + package
|
69
|
+
response = get_json(uri)
|
70
|
+
|
71
|
+
JSON.parse(response)
|
72
|
+
rescue JSON::ParserError
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_json(uri)
|
77
|
+
request = Net::HTTP::Get.new(uri)
|
78
|
+
request["Content-Type"] = "application/json"
|
79
|
+
|
80
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
|
81
|
+
http.request(request)
|
82
|
+
}
|
83
|
+
|
84
|
+
response.body
|
85
|
+
rescue => error
|
86
|
+
raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_latest_version(response)
|
90
|
+
latest_version = response.dig('dist-tags', 'latest')
|
91
|
+
return latest_version if latest_version
|
92
|
+
|
93
|
+
return unless response['versions']
|
94
|
+
|
95
|
+
response['versions'].keys.map { |v| Gem::Version.new(v) rescue nil }.compact.sort.last
|
96
|
+
end
|
97
|
+
|
98
|
+
def outdated?(current_version, latest_version)
|
99
|
+
Gem::Version.new(current_version) < Gem::Version.new(latest_version)
|
100
|
+
rescue ArgumentError
|
101
|
+
current_version.to_s < latest_version.to_s
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_audit
|
105
|
+
uri = self.class.base_uri.dup
|
106
|
+
uri.path = "/-/npm/v1/security/advisories/bulk"
|
107
|
+
|
108
|
+
body = packages_with_versions.each.with_object({}) { |(package, version), data|
|
109
|
+
data[package] ||= []
|
110
|
+
data[package] << version
|
111
|
+
}
|
112
|
+
return {} if body.empty?
|
113
|
+
|
114
|
+
response = post_json(uri, body)
|
115
|
+
JSON.parse(response.body)
|
116
|
+
end
|
117
|
+
|
118
|
+
def post_json(uri, body)
|
119
|
+
Net::HTTP.post(uri, body.to_json, "Content-Type" => "application/json")
|
120
|
+
rescue => error
|
121
|
+
raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Importmap
|
6
|
+
class Packager
|
7
|
+
Error = Class.new(StandardError)
|
8
|
+
HTTPError = Class.new(Error)
|
9
|
+
ServiceError = Error.new(Error)
|
10
|
+
|
11
|
+
singleton_class.attr_accessor :endpoint
|
12
|
+
self.endpoint = URI("https://api.jspm.io/generate")
|
13
|
+
|
14
|
+
attr_reader :vendor_path
|
15
|
+
|
16
|
+
def initialize(importmap_path = "config/importmap.rb", vendor_path: "vendor/javascript")
|
17
|
+
@importmap_path = Pathname.new(importmap_path)
|
18
|
+
@vendor_path = Pathname.new(vendor_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def import(*packages, env: "production", from: "jspm")
|
22
|
+
response = post_json({
|
23
|
+
"install" => Array(packages),
|
24
|
+
"flattenScope" => true,
|
25
|
+
"env" => [ "browser", "module", env ],
|
26
|
+
"provider" => from.to_s,
|
27
|
+
})
|
28
|
+
|
29
|
+
case response.code
|
30
|
+
when "200" then extract_parsed_imports(response)
|
31
|
+
when "404", "401" then nil
|
32
|
+
else handle_failure_response(response)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def pin_for(package, url)
|
37
|
+
%(pin "#{package}", to: "#{url}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def vendored_pin_for(package, url)
|
41
|
+
filename = package_filename(package)
|
42
|
+
version = extract_package_version_from(url)
|
43
|
+
|
44
|
+
if "#{package}.js" == filename
|
45
|
+
%(pin "#{package}" # #{version})
|
46
|
+
else
|
47
|
+
%(pin "#{package}", to: "#{filename}" # #{version})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def packaged?(package)
|
52
|
+
importmap.match(/^pin ["']#{package}["'].*$/)
|
53
|
+
end
|
54
|
+
|
55
|
+
def download(package, url)
|
56
|
+
ensure_vendor_directory_exists
|
57
|
+
remove_existing_package_file(package)
|
58
|
+
download_package_file(package, url)
|
59
|
+
end
|
60
|
+
|
61
|
+
def remove(package)
|
62
|
+
remove_existing_package_file(package)
|
63
|
+
remove_package_from_importmap(package)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def post_json(body)
|
68
|
+
Net::HTTP.post(self.class.endpoint, body.to_json, "Content-Type" => "application/json")
|
69
|
+
rescue => error
|
70
|
+
raise HTTPError, "Unexpected transport error (#{error.class}: #{error.message})"
|
71
|
+
end
|
72
|
+
|
73
|
+
def extract_parsed_imports(response)
|
74
|
+
JSON.parse(response.body).dig("map", "imports")
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_failure_response(response)
|
78
|
+
if error_message = parse_service_error(response)
|
79
|
+
raise ServiceError, error_message
|
80
|
+
else
|
81
|
+
raise HTTPError, "Unexpected response code (#{response.code})"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_service_error(response)
|
86
|
+
JSON.parse(response.body.to_s)["error"]
|
87
|
+
rescue JSON::ParserError
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def importmap
|
92
|
+
@importmap ||= File.read(@importmap_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def ensure_vendor_directory_exists
|
97
|
+
FileUtils.mkdir_p @vendor_path
|
98
|
+
end
|
99
|
+
|
100
|
+
def remove_existing_package_file(package)
|
101
|
+
FileUtils.rm_rf vendored_package_path(package)
|
102
|
+
end
|
103
|
+
|
104
|
+
def remove_package_from_importmap(package)
|
105
|
+
all_lines = File.readlines(@importmap_path)
|
106
|
+
with_lines_removed = all_lines.grep_v(/pin ["']#{package}["']/)
|
107
|
+
|
108
|
+
File.open(@importmap_path, "w") do |file|
|
109
|
+
with_lines_removed.each { |line| file.write(line) }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def download_package_file(package, url)
|
114
|
+
response = Net::HTTP.get_response(URI(url))
|
115
|
+
|
116
|
+
if response.code == "200"
|
117
|
+
save_vendored_package(package, response.body)
|
118
|
+
else
|
119
|
+
handle_failure_response(response)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def save_vendored_package(package, source)
|
124
|
+
File.open(vendored_package_path(package), "w+") do |vendored_package|
|
125
|
+
vendored_package.write remove_sourcemap_comment_from(source).force_encoding("UTF-8")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def remove_sourcemap_comment_from(source)
|
130
|
+
source.gsub(/^\/\/# sourceMappingURL=.*/, "")
|
131
|
+
end
|
132
|
+
|
133
|
+
def vendored_package_path(package)
|
134
|
+
@vendor_path.join(package_filename(package))
|
135
|
+
end
|
136
|
+
|
137
|
+
def package_filename(package)
|
138
|
+
package.gsub("/", "--") + ".js"
|
139
|
+
end
|
140
|
+
|
141
|
+
def extract_package_version_from(url)
|
142
|
+
url.match(/@\d+\.\d+\.\d+/)&.to_a&.first
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Importmap::Reloader
|
2
|
+
delegate :execute_if_updated, :execute, :updated?, to: :updater
|
3
|
+
|
4
|
+
def reload!
|
5
|
+
import_map_paths.each { |path| Importmap.framework.application.importmap.draw(path) }
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def updater
|
10
|
+
@updater ||= config.file_watcher.new(import_map_paths) { reload! }
|
11
|
+
end
|
12
|
+
|
13
|
+
def import_map_paths
|
14
|
+
config.importmap.paths
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
Importmap.framework.application.config
|
19
|
+
end
|
20
|
+
end
|
data/lib/importmap.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
$stdout.sync = true unless ENV["IMPORTMAP_STDOUT_SYNC"] == "0"
|
2
|
+
|
3
|
+
$:.unshift(File.expand_path("../", __FILE__))
|
4
|
+
|
5
|
+
require "importmap/autoloader"
|
6
|
+
Importmap::Autoloader.setup
|
7
|
+
|
8
|
+
require "memoist"
|
9
|
+
require "pathname"
|
10
|
+
require "rainbow/ext/string"
|
11
|
+
|
12
|
+
module Importmap
|
13
|
+
class Error < StandardError; end
|
14
|
+
|
15
|
+
def framework
|
16
|
+
Framework.instance.framework_class
|
17
|
+
end
|
18
|
+
|
19
|
+
def framework_boot
|
20
|
+
Framework.instance.boot
|
21
|
+
end
|
22
|
+
|
23
|
+
extend self
|
24
|
+
end
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
describe Importmap::CLI do
|
2
|
+
before(:all) do
|
3
|
+
@args = "--from Tung"
|
4
|
+
end
|
5
|
+
|
6
|
+
describe "importmap" do
|
7
|
+
it "hello" do
|
8
|
+
out = execute("exe/importmap hello world #{@args}")
|
9
|
+
expect(out).to include("from: Tung\nHello world")
|
10
|
+
end
|
11
|
+
|
12
|
+
commands = {
|
13
|
+
"hell" => "hello",
|
14
|
+
"hello" => "name",
|
15
|
+
"hello -" => "--from",
|
16
|
+
"hello name" => "--from",
|
17
|
+
"hello name --" => "--from",
|
18
|
+
}
|
19
|
+
commands.each do |command, expected_word|
|
20
|
+
it "completion #{command}" do
|
21
|
+
out = execute("exe/importmap completion #{command}")
|
22
|
+
expect(out).to include(expected_word) # only checking for one word for simplicity
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
ENV["IMPORTMAP_TEST"] = "1"
|
2
|
+
|
3
|
+
# CodeClimate test coverage: https://docs.codeclimate.com/docs/configuring-test-coverage
|
4
|
+
# require 'simplecov'
|
5
|
+
# SimpleCov.start
|
6
|
+
|
7
|
+
require "pp"
|
8
|
+
require "byebug"
|
9
|
+
root = File.expand_path("../", File.dirname(__FILE__))
|
10
|
+
require "#{root}/lib/importmap"
|
11
|
+
|
12
|
+
module Helper
|
13
|
+
def execute(cmd)
|
14
|
+
puts "Running: #{cmd}" if show_command?
|
15
|
+
out = `#{cmd}`
|
16
|
+
puts out if show_command?
|
17
|
+
out
|
18
|
+
end
|
19
|
+
|
20
|
+
# Added SHOW_COMMAND because DEBUG is also used by other libraries like
|
21
|
+
# bundler and it shows its internal debugging logging also.
|
22
|
+
def show_command?
|
23
|
+
ENV['DEBUG'] || ENV['SHOW_COMMAND']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec.configure do |c|
|
28
|
+
c.include Helper
|
29
|
+
end
|