grundler 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: 7410db73be9001084a6c93445378025b66d07326006d0a161e9881949330bf8a
4
+ data.tar.gz: '09eac82869728e54c233493ee6542956cfcda0f7950e7c55ac1ec45117d1dbe4'
5
+ SHA512:
6
+ metadata.gz: 998cf5e2ed076fb77b5ad53f1e6f3b922906045d6429d045b4a146b1349d276d93e326dddee50ad0cbd78c6bd19ea64f705e9e80100eb36f2927a465e613c05e
7
+ data.tar.gz: a02dfd2d7052e1de7977e9ab587208db688c69522ee2d770c3cf6852565f5e03d9282d889dec0948489ea1c89f5909b42587e87567f25d75fee37d96c08e20ff
@@ -0,0 +1,18 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.0
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.5
17
+ bundle install
18
+ bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4.0
3
+ NewCops: enable
4
+
5
+ Layout/LineLength:
6
+ Max: 120
7
+
8
+ Metrics/MethodLength:
9
+ Max: 24
10
+
11
+ Style/Documentation:
12
+ Enabled: false
13
+
14
+ Style/FrozenStringLiteralComment:
15
+ EnforcedStyle: never
16
+
17
+ Style/StringLiterals:
18
+ Enabled: true
19
+ EnforcedStyle: double_quotes
20
+
21
+ Style/StringLiteralsInInterpolation:
22
+ Enabled: true
23
+ EnforcedStyle: double_quotes
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in grundler.gemspec
4
+ gemspec
5
+
6
+ gem "minitest", "~> 5.0"
7
+ gem "rake", "~> 13.0"
8
+ gem "rubocop", "~> 1.7"
9
+ gem "rubocop-minitest", "~> 0.10.3"
10
+ gem "rubocop-rake", "~> 0.5.1"
11
+ gem "webmock", "~> 3.11.1"
data/Gemfile.lock ADDED
@@ -0,0 +1,82 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ grundler (0.1.0)
5
+ down (~> 5.2.0)
6
+ http (~> 4.3.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.7.0)
12
+ public_suffix (>= 2.0.2, < 5.0)
13
+ ast (2.4.1)
14
+ crack (0.4.5)
15
+ rexml
16
+ domain_name (0.5.20190701)
17
+ unf (>= 0.0.5, < 1.0.0)
18
+ down (5.2.0)
19
+ addressable (~> 2.5)
20
+ ffi (1.14.2)
21
+ ffi-compiler (1.0.1)
22
+ ffi (>= 1.0.0)
23
+ rake
24
+ hashdiff (1.0.1)
25
+ http (4.3.0)
26
+ addressable (~> 2.3)
27
+ http-cookie (~> 1.0)
28
+ http-form_data (~> 2.2)
29
+ http-parser (~> 1.2.0)
30
+ http-cookie (1.0.3)
31
+ domain_name (~> 0.5)
32
+ http-form_data (2.3.0)
33
+ http-parser (1.2.3)
34
+ ffi-compiler (>= 1.0, < 2.0)
35
+ minitest (5.14.2)
36
+ parallel (1.20.1)
37
+ parser (3.0.0.0)
38
+ ast (~> 2.4.1)
39
+ public_suffix (4.0.6)
40
+ rainbow (3.0.0)
41
+ rake (13.0.3)
42
+ regexp_parser (2.0.3)
43
+ rexml (3.2.4)
44
+ rubocop (1.8.1)
45
+ parallel (~> 1.10)
46
+ parser (>= 3.0.0.0)
47
+ rainbow (>= 2.2.2, < 4.0)
48
+ regexp_parser (>= 1.8, < 3.0)
49
+ rexml
50
+ rubocop-ast (>= 1.2.0, < 2.0)
51
+ ruby-progressbar (~> 1.7)
52
+ unicode-display_width (>= 1.4.0, < 3.0)
53
+ rubocop-ast (1.4.0)
54
+ parser (>= 2.7.1.5)
55
+ rubocop-minitest (0.10.3)
56
+ rubocop (>= 0.87, < 2.0)
57
+ rubocop-rake (0.5.1)
58
+ rubocop
59
+ ruby-progressbar (1.11.0)
60
+ unf (0.1.4)
61
+ unf_ext
62
+ unf_ext (0.0.7.7)
63
+ unicode-display_width (2.0.0)
64
+ webmock (3.11.1)
65
+ addressable (>= 2.3.6)
66
+ crack (>= 0.3.2)
67
+ hashdiff (>= 0.4.0, < 2.0.0)
68
+
69
+ PLATFORMS
70
+ x86_64-darwin-19
71
+
72
+ DEPENDENCIES
73
+ grundler!
74
+ minitest (~> 5.0)
75
+ rake (~> 13.0)
76
+ rubocop (~> 1.7)
77
+ rubocop-minitest (~> 0.10.3)
78
+ rubocop-rake (~> 0.5.1)
79
+ webmock (~> 3.11.1)
80
+
81
+ BUNDLED WITH
82
+ 2.2.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Johan Halse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # Grundler
2
+
3
+ The no-faff Ruby frontend bundler.
4
+
5
+ Now that all the evergreen browsers have support for JavaScript module loading, we can finally pretend like node.js never happened! Grundler is the sinecure for everyone who's sick to death of npm, yarn, babel, webpack, parcel, rollup, vite, fartspray, and snowpack. And I only made one of those up.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem "grundler"
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install grundler
22
+
23
+ ## But we have enough package managers, please stop
24
+
25
+ Sorry! But Grundler fills one of the most _important niches_ in the Ruby ecosystem: MINE. Specifically, that would be the niche where I sit, precariously perched, cursing the bloat and complexity of node's module ecosystem, longing for the easy and carefree days of dropping a .js file into a "lib" folder and calling it a day. Seriously, we got ourselves into this hellscape just because we didn't think globals were enterprise-grade or whatever?! Like what were people even
26
+
27
+ ## Alright, settle down, explain how it works
28
+
29
+ You've totally used a package manager before. You'll need to use one to install this one, in fact. So you probably know most of the stuff you need to know. Grundler, as a design goal, tries to 1) leverage the new browser-supported module format and 2) do as little as possible. You won't find any script sections or dev dependencies in its configuration. You can't run commands through it, shrinkwrap, or add prepublish steps. It'll only grab packages for you off npm's repository so you can use them and you should be damn well content with that.
30
+
31
+ ## Please tell me you didn't invent a new configuration file though
32
+
33
+ I'm not below that sort of thing but no, it reads your `package.json` kind of like you'd expect. Grundler only bothers with the important part, the `{ "dependencies" }` section. If you expect it to also run your dev server or test suite or whatever, you will be disappointed. You might be disappointed anyway but I'd honestly prefer you at least be disappointed with the right things?
34
+
35
+ ## So what actually happens when I add a package then
36
+
37
+ Grundler goes to everyone's favorite JavaScript repository, [npm](https://www.npmjs.com/), to look for the package you wanted to install. If it's found, it'll look up the most recent version, download its tarball, and analyze the JSON metadata to see if it uses the new ES module format. If it does, bingo, we can untar the module file _and only the module file_ to our dependency folder (currently called [nodules](https://twitter.com/hejsna/status/987043794427240448) because come on, it was **right there** but the node people were too buttoned-up I guess). This is cool because it's reminiscent of the old "go to someone's site and download a JS file" but structured and reproducible.
38
+
39
+ If the package you're trying to fetch isn't using ES module format though, you're in trouble.
40
+
41
+ ## Trouble
42
+
43
+ Trouble. Grundler has a thing I've aptly named `CrapMode` that gets engaged whenever a package is using one of the old bothersome and browser-incompatible module formats (which, unfortunately, is pretty often). It will wrap the library in a simple little shim of sorts, to fool the module into believing it can hook into `module.exports` but AHA that's a honeypot we put there and then we can export. It's bound to fail in mysterious ways but it turns out there are a lot of packages that work just fine with it.
44
+
45
+ So you may get lucky. Or you may not. People have really been contorting themselves over the years doing weird shit to get their packages loading across the various formats the Internet has coughed up, and `module.exports` might not be a thing they use. Either way, if the package is one you really want, the responsible and adult thing to do is to go to their repository and help them out with a PR to convert to the new module format. Node has had support for quite a while, now browsers have it too — we're finally on track towards the compilation-free past/future again. Contribute and be part of it!
46
+
47
+ ## And how do I use my freshly downloaded package again
48
+
49
+ You import it, like you would any file you wrote yourself. Look:
50
+
51
+ ```bash
52
+ $ bundle exec grundle add three
53
+ Installing three 0.124.0
54
+ ```
55
+
56
+ That should result in a file called `nodules/three.js`. That's your package, ready for pickup! Now, in order to import things in the browser, you need to opt in to the browser's module system by using `type="module"` in your script tag:
57
+
58
+ ```html
59
+ <script src="main.js" type="module"></script>
60
+ ```
61
+
62
+ And inside main.js you can do:
63
+
64
+ ```javascript
65
+ import * as THREE from "./nodules/three.js";
66
+ console.log(THREE);
67
+ ```
68
+
69
+ And if that thing is an ES module, or the CrapMode shim worked, you're good to go!
70
+
71
+ ## That import statement though, slashes and stuff, ew
72
+
73
+ I hear you. It's not as pretty as just `from "three"`. People are working on this problem, with something called [import maps](https://github.com/WICG/import-maps). But it's not THAT ugly and look: the code is there. It can be loaded, just the way you'd expect if a teammate had written the code. You don't have npm's hundreds of megabytes of dep-tree lurking beneath the depths, ready to kneecap your CI system and bog down your builds. Let's hope we can all get back to the fairytale land of "it's just another file in your project". I'd even check the `nodules` folder in to your project if I were you — after all, in the end you're responsible for everything you ship, so why do we keep pretending our dependencies are this pristine thing, never to be touched?
74
+
75
+ ## But I'm using TypeScript, if you just download the compiled and minified JS distribution I won't have my d.ts files and
76
+
77
+ Go away! Contemplate your life choices. I will have no truck with your transpiled abomination. The entire point of Grundler is to dramatically shorten the build chain, get back to just writing JS files, doing away with build servers and transpilation once and for all. Plus everyone knows TypeScript is just .NET wearing JavaScript's skin, ready to murder you in your sleep with a rictus grin on its rotting, lying face.
78
+
79
+ ## Ok but what about deployment, won't this ruin my artisanal never-expiring cached CDN
80
+
81
+ Yes. I'm throwing my hands up here and again saying look, I'm sorry, but browsers don't do the import maps thing yet. And adding version numbers to downloaded libs would be murder on your actual source files: going through every file in your project that has `import * from thing-0.10.1.js` and replacing it with `thing-0.10.2.js` every time you bumped versions would absolutely SUCK.
82
+
83
+ You realistically have three options at this time:
84
+
85
+ 1) Weep and use Webpack
86
+ 2) Ditch your far-future cache and trust people's browsers to do the right thing
87
+ 3) Use an import-aware fingerprint tool when building for production
88
+
89
+ ## But... there IS no import-aware fingerprinting tool
90
+
91
+ Ha! Pop quiz, hotshot: what has two thumbs and has just written that kind of fingerprinting tool? It trawls a directory full of js files, copies them to md5-stamped files, and rewrites all import statements to use the stamped files. I'll put it up once it's been documented and more thoroughly tested. And once I've made sure it's actually a good idea.
92
+
93
+ Yes, yes, it's a build step, which makes me a little sad, but fingers crossed we can get rid of it when browsers have robust support for import maps. I bet the Babel crew have said something similar but then it kind of never happened and the tools got entrenched and suddenly everyone's stuck in the labyrinth with no way out and an angry minotaur belching somewhere behind them. But you can trust me! I'm a very honest-looking guy and I totally mean it: ship import maps and this won't be a problem anymore.
94
+
95
+ ## Cool, how do I use Grundler then
96
+
97
+ Grundler tries its best to not do very much. No spiderweb of metadata or configuration, no weird executable sandbox things, and only four commands:
98
+
99
+ ### Adding packages
100
+
101
+ Run `grundle add [list of packages]`. If you wanted to install, say, [three.js](https://github.com/mrdoob/three.js/) and [ky](https://github.com/sindresorhus/ky/) and have installed Grundler from your Gemfile it'd look like this:
102
+
103
+ ```bash
104
+ bundle exec grundle add three ky
105
+ ```
106
+
107
+ Grundler will go and find the latest version of those packages, install them, and add them to the `package.json` file.
108
+
109
+ ### Installing from package.json
110
+
111
+ Once you've added a few packages, you can distribute the package.json and install using that, and Grundler will fetch the version specified. To install from package.json, run:
112
+
113
+ ```bash
114
+ bundle exec grundle install
115
+ ```
116
+
117
+ ### Updating packages
118
+
119
+ Sometimes a package has been updated! To grab all the newest versions, run:
120
+
121
+ ```bash
122
+ bundle exec grundle update
123
+ ```
124
+
125
+ All your packages will be updated and written to package.json. You currently can't use the `update` command to update single packages, but Grundler is dumb enough that adding the packages again by running `add package1 package2` will do that for you.
126
+
127
+ ### Removing packages
128
+
129
+ Tired of a package? No module export and CrapMode didn't work? Run:
130
+
131
+ ```bash
132
+ bundle exec grundle remove packagename
133
+ ```
134
+
135
+ It'll be removed from your nodules folder and your `package.json` file. Grundler will also get rid of any empty directories left after a removal.
136
+
137
+ ## Configuration
138
+
139
+ There's only one configuration directive at this point and it's called `nodulePath`. Say you have all your JS source files in a directory called `src`. Then you might want your dependencies to live in `src/nodules`. Add the `nodulePath` directive to your package.json like so:
140
+ ```json
141
+ {
142
+ "dependencies": {},
143
+ "nodulePath": "./src/nodules"
144
+ }
145
+ ```
146
+
147
+ That'll make all of Grundler's operations work on that directory instead.
148
+
149
+ ## Development
150
+
151
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
152
+
153
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
154
+
155
+ Testing is done with [Minitest](https://github.com/seattlerb/minitest) and mocking of npm is done with [webmock](https://github.com/bblimke/webmock). I initially wanted to use VCR for the JSON stubs but there seemed to be a lot of configuration and nothing worked and so I just put them there by hand and this is all TMI and never mind, there's like 200 lines of tests in a single file, you'll get the hang of it quickly.
156
+
157
+ ## Contributing
158
+
159
+ Bug reports and pull requests are welcome on GitHub at https://github.com/johanhalse/grundler.
160
+
161
+ ## License
162
+
163
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ require "rubocop/rake_task"
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require "bundler/setup"
3
+ require "grundler"
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require "pry"
10
+ # Pry.start
11
+
12
+ require "irb"
13
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/grundle ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "grundler"
3
+
4
+ Grundler::CLI.new
data/grundler.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ require_relative "lib/grundler/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "grundler"
5
+ spec.version = Grundler::VERSION
6
+ spec.authors = ["Johan Halse"]
7
+ spec.email = ["johan@hal.se"]
8
+
9
+ spec.summary = "The no-faff frontend bundler"
10
+ spec.description = "Grundler is the simplest (as in most understandable) way to install npm packages."
11
+ spec.homepage = "https://github.com/johanhalse/grundler"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
14
+
15
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/johanhalse/grundler"
19
+ spec.metadata["changelog_uri"] = "https://github.com/johanhalse/grundler/CHANGES.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "down", "~> 5.2.0"
31
+ spec.add_dependency "http", "~> 4.3.0"
32
+ end
data/lib/grundler.rb ADDED
@@ -0,0 +1,43 @@
1
+ require "http"
2
+ require "down/http"
3
+
4
+ require_relative "grundler/version"
5
+ require_relative "grundler/package_json_writer"
6
+ require_relative "grundler/commands/add"
7
+ require_relative "grundler/commands/install"
8
+ require_relative "grundler/commands/update"
9
+ require_relative "grundler/commands/remove"
10
+ require_relative "grundler/commands/help"
11
+
12
+ module Grundler
13
+ DEFAULT_NODULE_PATH = "#{Dir.pwd}/nodules".freeze
14
+ LOCKFILE_PATH = "#{Dir.pwd}/package.json".freeze
15
+ COMMANDS = %w[add install update remove help].freeze
16
+
17
+ class CLI
18
+ def initialize
19
+ json_writer = PackageJsonWriter.new(lockfile_path)
20
+ Grundler::Commands.const_get(current_command).new(self, arguments, json_writer)
21
+ end
22
+
23
+ def current_command
24
+ COMMANDS.find { |c| ARGV.first == c }&.capitalize || "Help"
25
+ end
26
+
27
+ def arguments
28
+ @switches = ARGV.select { |a| a[0] == "-" }
29
+
30
+ ARGV.drop(1) - @switches
31
+ end
32
+
33
+ def nodule_path
34
+ @nodule_path ||=
35
+ (JSON.parse(File.read(LOCKFILE_PATH))["nodulePath"] if File.exist?(LOCKFILE_PATH)) ||
36
+ DEFAULT_NODULE_PATH
37
+ end
38
+
39
+ def lockfile_path
40
+ LOCKFILE_PATH
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "common"
2
+
3
+ module Grundler
4
+ module Commands
5
+ class Add
6
+ include Common
7
+
8
+ def initialize(cli, packages, json_writer)
9
+ super(cli)
10
+ if packages.empty?
11
+ puts "Must specify a package name!"
12
+ return
13
+ end
14
+
15
+ json_writer.add(added_packages(packages))
16
+ end
17
+
18
+ private
19
+
20
+ def added_packages(packages)
21
+ packages
22
+ .map { |package_name| install(latest_version(package_name)) }
23
+ .compact
24
+ .map { |package| [package["name"], package["version"]] }
25
+ .to_h
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ require "grundler/good_mode"
2
+ require "grundler/crap_mode"
3
+
4
+ module Grundler
5
+ module Commands
6
+ module Common
7
+ def initialize(cli)
8
+ @cli = cli
9
+ end
10
+
11
+ def module?(version)
12
+ !version["module"].nil? || version["type"] == "module"
13
+ end
14
+
15
+ def latest_version(package_name)
16
+ package_metadata = JSON.parse(HTTP.get("https://registry.npmjs.org/#{package_name}").to_s)
17
+ latest_version_number = package_metadata.dig("dist-tags", "latest")
18
+ package_metadata["error"] || package_metadata.dig("versions", latest_version_number)
19
+ end
20
+
21
+ def install(version)
22
+ return no_such_package if version == "Not found"
23
+
24
+ if module?(version)
25
+ GoodMode.new(@cli, version).write
26
+ else
27
+ CrapMode.new(@cli, version).write
28
+ end
29
+
30
+ version
31
+ end
32
+
33
+ def no_such_package
34
+ puts "That package could not be found in the npm repository!"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ module Grundler
2
+ module Commands
3
+ class Help
4
+ def initialize(_cli, _arguments, _json_writer)
5
+ puts Help.help_text
6
+ end
7
+
8
+ def self.help_text
9
+ <<~HELP_TEXT
10
+
11
+ Usage: grundle <command>
12
+
13
+ grundle add add package to project
14
+ grundle install install all packages in lockfile
15
+ grundle update update all packages in lockfile
16
+ grundle remove remove a package from project
17
+ grundle help display this help
18
+
19
+ HELP_TEXT
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require_relative "common"
2
+
3
+ module Grundler
4
+ module Commands
5
+ class Install
6
+ include Common
7
+
8
+ def initialize(cli, _arguments, _json_writer)
9
+ super(cli)
10
+ unless File.exist?(Grundler::LOCKFILE_PATH)
11
+ puts "No #{Grundler::LOCKFILE_PATH} file found!"
12
+ return
13
+ end
14
+
15
+ loaded_versions = JSON.parse(File.read(Grundler::LOCKFILE_PATH))["dependencies"]
16
+ loaded_versions.each { |k, v| install(specific_version(k, v)) }
17
+ end
18
+
19
+ private
20
+
21
+ def specific_version(package_name, version_number)
22
+ package_metadata = JSON.parse(HTTP.get("https://registry.npmjs.org/#{package_name}").to_s)
23
+ package_metadata.dig("versions", version_number.delete_prefix("^")) || "Not found"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require_relative "common"
2
+
3
+ module Grundler
4
+ module Commands
5
+ class Remove
6
+ include Common
7
+
8
+ def initialize(cli, packages, json_writer)
9
+ super(cli)
10
+ unless File.exist?(Grundler::LOCKFILE_PATH)
11
+ puts "No #{Grundler::LOCKFILE_PATH} file found!"
12
+ return
13
+ end
14
+
15
+ if packages.empty?
16
+ puts "Must specify a package name!"
17
+ return
18
+ end
19
+
20
+ json_writer.remove(delete(packages))
21
+ end
22
+
23
+ private
24
+
25
+ def delete(packages)
26
+ packages.each do |package|
27
+ puts "Removing #{package}"
28
+ FileUtils.rm "#{@cli.nodule_path}/#{package}.js" if File.exist?("#{@cli.nodule_path}/#{package}.js")
29
+ remove_directory_if_empty(package)
30
+ end
31
+
32
+ packages.map { |package| [package, package] }.to_h
33
+ end
34
+
35
+ def remove_directory_if_empty(package)
36
+ dirname = File.dirname("#{@cli.nodule_path}/#{package}.js")
37
+ FileUtils.rm_rf dirname if Dir.exist?(dirname) && Dir.empty?(dirname)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "common"
2
+
3
+ module Grundler
4
+ module Commands
5
+ class Update
6
+ include Common
7
+
8
+ def initialize(cli, _arguments, json_writer)
9
+ super(cli)
10
+ unless File.exist?(Grundler::LOCKFILE_PATH)
11
+ puts "No #{Grundler::LOCKFILE_PATH} file found!"
12
+ return
13
+ end
14
+
15
+ json_writer.add(
16
+ json_writer.existing_packages
17
+ .map { |k, v| update(k, latest_version(k), v) }
18
+ .compact
19
+ .to_h
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def update(name, latest_version, current_version_number)
26
+ if latest_version["version"] == current_version_number
27
+ puts "#{latest_version["name"]} is already the latest version (#{current_version_number})"
28
+ return
29
+ end
30
+
31
+ [name, install(latest_version)["version"]]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require_relative "mode"
2
+
3
+ class CrapMode < Mode
4
+ def write
5
+ notify
6
+ File.write(file_path, process(untar))
7
+ rescue Mode::NoEntryPointError
8
+ puts "\e[31mNo entry point found for file #{@version["name"]}\e[0m"
9
+ end
10
+
11
+ private
12
+
13
+ def notify
14
+ super
15
+
16
+ puts "\e[36mPackage #{@version["name"]} has no module defined and will be written in compatibility mode."
17
+ puts "If that doesn't work, you should consider opening a pull request to add ES module support."
18
+ puts "The package repository is here: #{@version.dig("repository", "url")}\e[0m"
19
+ end
20
+
21
+ def process(str)
22
+ %(
23
+ var module = { exports: {} };
24
+ (function(){#{str}}).call(window);
25
+ export default module.exports;
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,10 @@
1
+ require_relative "mode"
2
+
3
+ class GoodMode < Mode
4
+ def write
5
+ notify
6
+ File.write(file_path, untar)
7
+ rescue Mode::NoEntryPointError
8
+ puts "\e[31mNo entry point found for file #{@version["name"]}\e[0m"
9
+ end
10
+ end
@@ -0,0 +1,49 @@
1
+ require "rubygems/package"
2
+ require "zlib"
3
+
4
+ class Mode
5
+ class NoEntryPointError < StandardError; end
6
+
7
+ def initialize(cli, version)
8
+ @cli = cli
9
+ @version = version
10
+ end
11
+
12
+ private
13
+
14
+ def notify
15
+ puts "Installing #{@version["name"]} #{@version["version"]}"
16
+ end
17
+
18
+ def untar
19
+ tar = Gem::Package::TarReader.new(Zlib::GzipReader.open(tempfile.path))
20
+ file = tar.find { |f| f.full_name == index_file }
21
+ tar.close
22
+
23
+ raise NoEntryPointError if file.nil?
24
+
25
+ file.read
26
+ end
27
+
28
+ def tempfile
29
+ Down::Http.download(@version.dig("dist", "tarball"))
30
+ end
31
+
32
+ def file_path
33
+ file_path = "#{@cli.nodule_path}/#{@version["name"]}.js"
34
+ FileUtils.mkdir_p(File.dirname(file_path))
35
+ file_path
36
+ end
37
+
38
+ def index_file
39
+ @index_file ||= Pathname.new(index_file_path).cleanpath.to_s
40
+ end
41
+
42
+ def index_file_path
43
+ return "package/#{@version["module"]}" if @version["module"]
44
+ return "package/#{@version["exports"]}" if @version["exports"]
45
+ return "package/#{@version["main"]}" if @version["main"]
46
+
47
+ "package/index.js"
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ require "json"
2
+
3
+ module Grundler
4
+ class PackageJsonWriter
5
+ def initialize(path)
6
+ @path = path
7
+ end
8
+
9
+ def add(packages)
10
+ write(existing_packages.merge(packages))
11
+ end
12
+
13
+ def remove(packages)
14
+ write(
15
+ existing_packages.delete_if { |k, _v| packages.include?(k) }
16
+ )
17
+ end
18
+
19
+ def existing_packages
20
+ existing_content["dependencies"]
21
+ end
22
+
23
+ private
24
+
25
+ def write(packages)
26
+ File.write(
27
+ @path,
28
+ JSON.pretty_generate(
29
+ existing_content.merge({ "dependencies" => packages })
30
+ )
31
+ )
32
+ end
33
+
34
+ def existing_content
35
+ @existing_content ||= load_existing_content
36
+ end
37
+
38
+ def load_existing_content
39
+ JSON.parse(File.read(@path))
40
+ rescue StandardError
41
+ { "dependencies" => {} }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Grundler
2
+ VERSION = "0.1.0".freeze
3
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grundler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Johan Halse
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-04-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: down
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: http
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.3.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.3.0
41
+ description: Grundler is the simplest (as in most understandable) way to install npm
42
+ packages.
43
+ email:
44
+ - johan@hal.se
45
+ executables:
46
+ - grundle
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - ".github/workflows/main.yml"
51
+ - ".gitignore"
52
+ - ".rubocop.yml"
53
+ - Gemfile
54
+ - Gemfile.lock
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - bin/console
59
+ - bin/setup
60
+ - exe/grundle
61
+ - grundler.gemspec
62
+ - lib/grundler.rb
63
+ - lib/grundler/commands/add.rb
64
+ - lib/grundler/commands/common.rb
65
+ - lib/grundler/commands/help.rb
66
+ - lib/grundler/commands/install.rb
67
+ - lib/grundler/commands/remove.rb
68
+ - lib/grundler/commands/update.rb
69
+ - lib/grundler/crap_mode.rb
70
+ - lib/grundler/good_mode.rb
71
+ - lib/grundler/mode.rb
72
+ - lib/grundler/package_json_writer.rb
73
+ - lib/grundler/version.rb
74
+ homepage: https://github.com/johanhalse/grundler
75
+ licenses:
76
+ - MIT
77
+ metadata:
78
+ allowed_push_host: https://rubygems.org
79
+ homepage_uri: https://github.com/johanhalse/grundler
80
+ source_code_uri: https://github.com/johanhalse/grundler
81
+ changelog_uri: https://github.com/johanhalse/grundler/CHANGES.md
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.4.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.2.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: The no-faff frontend bundler
101
+ test_files: []