jekyll-remote-theme 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c9167cea38476d7fc4ef44836e821cb07d5bcabb665e834a8df9f1cae315b70
4
- data.tar.gz: 28698c0255ba7c3b9b85758e7ed52c899ffbdd49460064513d8a28a19d174b49
3
+ metadata.gz: 4abb7a44afc1640f9d22b6ee47386bf6181a167f579924b7092d1f151e2f2b6f
4
+ data.tar.gz: 89efc46a4dcaf23298a058e5366b512ae3934a0cc0a0b1fb34b7d7dd1bdc29db
5
5
  SHA512:
6
- metadata.gz: 5c9f12811ead638abbb9022f63221ef49fd6bd2f743e42d58645431aa0565946b74324131710d76ed9567adc812b316425f47e41c55ba379a4073f7812ec5b0a
7
- data.tar.gz: 4ae168e2f471af7bfa0cdc39a479f3bb05fe4420d30a3036206f29b50f49f267004fa8ac4deaa4da26f887eae3f3b5e38b521cb189ed479d5262aa289d33dc11
6
+ metadata.gz: 986f1ac1e5c28c63e7c62cee0a1c42b2c896f1459b85edbca13fa49e80c18935753133645e98cfa3e520e37f02f10adb2175112aa502c4d0b18c1594d4f1b0a9
7
+ data.tar.gz: 88da41af030eab9c282f8633b24d6906fbb63f5add53fc93cd87d123a42015b25de003295e737c6952ad55961851b77fc82014903a00f7969eb9b885e88583d0
@@ -9,6 +9,7 @@ module Jekyll
9
9
  NET_HTTP_ERRORS = [
10
10
  Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::OpenTimeout,
11
11
  Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
12
+ OpenSSL::SSL::SSLError,
12
13
  ].freeze
13
14
 
14
15
  def initialize(theme)
@@ -16,6 +17,11 @@ module Jekyll
16
17
  end
17
18
 
18
19
  def run
20
+ if theme.local_theme?
21
+ Jekyll.logger.debug LOG_KEY, "Using local theme at #{theme.root}"
22
+ return
23
+ end
24
+
19
25
  if downloaded?
20
26
  Jekyll.logger.debug LOG_KEY, "Using existing #{theme.name_with_owner}"
21
27
  return
@@ -26,6 +32,8 @@ module Jekyll
26
32
  end
27
33
 
28
34
  def downloaded?
35
+ return true if theme.local_theme?
36
+
29
37
  @downloaded ||= theme_dir_exists? && !theme_dir_empty?
30
38
  end
31
39
 
@@ -37,11 +45,12 @@ module Jekyll
37
45
  @zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"], :binmode => true)
38
46
  end
39
47
 
48
+ # rubocop:disable Metrics/AbcSize
40
49
  def download
41
50
  Jekyll.logger.debug LOG_KEY, "Downloading #{zip_url} to #{zip_file.path}"
42
- Net::HTTP.start(zip_url.host, zip_url.port, :use_ssl => true) do |http|
51
+ http_class.start(zip_url.host, zip_url.port, :use_ssl => true) do |http|
43
52
  http.request(request) do |response|
44
- raise_unless_sucess(response)
53
+ raise_unless_success(response)
45
54
  enforce_max_file_size(response.content_length)
46
55
  response.read_body do |chunk|
47
56
  zip_file.write chunk
@@ -52,6 +61,7 @@ module Jekyll
52
61
  rescue *NET_HTTP_ERRORS => e
53
62
  raise DownloadError, e.message
54
63
  end
64
+ # rubocop:enable Metrics/AbcSize
55
65
 
56
66
  def request
57
67
  return @request if defined? @request
@@ -61,10 +71,18 @@ module Jekyll
61
71
  @request
62
72
  end
63
73
 
64
- def raise_unless_sucess(response)
74
+ def raise_unless_success(response)
65
75
  return if response.is_a?(Net::HTTPSuccess)
66
76
 
67
- raise DownloadError, "#{response.code} - #{response.message} - Loading URL: #{zip_url}"
77
+ case response.code
78
+ when "404"
79
+ raise DownloadError, "The repository '#{theme.name_with_owner}' could not be found. " \
80
+ "Please check that the repository name is correct, " \
81
+ "publicly accessible, and contains a valid Jekyll theme. " \
82
+ "URL: #{zip_url}"
83
+ else
84
+ raise DownloadError, "#{response.code} - #{response.message} - Loading URL: #{zip_url}"
85
+ end
68
86
  end
