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

Spike out jekyll-remote-theme #1

Merged
merged 30 commits into from Oct 20, 2017
Merged

Spike out jekyll-remote-theme #1

merged 30 commits into from Oct 20, 2017

Conversation

benbalter
Copy link
Owner

@benbalter benbalter commented Sep 8, 2017

This is a first pass at arbitrary/remote theme support for Jekyll (and hopefully eventually GitHub Pages).

Usage

in _config.yml add

remote_theme: foo/bar

or

remote_theme: foo/bar@git_ref

What it does

Clones Downloads and unzips the remote theme to _theme a temporary directory and sets it as site.theme

Gotchas

  1. I'd love to be able to just use the theme key, but the way the Site object is created, it'd raise a "dependency not found error". We could theoretically monkey patch, but I thought remote_theme was a cleaner solution. (some of the test are still intended to go the other way)

2. After the initial clone, _theme exists in the source dir. This is fine, until that _theme dir is committed and pushed, or the theme is update (meaning the two are out of sync. Potential workarounds: (1) warn if _theme isn't in .gitignore, (2) Do a fresh clone on each build (3) clone to a random temp dir outside of source.

/cc @parkr

Copy link

@parkr parkr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good!! Nice work 🎉

return false
end

Jekyll.logger.info "Remote theme: ", "Cloning into #{git_url}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cloning into path, right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooph. Verified locally and this is currently the opposite of how Git is implemented (and users would expect). Will update.


private

def clone_command
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In safe mode, we'll want to disable any credentials access and restrict to the https protocol only.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's restricted to HTTPS because the use can only pass the name + owner + ref. We lock it to https://github.com/*

@benbalter
Copy link
Owner Author

@pathawks, @DirtyF by no means expected, but I respect your 👀, if you're interested in this functionality (potentially for GitHub Pages), and have a moment to review, I'd ❤️ any feedback you could provide on the implementation.

@benbalter
Copy link
Owner Author

Some additional context...

Current implementation

After several iterations and discussions with @parkr the process is as follows:

  • The plugin curls the codeload version of the user-supplied repo (+ git ref) to a temporary directory
  • We unzip the theme to a temporary directory
  • We set a custom Jekyll::RemoteTheme::Theme as Jekyll's site.theme, with the root in the temporary directory
  • We build the site as if it were a Gem-based theme (but no support for Ruby dependencies)

Other considerations

The first implementations stored the site in a _themes folder (where it would likely be committed to the repo when downloaded locally) and used Git to clone the repo (which uses the local users creds, etc.).

Since we don't need the history, and don't want the checkout to live where it can be committed, we opted for curl + unzip in a temp dir that gets destroyed after build.

@pathawks
Copy link

This is awesome! 😮:+1:

Copy link

@parkr parkr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very, very nice! Download, extract, wire up. Cleanly done. Just a few notes for some nits (including some suggestions for a later iteration around optimizing for bad shelling-out environments) but no blockers near as I can tell.

One thing I might recommend is a simple smoke test. You have excellent unit tests. It'd be great to have a simple script and a fixture jekyll site that just runs jekyll build -s fixture_site -d output and checks that the theme is properly loaded etc. The actual Jekyll integration isn't well-tested unless my 10PM head cold/flu brain is making my memory fuzzy.

Anyway, excellent work. ✨

s.summary = "Jekyll plugin for building Jekyll sites with any GitHub-hosted theme"

s.files = `git ls-files app lib`.split("\n")
s.platform = Gem::Platform::RUBY
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is platform required here? This might limit things...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/shrug. Was part of the boilerplate. Removed via fa461f2.

@@ -0,0 +1,27 @@
require "jekyll"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add the magic # frozen_string_literal: true comment to the top of all your Ruby files? I'm surprised Jekyll 3.6's rubocop.yml file didn't include that...

Once that's done, you can get rid of the .freeze calls.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe jekyll/jekyll#6444 was the reason it wasn't added. Added via 8cc5936.

autoload :VERSION, "jekyll-remote-theme/version"

CONFIG_KEY = "remote_theme".freeze
LOG_KEY = "Remmote Theme:".freeze
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remmote

class Downloader
include RemoteTheme::Executor

HOST = "https://codeload.github.com".freeze
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be useful to allow us to override these with environment variables? Maybe a later version.

return
end

download
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this fails, how will a user be told?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, if Executor gets a non-zero exit code, it raises a DownloadError and dumps stdout.

end

it "extracts the theme" do
expect("#{theme.root}/_layouts/default.html").to be_an_existing_file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉


context "without a theme" do
before { site.config["theme"] = nil }
before { subject.munge! }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these be part of the same before block instead of having 2?

end

context "without a theme" do
before { site.config["theme"] = nil }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe this could be done via a let(:config)?

RSpec.describe Jekyll::RemoteTheme::Theme do
let(:owner) { "foo" }
let(:name) { "bar" }
let(:nwo) { "foo/bar" }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let(:nwo) { "#{owner}/#{name}" }?

let(:nwo) { " FoO/bAr " }

it "normalizes the nwo" do
expect(subject.instance_variable_get("@raw_theme")).to eql("foo/bar")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know for sure that codeload will respect these kinds of capitalization differences? E.g. my theme is BenBalter/foOOOO but we request benbalter/fooooo.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I tested and it's case insensitive.

@DirtyF
Copy link
Contributor

DirtyF commented Oct 20, 2017

This is so cool. Users gonna love this. Well done @benbalter

One question though, how are theme dependencies handled? 📦📦📦📦

The typical use case I imagine for this (correct me if I'm wrong), is that one might want to try a few different themes without having to fork a repo or install anything. You just change the value for the remote_theme in your config. And this is what I've done with the default jekyll new website. I removed theme: minima to use remote_theme: benbalter/retlab instead.

One of the dependencies isn't met so here is what I got:

 Remmote Theme: Using theme benbalter/retlab
  Liquid Exception: Liquid syntax error (/private/var/folders/4s/1tjg18y173934vzsz9lg3mf80000gn/T/jekyll-remote-theme-20171020-80204-1l6htmc/retlab-master/_includes/improve.html line 7): Unknown tag 'github_edit_link' included in /_layouts/page.html
jekyll 3.6.0 | Error:  Liquid syntax error (/private/var/folders/4s/1tjg18y173934vzsz9lg3mf80000gn/T/jekyll-remote-theme-20171020-80204-1l6htmc/retlab-master/_includes/improve.html line 7): Unknown tag 'github_edit_link' included

Hence my question: if jekyll-remote-theme is a way to use external themes on GitHub Pages how do you deal with theme dependencies?

Maybe this does not qualify for a 0.1 version but it's a common issue users will face rapidly.

For Jekyll users that don't host their site on GitHub Pages, should jekyll-remote handle this for the user or at least warn him that the theme has dependencies?

If you ask me, an optimal experience would be:

Using remote theme: benbalter/retlab
Installing theme dependencies…

@benbalter
Copy link
Owner Author

benbalter commented Oct 20, 2017

how are theme dependencies handled?

@DirtyF great question. Right now, they're not. Since we're allowing any user-supplied theme, we have to treat the theme as untrusted, arbitrary user input. In order to determine dependencies, we'd have to parse/eval the Gemspec, which could lead to arbitrary code execution.

In a subsequent version, we could theoretically use e.g., regex to determine dependencies, and auto require any whitelisted dependency. We likely won't be able to support arbitrary plugins given our current setup.

Edit: For now, you'll have to add the dependencies to the config file.

Copy link

@parkr parkr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

*timeout_command, "curl", "--url", zip_url, "--output", zip_file.path,
"--user-agent", USER_AGENT, "--fail", "--silent", "--show-error",
]
run_command(*cmd)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for a later iteration, but retries here might be nice. A network blip could cause downloads to fail so giving this a rescue ExecutionError with some recursion might be helpful to prevent these kinds of blips.

def download(tries: 3)
  run_command(*cmd)
rescue ExecutionError => e
  raise e if tries <= 0
  download(tries: tries - 1)
end

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, but hoping to follow up in a subsequent pass with #2 and #3 making this unnecessary.

@benbalter
Copy link
Owner Author

🤘 Thanks for all the feedback. Going to merge this initial spike, get out an initial release, and start work on #2 and #3.

@benbalter benbalter merged commit 4e20221 into master Oct 20, 2017
@benbalter benbalter deleted the spike branch October 20, 2017 18:14
@daviddarnes daviddarnes mentioned this pull request Oct 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants