npm-mirror 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0b7ed4781e984fb77c715836c810d3c3aed5247c
4
+ data.tar.gz: 4063e539e0a1de7e343f8f3fd3ca4609fbce6a32
5
+ SHA512:
6
+ metadata.gz: b7c5c7801ac4c8b286eef13a628f79761333a490bd53befeb0ede58b3138cd3724290b0f8783f452dcfb54422380dd57e7929e71d73c7592def78b7d8c0b013e
7
+ data.tar.gz: 6a884e45e6107b010440f424fef1a0da68c2cb57bb9ff1213c5710c6682c3b963f6e82c38ff8480c5ebb69de51b86f44739eb837fdab4cba5a15e43d752fe703
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in npm-mirror.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Yue Du
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.
@@ -0,0 +1,65 @@
1
+ # NPM Mirror
2
+
3
+ A NPM Mirror that doesn't need couchdb.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'npm-mirror'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ ## Usage
16
+
17
+ $ bundle exec bin/npm-mirror [path/to/config.yml]
18
+
19
+ Or:
20
+
21
+ $ npm-mirror [path/to/config.yml]
22
+
23
+ Here is an example of config.yml
24
+
25
+ - from: http://registry.npmjs.org/
26
+ to: /data/mirrors/npm
27
+ server: http://mymirrors.com/npm/
28
+ parallelism: 10
29
+ recheck: false
30
+
31
+ Serving via Nginx
32
+
33
+ server {
34
+ listen 0.0.0.0:80;
35
+ server_name mymirrors.com;
36
+ root /data/mirrors/;
37
+ location /npm/ {
38
+ index index.json;
39
+
40
+ location ~ \.etag$ {
41
+ return 404;
42
+ }
43
+
44
+ location ~ /index\.json$ {
45
+ default_type application/json;
46
+ }
47
+
48
+ # for npm search
49
+ location = /npm/-/all/since {
50
+ rewrite ^ /npm/-/all/;
51
+ }
52
+ }
53
+ }
54
+
55
+ npm install from your mirror
56
+
57
+ npm install -r http://mymirrors.com/npm/ package
58
+
59
+ ## Contributing
60
+
61
+ 1. Fork it ( https://github.com/ifduyue/npm-mirror/fork )
62
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
63
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
64
+ 4. Push to the branch (`git push origin my-new-feature`)
65
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'npm/mirror'
5
+
6
+ if ARGV.empty?
7
+ Npm::Mirror::Mirror.new.run
8
+ else
9
+ ARGV.each do |config_file|
10
+ configs = YAML.load_file config_file
11
+ fail "Invalid config file #{config_file}" unless configs.respond_to? :each
12
+
13
+ configs.each do |config|
14
+ from = config['from']
15
+ to = File.expand_path config['to']
16
+ server = config['server']
17
+ parallelism = config['parallelism']
18
+ recheck = config['recheck']
19
+ mirror = Npm::Mirror::Mirror.new(from, to, server, parallelism, recheck)
20
+ mirror.run
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+ require 'npm/mirror'
5
+
6
+ if ARGV.empty?
7
+ Npm::Mirror::Mirror.new.correct_tarball_url
8
+ else
9
+ ARGV.each do |config_file|
10
+ configs = YAML.load_file config_file
11
+ fail "Invalid config file #{config_file}" unless configs.respond_to? :each
12
+
13
+ configs.each do |config|
14
+ from = config['from']
15
+ to = File.expand_path config['to']
16
+ server = config['server']
17
+ parallelism = config['parallelism']
18
+ mirror = Npm::Mirror::Mirror.new(from, to, server, parallelism)
19
+ mirror.correct_tarball_url
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,224 @@
1
+ require 'net/http/persistent'
2
+ require 'json'
3
+
4
+ require 'npm/mirror/version'
5
+ require 'npm/mirror/config'
6
+
7
+ module Npm
8
+ module Mirror
9
+ autoload :Pool, 'npm/mirror/pool'
10
+
11
+ class Error < StandardError; end
12
+
13
+ class Mirror
14
+ attr_reader :from, :to, :server, :parallelism
15
+
16
+ def initialize(from = DEFAULT_FROM, to = DEFAULT_TO,
17
+ server = DEFAULT_SERVER, parallelism = 10,
18
+ recheck = false)
19
+ @from, @to, @server = from, to, server
20
+ parallelism ||= 10
21
+ recheck ||= false
22
+ @pool = Pool.new parallelism
23
+ @http = Net::HTTP::Persistent.new self.class.name
24
+ @recheck = recheck
25
+
26
+ puts "Mirroring : #{from}"
27
+ puts "Datadir : #{to}"
28
+ puts "Server : #{server}"
29
+ puts "Parallelism: #{parallelism}"
30
+ puts "Recheck : #{recheck}"
31
+ end
32
+
33
+ def from(*args)
34
+ File.join @from, *args
35
+ end
36
+
37
+ def to(*args)
38
+ File.join @to, *args
39
+ end
40
+
41
+ def link(*args)
42
+ File.join @server, *args
43
+ end
44
+
45
+ def id
46
+ l = @pool.size.to_s.size
47
+ if Thread.current[:id]
48
+ "Thread-#{Thread.current[:id].to_s.rjust(l, '0')}"
49
+ else
50
+ 'Master'.ljust(%w(Thread- Master).map(&:size).max + l)
51
+ end
52
+ end
53
+
54
+ def fetch(url, path = nil)
55
+ uri = URI url
56
+ etag = etag_for path
57
+
58
+ req = Net::HTTP::Get.new uri.path
59
+ req.add_field 'If-None-Match', etag if etag
60
+
61
+ begin
62
+ resp = @http.request uri, req
63
+ fail Error, '503' if resp.code == '503' # 503 backend read error
64
+ rescue => e
65
+ puts "[#{id}] Error fetching #{uri.path}: #{e.inspect}"
66
+ sleep 10
67
+ retry
68
+ end
69
+
70
+ puts "[#{id}] Fetched(#{resp.code}): #{uri}"
71
+ case resp.code.to_i
72
+ when 301 # Moved
73
+ return fetch resp['location'], path
74
+ when 302 # Found
75
+ return fetch resp['location'], path
76
+ when 200
77
+ return resp
78
+ when 304 # Not modified
79
+ return resp if @recheck
80
+ return nil
81
+ when 403
82
+ return nil
83
+ when 404 # couchdb returns json even it's 404
84
+ return resp
85
+ else
86
+ fail Error, "unexpected response #{resp.inspect}"
87
+ end
88
+ end
89
+
90
+ def fetch_index
91
+ url = from '-/all'
92
+ path = to '-/all/index.json'
93
+ resp = fetch url, path
94
+ fail Error, "Failed to fetch #{url}" if resp.nil?
95
+
96
+ if resp.code == '304'
97
+ if File.exist? path
98
+ json = JSON.load(File.open(path, 'rb').read)
99
+ else
100
+ json = {}
101
+ end
102
+ else
103
+ json = JSON.load resp.body
104
+ end
105
+
106
+ @pool.run
107
+
108
+ packages = json.keys
109
+ packages.each_slice(@pool.size * 10) do |slice|
110
+ slice.each do |package|
111
+ next if package.start_with? '_'
112
+ @pool.enqueue_job(package, &method(:fetch_package))
113
+ end
114
+ sleep 0.5
115
+ end
116
+
117
+ @pool.enqueue_job do
118
+ write_file path, resp.body, resp['etag']
119
+ end unless resp.code == '304'
120
+ end
121
+
122
+ def fetch_package(package)
123
+ url = from package
124
+ path = to package, 'index.json'
125
+ resp = fetch url, path
126
+ return if resp.nil?
127
+
128
+ if resp.code == '304'
129
+ json = JSON.load(File.open(path, 'rb').read)
130
+ tarball_links json
131
+ else
132
+ json = JSON.load resp.body
133
+ tarball_links json
134
+ write_file path, json.to_json, resp['etag']
135
+ write_package_versions(package, json)
136
+ end
137
+ end
138
+
139
+ def write_package_versions(package, json)
140
+ return unless json['versions']
141
+ json['versions'].each do |k, v|
142
+ path = to package, k, 'index.json'
143
+ write_file path, v.to_json, nil
144
+ end
145
+ end
146
+
147
+ def fetch_tarball(tarball_uri)
148
+ url = from tarball_uri
149
+ path = to tarball_uri
150
+ resp = fetch url, path
151
+ return if resp.nil? || resp.code == '304' || resp.body.size.zero?
152
+ write_file path, resp.body, resp['etag']
153
+ end
154
+
155
+ def write_file(path, bytes, etag = nil)
156
+ FileUtils.mkdir_p File.dirname(path)
157
+ File.open(path, 'wb') { |f| f << bytes }
158
+ File.open(path_for_etag(path), 'wb') { |f| f << etag } if etag
159
+ end
160
+
161
+ def tarball_links(json)
162
+ json.each do |_, v|
163
+ if v.is_a? Hash
164
+ if v['shasum'] && v['tarball']
165
+ if v['tarball'].start_with?(@from)
166
+ tarball = v['tarball'].split(/^#{@from}/, 2).last
167
+ v['tarball'] = link tarball
168
+ elsif v['tarball'].start_with?(@server)
169
+ tarball = v['tarball'].split(/^#{@server}/, 2).last
170
+ end
171
+ @pool.enqueue_job(tarball, &method(:fetch_tarball))
172
+ else
173
+ tarball_links v
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ def run
180
+ fetch_index
181
+ @pool.run_til_done
182
+ end
183
+
184
+ def path_for_etag(path)
185
+ if path.nil?
186
+ nil
187
+ elsif File.directory? path
188
+ File.join path, '.etag'
189
+ else
190
+ dirname, basename = File.split path
191
+ File.join dirname, ".#{basename}.etag"
192
+ end
193
+ end
194
+
195
+ def etag_for(path)
196
+ etag_path = path_for_etag path
197
+ if !etag_path
198
+ nil
199
+ elsif File.file?(etag_path) && File.readable?(etag_path)
200
+ File.open(etag_path, 'rb') { |f| f.readline.strip }
201
+ else
202
+ nil
203
+ end
204
+ end
205
+
206
+ def correct_tarball_url
207
+ Dir.glob("#{to}/*/index.json").each do |filename|
208
+ json = JSON.load(File.open(filename, 'rb').read)
209
+ next unless json['versions']
210
+ versions = json['versions'].keys
211
+ versions.each do |version|
212
+ next unless json['versions'][version]['dist']
213
+ tarball = URI json['versions'][version]['dist']['tarball']
214
+ tarball = link tarball.path
215
+ json['versions'][version]['dist']['tarball'] = tarball
216
+ end
217
+ File.open(filename, 'wb') { |f| f << json.to_json }
218
+ write_package_versions json['name'], json
219
+ puts filename
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,9 @@
1
+ require 'tmpdir'
2
+
3
+ module Npm
4
+ module Mirror
5
+ DEFAULT_FROM = 'http://registry.npmjs.org/'
6
+ DEFAULT_TO = File.join(Dir.tmpdir, 'npm-mirror')
7
+ DEFAULT_SERVER = 'http://localhost/'
8
+ end
9
+ end
@@ -0,0 +1,65 @@
1
+ require 'thread'
2
+
3
+ module Npm
4
+ module Mirror
5
+ class Pool
6
+ attr_accessor :size
7
+
8
+ def initialize(size)
9
+ @size = size
10
+ @queue = Queue.new
11
+ @running = false
12
+ end
13
+
14
+ def running?
15
+ @running
16
+ end
17
+
18
+ def run
19
+ return if running?
20
+ @running = true
21
+ @threads = Array.new(@size) do |i|
22
+ Thread.new do
23
+ Thread.current[:id] = i
24
+ catch(:exit) do
25
+ loop do
26
+ job, args = @queue.pop
27
+ job.call(*args)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ def run_til_done
35
+ run
36
+
37
+ until @queue.empty? && @queue.num_waiting == @size
38
+ @threads.each { |t| t.join 0.2 }
39
+ l = @size.to_s.size
40
+ master = 'Master'.ljust(%w(Thread- Master).map(&:size).max + l)
41
+ puts "[#{master}] Queue size: #{@queue.size}, Waiting thread: " \
42
+ "#{@queue.num_waiting}"
43
+ end
44
+
45
+ terminate
46
+ end
47
+
48
+ def terminate
49
+ return unless running?
50
+
51
+ @running = false
52
+ @size.times do
53
+ enqueue_job { throw :exit }
54
+ end
55
+
56
+ @threads.each { |t| t.join 0.2 }
57
+ @threads.each { |t| t.kill }
58
+ end
59
+
60
+ def enqueue_job(*args, &block)
61
+ @queue << [block, args]
62
+ end
63
+ end # class Pool
64
+ end # Mirror
65
+ end # module Npm
@@ -0,0 +1,5 @@
1
+ module Npm
2
+ module Mirror
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'npm/mirror/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "npm-mirror"
8
+ spec.version = Npm::Mirror::VERSION
9
+ spec.authors = ["Yue Du"]
10
+ spec.email = ["ifduyue@gmail.com"]
11
+ spec.summary = %q{NPM Mirror}
12
+ spec.homepage = "https://github.com/ifduyue/npm-mirror"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "net-http-persistent", "~> 2.9.4"
21
+ spec.add_dependency "json"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "pry"
26
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: npm-mirror
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yue Du
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-http-persistent
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 2.9.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 2.9.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - ifduyue@gmail.com
86
+ executables:
87
+ - npm-mirror
88
+ - npm-mirror-correct-tarball-url
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - .gitignore
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/npm-mirror
98
+ - bin/npm-mirror-correct-tarball-url
99
+ - lib/npm/mirror.rb
100
+ - lib/npm/mirror/config.rb
101
+ - lib/npm/mirror/pool.rb
102
+ - lib/npm/mirror/version.rb
103
+ - npm-mirror.gemspec
104
+ homepage: https://github.com/ifduyue/npm-mirror
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.2.2
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: NPM Mirror
128
+ test_files: []