69
87
 
70
88
  def enforce_max_file_size(size)
@@ -96,6 +114,52 @@ module Jekyll
96
114
  ).normalize
97
115
  end
98
116
 
117
+ # Returns an HTTP class that respects proxy environment variables
118
+ def http_class
119
+ @http_class ||= Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass)
120
+ end
121
+
122
+ # Extracts proxy settings from environment variables
123
+ def proxy_uri
124
+ return @proxy_uri if defined?(@proxy_uri)
125
+
126
+ proxy_env = find_proxy_env_var
127
+ @proxy_uri = parse_proxy_uri(proxy_env)
128
+ end
129
+
130
+ def find_proxy_env_var
131
+ # Check for HTTPS proxy first if the URL uses HTTPS
132
+ if theme.scheme == "https"
133
+ ENV["https_proxy"] || ENV["HTTPS_PROXY"] || ENV["http_proxy"] || ENV["HTTP_PROXY"]
134
+ else
135
+ ENV["http_proxy"] || ENV["HTTP_PROXY"]
136
+ end
137
+ end
138
+
139
+ def parse_proxy_uri(proxy_env)
140
+ return nil unless proxy_env
141
+
142
+ Addressable::URI.parse(proxy_env)
143
+ rescue Addressable::URI::InvalidURIError
144
+ nil
145
+ end
146
+
147
+ def proxy_host
148
+ proxy_uri&.host
149
+ end
150
+
151
+ def proxy_port
152
+ proxy_uri&.port
153
+ end
154
+
155
+ def proxy_user
156
+ proxy_uri&.user
157
+ end
158
+
159
+ def proxy_pass
160
+ proxy_uri&.password
161
+ end
162
+
99
163
  def theme_dir_exists?
100
164
  theme.root && Dir.exist?(theme.root)
101
165
  end
@@ -12,6 +12,15 @@ module Jekyll
12
12
  DEPENDENCY_PREFIX = %r!^\s*[a-z]+\.add_(?:runtime_)?dependency!.freeze
13
13
  DEPENDENCY_REGEX = %r!#{DEPENDENCY_PREFIX}\(?\s*["']([a-z_-]+)["']!.freeze
14
14
 
