importmap 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: 84dd54a2297318f246781b1acee8edd024c4ca2650a65a287269416d2dd903a3
4
+ data.tar.gz: 203d783fc05f8a442440aa17c072d8a875e11fbd16cc634406f1c348aa1f28af
5
+ SHA512:
6
+ metadata.gz: ffe2c28e6e9312108f9c0bdce4eabc1b5f8a30dc5c8428e266ed5ee7cf98d6918f48b79acf38dfea5f560dbd195a899b8c0faafa1567712d036b2bfcf9021023
7
+ data.tar.gz: 2a502c24447c2530fd3072a7f98a5f0577546e988469880a040240cb4a931a12e308d47443b6a6db54b8deadbfa9b78162b0133de06950c7550183a82a409d3a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ /.bundle
4
+ /.config
5
+ /.yardoc
6
+ /_yardoc
7
+ /coverage
8
+ /doc/
9
+ /Gemfile.lock
10
+ /InstalledFiles
11
+ /lib/bundler/man
12
+ /pkg
13
+ /rdoc
14
+ /spec/reports
15
+ /test/tmp
16
+ /test/version_tmp
17
+ /tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format documentation
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
+
6
+ ## [0.1.0]
7
+ - Initial release.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem dependencies in importmap.gemspec
4
+ gemspec
5
+
6
+ gem "codeclimate-test-reporter", group: :test, require: nil
data/Guardfile ADDED
@@ -0,0 +1,19 @@
1
+ guard "bundler", cmd: "bundle" do
2
+ watch("Gemfile")
3
+ watch(/^.+\.gemspec/)
4
+ end
5
+
6
+ guard :rspec, cmd: "bundle exec rspec" do
7
+ require "guard/rspec/dsl"
8
+ dsl = Guard::RSpec::Dsl.new(self)
9
+
10
+ # RSpec files
11
+ rspec = dsl.rspec
12
+ watch(rspec.spec_helper) { rspec.spec_dir }
13
+ watch(rspec.spec_support) { rspec.spec_dir }
14
+ watch(rspec.spec_files)
15
+
16
+ # Ruby files
17
+ ruby = dsl.ruby
18
+ dsl.watch_spec_files_for(ruby.lib_files)
19
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) Tung Nguyen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Importmap
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/jets-responders.png)](http://badge.fury.io/rb/jets-responders)
4
+
5
+ [![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
6
+
7
+ [![BoltOps Learn Badge](https://img.boltops.com/boltops-learn/boltops-learn.png)](https://learn.boltops.com)
8
+
9
+ Importmap library that is use as a part of [importmap-jets](https://github.com/rubyonjets/importmap-jets).
10
+
11
+ ## Usage
12
+
13
+ importmap audit # Run a security audit
14
+ importmap json # Show the full importmap in json
15
+ importmap outdated # Check for outdated packages
16
+ importmap packages # Print out packages with version numbers
17
+ importmap pin [*PACKAGES] # Pin new packages
18
+ importmap unpin [*PACKAGES] # Unpin existing packages
19
+ importmap version # prints version
20
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ task default: :spec
5
+
6
+ RSpec::Core::RakeTask.new
data/exe/importmap ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap ^C
4
+ Signal.trap("INT") {
5
+ puts "\nCtrl-C detected. Exiting..."
6
+ sleep 0.1
7
+ exit
8
+ }
9
+
10
+ $:.unshift(File.expand_path("../../lib", __FILE__))
11
+ require "importmap"
12
+ require "importmap/cli"
13
+
14
+ Importmap::CLI.start(ARGV)
data/importmap.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "importmap/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "importmap"
8
+ spec.version = Importmap::VERSION
9
+ spec.authors = ["Tung Nguyen"]
10
+ spec.email = ["tongueroo@gmail.com"]
11
+ spec.summary = "Importmap Library and CLI"
12
+ spec.homepage = "https://github.com/rubyonjets/importmap"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = File.directory?('.git') ? `git ls-files`.split($/) : Dir.glob("**/*")
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "memoist"
23
+ spec.add_dependency "rainbow"
24
+ spec.add_dependency "thor"
25
+ spec.add_dependency "zeitwerk"
26
+
27
+ spec.add_development_dependency "bundler"
28
+ spec.add_development_dependency "byebug"
29
+ spec.add_development_dependency "cli_markdown"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "rspec"
32
+ end
@@ -0,0 +1,22 @@
1
+ require "zeitwerk"
2
+
3
+ module Importmap
4
+ class Autoloader
5
+ class Inflector < Zeitwerk::Inflector
6
+ def camelize(basename, _abspath)
7
+ map = { cli: "CLI", version: "VERSION" }
8
+ map[basename.to_sym] || super
9
+ end
10
+ end
11
+
12
+ class << self
13
+ def setup
14
+ loader = Zeitwerk::Loader.new
15
+ loader.inflector = Inflector.new
16
+ lib = File.dirname(__dir__)
17
+ loader.push_dir(lib) # lib
18
+ loader.setup
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ ## Examples
2
+
3
+ importmap completion
4
+
5
+ Prints words for TAB auto-completion.
6
+
7
+ importmap completion
8
+ importmap completion hello
9
+ importmap completion hello name
10
+
11
+ To enable, TAB auto-completion add the following to your profile:
12
+
13
+ eval $(importmap completion_script)
14
+
15
+ Auto-completion example usage:
16
+
17
+ importmap [TAB]
18
+ importmap hello [TAB]
19
+ importmap hello name [TAB]
20
+ importmap hello name --[TAB]
@@ -0,0 +1,3 @@
1
+ To use, add the following to your `~/.bashrc` or `~/.profile`
2
+
3
+ eval $(importmap completion_script)
@@ -0,0 +1,5 @@
1
+ ## Examples
2
+
3
+ importmap pin
4
+ importmap pin NAME
5
+ importmap pin NAME --download
@@ -0,0 +1,11 @@
1
+ class Importmap::CLI
2
+ module Help
3
+ class << self
4
+ def text(namespaced_command)
5
+ path = namespaced_command.to_s.gsub(':','/')
6
+ path = File.expand_path("../help/#{path}.md", __FILE__)
7
+ IO.read(path) if File.exist?(path)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,156 @@
1
+ module Importmap
2
+ class CLI < Command
3
+ include Thor::Actions
4
+
5
+ desc "pin [*PACKAGES]", "Pin new packages"
6
+ option :env, type: :string, aliases: :e, default: "production"
7
+ option :from, type: :string, aliases: :f, default: "jspm"
8
+ option :download, type: :boolean, aliases: :d, default: false
9
+ def pin(*packages)
10
+ if imports = packager.import(*packages, env: options[:env], from: options[:from])
11
+ imports.each do |package, url|
12
+ if options[:download]
13
+ puts %(Pinning "#{package}" to #{packager.vendor_path}/#{package}.js via download from #{url})
14
+ packager.download(package, url)
15
+ pin = packager.vendored_pin_for(package, url)
16
+ else
17
+ puts %(Pinning "#{package}" to #{url})
18
+ pin = packager.pin_for(package, url)
19
+ end
20
+
21
+ if packager.packaged?(package)
22
+ gsub_file("config/importmap.rb", /^pin "#{package}".*$/, pin, verbose: false)
23
+ else
24
+ append_to_file("config/importmap.rb", "#{pin}\n", verbose: false)
25
+ end
26
+ end
27
+ else
28
+ puts "Couldn't find any packages in #{packages.inspect} on #{options[:from]}"
29
+ end
30
+ end
31
+
32
+ desc "unpin [*PACKAGES]", "Unpin existing packages"
33
+ option :env, type: :string, aliases: :e, default: "production"
34
+ option :from, type: :string, aliases: :f, default: "jspm"
35
+ option :download, type: :boolean, aliases: :d, default: false
36
+ def unpin(*packages)
37
+ if imports = packager.import(*packages, env: options[:env], from: options[:from])
38
+ imports.each do |package, url|
39
+ if packager.packaged?(package)
40
+ if options[:download]
41
+ puts %(Unpinning and removing "#{package}")
42
+ else
43
+ puts %(Unpinning "#{package}")
44
+ end
45
+
46
+ packager.remove(package)
47
+ end
48
+ end
49
+ else
50
+ puts "Couldn't find any packages in #{packages.inspect} on #{options[:from]}"
51
+ end
52
+ end
53
+
54
+ desc "json", "Show the full importmap in json"
55
+ def json
56
+ Importmap.framework_boot
57
+ puts Importmap.framework.application.importmap.to_json(resolver: ActionController::Base.helpers)
58
+ end
59
+
60
+ desc "audit", "Run a security audit"
61
+ def audit
62
+ vulnerable_packages = npm.vulnerable_packages
63
+
64
+ if vulnerable_packages.any?
65
+ table = [["Package", "Severity", "Vulnerable versions", "Vulnerability"]]
66
+ vulnerable_packages.each { |p| table << [p.name, p.severity, p.vulnerable_versions, p.vulnerability] }
67
+
68
+ puts_table(table)
69
+ vulnerabilities = 'vulnerability'.pluralize(vulnerable_packages.size)
70
+ severities = vulnerable_packages.map(&:severity).tally.sort_by(&:last).reverse
71
+ .map { |severity, count| "#{count} #{severity}" }
72
+ .join(", ")
73
+ puts " #{vulnerable_packages.size} #{vulnerabilities} found: #{severities}"
74
+
75
+ exit 1
76
+ else
77
+ puts "No vulnerable packages found"
78
+ end
79
+ end
80
+
81
+ desc "outdated", "Check for outdated packages"
82
+ def outdated
83
+ outdated_packages = npm.outdated_packages
84
+
85
+ if outdated_packages.any?
86
+ table = [["Package", "Current", "Latest"]]
87
+ outdated_packages.each { |p| table << [p.name, p.current_version, p.latest_version || p.error] }
88
+
89
+ puts_table(table)
90
+ packages = 'package'.pluralize(outdated_packages.size)
91
+ puts " #{outdated_packages.size} outdated #{packages} found"
92
+
93
+ exit 1
94
+ else
95
+ puts "No outdated packages found"
96
+ end
97
+ end
98
+
99
+ desc "packages", "Print out packages with version numbers"
100
+ def packages
101
+ puts npm.packages_with_versions.map { |x| x.join(' ') }
102
+ end
103
+
104
+ desc "completion *PARAMS", "Prints words for auto-completion."
105
+ long_desc Help.text(:completion)
106
+ def completion(*params)
107
+ Completer.new(CLI, *params).run
108
+ end
109
+
110
+ desc "completion_script", "Generates a script that can be eval to setup auto-completion."
111
+ long_desc Help.text(:completion_script)
112
+ def completion_script
113
+ Completer::Script.generate
114
+ end
115
+
116
+ desc "version", "prints version"
117
+ def version
118
+ puts Importmap::VERSION
119
+ end
120
+
121
+ private
122
+ def packager
123
+ @packager ||= Importmap::Packager.new
124
+ end
125
+
126
+ def npm
127
+ @npm ||= Importmap::Npm.new
128
+ end
129
+
130
+ def remove_line_from_file(path, pattern)
131
+ path = File.expand_path(path, destination_root)
132
+
133
+ all_lines = File.readlines(path)
134
+ with_lines_removed = all_lines.select { |line| line !~ pattern }
135
+
136
+ File.open(path, "w") do |file|
137
+ with_lines_removed.each { |line| file.write(line) }
138
+ end
139
+ end
140
+
141
+ def puts_table(array)
142
+ column_sizes = array.reduce([]) do |lengths, row|
143
+ row.each_with_index.map{ |iterand, index| [lengths[index] || 0, iterand.to_s.length].max }
144
+ end
145
+
146
+ puts head = "+" + (column_sizes.map { |s| "-" * (s + 2) }.join('+')) + '+'
147
+ array.each_with_index do |row, row_number|
148
+ row = row.fill(nil, row.size..(column_sizes.size - 1))
149
+ row = row.each_with_index.map { |v, i| v.to_s + " " * (column_sizes[i] - v.to_s.length) }
150
+ puts "| " + row.join(" | ") + " |"
151
+ puts head if row_number == 0
152
+ end
153
+ puts head
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,89 @@
1
+ require "thor"
2
+
3
+ # Override thor's long_desc identation behavior
4
+ # https://github.com/erikhuda/thor/issues/398
5
+ class Thor
6
+ module Shell
7
+ class Basic
8
+ def print_wrapped(message, options = {})
9
+ message = "\n#{message}" unless message[0] == "\n"
10
+ stdout.puts message
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ module Importmap
17
+ class Command < Thor
18
+ class << self
19
+ def dispatch(m, args, options, config)
20
+ # Allow calling for help via:
21
+ # importmap command help
22
+ # importmap command -h
23
+ # importmap command --help
24
+ # importmap command -D
25
+ #
26
+ # as well thor's normal way:
27
+ #
28
+ # importmap help command
29
+ help_flags = Thor::HELP_MAPPINGS + ["help"]
30
+ if args.length > 1 && !(args & help_flags).empty?
31
+ args -= help_flags
32
+ args.insert(-2, "help")
33
+ end
34
+
35
+ # importmap version
36
+ # importmap --version
37
+ # importmap -v
38
+ version_flags = ["--version", "-v"]
39
+ if args.length == 1 && !(args & version_flags).empty?
40
+ args = ["version"]
41
+ end
42
+
43
+ super
44
+ end
45
+
46
+ # Override command_help to include the description at the top of the
47
+ # long_description.
48
+ def command_help(shell, command_name)
49
+ meth = normalize_command_name(command_name)
50
+ command = all_commands[meth]
51
+ alter_command_description(command)
52
+ super
53
+ end
54
+
55
+ def alter_command_description(command)
56
+ return unless command
57
+
58
+ # Add description to beginning of long_description
59
+ long_desc = if command.long_description
60
+ "#{command.description}\n\n#{command.long_description}"
61
+ else
62
+ command.description
63
+ end
64
+
65
+ # add reference url to end of the long_description
66
+ unless website.empty?
67
+ full_command = [command.ancestor_name, command.name].compact.join('-')
68
+ url = "#{website}/reference/importmap-#{full_command}"
69
+ long_desc += "\n\nHelp also available at: #{url}"
70
+ end
71
+
72
+ command.long_description = long_desc
73
+ end
74
+ private :alter_command_description
75
+
76
+ # meant to be overriden
77
+ def website
78
+ ""
79
+ end
80
+
81
+ # https://github.com/erikhuda/thor/issues/244
82
+ # Deprecation warning: Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `Lono::CLI`
83
+ # You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.
84
+ def exit_on_failure?
85
+ true
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ class Importmap::Completer
2
+ class Script
3
+ def self.generate
4
+ bash_script = File.expand_path("script.sh", File.dirname(__FILE__))
5
+ puts "source #{bash_script}"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ _importmap() {
2
+ COMPREPLY=()
3
+ local word="${COMP_WORDS[COMP_CWORD]}"
4
+ local words=("${COMP_WORDS[@]}")
5
+ unset words[0]
6
+ local completion=$(importmap completion ${words[@]})
7
+ COMPREPLY=( $(compgen -W "$completion" -- "$word") )
8
+ }
9
+
10
+ complete -F _importmap importmap
@@ -0,0 +1,158 @@
1
+ =begin
2
+ Code Explanation:
3
+
4
+ There are 3 types of things to auto-complete:
5
+
6
+ 1. command: the command itself
7
+ 2. parameters: command parameters.
8
+ 3. options: command options
9
+
10
+ Here's an example:
11
+
12
+ mycli hello name --from me
13
+
14
+ * command: hello
15
+ * parameters: name
16
+ * option: --from
17
+
18
+ When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
19
+
20
+ ## Arity
21
+
22
+ For example, say you had a method for a CLI command with the following form:
23
+
24
+ CLI COMMAND service count --cluster development
25
+
26
+ It's equivalent ruby method:
27
+
28
+ scale(service, count) = has an arity of 2
29
+
30
+ So typing:
31
+
32
+ CLI COMMAND service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
33
+
34
+ So the completion should only show options, something like this:
35
+
36
+ --noop --verbose --cluster
37
+
38
+ ## Splat Arguments
39
+
40
+ When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
41
+
42
+ ship(service) = 1
43
+ scale(service, count) = 2
44
+ foo(example, *rest) = -2
45
+
46
+ Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
47
+
48
+ Here are some test cases, hit TAB after typing the command:
49
+
50
+ importmap completion
51
+ importmap completion hello
52
+ importmap completion hello name
53
+ importmap completion hello name --
54
+ importmap completion hello name --noop
55
+
56
+ importmap completion
57
+ importmap completion sub:goodbye
58
+ importmap completion sub:goodbye name
59
+
60
+ ## Subcommands and Thor::Group Registered Commands
61
+
62
+ Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the CLI COMMAND.
63
+
64
+ * regular command: CLI COMMAND
65
+ * subcommand: CLI COMMAND
66
+ * Thor::Group command: CLI COMMAND
67
+
68
+ Auto-completion accounts for each of these type of commands.
69
+ =end
70
+ module Importmap
71
+ class Completer
72
+ def initialize(command_class, *params)
73
+ @params = params
74
+ @current_command = @params[0]
75
+ @command_class = command_class # CLI initiall
76
+ end
77
+
78
+ def run
79
+ if subcommand?(@current_command)
80
+ subcommand_class = @command_class.subcommand_classes[@current_command]
81
+ @params.shift # destructive
82
+ Completer.new(subcommand_class, *@params).run # recursively use subcommand
83
+ return
84
+ end
85
+
86
+ # full command has been found!
87
+ unless found?(@current_command)
88
+ puts all_commands
89
+ return
90
+ end
91
+
92
+ # will only get to here if command aws found (above)
93
+ arity = @command_class.instance_method(@current_command).arity.abs
94
+ if @params.size > arity or thor_group_command?
95
+ puts options_completion
96
+ else
97
+ puts params_completion
98
+ end
99
+ end
100
+
101
+ def subcommand?(command)
102
+ @command_class.subcommands.include?(command)
103
+ end
104
+
105
+ # hacky way to detect that command is a registered Thor::Group command
106
+ def thor_group_command?
107
+ command_params(raw=true) == [[:rest, :args]]
108
+ end
109
+
110
+ def found?(command)
111
+ public_methods = @command_class.public_instance_methods(false)
112
+ command && public_methods.include?(command.to_sym)
113
+ end
114
+
115
+ # all top-level commands
116
+ def all_commands
117
+ commands = @command_class.all_commands.reject do |k,v|
118
+ v.is_a?(Thor::HiddenCommand)
119
+ end
120
+ commands.keys
121
+ end
122
+
123
+ def command_params(raw=false)
124
+ params = @command_class.instance_method(@current_command).parameters
125
+ # Example:
126
+ # >> Sub.instance_method(:goodbye).parameters
127
+ # => [[:req, :name]]
128
+ # >>
129
+ raw ? params : params.map!(&:last)
130
+ end
131
+
132
+ def params_completion
133
+ offset = @params.size - 1
134
+ offset_params = command_params[offset..-1]
135
+ command_params[offset..-1].first
136
+ end
137
+
138
+ def options_completion
139
+ used = ARGV.select { |a| a.include?('--') } # so we can remove used options
140
+
141
+ method_options = @command_class.all_commands[@current_command].options.keys
142
+ class_options = @command_class.class_options.keys
143
+
144
+ all_options = method_options + class_options + ['help']
145
+
146
+ all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
147
+ filtered_options = all_options - used
148
+ filtered_options.uniq
149
+ end
150
+
151
+ # Useful for debugging. Using puts messes up completion.
152
+ def log(msg)
153
+ File.open("/tmp/complete.log", "a") do |file|
154
+ file.puts(msg)
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,11 @@
1
+ class Importmap::Framework
2
+ module JetsFramework
3
+ def framework_class
4
+ Jets
5
+ end
6
+
7
+ def boot
8
+ Jets.boot
9
+ end
10
+ end
11
+ end