Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use themes hosted on enterprise GitHub instances. #53

Merged
merged 4 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,22 @@ Jekyll plugin for building Jekyll sites with any public GitHub-hosted theme
```yml
remote_theme: benbalter/retlab
```
or <sup>1</sup>
```yml
remote_theme: http[s]://github.<Enterprise>.com/benbalter/retlab
```
<sup>1</sup> The codeload subdomain needs to be available on your github enterprise instance for this to work.

## Declaring your theme

Remote themes are specified by the `remote_theme` key in the site's config.

Remote themes must be in the form of `OWNER/REPOSITORY`, and must represent a public GitHub-hosted Jekyll theme. See [the Jekyll documentation](https://jekyllrb.com/docs/themes/) for more information on authoring a theme. Note that you do not need to upload the gem to RubyGems or include a `.gemspec` file.
For public GitHub, remote themes must be in the form of `OWNER/REPOSITORY`, and must represent a public GitHub-hosted Jekyll theme. See [the Jekyll documentation](https://jekyllrb.com/docs/themes/) for more information on authoring a theme. Note that you do not need to upload the gem to RubyGems or include a `.gemspec` file.

You may also optionally specify a branch, tag, or commit to use by appending an `@` and the Git ref (e.g., `benbalter/retlab@v1.0.0` or `benbalter/retlab@develop`). If you don't specify a Git ref, the `master` branch will be used.

For Enterprise GitHub, remote themes must be in the form of `http[s]://GITHUBHOST.com/OWNER/REPOSITORY`, and must represent a public (non-private repository) GitHub-hosted Jekyll theme. Other than requiring the fully qualified domain name of the enterprise GitHub instance, this works exactly the same as the public usage.

## Debugging

Adding `--verbose` to the `build` or `serve` command may provide additional information.
1 change: 1 addition & 0 deletions jekyll-remote-theme.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.require_paths = ["lib"]
s.license = "MIT"

s.add_dependency "addressable", "~> 2.0"
s.add_dependency "jekyll", "~> 3.5"
s.add_dependency "rubyzip", ">= 1.2.1", "< 3.0"
s.add_development_dependency "jekyll-theme-primer", "~> 0.5"
Expand Down
7 changes: 4 additions & 3 deletions lib/jekyll-remote-theme/downloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
module Jekyll
module RemoteTheme
class Downloader
HOST = "https://codeload.github.com"
PROJECT_URL = "https://github.com/benbalter/jekyll-remote-theme"
USER_AGENT = "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})"
MAX_FILE_SIZE = 1 * (1024 * 1024 * 1024) # Size in bytes (1 GB)
Expand Down Expand Up @@ -90,8 +89,10 @@ def unzip

# Full URL to codeload zip download endpoint for the given theme
def zip_url
@zip_url ||= Addressable::URI.join(
HOST, "#{theme.owner}/", "#{theme.name}/", "zip/", theme.git_ref
@zip_url ||= Addressable::URI.new(
:scheme => theme.scheme,
:host => "codeload.#{theme.host}",
:path => [theme.owner, theme.name, "zip", theme.git_ref].join("/")
).normalize
end

Expand Down
44 changes: 41 additions & 3 deletions lib/jekyll-remote-theme/theme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class Theme < Jekyll::Theme
#
# 1. owner/theme-name - a GitHub owner + theme-name string
# 2. owner/theme-name@git_ref - a GitHub owner + theme-name + Git ref string
# 3. http[s]://github.<yourEnterprise>.com/owner/theme-name
# - An enterprise GitHub instance + a GitHub owner + a theme-name string
# 4. http[s]://github.<yourEnterprise>.com/owner/theme-name@git_ref
# - An enterprise GitHub instance + a GitHub owner + a theme-name + Git ref string
def initialize(raw_theme)
@raw_theme = raw_theme.to_s.downcase.strip
super(@raw_theme)
Expand All @@ -27,13 +31,23 @@ def owner
theme_parts[:owner]
end

def host
uri&.host
end

def scheme
uri&.scheme
end

def name_with_owner
[owner, name].join("/")
end
alias_method :nwo, :name_with_owner

def valid?
theme_parts && name && owner
return false unless uri && theme_parts && name && owner

host && valid_hosts.include?(host)
end

def git_ref
Expand All @@ -45,19 +59,43 @@ def root
end

def inspect
"#<Jekyll::RemoteTheme::Theme owner=\"#{owner}\" name=\"#{name}\"" \
"#<Jekyll::RemoteTheme::Theme host=\"#{host}\" owner=\"#{owner}\" name=\"#{name}\"" \
" ref=\"#{git_ref}\" root=\"#{root}\">"
end

private

def uri
return @uri if defined? @uri

@uri = if @raw_theme =~ THEME_REGEX
Addressable::URI.new(
:scheme => "https",
:host => "github.com",
:path => @raw_theme
)
else
Addressable::URI.parse @raw_theme
end
rescue Addressable::URI::InvalidURIError
@uri = nil
end

def theme_parts
@theme_parts ||= @raw_theme.match(THEME_REGEX)
@theme_parts ||= uri.path[1..-1].match(THEME_REGEX) if uri
end

def gemspec
@gemspec ||= MockGemspec.new(self)
end

def valid_hosts
@valid_hosts ||= [
"github.com",
ENV["PAGES_GITHUB_HOSTNAME"],
ENV["GITHUB_HOSTNAME"],
].compact.to_set
end
end
end
end
20 changes: 18 additions & 2 deletions spec/jekyll-remote-theme/downloader_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# frozen_string_literal: true

RSpec.describe Jekyll::RemoteTheme::Downloader do
let(:nwo) { "pages-themes/primer" }
let(:theme) { Jekyll::RemoteTheme::Theme.new(nwo) }
let(:raw_theme) { "pages-themes/primer" }
let(:theme) { Jekyll::RemoteTheme::Theme.new(raw_theme) }
subject { described_class.new(theme) }

before { reset_tmp_dir }
Expand Down Expand Up @@ -40,6 +40,22 @@
end
end

context "zip_url" do
it "builds the zip url" do
expected = "https://codeload.github.com/pages-themes/primer/zip/master"
expect(subject.send(:zip_url).to_s).to eql(expected)
end

context "a custom host" do
let(:raw_theme) { "http://example.com/pages-themes/primer" }

it "builds the zip url" do
expected = "http://codeload.example.com/pages-themes/primer/zip/master"
expect(subject.send(:zip_url).to_s).to eql(expected)
end
end
end

context "with zip_url stubbed" do
before { allow(subject).to receive(:zip_url) { Addressable::URI.parse zip_url } }

Expand Down
86 changes: 85 additions & 1 deletion spec/jekyll-remote-theme/theme_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
# frozen_string_literal: true

RSpec.describe Jekyll::RemoteTheme::Theme do
let(:scheme) { nil }
let(:host) { nil }
let(:owner) { "foo" }
let(:name) { "bar" }
let(:nwo) { "#{owner}/#{name}" }
let(:git_ref) { nil }
let(:raw_theme) { git_ref ? "#{nwo}@#{git_ref}" : nwo }
let(:raw_theme) do
raw_theme = +""
raw_theme << "#{scheme}://#{host}/" if scheme && host
raw_theme << nwo.to_s
raw_theme << "@#{git_ref}" if git_ref
raw_theme
end
subject { described_class.new(raw_theme) }

it "stores the theme" do
Expand All @@ -28,6 +36,14 @@
expect(subject.owner).to eql(owner)
end

it "uses the default host" do
expect(subject.host).to eql("github.com")
end

it "uses the default scheme" do
expect(subject.scheme).to eql("https")
end

it "builds the name with owner" do
expect(subject.name_with_owner).to eql(nwo)
expect(subject.nwo).to eql(nwo)
Expand Down Expand Up @@ -80,4 +96,72 @@
it "exposes gemspec" do
expect(subject.send(:gemspec)).to be_a(Jekyll::RemoteTheme::MockGemspec)
end

context "a full URL" do
let(:host) { "github.com" }
let(:scheme) { "https" }

it "extracts the name" do
expect(subject.name).to eql(name)
end

it "extracts the owner" do
expect(subject.owner).to eql(owner)
end

it "extracts the host" do
expect(subject.host).to eql("github.com")
end

it "extracts the scheme" do
expect(subject.scheme).to eql("https")
end

it "is valid" do
with_env "GITHUB_HOSTNAME", "enterprise.github.com" do
expect(subject).to be_valid
end
end

context "a custom host" do
let(:host) { "example.com" }
let(:scheme) { "http" }

it "extracts the name" do
expect(subject.name).to eql(name)
end

it "extracts the owner" do
expect(subject.owner).to eql(owner)
end

it "extracts the host" do
expect(subject.host).to eql(host)
end

it "extracts the scheme" do
expect(subject.scheme).to eql(scheme)
end

it "is valid if a whitelisted host name" do
with_env "GITHUB_HOSTNAME", "example.com" do
expect(subject).to be_valid
end
end

it "is invalid if not a whitelisted host name" do
with_env "GITHUB_HOSTNAME", "enterprise.github.com" do
expect(subject).to_not be_valid
end
end

context "with a git ref" do
let(:git_ref) { "foo" }

it "parses the git ref" do
expect(subject.git_ref).to eql(git_ref)
end
end
end
end
end
7 changes: 7 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,10 @@ def make_site(options = {})
config = Jekyll.configuration config_defaults.merge(options)
Jekyll::Site.new(config)
end

def with_env(key, value)
old_env = ENV[key]
ENV[key] = value
yield
ENV[key] = old_env
end