15
+ # Regex patterns for extracting gemspec metadata
16
+ AUTHORS_REGEX = %r!^\s*[a-z_]+\.authors\s*=\s*\[(.*?)\]!m.freeze
17
+ VERSION_REGEX = %r!^\s*[a-z_]+\.version\s*=\s*["']([^"']+)["']!.freeze
18
+ SUMMARY_REGEX = %r!^\s*[a-z_]+\.summary\s*=\s*["'](.*?)["']!.freeze
19
+ DESCRIPTION_REGEX = %r!^\s*[a-z_]+\.description\s*=\s*["'](.*?)["']!.freeze
20
+
21
+ # Default version when gemspec is missing or version cannot be determined
22
+ DEFAULT_VERSION = "0.0.0"
23
+
15
24
  def initialize(theme)
16
25
  @theme = theme
17
26
  end
@@ -22,6 +31,63 @@ module Jekyll
22
31
  end
23
32
  end
24
33
 
34
+ # Returns an array of authors from the gemspec
35
+ def authors
36
+ @authors ||= begin
37
+ return [] unless contents
38
+
39
+ match = contents.match(AUTHORS_REGEX)
40
+ return [] unless match
41
+
42
+ # Extract author names from the array string
43
+ match[1].scan(%r!["']([^"']+)["']!).flatten
44
+ end
45
+ end
46
+
47
+ # Returns the version from the gemspec as a Gem::Version object
48
+ # Note: This extracts literal version strings like "1.2.3" from the gemspec.
49
+ # It cannot evaluate version constants like MyGem::VERSION.
50
+ def version
51
+ @version ||= begin
52
+ return Gem::Version.new(DEFAULT_VERSION) unless contents
53
+
54
+ match = contents.match(VERSION_REGEX)
55
+ return Gem::Version.new(DEFAULT_VERSION) unless match
56
+
57
+ # Extract the version string and convert to Gem::Version
58
+ Gem::Version.new(match[1])
59
+ rescue ArgumentError
60
+ # If the version string is invalid, return default
61
+ Gem::Version.new(DEFAULT_VERSION)
62
+ end
63
+ end
64
+
65
+ # Returns the summary from the gemspec
66
+ def summary
67
+ @summary ||= begin
68
+ return "" unless contents
69
+
70
+ match = contents.match(SUMMARY_REGEX)
71
+ match ? match[1] : ""
72
+ end
73
+ end
74
+
75
+ # Returns the description from the gemspec
76
+ def description
77
+ @description ||= begin
78
+ return nil unless contents
79
+
80
+ match = contents.match(DESCRIPTION_REGEX)
81
+ match ? match[1] : nil
82
+ end
83
+ end
84
+
85
+ # Returns metadata hash from the gemspec
86
+ # Note: Metadata parsing is not currently implemented
87
+ def metadata
88
+ @metadata ||= {}
89
+ end
90
+
25
91
  private
26
92
 
27
93
  def contents
@@ -55,9 +55,22 @@ module Jekyll
55
55
  site.theme.configure_sass if site.theme.respond_to?(:configure_sass)
56
56
  site.send(:configure_include_paths)
57
57
  site.plugin_manager.require_theme_deps
58
+ initialize_github_metadata
59
+ end
60
+
61
+ # Initialize GitHub metadata munger if it was loaded after :after_init hook
62
+ def initialize_github_metadata
63
+ return unless defined?(Jekyll::GitHubMetadata::SiteGitHubMunger)
64
+ return if Jekyll::GitHubMetadata::SiteGitHubMunger.global_munger
65
+
66
+ munger = Jekyll::GitHubMetadata::SiteGitHubMunger.new(site)
67
+ munger.munge!
68
+ Jekyll::GitHubMetadata::SiteGitHubMunger.global_munger = munger
58
69
  end
59
70
 
60
71
  def enqueue_theme_cleanup
72
+ return if theme.local_theme? # Don't clean up local theme directories
73
+
61
74
  at_exit do
62
75
  Jekyll.logger.debug LOG_KEY, "Cleaning up #{theme.root}"
63
76
  FileUtils.rm_rf theme.root
@@ -5,7 +5,7 @@ module Jekyll
5
5
  class Theme < Jekyll::Theme
6
6
  OWNER_REGEX = %r!(?<owner>[a-z0-9\-]+)!i.freeze
7
7
  NAME_REGEX = %r!(?<name>[a-z0-9\._\-]+)!i.freeze
8
- REF_REGEX = %r!@(?<ref>[a-z0-9\._\-]+)!i.freeze # May be a branch, tag, or commit
8
+ REF_REGEX = %r!@(?<ref>[a-z0-9\._\-]+)!i.freeze # May be a branch, tag, commit, or "latest"
9
9
  THEME_REGEX = %r!\A#{OWNER_REGEX}/#{NAME_REGEX}(?:#{REF_REGEX})?\z!i.freeze
10
10
 
11
11
  # Initializes a new Jekyll::RemoteTheme::Theme
@@ -18,16 +18,25 @@ module Jekyll
18
18
  # - An enterprise GitHub instance + a GitHub owner + a theme-name string
19
19
  # 4. http[s]://github.<yourEnterprise>.com/owner/theme-name@git_ref
20
20
  # - An enterprise GitHub instance + a GitHub owner + a theme-name + Git ref string
21
+ # 5. /absolute/path/to/theme - an absolute local file path
22
+ # 6. ../relative/path/to/theme - a relative local file path
23
+ # 7. ~/path/to/theme - a home directory relative path
21
24
  def initialize(raw_theme)
22
- @raw_theme = raw_theme.to_s.downcase.strip
25
+ original_theme = raw_theme.to_s.strip
26
+ local_path = looks_like_local_path?(original_theme)
27
+ @raw_theme = local_path ? original_theme : original_theme.downcase
23
28
  super(@raw_theme)
24
29
  end
25
30
 
26
31
  def name
32
+ return File.basename(expanded_local_path) if local_theme?
33
+
27
34
  theme_parts[:name]
28
35
  end
29
36
 
30
37
  def owner
38
+ return "local" if local_theme?
39
+
31
40
  theme_parts[:owner]
32
41
  end
33
42
 
@@ -45,17 +54,23 @@ module Jekyll
45
54
  alias_method :nwo, :name_with_owner
46
55
 
47
56
  def valid?
48
- return false unless uri && theme_parts && name && owner
57
+ return local_path_valid? if local_theme?
49
58
 
50
- host && valid_hosts.include?(host)
59
+ remote_theme_valid?
51
60
  end
52
61
 
53
62
  def git_ref
54
- theme_parts[:ref] || "HEAD"
63
+ return "HEAD" if local_theme?
64
+
65
+ parsed_ref = theme_parts[:ref]
66
+ return "HEAD" unless parsed_ref
67
+ return resolve_latest_release if parsed_ref == "latest"
68
+
69
+ parsed_ref
55
70
  end
56
71
 
57
72
  def root
58
- @root ||= File.realpath Dir.mktmpdir(TEMP_PREFIX)
73
+ @root ||= local_theme? ? expanded_local_path : File.realpath(Dir.mktmpdir(TEMP_PREFIX))
59
74
  end
60
75
 
61
76
  def inspect
@@ -63,10 +78,35 @@ module Jekyll
63
78
  " ref=\"#{git_ref}\" root=\"#{root}\">"
64
79
  end
65
80
 
81
+ def local_theme?
82
+ @local_theme ||= looks_like_local_path?(@raw_theme)
83
+ end
84
+
66
85
  private
67
86
 
87
+ def looks_like_local_path?(path)
88
+ # Check if it looks like a local path
89
+ # Supports: /, ./, ../, ~/ (Unix-style) and drive letters (Windows-style)
90
+ path.start_with?("/", "./", "../", "~/") || path.match?(%r!\A[a-z]:[/\\]!i)
91
+ end
92
+
93
+ def expanded_local_path
94
+ @expanded_local_path ||= File.expand_path(@raw_theme)
95
+ end
96
+
97
+ def local_path_valid?
98
+ Dir.exist?(expanded_local_path)
99
+ end
100
+
101
+ def remote_theme_valid?
102
+ return false unless uri && theme_parts && name && owner
103
+
104
+ host && valid_hosts.include?(host)
105
+ end
106
+
68
107
  def uri
69
108
  return @uri if defined? @uri
109
+ return @uri = nil if local_theme?
70
110
 
71
111
  @uri = if THEME_REGEX.match?(@raw_theme)
72
112
  Addressable::URI.new(
@@ -82,6 +122,8 @@ module Jekyll
82
122
  end
83
123
 
84
124
  def theme_parts
125
+ return nil if local_theme?
126
+
85
127
  @theme_parts ||= uri.path[1..-1].match(THEME_REGEX) if uri
86
128
  end
87
129
 
@@ -96,6 +138,100 @@ module Jekyll
96
138
  ENV["GITHUB_HOSTNAME"],
97
139
  ].compact.to_set
98
140
  end
141
+
142
+ def resolve_latest_release
143
+ return @resolved_latest_release if defined? @resolved_latest_release
144
+
145
+ Jekyll.logger.debug LOG_KEY, "Resolving @latest for #{name_with_owner}"
146
+
147
+ @resolved_latest_release = fetch_latest_release_tag || "HEAD"
148
+ end
149
+
150
+ def fetch_latest_release_tag
151
+ api_url = build_api_url
152
+ response = make_api_request(api_url)
153
+
154
+ if response.is_a?(Net::HTTPSuccess)
155
+ parse_tag_from_response(response)
156
+ else
157
+ log_no_releases_warning
158
+ nil
159
+ end
160
+ rescue StandardError => e
161
+ log_api_error(e)
162
+ nil
163
+ end
164
+
165
+ def build_api_url
166
+ Addressable::URI.new(
167
+ :scheme => scheme,
168
+ :host => api_host,
169
+ :path => api_path
170
+ )
171
+ end
172
+
173
+ def make_api_request(api_url)
174
+ Net::HTTP.start(
175
+ api_url.host,
176
+ api_url.port,
177
+ :use_ssl => api_url.scheme == "https"
178
+ ) do |http|
179
+ request = Net::HTTP::Get.new(api_url.request_uri)
180
+ request["Accept"] = "application/vnd.github.v3+json"
181
+ request["User-Agent"] = Downloader::USER_AGENT
182
+ http.request(request)
183
+ end
184
+ end
185
+
186
+ def parse_tag_from_response(response)
187
+ data = JSON.parse(response.body)
188
+ tag = data["tag_name"]
189
+
190
+ if tag.nil? || tag.empty?
191
+ Jekyll.logger.warn LOG_KEY,
192
+ "No tag_name in API response for #{name_with_owner}, using HEAD"
193
+ return nil
194
+ end
195
+
196
+ Jekyll.logger.debug LOG_KEY, "Resolved @latest to #{tag} for #{name_with_owner}"
197
+ tag
198
+ rescue JSON::ParserError => e
199
+ Jekyll.logger.warn LOG_KEY,
200
+ "Failed to parse API response for #{name_with_owner}: " \
201
+ "#{e.message}, using HEAD"
202
+ nil
203
+ end
204
+
205
+ def log_no_releases_warning
206
+ Jekyll.logger.warn LOG_KEY,
207
+ "No releases found for #{name_with_owner}, using HEAD"
208
+ end
209
+
210
+ def log_api_error(error)
211
+ Jekyll.logger.warn LOG_KEY,
212
+ "Failed to fetch latest release for #{name_with_owner}: " \
213
+ "#{error.message}, using HEAD"
214
+ end
215
+
216
+ def api_host
217
+ case host
218
+ when "github.com"
219
+ "api.github.com"
220
+ else
221
+ # For GitHub Enterprise, API is typically at hostname/api/v3
222
+ host
223
+ end
224
+ end
225
+
226
+ def api_path
227
+ case host
228
+ when "github.com"
229
+ "/repos/#{name_with_owner}/releases/latest"
230
+ else
231
+ # For GitHub Enterprise
232
+ "/api/v3/repos/#{name_with_owner}/releases/latest"
233
+ end
234
+ end
99
235
  end
100
236
  end
101
237
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module RemoteTheme
5
- VERSION = "0.4.3"
5
+ VERSION = "0.5.0"
6
6
  end
7
7
  end
@@ -4,8 +4,10 @@ require "jekyll"
4
4
  require "fileutils"
5
5
  require "tempfile"
6
6
  require "addressable"
7
+ require "openssl"
7
8
  require "net/http"
8
9
  require "zip"
10
+ require "json"
9
11
 
10
12
  $LOAD_PATH.unshift(File.dirname(__FILE__))
11
13
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-remote-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Balter
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-03-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: addressable
@@ -51,9 +50,9 @@ dependencies:
51
50
  - - ">="
52
51
  - !ruby/object:Gem::Version
53
52
  version: '1.0'
54
- - - "<="
53
+ - - "<"
55
54
  - !ruby/object:Gem::Version
56
- version: 3.0.0
55
+ version: '4.0'
57
56
  - - "!="
58
57
  - !ruby/object:Gem::Version
59
58
  version: 2.0.0
@@ -64,12 +63,32 @@ dependencies:
64
63
  - - ">="
65
64
  - !ruby/object:Gem::Version
66
65
  version: '1.0'
67
- - - "<="
66
+ - - "<"
68
67
  - !ruby/object:Gem::Version
69
- version: 3.0.0
68
+ version: '4.0'
70
69
  - - "!="
71
70
  - !ruby/object:Gem::Version
72
71
  version: 2.0.0
72
+ - !ruby/object:Gem::Dependency
73
+ name: openssl
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '3.0'
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 3.1.2
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.1.2
73
92
  - !ruby/object:Gem::Dependency
74
93
  name: rubyzip
75
94
  requirement: !ruby/object:Gem::Requirement
@@ -202,7 +221,6 @@ dependencies:
202
221
  - - "~>"
203
222
  - !ruby/object:Gem::Version
204
223
  version: '3.0'
205
- description:
206
224
  email:
207
225
  - ben.balter@github.com
208
226
  executables: []
@@ -219,7 +237,6 @@ homepage: https://github.com/benbalter/jekyll-remote-theme
219
237
  licenses:
220
238
  - MIT
221
239
  metadata: {}
222
- post_install_message:
223
240
  rdoc_options: []
224
241
  require_paths:
225
242
  - lib
@@ -234,8 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
251
  - !ruby/object:Gem::Version
235
252
  version: '0'
236
253
  requirements: []
237
- rubygems_version: 3.2.14
238
- signing_key:
254
+ rubygems_version: 4.0.10
239
255
  specification_version: 4
240
256
  summary: Jekyll plugin for building Jekyll sites with any GitHub-hosted theme
241
257
  test_files: []