grundler 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/.github/workflows/main.yml +18 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +23 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +21 -0
- data/README.md +163 -0
- data/Rakefile +14 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/exe/grundle +4 -0
- data/grundler.gemspec +32 -0
- data/lib/grundler.rb +43 -0
- data/lib/grundler/commands/add.rb +29 -0
- data/lib/grundler/commands/common.rb +38 -0
- data/lib/grundler/commands/help.rb +23 -0
- data/lib/grundler/commands/install.rb +27 -0
- data/lib/grundler/commands/remove.rb +41 -0
- data/lib/grundler/commands/update.rb +35 -0
- data/lib/grundler/crap_mode.rb +28 -0
- data/lib/grundler/good_mode.rb +10 -0
- data/lib/grundler/mode.rb +49 -0
- data/lib/grundler/package_json_writer.rb +44 -0
- data/lib/grundler/version.rb +3 -0
- metadata +101 -0
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
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
data/exe/grundle
ADDED
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,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
|
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: []
|