importmap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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