├── .circleci
└── config.yml
├── .gitignore
├── .standard.yml
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
├── console
└── setup
├── example
├── .browserslistrc
├── .env
├── .gitignore
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
│ ├── assets
│ │ ├── config
│ │ │ └── manifest.js
│ │ ├── images
│ │ │ └── .keep
│ │ └── stylesheets
│ │ │ └── application.css
│ ├── channels
│ │ └── application_cable
│ │ │ ├── channel.rb
│ │ │ └── connection.rb
│ ├── controllers
│ │ ├── application_controller.rb
│ │ ├── concerns
│ │ │ └── .keep
│ │ └── docs
│ │ │ └── api
│ │ │ └── houses_controller.rb
│ ├── helpers
│ │ └── application_helper.rb
│ ├── javascript
│ │ ├── channels
│ │ │ ├── consumer.js
│ │ │ └── index.js
│ │ └── packs
│ │ │ └── application.js
│ ├── jobs
│ │ └── application_job.rb
│ ├── mailers
│ │ └── application_mailer.rb
│ ├── models
│ │ ├── application_record.rb
│ │ └── concerns
│ │ │ └── .keep
│ └── views
│ │ └── layouts
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ └── mailer.text.erb
├── babel.config.js
├── bin
│ ├── bundle
│ ├── rails
│ ├── rake
│ ├── setup
│ ├── webpack
│ ├── webpack-dev-server
│ └── yarn
├── config.ru
├── config
│ ├── application.rb
│ ├── boot.rb
│ ├── cable.yml
│ ├── credentials.yml.enc
│ ├── database.yml
│ ├── environment.rb
│ ├── environments
│ │ ├── development.rb
│ │ ├── production.rb
│ │ └── test.rb
│ ├── initializers
│ │ ├── application_controller_renderer.rb
│ │ ├── assets.rb
│ │ ├── backtrace_silencers.rb
│ │ ├── content_security_policy.rb
│ │ ├── cookies_serializer.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mime_types.rb
│ │ ├── static.rb
│ │ └── wrap_parameters.rb
│ ├── locales
│ │ └── en.yml
│ ├── puma.rb
│ ├── routes.rb
│ ├── spring.rb
│ ├── storage.yml
│ ├── webpack
│ │ ├── development.js
│ │ ├── environment.js
│ │ ├── production.js
│ │ └── test.js
│ └── webpacker.yml
├── cypress.json
├── cypress
│ ├── integration
│ │ └── static-rails.js
│ ├── plugins
│ │ └── index.js
│ └── support
│ │ ├── commands.js
│ │ └── index.js
├── db
│ └── seeds.rb
├── lib
│ ├── assets
│ │ └── .keep
│ └── tasks
│ │ └── .keep
├── log
│ └── .keep
├── package.json
├── postcss.config.js
├── public
│ ├── 404.html
│ ├── 422.html
│ ├── 500.html
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── favicon.ico
│ ├── index.html
│ └── robots.txt
├── script
│ └── test
├── static
│ ├── blog-docs
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── index.html
│ │ ├── package-lock.json
│ │ └── package.json
│ ├── blog
│ │ ├── .gitignore
│ │ ├── archetypes
│ │ │ └── default.md
│ │ ├── config.toml
│ │ ├── content
│ │ │ └── posts
│ │ │ │ └── my-first-post.md
│ │ └── themes
│ │ │ └── ultralight
│ │ │ ├── archetypes
│ │ │ └── default.md
│ │ │ ├── assets
│ │ │ ├── js
│ │ │ │ └── index.js
│ │ │ ├── sass
│ │ │ │ ├── code.css
│ │ │ │ ├── layout.sass
│ │ │ │ ├── main.sass
│ │ │ │ ├── readability.sass
│ │ │ │ └── vars.sass
│ │ │ └── vendor
│ │ │ │ └── css
│ │ │ │ └── modern-normalize.css
│ │ │ ├── layouts
│ │ │ ├── 404.html
│ │ │ ├── _default
│ │ │ │ ├── baseof.html
│ │ │ │ ├── list.html
│ │ │ │ └── single.html
│ │ │ ├── index.html
│ │ │ ├── index.rss.xml
│ │ │ └── partials
│ │ │ │ ├── css.html
│ │ │ │ ├── footer.html
│ │ │ │ ├── head.html
│ │ │ │ ├── header.html
│ │ │ │ ├── js.html
│ │ │ │ ├── posts-list.html
│ │ │ │ └── social-preview.html
│ │ │ └── theme.toml
│ ├── docs
│ │ ├── .bundle
│ │ │ └── config
│ │ ├── .gitignore
│ │ ├── 404.html
│ │ ├── Gemfile
│ │ ├── Gemfile.lock
│ │ ├── _config.yml
│ │ ├── _posts
│ │ │ └── 2020-04-27-welcome-to-jekyll.markdown
│ │ ├── about.markdown
│ │ ├── assets
│ │ │ ├── a_css.css
│ │ │ ├── a_css.css.gz
│ │ │ ├── a_html.html
│ │ │ ├── a_html.html.gz
│ │ │ ├── a_javascript.js
│ │ │ ├── a_javascript.js.gz
│ │ │ ├── a_json.json
│ │ │ ├── a_json.json.gz
│ │ │ ├── an_image.png
│ │ │ └── an_image.png.gz
│ │ └── index.markdown
│ └── marketing
│ │ ├── .gitignore
│ │ ├── archetypes
│ │ └── default.md
│ │ ├── config.toml
│ │ ├── content
│ │ └── posts
│ │ │ └── my-first-post.md
│ │ ├── layouts
│ │ └── 404.html
│ │ └── themes
│ │ └── ultralight
│ │ ├── archetypes
│ │ └── default.md
│ │ ├── assets
│ │ ├── js
│ │ │ └── index.js
│ │ ├── sass
│ │ │ ├── code.css
│ │ │ ├── layout.sass
│ │ │ ├── main.sass
│ │ │ ├── readability.sass
│ │ │ └── vars.sass
│ │ └── vendor
│ │ │ └── css
│ │ │ └── modern-normalize.css
│ │ ├── layouts
│ │ ├── 404.html
│ │ ├── _default
│ │ │ ├── baseof.html
│ │ │ ├── list.html
│ │ │ └── single.html
│ │ ├── index.html
│ │ ├── index.rss.xml
│ │ └── partials
│ │ │ ├── css.html
│ │ │ ├── footer.html
│ │ │ ├── head.html
│ │ │ ├── header.html
│ │ │ ├── js.html
│ │ │ ├── posts-list.html
│ │ │ └── social-preview.html
│ │ └── theme.toml
├── storage
│ └── .keep
├── test
│ ├── application_system_test_case.rb
│ ├── channels
│ │ └── application_cable
│ │ │ └── connection_test.rb
│ ├── controllers
│ │ └── .keep
│ ├── fixtures
│ │ ├── .keep
│ │ └── files
│ │ │ └── .keep
│ ├── helpers
│ │ └── .keep
│ ├── integration
│ │ └── .keep
│ ├── mailers
│ │ └── .keep
│ ├── models
│ │ └── .keep
│ ├── system
│ │ └── .keep
│ └── test_helper.rb
├── tmp
│ └── .keep
├── vendor
│ └── .keep
└── yarn.lock
├── lib
├── generators
│ ├── static_rails
│ │ └── initializer_generator.rb
│ └── templates
│ │ └── static.rb
├── static-rails.rb
├── static-rails
│ ├── compile.rb
│ ├── configuration.rb
│ ├── determines_whether_to_handle_request.rb
│ ├── error.rb
│ ├── file_handler.rb
│ ├── gets_csrf_token.rb
│ ├── matches_request_to_static_site.rb
│ ├── proxy_middleware.rb
│ ├── rack_server_check.rb
│ ├── railtie.rb
│ ├── request_forgery_protection_fallback.rb
│ ├── server.rb
│ ├── server_store.rb
│ ├── site.rb
│ ├── site_middleware.rb
│ ├── site_plus_csrf_middleware.rb
│ ├── static_middleware.rb
│ ├── validates_csrf_token.rb
│ ├── version.rb
│ └── waits_for_connection.rb
└── tasks
│ └── static-rails.rake
├── script
├── setup
└── test
├── static-rails.gemspec
└── test
└── test_helper.rb
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/ruby:2.7-node-browsers
6 | environment:
7 | RUBYOPT: "-W:no-deprecated -W:no-experimental"
8 | steps:
9 | - checkout
10 |
11 | - run: echo 127.0.0.1 blog.localhost | sudo tee -a /etc/hosts
12 |
13 | # One of the apps needs hugo
14 | - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends hugo
15 |
16 | # Make sure we use the right Bundler
17 | - run: gem install bundler --version `tail -1 Gemfile.lock`
18 |
19 | # Bundle install dependencies
20 | - type: cache-restore
21 | key: v1-main-{{ checksum "Gemfile.lock" }}
22 |
23 | # Bundle install dependencies for example app
24 | - type: cache-restore
25 | key: v1-example-{{ checksum "example/Gemfile.lock" }}
26 |
27 | # Yarn dependencies
28 | - restore_cache:
29 | keys:
30 | - v2-yarn-{{ checksum "example/yarn.lock" }}
31 | # fallback to using the latest cache if no exact match is found
32 | - v2-yarn-
33 |
34 | # Bundle install dependencies for jekyll app
35 | - type: cache-restore
36 | key: v1-jekyll-{{ checksum "example/static/docs/Gemfile.lock" }}
37 |
38 | # Npm install for Eleventy app
39 | - restore_cache:
40 | keys:
41 | - v1-eleventy-{{ checksum "example/static/blog-docs/package-lock.json" }}
42 | # fallback to using the latest cache if no exact match is found
43 | - v1-eleventy-
44 |
45 | - run: ./script/setup
46 |
47 | - type: cache-save
48 | key: v1-main-{{ checksum "Gemfile.lock" }}
49 | paths:
50 | - vendor/bundle
51 |
52 | - type: cache-save
53 | key: v1-example-{{ checksum "example/Gemfile.lock" }}
54 | paths:
55 | - example/vendor/bundle
56 |
57 | - save_cache:
58 | paths:
59 | - example/node_modules
60 | - ~/.cache
61 | key: v2-yarn-{{ checksum "example/yarn.lock" }}
62 |
63 | - type: cache-save
64 | key: v1-jekyll-{{ checksum "example/static/docs/Gemfile.lock" }}
65 | paths:
66 | - example/static/docs/vendor/bundle
67 |
68 | - save_cache:
69 | paths:
70 | - example/static/blog-docs/node_modules
71 | key: v1-eleventy-{{ checksum "example/static/blog-docs/package-lock.json" }}
72 |
73 | - run: ./script/test
74 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /_yardoc/
4 | /coverage/
5 | /doc/
6 | /pkg/
7 | /spec/reports/
8 | /tmp/
9 | /vendor/bundle
10 | /example/vendor/bundle
11 |
--------------------------------------------------------------------------------
/.standard.yml:
--------------------------------------------------------------------------------
1 | ruby_version: 2.5
2 | ignore:
3 | - example/**/*
4 | - node_modules/**/*
5 | - vendor/**/*
6 |
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: ruby
3 | cache: bundler
4 | rvm:
5 | - 2.7.0
6 | before_install: gem install bundler -v 2.1.4
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.1.0
2 |
3 | * Add support for Rails 6.1 when CSRF is enabled
4 | * Increase required Ruby to 2.5
5 |
6 | ## 0.0.14
7 |
8 | * HTML & XML won't be cached by default in production
9 | [#20](https://github.com/testdouble/static-rails/pull/20)
10 |
11 | ## 0.0.13
12 |
13 | * 404 pages served in production via a site's `compile_404_file_path` setting
14 | will now also send the HTTP status code of 404 instead of 200
15 |
16 | ## 0.0.12
17 |
18 | * Fix an issue in which enabling force_ssl would result in redirects to the
19 | obfuscated `/_static_rails/` path. Resolved this by placing the static-rails
20 | middleware after `ActionDispatch::SSL`. Note that this will break if you
21 | remove `Rack::SendFile` from your app's middleware stack
22 |
23 | ## 0.0.11
24 |
25 | * Inline the `ActionDispatch::FileHandler` from Rails master so that we can
26 | target a single stable version of its API and control what MIME types it
27 | considers to be compressible (bonus is that it effectively backports brotli
28 | compression to pre-6.1 rails apps)
29 |
30 | ## 0.0.10
31 |
32 | * Change default `cache-control` header for static assets being served from disk
33 | from `no-cache` to `"public; max-age=31536000"`
34 |
35 | ## 0.0.9
36 |
37 | * When using CSRF protection, the artificial path info will now be
38 | "__static_rails__" instead of a random string, to make logs appear cleaner
39 | * Attempt to guard against future internal changes to Rails' request forgery
40 | protection by adding `method_missing` that calls through
41 |
42 | ## 0.0.8
43 |
44 | * Add support for the [CSRF
45 | changes](https://github.com/rails/rails/commit/358ff18975f26e820ea355ec113ffc5228e59af8) in Rails 6.0.3.1
46 |
47 | ## 0.0.7
48 |
49 | * Ensure that CSRF tokens are valid, at the cost of some performance and
50 | reliance on additional Rails internals. As a result CSRF cookie setting is now
51 | disabled by default [#6](https://github.com/testdouble/static-rails/pull/6)
52 |
53 | ## 0.0.6
54 |
55 | * Fix an issue where `ActionDispatch::FileHandler` won't be loaded in the event
56 | that static-rails is serving compiled assets but Rails is not
57 |
58 | ## 0.0.5
59 |
60 | * Add a site option `compile_404_file_path` to specify a file to be used as a
61 | 404 page when serving compiled assets and no file is found
62 |
63 | ## 0.0.4
64 |
65 | * Add a cookie named `_csrf_token` by default to all static site requests, so
66 | that your static sites can make CSRF-protected requests of your server
67 | ([#4](https://github.com/testdouble/static-rails/pull/4))
68 |
69 | ## 0.0.3
70 |
71 | * Add `url_skip_paths_starting_with` array of strings option to site
72 | configuration. Will fall through to the next matching site or all the way to
73 | the Rails app.
74 |
75 | ## 0.0.2
76 |
77 | * Add `env` hash option to site configuration
78 |
79 | ## 0.0.1
80 |
81 | * Initial release
82 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem "rake", "~> 13.0"
6 | gem "minitest", "~> 5.0"
7 | gem "standard"
8 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | static-rails (0.1.0)
5 | rack-proxy (~> 0.6)
6 | railties (>= 5.0.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionpack (6.1.4)
12 | actionview (= 6.1.4)
13 | activesupport (= 6.1.4)
14 | rack (~> 2.0, >= 2.0.9)
15 | rack-test (>= 0.6.3)
16 | rails-dom-testing (~> 2.0)
17 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
18 | actionview (6.1.4)
19 | activesupport (= 6.1.4)
20 | builder (~> 3.1)
21 | erubi (~> 1.4)
22 | rails-dom-testing (~> 2.0)
23 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
24 | activesupport (6.1.4)
25 | concurrent-ruby (~> 1.0, >= 1.0.2)
26 | i18n (>= 1.6, < 2)
27 | minitest (>= 5.1)
28 | tzinfo (~> 2.0)
29 | zeitwerk (~> 2.3)
30 | ast (2.4.2)
31 | builder (3.2.4)
32 | concurrent-ruby (1.1.9)
33 | crass (1.0.6)
34 | erubi (1.10.0)
35 | i18n (1.8.10)
36 | concurrent-ruby (~> 1.0)
37 | loofah (2.10.0)
38 | crass (~> 1.0.2)
39 | nokogiri (>= 1.5.9)
40 | method_source (1.0.0)
41 | mini_portile2 (2.5.3)
42 | minitest (5.14.4)
43 | nokogiri (1.11.7)
44 | mini_portile2 (~> 2.5.0)
45 | racc (~> 1.4)
46 | parallel (1.20.1)
47 | parser (3.0.2.0)
48 | ast (~> 2.4.1)
49 | racc (1.5.2)
50 | rack (2.2.3)
51 | rack-proxy (0.7.0)
52 | rack
53 | rack-test (1.1.0)
54 | rack (>= 1.0, < 3)
55 | rails-dom-testing (2.0.3)
56 | activesupport (>= 4.2.0)
57 | nokogiri (>= 1.6)
58 | rails-html-sanitizer (1.3.0)
59 | loofah (~> 2.3)
60 | railties (6.1.4)
61 | actionpack (= 6.1.4)
62 | activesupport (= 6.1.4)
63 | method_source
64 | rake (>= 0.13)
65 | thor (~> 1.0)
66 | rainbow (3.0.0)
67 | rake (13.0.6)
68 | regexp_parser (2.1.1)
69 | rexml (3.2.5)
70 | rubocop (1.18.3)
71 | parallel (~> 1.10)
72 | parser (>= 3.0.0.0)
73 | rainbow (>= 2.2.2, < 4.0)
74 | regexp_parser (>= 1.8, < 3.0)
75 | rexml
76 | rubocop-ast (>= 1.7.0, < 2.0)
77 | ruby-progressbar (~> 1.7)
78 | unicode-display_width (>= 1.4.0, < 3.0)
79 | rubocop-ast (1.8.0)
80 | parser (>= 3.0.1.1)
81 | rubocop-performance (1.11.4)
82 | rubocop (>= 1.7.0, < 2.0)
83 | rubocop-ast (>= 0.4.0)
84 | ruby-progressbar (1.11.0)
85 | standard (1.1.5)
86 | rubocop (= 1.18.3)
87 | rubocop-performance (= 1.11.4)
88 | thor (1.1.0)
89 | tzinfo (2.0.4)
90 | concurrent-ruby (~> 1.0)
91 | unicode-display_width (2.0.0)
92 | zeitwerk (2.4.2)
93 |
94 | PLATFORMS
95 | ruby
96 |
97 | DEPENDENCIES
98 | minitest (~> 5.0)
99 | rake (~> 13.0)
100 | standard
101 | static-rails!
102 |
103 | BUNDLED WITH
104 | 2.2.15
105 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020 Test Double, LLC
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # static-rails
2 |
3 | [](https://circleci.com/gh/testdouble/static-rails)
4 |
5 | ## Build and serve your static sites from your Rails app
6 |
7 | **tl;dr in development, static-rails runs your static site generators &
8 | proxies requests; in production, it compiles and serves the final assets**
9 |
10 | Static site generators are hot right now. Maybe you're hip with "the
11 | [Jamstack](https://jamstack.org)", or maybe your API documentation is generated
12 | by [Hugo](https://gohugo.io), or maybe your marketing folks use
13 | [Jekyll](https://jekyllrb.com) for the company blog.
14 |
15 | Up until now, compiling static assets with any degree of sophistication beyond
16 | dumping them in your app's `public/` directory represented a significant
17 | deviation from the "Rails Way". But the alternative—keeping your static sites
18 | wholly separate from your Rails app—raises myriad operational challenges, from
19 | tracking multiple git repositories, to managing multiple server configurations,
20 | to figuring out a way to share common JavaScript and CSS assets between them
21 | all.
22 |
23 | No longer! static-rails lets you use your static asset generators of choice
24 | without forcing you to abandon your monolithic Rails architecture.
25 |
26 | Here's what it does:
27 |
28 | * In development, static-rails launches your sites' local servers and then
29 | proxies any requests to wherever you've mounted them in your Rails app so you
30 | can start a single server and transition work between your static sites and
31 | Rails app seamlessly
32 |
33 | * When deploying, static-rails will compile all your static assets when `rake
34 | assets:precompile` is run, meaning your assets will be built automatically
35 | when pushed to a platform like Heroku
36 |
37 | * In production, static-rails will serve your sites' compiled assets from disk
38 | with a similar features and performance to what you're familiar with if you've
39 | ever hosted files out of your `public/` directory
40 |
41 | ## Install
42 |
43 | Add this to your Gemfile:
44 |
45 | ```
46 | gem "static-rails"
47 | ```
48 |
49 | Then run this generator to create a configuration file
50 | `config/initializers/static.rb`:
51 |
52 | ```
53 | $ rails g static_rails:initializer
54 | ```
55 |
56 | You can check out the configuration options in the [generated file's
57 | comments]().
58 |
59 | Want an example of setting things up? You're in luck, there's an [example
60 | app](/example) right in this repo!
61 |
62 | ## Configuring the gem
63 |
64 | **(Want to dive right in? The generated initializer [enumerates every
65 | option](/lib/generators/templates/static.rb) and the [example app's
66 | config](https://github.com/testdouble/static-rails/blob/main/example/config/initializers/static.rb)
67 | sets up 4 sites.)**
68 |
69 | ### Top-level configuration
70 |
71 | So, what should you stick in your initializer's `StaticRails.config do |config|`
72 | block? These options are set right off the `config` object and control the
73 | overall behavior of the gem itself, across all your static sites:
74 |
75 | * **config.proxy_requests** (Default: `!Rails.env.production?`) when true,
76 | the gem's middleware requests that match where you've mounted your static site
77 | and proxy them to the development server
78 |
79 | * **config.serve_compiled_assets** (Default: `Rails.env.production?`) when true,
80 | the gem's middleware will find your static assets on disk and serve them using
81 | the same code that Rails uses to serve files out of `public/`
82 |
83 | * **config.ping_server_timeout** (Default: `5`) the number of seconds that (when
84 | `proxy_requests` is true, that the gem will wait for a response from a static
85 | site's server on any given request before timing out and raising an error
86 |
87 | * **config.set_csrf_token_cookie** (Default: `false`) when true, the gem's
88 | middleware will set a cookie named `_csrf_token` with each request of your
89 | static site. You can use this to set the `'x-csrf-token'` header on any
90 | requests from your site back to routes hosted by the Rails app that are
91 | [protected from CSRF
92 | forgery](https://guides.rubyonrails.org/security.html#cross-site-request-forgery-csrf)
93 | (if you're not using Rails' cookie store for sessions or you're okay with API
94 | calls bypassing Rails CSRF, leave this off)
95 |
96 | ### Configuring your static sites themselves
97 |
98 | To tell the gem about your static sites, assign an array of hashes as `sites`
99 | (e.g. `config.sites = [{…}]`). Each of those hashes have the following options:
100 |
101 | * **name** (Required) A unique name for the site (primarily used for
102 | logging)
103 |
104 | * **source_dir** (Required) The file path (relative to the Rails app's root) to
105 | the static site's project directory
106 |
107 | * **url_subdomain** (Default: `nil`) Constrains the static site's assets to only
108 | be served for requests to a given subdomain (e.g. for a Rails app hosting
109 | `example.com`, a Hugo site at `blog.example.com` would set this to `"blog"`)
110 |
111 | * **url_root_path** (Default: `/`) The base URL path at which to mount the
112 | static site (e.g. if you want your Jekyll site hosted at `example.com/docs`,
113 | you'd set this to `/docs`). For most static site generators, you'll want to
114 | configure it to serve assets from the same path so links and other references
115 | are correct (see below for examples)
116 |
117 | * **url_skip_paths_starting_with** (Default: `[]`) If you want to mount your
118 | static site to `/` but allow the Rails app to serve APIs from `/api`, you can
119 | set the path prefix `["/api"]` here to tell the gem's middleware not to try to
120 | proxy or serve the request from your static site, but rather let Rails handle
121 | it
122 |
123 | * **start_server** (Default `!Rails.env.production?) When true, the gem will
124 | start the site's server (and if it ever exits, restart it) as your Rails app
125 | is booting up. All output from the server will be forwarded to STDOUT/STDERR
126 |
127 | * **server_command** (Required if `start_server` is true) the command to run to
128 | start the site's server, from the working directory of `source_dir` (e.g.
129 | `hugo server`)
130 |
131 | * **ping_server** (Default: true) if this and `start_server` are both true, then
132 | wait to proxy any requests until the server is accepting TCP connections
133 |
134 | * **env** (Default: `{}`) any environment variables you need to pass to either
135 | the server or compile commands (e.g. `env: {"BUNDLE_PATH" =>
136 | "vendor/bundle"}`). Note that this configuration file is Ruby, so if you need
137 | to provide different env vars based on Rails environment, you have the power
138 | to do that!
139 |
140 | * **server_host** (Default: `localhost`) the host your static site's server will
141 | run on
142 |
143 | * **server_port** (Required if `proxy_requests` is true) the port your static
144 | site's server will accept requests on
145 |
146 | * **server_path** (Default: `"/"`) the root URL path to which requests should be
147 | proxied
148 |
149 | * **compile_comand** (Required) the command to be run by both the
150 | `static:compile` and `assets:precompile` Rake commands (e.g. `npm run build`),
151 | with working directory set to the site's `source_dir`
152 |
153 | * **compile_dir** (Required when `serve_compiled_assets` is true) the root file
154 | path to which production assets are compiled, relative to the site's
155 | `source_dir`
156 |
157 | * **compile_404_file_path** (Optional) When `serve_compiled_assets` is true,
158 | this file (relative to the `compile_dir`) will be served whenever the
159 | request's path does not match a file on disk
160 |
161 | ## Configuring your static site generators
162 |
163 | Assuming you won't be mounting your static site to your app's root `/` path,
164 | you'll probably need to configure its base URL path somehow. Here are
165 | some tips (and if your tool of choice isn't listed, we'd love a [pull
166 | request](https://github.com/testdouble/static-rails/edit/main/README.md)!):
167 |
168 | ### Using Jekyll
169 |
170 | If you have a Jekyll app that you plan on serving from a non-root path (say,
171 | `/docs`), then you'll need to set the `baseurl` in `_config.yml`:
172 |
173 | ```yml
174 | baseurl: "/docs"
175 | ```
176 |
177 | (Note that this means running the Jekyll application directly using `bundle exec
178 | jekyll serve` will also start serving at `http://127.0.0.1:4000/docs/`)
179 |
180 | ### Using Hugo
181 |
182 | If you are mounting your [Hugo](https://gohugo.io) app to anything but the root
183 | path, you'll need to specify that path in the `baseURL` of your root
184 | `config.toml` file, like so:
185 |
186 | ```toml
187 | baseURL = "http://blog.example.com/docs"
188 | ```
189 |
190 | Additionally, getting Hugo to play nicely when being proxied by your Rails
191 | server in development and test can be a little tricky, because most themes will
192 | render fully-qualified URLs into markup when running the `hugo server` command.
193 | That means if you're forwarding `http://blog.localhost:3000/docs` to a Hugo
194 | server running on `http://localhost:1313`, it's very likely the static files
195 | (e.g. all the links on the page) will have references to
196 | `http://localhost:1313`, which may result in accidentally navigating away from
197 | your Rails development server unexpectedly.
198 |
199 | To mitigate this, there are a few things you can do:
200 |
201 | * Favor `.RelPermalink` in your templates over `.Permalink` where possible.
202 | * In place of referring to `{{.Site.BaseURL}}` in your templates, generate a
203 | base path with `{{ "/" | relURL }}` (given the above `baseURL`, this will
204 | render `"/marketing/"`)
205 |
206 | Also, because Hugo will serve `/livereload.js` from the root, live-reloading probably
207 | won't work in development when running through the static-rails proxy.
208 | You might consider disabling it with `--disableLiveReload` unless you're serving
209 | Hugo from a root path ("`/`").
210 |
211 | A static-rails config for a Hugo configuration in `sites` might look like:
212 |
213 | ```rb
214 | {
215 | name: "docs",
216 | url_root_path: "/docs",
217 | source_dir: "static/docs",
218 | server_command: "hugo server",
219 | server_port: 8080,
220 | compile_command: "hugo",
221 | compile_dir: "static/docs/public"
222 | }
223 | ```
224 |
225 | ### Using Eleventy
226 |
227 | If you are mounting your [Eleventy](https://www.11ty.dev) app to anything but
228 | the root path, you'll want to configure a path prefix in `.eleventy.js`
229 |
230 | ```js
231 | module.exports = {
232 | pathPrefix: "/docs/"
233 | }
234 | ```
235 |
236 | Alternatively, you can specify this from the command line with `--pathprefix
237 | /docs`.
238 |
239 | A static-rails config for an Eleventy configuration in `sites` might look like:
240 |
241 | ```rb
242 | {
243 | name: "docs",
244 | url_root_path: "/docs",
245 | source_dir: "static/docs",
246 | server_command: "npx @11ty/eleventy --serve --pathprefix /docs",
247 | server_port: 8080,
248 | compile_command: "npx @11ty/eleventy --pathprefix /docs",
249 | compile_dir: "static/docs/_site"
250 | }
251 | ```
252 |
253 | ### Using Gatsby
254 |
255 | ⚠️ Gatsby is unlikely to work in development mode, due to [this
256 | issue](https://github.com/gatsbyjs/gatsby/issues/18143), wherein all the assets
257 | are actually served over a socket.io WebSocket channel and not able to be
258 | proxied effectively. ⚠️
259 |
260 | If you're mounting a [Gatsby](https://www.gatsbyjs.org) site to a non-root path
261 | (e.g. in static-rails, you've configured its `url_root_path` to, say,
262 | `careers`), then you'll want to configure the same root path in Gatsby as well,
263 | so that its development servers and built assets line up correctly.
264 |
265 | To do this, first add the `pathPrefix` property to `gatsby-config.js`:
266 |
267 | ```js
268 | module.exports = {
269 | pathPrefix: `/careers`,
270 | // …
271 | }
272 | ```
273 |
274 | Next, add the flag `--prefix-paths` to both the Gatsby site's `server_command`
275 | and `compile_command`, or else the setting will be ignored.
276 |
277 | A static-rails config for a Gatsby configuration in `sites` might look like:
278 |
279 | ```rb
280 | {
281 | name: "gatsby",
282 | url_root_path: "/docs",
283 | source_dir: "static/docs",
284 | server_command: "npx gatsby develop --prefix-paths",
285 | server_port: 8000,
286 | compile_command: "npx gatsby build --prefix-paths",
287 | compile_dir: "static/docs/public"
288 | },
289 | ```
290 |
291 | ## Code of Conduct
292 |
293 | This project follows Test Double's [code of
294 | conduct](https://testdouble.com/code-of-conduct) for all community interactions,
295 | including (but not limited to) one-on-one communications, public posts/comments,
296 | code reviews, pull requests, and GitHub issues. If violations occur, Test Double
297 | will take any action they deem appropriate for the infraction, up to and
298 | including blocking a user from the organization's repositories.
299 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 | require "standard/rake"
4 |
5 | Rake::TestTask.new(:test) do |t|
6 | t.libs << "test"
7 | t.libs << "lib"
8 | t.test_files = FileList["test/**/*_test.rb"]
9 | end
10 |
11 | task default: ["standard:fix", :test]
12 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "rails/static/site"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/example/.browserslistrc:
--------------------------------------------------------------------------------
1 | defaults
2 |
--------------------------------------------------------------------------------
/example/.env:
--------------------------------------------------------------------------------
1 | CYPRESS_RAILS_PORT=3009
2 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 | /db/*.sqlite3-*
14 |
15 | # Ignore all logfiles and tempfiles.
16 | /log/*
17 | /tmp/*
18 | !/log/.keep
19 | !/tmp/.keep
20 |
21 | # Ignore uploaded files in development.
22 | /storage/*
23 | !/storage/.keep
24 |
25 | /public/assets
26 | .byebug_history
27 |
28 | # Ignore master key for decrypting credentials and more.
29 | /config/master.key
30 |
31 | /public/packs
32 | /public/packs-test
33 | /node_modules
34 | /yarn-error.log
35 | yarn-debug.log*
36 | .yarn-integrity
37 |
--------------------------------------------------------------------------------
/example/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby "~> 2.7.0"
5 |
6 | gem "rails"
7 | gem "sqlite3"
8 | gem "puma"
9 | gem "sass-rails"
10 | gem "webpacker"
11 | gem "turbolinks"
12 | gem "jbuilder"
13 |
14 | gem "standard"
15 | gem "static-rails", path: ".."
16 | gem "cypress-rails"
17 | gem "dotenv-rails"
18 |
19 | gem "bootsnap", require: false
20 |
21 | group :development, :test do
22 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw]
23 | gem "pry-rails"
24 | end
25 |
26 | group :development do
27 | gem "web-console"
28 | gem "listen"
29 | end
30 |
31 | group :test do
32 | gem "capybara"
33 | gem "selenium-webdriver"
34 | gem "webdrivers"
35 | end
36 |
37 | gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
38 |
--------------------------------------------------------------------------------
/example/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ..
3 | specs:
4 | static-rails (0.1.0)
5 | rack-proxy (~> 0.6)
6 | railties (>= 5.0.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (6.1.4)
12 | actionpack (= 6.1.4)
13 | activesupport (= 6.1.4)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | actionmailbox (6.1.4)
17 | actionpack (= 6.1.4)
18 | activejob (= 6.1.4)
19 | activerecord (= 6.1.4)
20 | activestorage (= 6.1.4)
21 | activesupport (= 6.1.4)
22 | mail (>= 2.7.1)
23 | actionmailer (6.1.4)
24 | actionpack (= 6.1.4)
25 | actionview (= 6.1.4)
26 | activejob (= 6.1.4)
27 | activesupport (= 6.1.4)
28 | mail (~> 2.5, >= 2.5.4)
29 | rails-dom-testing (~> 2.0)
30 | actionpack (6.1.4)
31 | actionview (= 6.1.4)
32 | activesupport (= 6.1.4)
33 | rack (~> 2.0, >= 2.0.9)
34 | rack-test (>= 0.6.3)
35 | rails-dom-testing (~> 2.0)
36 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
37 | actiontext (6.1.4)
38 | actionpack (= 6.1.4)
39 | activerecord (= 6.1.4)
40 | activestorage (= 6.1.4)
41 | activesupport (= 6.1.4)
42 | nokogiri (>= 1.8.5)
43 | actionview (6.1.4)
44 | activesupport (= 6.1.4)
45 | builder (~> 3.1)
46 | erubi (~> 1.4)
47 | rails-dom-testing (~> 2.0)
48 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
49 | activejob (6.1.4)
50 | activesupport (= 6.1.4)
51 | globalid (>= 0.3.6)
52 | activemodel (6.1.4)
53 | activesupport (= 6.1.4)
54 | activerecord (6.1.4)
55 | activemodel (= 6.1.4)
56 | activesupport (= 6.1.4)
57 | activestorage (6.1.4)
58 | actionpack (= 6.1.4)
59 | activejob (= 6.1.4)
60 | activerecord (= 6.1.4)
61 | activesupport (= 6.1.4)
62 | marcel (~> 1.0.0)
63 | mini_mime (>= 1.1.0)
64 | activesupport (6.1.4)
65 | concurrent-ruby (~> 1.0, >= 1.0.2)
66 | i18n (>= 1.6, < 2)
67 | minitest (>= 5.1)
68 | tzinfo (~> 2.0)
69 | zeitwerk (~> 2.3)
70 | addressable (2.8.0)
71 | public_suffix (>= 2.0.2, < 5.0)
72 | ast (2.4.2)
73 | bindex (0.8.1)
74 | bootsnap (1.7.6)
75 | msgpack (~> 1.0)
76 | builder (3.2.4)
77 | byebug (11.1.3)
78 | capybara (3.35.3)
79 | addressable
80 | mini_mime (>= 0.1.3)
81 | nokogiri (~> 1.8)
82 | rack (>= 1.6.0)
83 | rack-test (>= 0.6.3)
84 | regexp_parser (>= 1.5, < 3.0)
85 | xpath (~> 3.2)
86 | childprocess (3.0.0)
87 | coderay (1.1.3)
88 | concurrent-ruby (1.1.9)
89 | crass (1.0.6)
90 | cypress-rails (0.5.1)
91 | puma (>= 3.8.0)
92 | railties (>= 5.2.0)
93 | dotenv (2.7.6)
94 | dotenv-rails (2.7.6)
95 | dotenv (= 2.7.6)
96 | railties (>= 3.2)
97 | erubi (1.10.0)
98 | ffi (1.15.3)
99 | globalid (0.4.2)
100 | activesupport (>= 4.2.0)
101 | i18n (1.8.10)
102 | concurrent-ruby (~> 1.0)
103 | jbuilder (2.11.2)
104 | activesupport (>= 5.0.0)
105 | listen (3.6.0)
106 | rb-fsevent (~> 0.10, >= 0.10.3)
107 | rb-inotify (~> 0.9, >= 0.9.10)
108 | loofah (2.10.0)
109 | crass (~> 1.0.2)
110 | nokogiri (>= 1.5.9)
111 | mail (2.7.1)
112 | mini_mime (>= 0.1.1)
113 | marcel (1.0.1)
114 | method_source (1.0.0)
115 | mini_mime (1.1.0)
116 | mini_portile2 (2.5.3)
117 | minitest (5.14.4)
118 | msgpack (1.4.2)
119 | nio4r (2.5.7)
120 | nokogiri (1.11.7)
121 | mini_portile2 (~> 2.5.0)
122 | racc (~> 1.4)
123 | parallel (1.20.1)
124 | parser (3.0.2.0)
125 | ast (~> 2.4.1)
126 | pry (0.14.1)
127 | coderay (~> 1.1)
128 | method_source (~> 1.0)
129 | pry-rails (0.3.9)
130 | pry (>= 0.10.4)
131 | public_suffix (4.0.6)
132 | puma (5.3.2)
133 | nio4r (~> 2.0)
134 | racc (1.5.2)
135 | rack (2.2.3)
136 | rack-proxy (0.7.0)
137 | rack
138 | rack-test (1.1.0)
139 | rack (>= 1.0, < 3)
140 | rails (6.1.4)
141 | actioncable (= 6.1.4)
142 | actionmailbox (= 6.1.4)
143 | actionmailer (= 6.1.4)
144 | actionpack (= 6.1.4)
145 | actiontext (= 6.1.4)
146 | actionview (= 6.1.4)
147 | activejob (= 6.1.4)
148 | activemodel (= 6.1.4)
149 | activerecord (= 6.1.4)
150 | activestorage (= 6.1.4)
151 | activesupport (= 6.1.4)
152 | bundler (>= 1.15.0)
153 | railties (= 6.1.4)
154 | sprockets-rails (>= 2.0.0)
155 | rails-dom-testing (2.0.3)
156 | activesupport (>= 4.2.0)
157 | nokogiri (>= 1.6)
158 | rails-html-sanitizer (1.3.0)
159 | loofah (~> 2.3)
160 | railties (6.1.4)
161 | actionpack (= 6.1.4)
162 | activesupport (= 6.1.4)
163 | method_source
164 | rake (>= 0.13)
165 | thor (~> 1.0)
166 | rainbow (3.0.0)
167 | rake (13.0.6)
168 | rb-fsevent (0.11.0)
169 | rb-inotify (0.10.1)
170 | ffi (~> 1.0)
171 | regexp_parser (2.1.1)
172 | rexml (3.2.5)
173 | rubocop (1.18.3)
174 | parallel (~> 1.10)
175 | parser (>= 3.0.0.0)
176 | rainbow (>= 2.2.2, < 4.0)
177 | regexp_parser (>= 1.8, < 3.0)
178 | rexml
179 | rubocop-ast (>= 1.7.0, < 2.0)
180 | ruby-progressbar (~> 1.7)
181 | unicode-display_width (>= 1.4.0, < 3.0)
182 | rubocop-ast (1.8.0)
183 | parser (>= 3.0.1.1)
184 | rubocop-performance (1.11.4)
185 | rubocop (>= 1.7.0, < 2.0)
186 | rubocop-ast (>= 0.4.0)
187 | ruby-progressbar (1.11.0)
188 | rubyzip (2.3.2)
189 | sass-rails (6.0.0)
190 | sassc-rails (~> 2.1, >= 2.1.1)
191 | sassc (2.4.0)
192 | ffi (~> 1.9)
193 | sassc-rails (2.1.2)
194 | railties (>= 4.0.0)
195 | sassc (>= 2.0)
196 | sprockets (> 3.0)
197 | sprockets-rails
198 | tilt
199 | selenium-webdriver (3.142.7)
200 | childprocess (>= 0.5, < 4.0)
201 | rubyzip (>= 1.2.2)
202 | semantic_range (3.0.0)
203 | sprockets (4.0.2)
204 | concurrent-ruby (~> 1.0)
205 | rack (> 1, < 3)
206 | sprockets-rails (3.2.2)
207 | actionpack (>= 4.0)
208 | activesupport (>= 4.0)
209 | sprockets (>= 3.0.0)
210 | sqlite3 (1.4.2)
211 | standard (1.1.5)
212 | rubocop (= 1.18.3)
213 | rubocop-performance (= 1.11.4)
214 | thor (1.1.0)
215 | tilt (2.0.10)
216 | turbolinks (5.2.1)
217 | turbolinks-source (~> 5.2)
218 | turbolinks-source (5.2.0)
219 | tzinfo (2.0.4)
220 | concurrent-ruby (~> 1.0)
221 | unicode-display_width (2.0.0)
222 | web-console (4.1.0)
223 | actionview (>= 6.0.0)
224 | activemodel (>= 6.0.0)
225 | bindex (>= 0.4.0)
226 | railties (>= 6.0.0)
227 | webdrivers (4.6.0)
228 | nokogiri (~> 1.6)
229 | rubyzip (>= 1.3.0)
230 | selenium-webdriver (>= 3.0, < 4.0)
231 | webpacker (5.4.0)
232 | activesupport (>= 5.2)
233 | rack-proxy (>= 0.6.1)
234 | railties (>= 5.2)
235 | semantic_range (>= 2.3.0)
236 | websocket-driver (0.7.5)
237 | websocket-extensions (>= 0.1.0)
238 | websocket-extensions (0.1.5)
239 | xpath (3.2.0)
240 | nokogiri (~> 1.8)
241 | zeitwerk (2.4.2)
242 |
243 | PLATFORMS
244 | ruby
245 |
246 | DEPENDENCIES
247 | bootsnap
248 | byebug
249 | capybara
250 | cypress-rails
251 | dotenv-rails
252 | jbuilder
253 | listen
254 | pry-rails
255 | puma
256 | rails
257 | sass-rails
258 | selenium-webdriver
259 | sqlite3
260 | standard
261 | static-rails!
262 | turbolinks
263 | tzinfo-data
264 | web-console
265 | webdrivers
266 | webpacker
267 |
268 | RUBY VERSION
269 | ruby 2.7.0p0
270 |
271 | BUNDLED WITH
272 | 2.2.15
273 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # static-rails-example
2 |
3 | This example uses the static-rails gem. To see how this is all configured, check
4 | out [config/initializers/static.rb](config/initializers/static.rb).
5 |
6 | ## Development mode
7 |
8 | See it in action in local development
9 |
10 | 1. Install Ruby & [hugo](https://gohugo.io/getting-started/installing/)
11 | 2. `cd static/docs && bundle` to set up the Jekyll app
12 | 3. `cd static/blog-docs && npm install` to set up the Eleventy app
13 | 4. Add this line to your system's `/etc/hosts` file: `127.0.0.1 blog.localhost`
14 | 5. In this project's root directory run `bundle && bin/rails s`
15 | 6. Visit any of the static sites within:
16 | * A Jekyll site at [http://localhost:3000/docs](localhost:3000/docs)
17 | * A Hugo site at [http://blog.localhost:3000](blog.localhost:3000)
18 | * An Eleventy site at [http://blog.localhost:3000/docs](blog.localhost:3000/docs)
19 | * A Hugo site at [http://localhost:3000](localhost:3000/marketing)
20 |
21 | ## Production mode
22 |
23 | See how things would look in production:
24 |
25 | 1. Run `bin/rake assets:precompile`
26 | 2. Inspect the built assets in
27 | * `static/docs/_site`
28 | * `static/blog/public`
29 | * `static/blog-docs/_site`
30 | * `static/marketing/public`
31 | 3. Run `RAILS_ENV=production bin/rails s` and verify the above URLs serve the
32 | production assets without running any dev servers
33 |
34 | ## Running tests
35 |
36 | This app also includes a Cypress test (via our
37 | [cypress-rails](https://github.com/testdouble/cypress-rails) gem).
38 |
39 | Run it with:
40 |
41 | ```
42 | $ ./script/test
43 | ```
44 |
45 | You can check out the [CI build
46 | output](https://circleci.com/gh/testdouble/static-rails), too.
47 |
--------------------------------------------------------------------------------
/example/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/example/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 |
--------------------------------------------------------------------------------
/example/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/app/assets/images/.keep
--------------------------------------------------------------------------------
/example/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/example/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/example/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/example/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | end
3 |
--------------------------------------------------------------------------------
/example/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/example/app/controllers/docs/api/houses_controller.rb:
--------------------------------------------------------------------------------
1 | module Docs
2 | module Api
3 | class HousesController < ApplicationController
4 | def create
5 | render json: {
6 | id: 22
7 | }
8 | end
9 |
10 | def show
11 | if params[:id] == "22"
12 | render json: {
13 | id: 22,
14 | fake: true
15 | }
16 | else
17 | head :internal_server_error
18 | end
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/example/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/example/app/javascript/channels/consumer.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 |
4 | import { createConsumer } from "@rails/actioncable"
5 |
6 | export default createConsumer()
7 |
--------------------------------------------------------------------------------
/example/app/javascript/channels/index.js:
--------------------------------------------------------------------------------
1 | // Load all the channels within this directory and all subdirectories.
2 | // Channel files must be named *_channel.js.
3 |
4 | const channels = require.context('.', true, /_channel\.js$/)
5 | channels.keys().forEach(channels)
6 |
--------------------------------------------------------------------------------
/example/app/javascript/packs/application.js:
--------------------------------------------------------------------------------
1 | // This file is automatically compiled by Webpack, along with any other files
2 | // present in this directory. You're encouraged to place your actual application logic in
3 | // a relevant structure within app/javascript and only use these pack files to reference
4 | // that code so it'll be compiled.
5 |
6 | require("@rails/ujs").start()
7 | require("turbolinks").start()
8 | require("@rails/activestorage").start()
9 | require("channels")
10 |
11 |
12 | // Uncomment to copy all static images under ../images to the output folder and reference
13 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
14 | // or the `imagePath` JavaScript helper below.
15 | //
16 | // const images = require.context('../images', true)
17 | // const imagePath = (name) => images(name, true)
18 |
--------------------------------------------------------------------------------
/example/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/example/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: "from@example.com"
3 | layout "mailer"
4 | end
5 |
--------------------------------------------------------------------------------
/example/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/example/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/app/models/concerns/.keep
--------------------------------------------------------------------------------
/example/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Example
5 | <%= csrf_meta_tags %>
6 | <%= csp_meta_tag %>
7 |
8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
9 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
10 |
11 |
12 |
13 | <%= yield %>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/example/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | var validEnv = ['development', 'test', 'production']
3 | var currentEnv = api.env()
4 | var isDevelopmentEnv = api.env('development')
5 | var isProductionEnv = api.env('production')
6 | var isTestEnv = api.env('test')
7 |
8 | if (!validEnv.includes(currentEnv)) {
9 | throw new Error(
10 | 'Please specify a valid `NODE_ENV` or ' +
11 | '`BABEL_ENV` environment variables. Valid values are "development", ' +
12 | '"test", and "production". Instead, received: ' +
13 | JSON.stringify(currentEnv) +
14 | '.'
15 | )
16 | }
17 |
18 | return {
19 | presets: [
20 | isTestEnv && [
21 | '@babel/preset-env',
22 | {
23 | targets: {
24 | node: 'current'
25 | }
26 | }
27 | ],
28 | (isProductionEnv || isDevelopmentEnv) && [
29 | '@babel/preset-env',
30 | {
31 | forceAllTransforms: true,
32 | useBuiltIns: 'entry',
33 | corejs: 3,
34 | modules: false,
35 | exclude: ['transform-typeof-symbol']
36 | }
37 | ]
38 | ].filter(Boolean),
39 | plugins: [
40 | 'babel-plugin-macros',
41 | '@babel/plugin-syntax-dynamic-import',
42 | isTestEnv && 'babel-plugin-dynamic-import-node',
43 | '@babel/plugin-transform-destructuring',
44 | [
45 | '@babel/plugin-proposal-class-properties',
46 | {
47 | loose: true
48 | }
49 | ],
50 | [
51 | '@babel/plugin-proposal-object-rest-spread',
52 | {
53 | useBuiltIns: true
54 | }
55 | ],
56 | [
57 | '@babel/plugin-transform-runtime',
58 | {
59 | helpers: false,
60 | regenerator: true,
61 | corejs: false
62 | }
63 | ],
64 | [
65 | '@babel/plugin-transform-regenerator',
66 | {
67 | async: false
68 | }
69 | ]
70 | ].filter(Boolean)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/example/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_version
64 | @bundler_version ||=
65 | env_var_version || cli_arg_version ||
66 | lockfile_version
67 | end
68 |
69 | def bundler_requirement
70 | return "#{Gem::Requirement.default}.a" unless bundler_version
71 |
72 | bundler_gem_version = Gem::Version.new(bundler_version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/example/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/example/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/example/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path('..', __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to setup or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:prepare'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/example/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/webpack_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::WebpackRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/example/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/dev_server_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::DevServerRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/example/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/example/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative "config/environment"
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/example/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module Example
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 6.0
13 |
14 | # Settings in config/environments/* take precedence over those specified here.
15 | # Application configuration can go into files in config/initializers
16 | # -- all .rb files in that directory are automatically loaded after loading
17 | # the framework and any gems in your application.
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/example/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/example/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: example_production
11 |
--------------------------------------------------------------------------------
/example/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | zBam0OzM39Zga5qVnBRga0hpA5erOO7mA9pUBOoXbk1Ur7YTMlgJfS9K59tyQBzHJB49MbGCLTzJOq23TzHH12gONTWPFyiOgK005Vci+B3rZzefxm/3D3u4YdvAfc/if2k+a97XdKBj0Z+fP11BBiuhlP3Ca398p4YL4Jt433fjXQQWoCRdI/ntN4B6dxJrno1i1C3fKZi4OMzfdP5xU1iS+vcvw8u484DNdb90cPVXLaIt8elhnRIOIMMtepWuOzRGSNn2FAQGTs9IDL4+0xj6AlSYvegT/zmtRMroNsqZxQ+K6qY85uPVYVswt4HnpI1nWBVtDyHg2uaTjcjtPEFKUQMp6dA3ABkMW9Gz5AyKyPB6WKX86NZnOG7RsCCO1hnzv2DZo5QP5WAR2hKagz3oVWPQjOm9tWls--c7mnZ8UROCfsFNdq--yt07Y7Ud//X0znGllixJ6A==
--------------------------------------------------------------------------------
/example/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: sqlite3
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | database: db/development.sqlite3
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | database: db/test.sqlite3
22 |
23 | production:
24 | <<: *default
25 | database: db/production.sqlite3
26 |
--------------------------------------------------------------------------------
/example/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/example/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | # Run rails dev:cache to toggle caching.
17 | if Rails.root.join("tmp", "caching-dev.txt").exist?
18 | config.action_controller.perform_caching = true
19 | config.action_controller.enable_fragment_cache_logging = true
20 |
21 | config.cache_store = :memory_store
22 | config.public_file_server.headers = {
23 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
24 | }
25 | else
26 | config.action_controller.perform_caching = false
27 |
28 | config.cache_store = :null_store
29 | end
30 |
31 | # Store uploaded files on the local file system (see config/storage.yml for options).
32 | config.active_storage.service = :local
33 |
34 | # Don't care if the mailer can't send.
35 | config.action_mailer.raise_delivery_errors = false
36 |
37 | config.action_mailer.perform_caching = false
38 |
39 | # Print deprecation notices to the Rails logger.
40 | config.active_support.deprecation = :log
41 |
42 | # Raise an error on page load if there are pending migrations.
43 | config.active_record.migration_error = :page_load
44 |
45 | # Highlight code that triggered database queries in logs.
46 | config.active_record.verbose_query_logs = true
47 |
48 | # Debug mode disables concatenation and preprocessing of assets.
49 | # This option may cause significant delays in view rendering with a large
50 | # number of complex assets.
51 | config.assets.debug = true
52 |
53 | # Suppress logger output for asset requests.
54 | config.assets.quiet = true
55 |
56 | # Raises error for missing translations.
57 | # config.action_view.raise_on_missing_translations = true
58 |
59 | # Use an evented file watcher to asynchronously detect changes in source code,
60 | # routes, locales, etc. This feature depends on the listen gem.
61 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
62 | end
63 |
--------------------------------------------------------------------------------
/example/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # Code is not reloaded between requests.
5 | config.cache_classes = true
6 |
7 | # Eager load code on boot. This eager loads most of Rails and
8 | # your application in memory, allowing both threaded web servers
9 | # and those relying on copy on write to perform better.
10 | # Rake tasks automatically ignore this option for performance.
11 | config.eager_load = true
12 |
13 | # Full error reports are disabled and caching is turned on.
14 | config.consider_all_requests_local = true
15 | config.action_controller.perform_caching = true
16 |
17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
19 | # config.require_master_key = true
20 |
21 | # Disable serving static files from the `/public` folder by default since
22 | # Apache or NGINX already handles this.
23 | config.public_file_server.enabled = true # ENV["RAILS_SERVE_STATIC_FILES"].present?
24 |
25 | # Compress CSS using a preprocessor.
26 | # config.assets.css_compressor = :sass
27 |
28 | # Do not fallback to assets pipeline if a precompiled asset is missed.
29 | config.assets.compile = false
30 |
31 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
32 | # config.action_controller.asset_host = 'http://assets.example.com'
33 |
34 | # Specifies the header that your server uses for sending files.
35 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
36 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
37 |
38 | # Store uploaded files on the local file system (see config/storage.yml for options).
39 | config.active_storage.service = :local
40 |
41 | # Mount Action Cable outside main process or domain.
42 | # config.action_cable.mount_path = nil
43 | # config.action_cable.url = 'wss://example.com/cable'
44 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
45 |
46 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
47 | # config.force_ssl = true
48 |
49 | # Use the lowest log level to ensure availability of diagnostic information
50 | # when problems arise.
51 | config.log_level = :debug
52 |
53 | # Prepend all log lines with the following tags.
54 | config.log_tags = [:request_id]
55 |
56 | # Use a different cache store in production.
57 | # config.cache_store = :mem_cache_store
58 |
59 | # Use a real queuing backend for Active Job (and separate queues per environment).
60 | # config.active_job.queue_adapter = :resque
61 | # config.active_job.queue_name_prefix = "example_production"
62 |
63 | config.action_mailer.perform_caching = false
64 |
65 | # Ignore bad email addresses and do not raise email delivery errors.
66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
67 | # config.action_mailer.raise_delivery_errors = false
68 |
69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
70 | # the I18n.default_locale when a translation cannot be found).
71 | config.i18n.fallbacks = true
72 |
73 | # Send deprecation notices to registered listeners.
74 | config.active_support.deprecation = :notify
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Use a different logger for distributed setups.
80 | # require 'syslog/logger'
81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
82 |
83 | if ENV["RAILS_LOG_TO_STDOUT"].present?
84 | logger = ActiveSupport::Logger.new(STDOUT)
85 | logger.formatter = config.log_formatter
86 | config.logger = ActiveSupport::TaggedLogging.new(logger)
87 | end
88 |
89 | # Do not dump schema after migrations.
90 | config.active_record.dump_schema_after_migration = false
91 |
92 | # Inserts middleware to perform automatic connection switching.
93 | # The `database_selector` hash is used to pass options to the DatabaseSelector
94 | # middleware. The `delay` is used to determine how long to wait after a write
95 | # to send a subsequent read to the primary.
96 | #
97 | # The `database_resolver` class is used by the middleware to determine which
98 | # database is appropriate to use based on the time delay.
99 | #
100 | # The `database_resolver_context` class is used by the middleware to set
101 | # timestamps for the last write to the primary. The resolver uses the context
102 | # class timestamps to determine how long to wait before reading from the
103 | # replica.
104 | #
105 | # By default Rails will store a last write timestamp in the session. The
106 | # DatabaseSelector middleware is designed as such you can define your own
107 | # strategy for connection switching and pass that into the middleware through
108 | # these configuration options.
109 | # config.active_record.database_selector = { delay: 2.seconds }
110 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
111 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
112 | end
113 |
--------------------------------------------------------------------------------
/example/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # The test environment is used exclusively to run your application's
2 | # test suite. You never need to work with it otherwise. Remember that
3 | # your test database is "scratch space" for the test suite and is wiped
4 | # and recreated between test runs. Don't rely on the data there!
5 |
6 | Rails.application.configure do
7 | # Settings specified here will take precedence over those in config/application.rb.
8 |
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot. This avoids loading your whole application
12 | # just for the purpose of running a single test. If you are using a tool that
13 | # preloads Rails for running tests, you may have to set it to true.
14 | config.eager_load = false
15 |
16 | # Configure public file server for tests with Cache-Control for performance.
17 | config.public_file_server.enabled = true
18 | config.public_file_server.headers = {
19 | "Cache-Control" => "public, max-age=3600"
20 | }
21 |
22 | # Show full error reports and disable caching.
23 | config.consider_all_requests_local = true
24 | config.action_controller.perform_caching = false
25 | config.cache_store = :null_store
26 |
27 | # Raise exceptions instead of rendering exception templates.
28 | config.action_dispatch.show_exceptions = false
29 |
30 | # Enable request forgery protection in test environment - we have a feature that depends on this working!
31 | config.action_controller.allow_forgery_protection = true
32 |
33 | # Store uploaded files on the local file system in a temporary directory.
34 | config.active_storage.service = :test
35 |
36 | config.action_mailer.perform_caching = false
37 |
38 | # Tell Action Mailer not to deliver emails to the real world.
39 | # The :test delivery method accumulates sent emails in the
40 | # ActionMailer::Base.deliveries array.
41 | config.action_mailer.delivery_method = :test
42 |
43 | # Print deprecation notices to the stderr.
44 | config.active_support.deprecation = :stderr
45 |
46 | # Raises error for missing translations.
47 | # config.action_view.raise_on_missing_translations = true
48 | end
49 |
--------------------------------------------------------------------------------
/example/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/example/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join("node_modules")
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/example/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/example/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 | # # If you are using webpack-dev-server then specify webpack-dev-server host
15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
16 |
17 | # # Specify URI for violation reports
18 | # # policy.report_uri "/csp-violation-report-endpoint"
19 | # end
20 |
21 | # If you are using UJS then enable automatic nonce generation
22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
23 |
24 | # Set the nonce only to specific directives
25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
26 |
27 | # Report CSP violations to a specified URI
28 | # For further information see the following documentation:
29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
30 | # Rails.application.config.content_security_policy_report_only = true
31 |
--------------------------------------------------------------------------------
/example/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/example/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/example/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/example/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/example/config/initializers/static.rb:
--------------------------------------------------------------------------------
1 | StaticRails.config do |config|
2 | config.set_csrf_token_cookie = true
3 |
4 | config.sites = [
5 | {
6 | name: "blog-docs",
7 | url_subdomain: "blog",
8 | url_root_path: "/docs",
9 | url_skip_paths_starting_with: ["/docs/api/"],
10 | source_dir: "static/blog-docs",
11 | server_command: "npx @11ty/eleventy --serve --pathprefix /docs",
12 | server_port: 8080,
13 | compile_command: "npx @11ty/eleventy --pathprefix /docs",
14 | compile_dir: "static/blog-docs/_site"
15 | },
16 | {
17 | name: "blog",
18 | url_subdomain: "blog",
19 | url_root_path: "/",
20 | url_skip_paths_starting_with: ["/docs/api/"],
21 | source_dir: "static/blog",
22 | server_command: "hugo server --disableLiveReload",
23 | server_port: 1313,
24 | compile_command: "hugo",
25 | compile_dir: "static/blog/public"
26 | },
27 | {
28 | name: "docs",
29 | url_root_path: "/docs",
30 | url_skip_paths_starting_with: ["/docs/api/"],
31 | source_dir: "static/docs",
32 | env: {"BUNDLE_PATH" => "vendor/bundle"},
33 | server_command: "bundle exec jekyll serve",
34 | server_port: 4000,
35 | compile_command: "bundle exec jekyll build",
36 | compile_dir: "static/docs/_site"
37 | },
38 | {
39 | name: "marketing",
40 | url_root_path: "/marketing",
41 | source_dir: "static/marketing",
42 | server_command: "hugo server -p 1314 --disableLiveReload",
43 | server_port: 1314,
44 | compile_command: "hugo",
45 | compile_dir: "static/marketing/public",
46 | compile_404_file_path: "404.html"
47 | }
48 | ]
49 | end
50 |
--------------------------------------------------------------------------------
/example/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/example/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/example/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
12 | #
13 | port ENV.fetch("PORT") { 3000 }
14 |
15 | # Specifies the `environment` that Puma will run in.
16 | #
17 | environment ENV.fetch("RAILS_ENV") { "development" }
18 |
19 | # Specifies the `pidfile` that Puma will use.
20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
21 |
22 | # Specifies the number of `workers` to boot in clustered mode.
23 | # Workers are forked web server processes. If using threads and workers together
24 | # the concurrency of the application would be max `threads` * `workers`.
25 | # Workers do not work on JRuby or Windows (both of which do not support
26 | # processes).
27 | #
28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
29 |
30 | # Use the `preload_app!` method when specifying a `workers` number.
31 | # This directive tells Puma to first boot the application and load code
32 | # before forking the application. This takes advantage of Copy On Write
33 | # process behavior so workers use less memory.
34 | #
35 | # preload_app!
36 |
37 | # Allow puma to be restarted by `rails restart` command.
38 | plugin :tmp_restart
39 |
--------------------------------------------------------------------------------
/example/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | constraints ->(req) { req.host.start_with?("blog") } do
3 | namespace :docs do
4 | namespace :api do
5 | resources :houses
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/example/config/spring.rb:
--------------------------------------------------------------------------------
1 | Spring.watch(
2 | ".ruby-version",
3 | ".rbenv-vars",
4 | "tmp/restart.txt",
5 | "tmp/caching-dev.txt"
6 | )
7 |
--------------------------------------------------------------------------------
/example/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/example/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/example/config/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker')
2 |
3 | module.exports = environment
4 |
--------------------------------------------------------------------------------
/example/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/example/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/example/config/webpacker.yml:
--------------------------------------------------------------------------------
1 | # Note: You must restart bin/webpack-dev-server for changes to take effect
2 |
3 | default: &default
4 | source_path: app/javascript
5 | source_entry_path: packs
6 | public_root_path: public
7 | public_output_path: packs
8 | cache_path: tmp/cache/webpacker
9 | check_yarn_integrity: false
10 | webpack_compile_output: true
11 |
12 | # Additional paths webpack should lookup modules
13 | # ['app/assets', 'engine/foo/app/assets']
14 | resolved_paths: []
15 |
16 | # Reload manifest.json on all requests so we reload latest compiled packs
17 | cache_manifest: false
18 |
19 | # Extract and emit a css file
20 | extract_css: false
21 |
22 | static_assets_extensions:
23 | - .jpg
24 | - .jpeg
25 | - .png
26 | - .gif
27 | - .tiff
28 | - .ico
29 | - .svg
30 | - .eot
31 | - .otf
32 | - .ttf
33 | - .woff
34 | - .woff2
35 |
36 | extensions:
37 | - .mjs
38 | - .js
39 | - .sass
40 | - .scss
41 | - .css
42 | - .module.sass
43 | - .module.scss
44 | - .module.css
45 | - .png
46 | - .svg
47 | - .gif
48 | - .jpeg
49 | - .jpg
50 |
51 | development:
52 | <<: *default
53 | compile: true
54 |
55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules
56 | check_yarn_integrity: true
57 |
58 | # Reference: https://webpack.js.org/configuration/dev-server/
59 | dev_server:
60 | https: false
61 | host: localhost
62 | port: 3035
63 | public: localhost:3035
64 | hmr: false
65 | # Inline should be set to true if using HMR
66 | inline: true
67 | overlay: true
68 | compress: true
69 | disable_host_check: true
70 | use_local_ip: false
71 | quiet: false
72 | pretty: false
73 | headers:
74 | 'Access-Control-Allow-Origin': '*'
75 | watch_options:
76 | ignored: '**/node_modules/**'
77 |
78 |
79 | test:
80 | <<: *default
81 | compile: true
82 |
83 | # Compile test packs to a separate directory
84 | public_output_path: packs-test
85 |
86 | production:
87 | <<: *default
88 |
89 | # Production depends on precompilation of packs prior to booting for performance.
90 | compile: false
91 |
92 | # Extract and emit a css file
93 | extract_css: true
94 |
95 | # Cache manifest.json for performance
96 | cache_manifest: true
97 |
--------------------------------------------------------------------------------
/example/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "screenshotsFolder": "tmp/cypress_screenshots",
3 | "videosFolder": "tmp/cypress_videos",
4 | "trashAssetsBeforeRuns": false
5 | }
6 |
--------------------------------------------------------------------------------
/example/cypress/integration/static-rails.js:
--------------------------------------------------------------------------------
1 |
2 | describe('rails-static stuff seems to work', () => {
3 | it('accesses the 11ty site at blog.localhost/docs', () => {
4 | // Make sure the literal index HTML file works
5 | cy.visit(`http://blog.localhost:3009/docs/index.html`)
6 | cy.get('p').should('have.text', 'Hi')
7 |
8 | // Also make sure hitting the directory without trailing / works
9 | cy.visit(`http://blog.localhost:3009/docs`)
10 | cy.get('p').should('have.text', 'Hi')
11 | cy.get('#api-result').should('contain.text', 'API liked our CSRF token and sent back ID: 22')
12 | })
13 |
14 | it('accesses the hugo site at blog.localhost', () => {
15 | cy.visit('http://blog.localhost:3009')
16 |
17 | cy.get('header a')
18 | .should('contain.text', 'My Blerg')
19 | .should('have.attr', 'href', '/')
20 |
21 | cy.contains('My First Post').click()
22 | cy.get('article').should('contain.text', 'I am a post')
23 | })
24 |
25 | it('accesses the jekyll site at /docs', () => {
26 | cy.visit('http://localhost:3009/docs')
27 |
28 | cy.get('.post-link').click()
29 | cy.get('.post-content').should('contain.text', 'find this post in your')
30 | })
31 |
32 | it('accesses the hugo site at /marketing', () => {
33 | cy.visit('http://localhost:3009/marketing')
34 |
35 | cy.get('header a')
36 | .should('contain.text', 'My Marketing Page')
37 | .should('have.attr', 'href', '/marketing/')
38 |
39 | cy.contains('My First Post').click()
40 | cy.get('article').should('contain.text', 'I am a post')
41 | })
42 |
43 | it('can access an API carved out within where the 11ty site is mounted', () => {
44 | cy.visit('http://blog.localhost:3009/docs')
45 | cy.getCookie('_csrf_token').then((cookie) => {
46 | cy.request({
47 | url: 'http://blog.localhost:3009/docs/api/houses',
48 | method: 'POST',
49 | headers: {
50 | 'x-csrf-token': decodeURIComponent(cookie.value)
51 | }
52 | }).then((res) => {
53 | cy.request(`http://blog.localhost:3009/docs/api/houses/${res.body.id}`).should((response) => {
54 | expect(response.status).to.eq(200)
55 | expect(response.body).to.have.property('id', 22)
56 | expect(response.body).to.have.property('fake', true)
57 | })
58 | })
59 | })
60 | })
61 |
62 | it('[Production only] assign better cache-control for images', () => {
63 | if (Cypress.env('RAILS_ENV') !== 'production') return
64 |
65 | cy.request('http://localhost:3009/docs/assets/an_image.png').should((response) => {
66 | expect(response.headers).to.include({
67 | 'cache-control': 'public; max-age=31536000',
68 | 'content-length': '4320',
69 | 'content-type': 'image/png'
70 | })
71 |
72 | const lastModified = response.headers['last-modified']
73 | expect(lastModified).not.to.be.empty
74 |
75 | const preModified = new Date(new Date(lastModified).getTime() - 10000).toUTCString()
76 | cy.request({
77 | url: 'http://localhost:3009/docs/assets/an_image.png',
78 | headers: {
79 | 'if-modified-since': preModified
80 | }
81 | }).should((response) => {
82 | expect(response.status).to.eq(200)
83 | })
84 |
85 | const postModified = new Date(new Date(lastModified).getTime() + 10000).toUTCString()
86 | cy.request({
87 | url: 'http://localhost:3009/docs/assets/an_image.png',
88 | headers: {
89 | 'if-modified-since': postModified
90 | }
91 | }).should((response) => {
92 | expect(response.status).to.eq(304)
93 | })
94 | })
95 | })
96 |
97 | it('[Production only] cache-control no cache for HTML', () => {
98 | if (Cypress.env('RAILS_ENV') !== 'production') return
99 |
100 | cy.request('http://blog.localhost:3009/docs/index.html').should((response) => {
101 | expect(response.headers).to.include({
102 | 'cache-control': 'no-cache, no-store',
103 | // 'content-length': '4320',
104 | 'content-type': 'text/html'
105 | })
106 | })
107 | })
108 |
109 | it('[Production only] gzips most file types', () => {
110 | if (Cypress.env('RAILS_ENV') !== 'production') return
111 |
112 | cy.request({
113 | url: 'http://localhost:3009/docs/assets/a_javascript.js',
114 | headers: {
115 | 'accept-encoding': 'gzip'
116 | }
117 | }).should(response => {
118 | expect(response.headers).to.include({
119 | 'vary': 'Accept-Encoding',
120 | 'content-encoding': 'gzip',
121 | 'content-length': '58',
122 | 'content-type': 'application/javascript'
123 | })
124 | })
125 |
126 | cy.request({
127 | url: 'http://localhost:3009/docs/assets/a_json.json',
128 | headers: {
129 | 'accept-encoding': 'gzip'
130 | }
131 | }).should(response => {
132 | expect(response.headers).to.include({
133 | 'vary': 'Accept-Encoding',
134 | 'content-encoding': 'gzip',
135 | 'content-length': '58',
136 | 'content-type': 'application/json'
137 | })
138 | })
139 |
140 | cy.request({
141 | url: 'http://localhost:3009/docs/assets/a_css.css',
142 | headers: {
143 | 'accept-encoding': 'gzip'
144 | }
145 | }).should(response => {
146 | expect(response.headers).to.include({
147 | 'vary': 'Accept-Encoding',
148 | 'content-encoding': 'gzip',
149 | 'content-type': 'text/css'
150 | })
151 | })
152 |
153 | cy.request({
154 | url: 'http://localhost:3009/docs/assets/a_html.html',
155 | headers: {
156 | 'accept-encoding': 'gzip'
157 | }
158 | }).should(response => {
159 | expect(response.headers).to.include({
160 | 'vary': 'Accept-Encoding',
161 | 'content-encoding': 'gzip',
162 | 'content-type': 'text/html'
163 | })
164 | })
165 |
166 | cy.request({
167 | url: 'http://localhost:3009/docs/assets/an_image.png',
168 | headers: {
169 | 'accept-encoding': 'gzip'
170 | }
171 | }).should(response => {
172 | expect(response.headers['content-encoding']).to.be.undefined
173 | expect(response.headers['vary']).to.be.undefined
174 | expect(response.headers).to.include({
175 | 'content-type': 'image/png'
176 | })
177 | })
178 | })
179 |
180 | it('[Production only] will serve up 404 pages with 404 status code', () => {
181 | if (Cypress.env('RAILS_ENV') !== 'production') return
182 | cy.visit('http://localhost:3009/marketing/fooberry', {
183 | failOnStatusCode: false
184 | })
185 |
186 | cy.contains('I am a 404 page for /marketing')
187 |
188 | cy.request({
189 | url: 'http://localhost:3009/marketing/lmao',
190 | failOnStatusCode: false
191 | }).should(res => {
192 | expect(res.status).to.eq(404)
193 | })
194 | })
195 |
196 |
197 | })
198 |
--------------------------------------------------------------------------------
/example/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/example/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/example/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
22 | // Force the _csrf_token to persist across tests, which will exercise our
23 | // SitePlusCsrfMiddleware middleware's fixing of invalid tokens
24 | Cypress.Cookies.defaults({
25 | preserve: "_csrf_token"
26 | })
27 |
--------------------------------------------------------------------------------
/example/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/example/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/lib/assets/.keep
--------------------------------------------------------------------------------
/example/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/lib/tasks/.keep
--------------------------------------------------------------------------------
/example/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/log/.keep
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "private": true,
4 | "dependencies": {
5 | "@rails/actioncable": "^6.0.0",
6 | "@rails/activestorage": "^6.0.0",
7 | "@rails/ujs": "^6.0.0",
8 | "@rails/webpacker": "^5.4.0",
9 | "turbolinks": "^5.2.0"
10 | },
11 | "version": "0.1.0",
12 | "devDependencies": {
13 | "cypress": "^8.0.0",
14 | "webpack-dev-server": "^3.10.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-import'),
4 | require('postcss-flexbugs-fixes'),
5 | require('postcss-preset-env')({
6 | autoprefixer: {
7 | flexbox: 'no-2009'
8 | },
9 | stage: 3
10 | })
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/example/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/example/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/example/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/example/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/example/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 | Hello I am an Internet
2 |
--------------------------------------------------------------------------------
/example/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/example/script/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | echo "--> Running tests in development, starting all static servers and proxying requests"
6 | bin/rake cypress:run
7 |
8 | echo "--> Precompiling all static site assets with rake assets:precompile"
9 | bin/rake assets:precompile
10 |
11 | echo "--> Running tests in production, *not* starting servers and serving files instead of proxying"
12 | RAILS_ENV=production CYPRESS_RAILS_CYPRESS_OPTS="--env RAILS_ENV=production" bin/rake cypress:run
13 |
--------------------------------------------------------------------------------
/example/static/blog-docs/.gitignore:
--------------------------------------------------------------------------------
1 | /_site
2 | /node_modules
3 |
--------------------------------------------------------------------------------
/example/static/blog-docs/README.md:
--------------------------------------------------------------------------------
1 | # Page header
2 |
--------------------------------------------------------------------------------
/example/static/blog-docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Page title
5 |
6 |
7 | Hi
8 |
9 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/example/static/blog-docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog-docs",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "@11ty/eleventy": "^0.12.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/example/static/blog/.gitignore:
--------------------------------------------------------------------------------
1 | /resources
2 | /public
3 |
--------------------------------------------------------------------------------
/example/static/blog/archetypes/default.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "{{ replace .Name "-" " " | title }}"
3 | date: {{ .Date }}
4 | draft: true
5 | ---
6 |
7 |
--------------------------------------------------------------------------------
/example/static/blog/config.toml:
--------------------------------------------------------------------------------
1 | baseURL = "http://blog.example.com/"
2 | languageCode = "en-us"
3 | title = "My Blerg"
4 | theme = "ultralight"
5 |
--------------------------------------------------------------------------------
/example/static/blog/content/posts/my-first-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "My First Post"
3 | date: 2020-04-29T16:49:04+09:00
4 | ---
5 | h2 Hello
6 |
7 | I am a post
8 |
9 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/archetypes/default.md:
--------------------------------------------------------------------------------
1 | +++
2 | +++
3 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/js/index.js:
--------------------------------------------------------------------------------
1 | // JS can go here.
2 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/sass/code.css:
--------------------------------------------------------------------------------
1 | /* LIGHT MODE */
2 | /* hugo gen chromastyles --style autumn */
3 |
4 | /* Background */ .chroma { background-color: #ffffff }
5 | /* Error */ .chroma .err { color: #ff0000; background-color: #ffaaaa }
6 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
7 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
8 | /* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
9 | /* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
10 | /* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
11 | /* Keyword */ .chroma .k { color: #0000aa }
12 | /* KeywordConstant */ .chroma .kc { color: #0000aa }
13 | /* KeywordDeclaration */ .chroma .kd { color: #0000aa }
14 | /* KeywordNamespace */ .chroma .kn { color: #0000aa }
15 | /* KeywordPseudo */ .chroma .kp { color: #0000aa }
16 | /* KeywordReserved */ .chroma .kr { color: #0000aa }
17 | /* KeywordType */ .chroma .kt { color: #00aaaa }
18 | /* NameAttribute */ .chroma .na { color: #1e90ff }
19 | /* NameBuiltin */ .chroma .nb { color: #00aaaa }
20 | /* NameClass */ .chroma .nc { color: #00aa00; text-decoration: underline }
21 | /* NameConstant */ .chroma .no { color: #aa0000 }
22 | /* NameDecorator */ .chroma .nd { color: #888888 }
23 | /* NameEntity */ .chroma .ni { color: #880000; font-weight: bold }
24 | /* NameFunction */ .chroma .nf { color: #00aa00 }
25 | /* NameNamespace */ .chroma .nn { color: #00aaaa; text-decoration: underline }
26 | /* NameTag */ .chroma .nt { color: #1e90ff; font-weight: bold }
27 | /* NameVariable */ .chroma .nv { color: #aa0000 }
28 | /* LiteralString */ .chroma .s { color: #aa5500 }
29 | /* LiteralStringAffix */ .chroma .sa { color: #aa5500 }
30 | /* LiteralStringBacktick */ .chroma .sb { color: #aa5500 }
31 | /* LiteralStringChar */ .chroma .sc { color: #aa5500 }
32 | /* LiteralStringDelimiter */ .chroma .dl { color: #aa5500 }
33 | /* LiteralStringDoc */ .chroma .sd { color: #aa5500 }
34 | /* LiteralStringDouble */ .chroma .s2 { color: #aa5500 }
35 | /* LiteralStringEscape */ .chroma .se { color: #aa5500 }
36 | /* LiteralStringHeredoc */ .chroma .sh { color: #aa5500 }
37 | /* LiteralStringInterpol */ .chroma .si { color: #aa5500 }
38 | /* LiteralStringOther */ .chroma .sx { color: #aa5500 }
39 | /* LiteralStringRegex */ .chroma .sr { color: #009999 }
40 | /* LiteralStringSingle */ .chroma .s1 { color: #aa5500 }
41 | /* LiteralStringSymbol */ .chroma .ss { color: #0000aa }
42 | /* LiteralNumber */ .chroma .m { color: #009999 }
43 | /* LiteralNumberBin */ .chroma .mb { color: #009999 }
44 | /* LiteralNumberFloat */ .chroma .mf { color: #009999 }
45 | /* LiteralNumberHex */ .chroma .mh { color: #009999 }
46 | /* LiteralNumberInteger */ .chroma .mi { color: #009999 }
47 | /* LiteralNumberIntegerLong */ .chroma .il { color: #009999 }
48 | /* LiteralNumberOct */ .chroma .mo { color: #009999 }
49 | /* OperatorWord */ .chroma .ow { color: #0000aa }
50 | /* Comment */ .chroma .c { color: #aaaaaa; font-style: italic }
51 | /* CommentHashbang */ .chroma .ch { color: #aaaaaa; font-style: italic }
52 | /* CommentMultiline */ .chroma .cm { color: #aaaaaa; font-style: italic }
53 | /* CommentSingle */ .chroma .c1 { color: #aaaaaa; font-style: italic }
54 | /* CommentSpecial */ .chroma .cs { color: #0000aa; font-style: italic }
55 | /* CommentPreproc */ .chroma .cp { color: #4c8317 }
56 | /* CommentPreprocFile */ .chroma .cpf { color: #4c8317 }
57 | /* GenericDeleted */ .chroma .gd { color: #aa0000 }
58 | /* GenericEmph */ .chroma .ge { font-style: italic }
59 | /* GenericError */ .chroma .gr { color: #aa0000 }
60 | /* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
61 | /* GenericInserted */ .chroma .gi { color: #00aa00 }
62 | /* GenericOutput */ .chroma .go { color: #888888 }
63 | /* GenericPrompt */ .chroma .gp { color: #555555 }
64 | /* GenericStrong */ .chroma .gs { font-weight: bold }
65 | /* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
66 | /* GenericTraceback */ .chroma .gt { color: #aa0000 }
67 | /* GenericUnderline */ .chroma .gl { text-decoration: underline }
68 | /* TextWhitespace */ .chroma .w { color: #bbbbbb }
69 |
70 | /* LIGHT MODE OVERRIDES: */
71 | /* Background */ .chroma { background-color: #f6f6f6 }
72 |
73 |
74 | /* DARK MODE */
75 | @media (prefers-color-scheme: dark) {
76 | /* hugo gen chromastyles --style monokai */
77 | /* Background */ .chroma { color: #f8f8f2; background-color: #272822 }
78 | /* Error */ .chroma .err { color: #960050; background-color: #1e0010 }
79 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
80 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
81 | /* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
82 | /* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
83 | /* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
84 | /* Keyword */ .chroma .k { color: #66d9ef }
85 | /* KeywordConstant */ .chroma .kc { color: #66d9ef }
86 | /* KeywordDeclaration */ .chroma .kd { color: #66d9ef }
87 | /* KeywordNamespace */ .chroma .kn { color: #f92672 }
88 | /* KeywordPseudo */ .chroma .kp { color: #66d9ef }
89 | /* KeywordReserved */ .chroma .kr { color: #66d9ef }
90 | /* KeywordType */ .chroma .kt { color: #66d9ef }
91 | /* NameAttribute */ .chroma .na { color: #a6e22e }
92 | /* NameClass */ .chroma .nc { color: #a6e22e }
93 | /* NameConstant */ .chroma .no { color: #66d9ef }
94 | /* NameDecorator */ .chroma .nd { color: #a6e22e }
95 | /* NameException */ .chroma .ne { color: #a6e22e }
96 | /* NameFunction */ .chroma .nf { color: #a6e22e }
97 | /* NameOther */ .chroma .nx { color: #a6e22e }
98 | /* NameTag */ .chroma .nt { color: #f92672 }
99 | /* Literal */ .chroma .l { color: #ae81ff }
100 | /* LiteralDate */ .chroma .ld { color: #e6db74 }
101 | /* LiteralString */ .chroma .s { color: #e6db74 }
102 | /* LiteralStringAffix */ .chroma .sa { color: #e6db74 }
103 | /* LiteralStringBacktick */ .chroma .sb { color: #e6db74 }
104 | /* LiteralStringChar */ .chroma .sc { color: #e6db74 }
105 | /* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 }
106 | /* LiteralStringDoc */ .chroma .sd { color: #e6db74 }
107 | /* LiteralStringDouble */ .chroma .s2 { color: #e6db74 }
108 | /* LiteralStringEscape */ .chroma .se { color: #ae81ff }
109 | /* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 }
110 | /* LiteralStringInterpol */ .chroma .si { color: #e6db74 }
111 | /* LiteralStringOther */ .chroma .sx { color: #e6db74 }
112 | /* LiteralStringRegex */ .chroma .sr { color: #e6db74 }
113 | /* LiteralStringSingle */ .chroma .s1 { color: #e6db74 }
114 | /* LiteralStringSymbol */ .chroma .ss { color: #e6db74 }
115 | /* LiteralNumber */ .chroma .m { color: #ae81ff }
116 | /* LiteralNumberBin */ .chroma .mb { color: #ae81ff }
117 | /* LiteralNumberFloat */ .chroma .mf { color: #ae81ff }
118 | /* LiteralNumberHex */ .chroma .mh { color: #ae81ff }
119 | /* LiteralNumberInteger */ .chroma .mi { color: #ae81ff }
120 | /* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff }
121 | /* LiteralNumberOct */ .chroma .mo { color: #ae81ff }
122 | /* Operator */ .chroma .o { color: #f92672 }
123 | /* OperatorWord */ .chroma .ow { color: #f92672 }
124 | /* Comment */ .chroma .c { color: #75715e }
125 | /* CommentHashbang */ .chroma .ch { color: #75715e }
126 | /* CommentMultiline */ .chroma .cm { color: #75715e }
127 | /* CommentSingle */ .chroma .c1 { color: #75715e }
128 | /* CommentSpecial */ .chroma .cs { color: #75715e }
129 | /* CommentPreproc */ .chroma .cp { color: #75715e }
130 | /* CommentPreprocFile */ .chroma .cpf { color: #75715e }
131 | /* GenericDeleted */ .chroma .gd { color: #f92672 }
132 | /* GenericEmph */ .chroma .ge { font-style: italic }
133 | /* GenericInserted */ .chroma .gi { color: #a6e22e }
134 | /* GenericStrong */ .chroma .gs { font-weight: bold }
135 | /* GenericSubheading */ .chroma .gu { color: #75715e }
136 | }
137 |
138 |
139 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/sass/layout.sass:
--------------------------------------------------------------------------------
1 | *:root
2 | --content-margin: 6vw
3 |
4 | @media (min-width: $wider-display)
5 | *:root
6 | --content-margin: 70px
7 |
8 | html, body
9 | margin: 0
10 | width: 100%
11 | height: 100%
12 | background-color: var(--bgbg)
13 | color: var(--font-color)
14 |
15 | #wrap
16 | min-height: 100vh
17 | display: flex
18 | flex-direction: column
19 | justify-content: space-between
20 | align-items: center
21 |
22 | header, footer
23 | display: flex
24 | justify-content: center
25 | align-items: center
26 | width: 100%
27 | min-height: 60px
28 | background-color: var(--accent)
29 |
30 | #main
31 | width: 83.2ex // <-- ~80 chars
32 | max-width: 100%
33 | background-color: var(--bg)
34 |
35 | @media (min-width: $page-width)
36 | box-shadow: 0px 6px 12px 3px var(--shadow-color)
37 |
38 | > :first-child
39 | margin: 0 var(--content-margin)
40 | padding: 46px 0
41 |
42 | @media (min-width: $page-width)
43 | margin-top: 23px
44 | margin-bottom: 23px
45 |
46 | blockquote
47 | margin-left: 2px
48 | margin-right: 6px
49 | padding-left: 16px
50 | border-left: 3px solid var(--blockquote-accent)
51 | color: var(--blockquote)
52 |
53 | pre
54 | padding: 6px 8px
55 |
56 | img
57 | margin: 0.5em auto
58 | width: calc(100% + 2 * var(--content-margin))
59 | margin-inline-start: calc(-1 * var(--content-margin))
60 |
61 | display: block
62 | height: auto
63 |
64 | border: 1px solid var(--accent)
65 | border-left: none
66 | border-right: none
67 |
68 | hr
69 | border: 0;
70 | height: 1px;
71 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0), var(--accent), rgba(0, 0, 0, 0));
72 |
73 | .aftermath
74 | text-align: center
75 |
76 | time
77 | font-weight: 100
78 | opacity: .3
79 |
80 | nav
81 | display: inline-block
82 | font-size: 1.4em
83 |
84 | ul
85 | display: inline-block
86 | list-style-type: none
87 | margin: 0
88 | padding: 0
89 |
90 | li
91 | display: inline-block
92 |
93 | &:not(:first-child)
94 | margin-left: 14px
95 |
96 | .posts-list
97 | .post
98 | margin-top: 44px
99 |
100 | &:last-of-type
101 | hr
102 | display: none
103 |
104 | .title
105 | width: 90%
106 | margin: 0 auto
107 | font-size: 1.25em
108 | font-weight: semibold
109 |
110 | time
111 | margin-top: 32px
112 | display: block
113 | text-align: center
114 |
115 |
116 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/sass/main.sass:
--------------------------------------------------------------------------------
1 | @import ../vendor/css/modern-normalize
2 |
3 | @import vars
4 | @import layout
5 | @import readability
6 | @import code
7 |
8 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/sass/readability.sass:
--------------------------------------------------------------------------------
1 | body
2 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol
3 | text-rendering: optimizeLegibility
4 | text-align: start
5 | word-wrap: break-word
6 | --webkit-font-smoothing: subpixel-antialiased
7 |
8 | header
9 | a
10 | color: white
11 | text-decoration: none
12 |
13 | nav
14 | a
15 | color: var(--realbg)
16 | text-decoration: none
17 |
18 | #main
19 | font-size: 18px
20 | line-height: 29px
21 |
22 | h1
23 | font-size: 1.5em
24 |
25 | h2
26 | font-size: 1.5em
27 |
28 | h3
29 | font-size: 1.25em
30 |
31 | h4, h5, h6
32 | font-size: 1em
33 | margin: 1em 0
34 |
35 | a
36 | color: var(--accent-bright)
37 | text-decoration: none
38 | font-weight: 400
39 |
40 | &:hover
41 | text-decoration: underline
42 | text-decoration-style: dotted
43 |
44 | article
45 | h1:first-of-type
46 | margin-top: 0
47 | margin-bottom: 0.5em
48 | font-weight: bold
49 | font-size: 1.95552em
50 | line-height: 1.2141em
51 |
52 | h2:first-of-type
53 | color: var(--subhead-color)
54 | font-size: 1.46664em
55 | margin-top: -0.35em
56 | line-height: 1.27275em
57 | font-weight: normal
58 |
59 |
60 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/sass/vars.sass:
--------------------------------------------------------------------------------
1 | $page-width: 800px
2 | $wider-display: 500px
3 |
4 | $accent: rgb(183, 64, 233)
5 |
6 | *:root
7 | --realbg: rgb(230, 230, 230)
8 | --bgbg: rgb(255, 255, 255)
9 | --bg: rgb(255, 255, 255)
10 | --accent: #{$accent}
11 | --accent-bright: #{$accent}
12 | --accent-light: #{lighten($accent, 20%)}
13 | --font-color: rgb(27, 27, 27)
14 | --subhead-color: rgba(27, 27, 27, 0.65)
15 | --shadow-color: rgba(0, 0, 0, 0.2)
16 | --blockquote: rgba(0, 0, 0, 0.65)
17 | --blockquote-accent: rgba(0, 0, 0, 0.1)
18 |
19 | @media (min-width: $page-width)
20 | *:root
21 | --bgbg: rgb(230, 230, 230)
22 |
23 | @media (prefers-color-scheme: dark)
24 | *:root
25 | --realbg: rgb(50, 50, 51)
26 | --bgbg: rgb(74, 74, 77)
27 | --bg: rgb(74, 74, 77)
28 | --accent: #{darken($accent, 28%)}
29 | --accent-bright: #{lighten($accent, 20%)}
30 | --accent-light: #{lighten($accent, 30%)}
31 | --font-color: rgba(255, 255, 255, 0.78)
32 | --subhead-color: rgba(255, 255, 255, 0.65)
33 | --blockquote: rgba(255, 255, 255, 0.6)
34 | --blockquote-accent: rgba(255, 255, 255, 0.15)
35 |
36 | @media (prefers-color-scheme: dark) and (min-width: $page-width)
37 | *:root
38 | --bgbg: rgb(50, 50, 51)
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/assets/vendor/css/modern-normalize.css:
--------------------------------------------------------------------------------
1 | /*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * Use a better box model (opinionated).
8 | */
9 |
10 | html {
11 | box-sizing: border-box;
12 | }
13 |
14 | *,
15 | *::before,
16 | *::after {
17 | box-sizing: inherit;
18 | }
19 |
20 | /**
21 | * Use a more readable tab size (opinionated).
22 | */
23 |
24 | :root {
25 | -moz-tab-size: 4;
26 | tab-size: 4;
27 | }
28 |
29 | /**
30 | * 1. Correct the line height in all browsers.
31 | * 2. Prevent adjustments of font size after orientation changes in iOS.
32 | */
33 |
34 | html {
35 | line-height: 1.15; /* 1 */
36 | -webkit-text-size-adjust: 100%; /* 2 */
37 | }
38 |
39 | /* Sections
40 | ========================================================================== */
41 |
42 | /**
43 | * Remove the margin in all browsers.
44 | */
45 |
46 | body {
47 | margin: 0;
48 | }
49 |
50 | /**
51 | * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
52 | */
53 |
54 | body {
55 | font-family:
56 | -apple-system,
57 | BlinkMacSystemFont,
58 | 'Segoe UI',
59 | Roboto,
60 | Helvetica,
61 | Arial,
62 | sans-serif,
63 | 'Apple Color Emoji',
64 | 'Segoe UI Emoji',
65 | 'Segoe UI Symbol';
66 | }
67 |
68 | /* Grouping content
69 | ========================================================================== */
70 |
71 | /**
72 | * Add the correct height in Firefox.
73 | */
74 |
75 | hr {
76 | height: 0;
77 | }
78 |
79 | /* Text-level semantics
80 | ========================================================================== */
81 |
82 | /**
83 | * Add the correct text decoration in Chrome, Edge, and Safari.
84 | */
85 |
86 | abbr[title] {
87 | text-decoration: underline dotted;
88 | }
89 |
90 | /**
91 | * Add the correct font weight in Chrome, Edge, and Safari.
92 | */
93 |
94 | b,
95 | strong {
96 | font-weight: bolder;
97 | }
98 |
99 | /**
100 | * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
101 | * 2. Correct the odd `em` font sizing in all browsers.
102 | */
103 |
104 | code,
105 | kbd,
106 | samp,
107 | pre {
108 | font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in all browsers.
122 | */
123 |
124 | sub,
125 | sup {
126 | font-size: 75%;
127 | line-height: 0;
128 | position: relative;
129 | vertical-align: baseline;
130 | }
131 |
132 | sub {
133 | bottom: -0.25em;
134 | }
135 |
136 | sup {
137 | top: -0.5em;
138 | }
139 |
140 | /* Forms
141 | ========================================================================== */
142 |
143 | /**
144 | * 1. Change the font styles in all browsers.
145 | * 2. Remove the margin in Firefox and Safari.
146 | */
147 |
148 | button,
149 | input,
150 | optgroup,
151 | select,
152 | textarea {
153 | font-family: inherit; /* 1 */
154 | font-size: 100%; /* 1 */
155 | line-height: 1.15; /* 1 */
156 | margin: 0; /* 2 */
157 | }
158 |
159 | /**
160 | * Remove the inheritance of text transform in Edge and Firefox.
161 | * 1. Remove the inheritance of text transform in Firefox.
162 | */
163 |
164 | button,
165 | select { /* 1 */
166 | text-transform: none;
167 | }
168 |
169 | /**
170 | * Correct the inability to style clickable types in iOS and Safari.
171 | */
172 |
173 | button,
174 | [type='button'],
175 | [type='reset'],
176 | [type='submit'] {
177 | -webkit-appearance: button;
178 | }
179 |
180 | /**
181 | * Remove the inner border and padding in Firefox.
182 | */
183 |
184 | button::-moz-focus-inner,
185 | [type='button']::-moz-focus-inner,
186 | [type='reset']::-moz-focus-inner,
187 | [type='submit']::-moz-focus-inner {
188 | border-style: none;
189 | padding: 0;
190 | }
191 |
192 | /**
193 | * Restore the focus styles unset by the previous rule.
194 | */
195 |
196 | button:-moz-focusring,
197 | [type='button']:-moz-focusring,
198 | [type='reset']:-moz-focusring,
199 | [type='submit']:-moz-focusring {
200 | outline: 1px dotted ButtonText;
201 | }
202 |
203 | /**
204 | * Correct the padding in Firefox.
205 | */
206 |
207 | fieldset {
208 | padding: 0.35em 0.75em 0.625em;
209 | }
210 |
211 | /**
212 | * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.
213 | */
214 |
215 | legend {
216 | padding: 0;
217 | }
218 |
219 | /**
220 | * Add the correct vertical alignment in Chrome and Firefox.
221 | */
222 |
223 | progress {
224 | vertical-align: baseline;
225 | }
226 |
227 | /**
228 | * Correct the cursor style of increment and decrement buttons in Safari.
229 | */
230 |
231 | [type='number']::-webkit-inner-spin-button,
232 | [type='number']::-webkit-outer-spin-button {
233 | height: auto;
234 | }
235 |
236 | /**
237 | * 1. Correct the odd appearance in Chrome and Safari.
238 | * 2. Correct the outline style in Safari.
239 | */
240 |
241 | [type='search'] {
242 | -webkit-appearance: textfield; /* 1 */
243 | outline-offset: -2px; /* 2 */
244 | }
245 |
246 | /**
247 | * Remove the inner padding in Chrome and Safari on macOS.
248 | */
249 |
250 | [type='search']::-webkit-search-decoration {
251 | -webkit-appearance: none;
252 | }
253 |
254 | /**
255 | * 1. Correct the inability to style clickable types in iOS and Safari.
256 | * 2. Change font properties to `inherit` in Safari.
257 | */
258 |
259 | ::-webkit-file-upload-button {
260 | -webkit-appearance: button; /* 1 */
261 | font: inherit; /* 2 */
262 | }
263 |
264 | /* Interactive
265 | ========================================================================== */
266 |
267 | /*
268 | * Add the correct display in Chrome and Safari.
269 | */
270 |
271 | summary {
272 | display: list-item;
273 | }
274 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/404.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/_default/baseof.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{- partial "head.html" . -}}
4 |
5 |
6 | {{- partial "header.html" . -}}
7 |
8 | {{- block "main" . }}{{- end }}
9 |
10 | {{- partial "footer.html" . -}}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/_default/list.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 |
6 |
7 | {{- partial "posts-list.html" . -}}
8 |
9 |
10 | {{ end }}
11 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/_default/single.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 | {{ .Title }}
4 | {{ .Content }}
5 |
6 |
7 | {{ if .Date }}
8 |
9 | {{ .Date.Format "2006.01.02" }}
10 |
11 | {{ end }}
12 |
13 |
14 | {{ end }}
15 |
16 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/index.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 |
6 |
7 | {{ with .Site.GetPage "/posts" }}
8 | {{- partial "posts-list.html" . -}}
9 | {{ end }}
10 |
11 |
12 | {{ end }}
13 |
14 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/index.rss.xml:
--------------------------------------------------------------------------------
1 | {{ printf "" | safeHTML }}
2 |
3 |
4 | {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}
5 | {{ .Permalink }}
6 | Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}
7 | Hugo -- gohugo.io {{ with .Site.LanguageCode }}
8 | {{.}} {{end}}{{ with .Site.Author.email }}
9 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}{{ with .Site.Author.email }}
10 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}{{ with .Site.Copyright }}
11 | {{.}} {{end}}{{ if not .Date.IsZero }}
12 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} {{ end }}
13 | {{ with .OutputFormats.Get "RSS" }}
14 | {{ printf " " .Permalink .MediaType | safeHTML }}
15 | {{ end }}
16 | {{ range .Pages }}
17 | -
18 |
{{ .Title }}
19 | {{ .Permalink }}
20 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
21 | {{ with .Site.Author.email }}{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}
22 | {{ .Permalink }}
23 | {{ .Content | html }}
24 |
25 | {{ end }}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/css.html:
--------------------------------------------------------------------------------
1 | {{ $bundleRaw := resources.Get "/sass/main.sass" | resources.ExecuteAsTemplate "/css/main.tmp.css" . }}
2 |
3 | {{ $cssOpts := (dict "targetPath" "/css/main.css" "enableSourceMap" true ) }}
4 | {{ $bundle := $bundleRaw | toCSS $cssOpts | minify | fingerprint }}
5 |
6 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ if .Site.Params.TwitterName }}
5 | tweet me
6 | {{ end }}
7 | {{ if .Site.Params.GithubName }}
8 | fork me
9 | {{ end }}
10 | {{ if .Site.Params.Email }}
11 | write me
12 | {{ end }}
13 |
14 |
15 | {{ partial "js.html" . }}
16 |
17 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ .Title }}
5 | {{ partial "social-preview.html" . }}
6 | {{ partial "css.html" . }}
7 | {{ range .AlternativeOutputFormats -}}
8 | {{ printf ` ` .Rel .MediaType.Type .RelPermalink $.Site.Title | safeHTML }}
9 | {{ end -}}
10 |
11 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/header.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/js.html:
--------------------------------------------------------------------------------
1 | {{ $index := resources.Get "/js/index.js" | minify }}
2 | {{ $scripts := slice $index | resources.Concat "/js/bundle.js" | fingerprint }}
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/posts-list.html:
--------------------------------------------------------------------------------
1 |
2 | {{ range .Pages.ByPublishDate.Reverse }}
3 |
4 |
7 |
8 | {{ .Date.Format "2006.01.02" }}
9 |
10 |
11 |
12 | {{ end }}
13 |
14 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/layouts/partials/social-preview.html:
--------------------------------------------------------------------------------
1 | {{ $description := .Params.Description | default "A thing @searls wrote for you." }}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/static/blog/themes/ultralight/theme.toml:
--------------------------------------------------------------------------------
1 | # theme.toml template for a Hugo theme
2 | # See https://github.com/gohugoio/hugoThemes#themetoml for an example
3 |
4 | name = "Ultralight"
5 | license = "Unlicensed"
6 | description = "Private"
7 | homepage = "http://github.com/searls/hugo-theme-ultralight"
8 | tags = []
9 | features = []
10 | min_version = "0.54"
11 |
12 | [author]
13 | name = "Justin Searls"
14 | homepage = "https://twitter.com/searls"
15 |
--------------------------------------------------------------------------------
/example/static/docs/.bundle/config:
--------------------------------------------------------------------------------
1 | ---
2 | BUNDLE_PATH: "vendor/bundle"
3 |
--------------------------------------------------------------------------------
/example/static/docs/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 | .sass-cache
3 | .jekyll-cache
4 | .jekyll-metadata
5 | vendor
6 |
--------------------------------------------------------------------------------
/example/static/docs/404.html:
--------------------------------------------------------------------------------
1 | ---
2 | permalink: /404.html
3 | layout: default
4 | ---
5 |
6 |
19 |
20 |
21 |
404
22 |
23 |
Page not found :(
24 |
The requested page could not be found.
25 |
26 |
--------------------------------------------------------------------------------
/example/static/docs/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "jekyll"
4 | gem "minima"
5 |
6 | group :jekyll_plugins do
7 | gem "jekyll-feed"
8 | end
9 |
10 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
11 | gem "tzinfo"
12 | gem "tzinfo-data"
13 | end
14 |
15 | gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform?
16 |
17 |
--------------------------------------------------------------------------------
/example/static/docs/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | addressable (2.8.0)
5 | public_suffix (>= 2.0.2, < 5.0)
6 | colorator (1.1.0)
7 | concurrent-ruby (1.1.9)
8 | em-websocket (0.5.2)
9 | eventmachine (>= 0.12.9)
10 | http_parser.rb (~> 0.6.0)
11 | eventmachine (1.2.7)
12 | ffi (1.15.3)
13 | forwardable-extended (2.6.0)
14 | http_parser.rb (0.6.0)
15 | i18n (1.8.10)
16 | concurrent-ruby (~> 1.0)
17 | jekyll (4.2.0)
18 | addressable (~> 2.4)
19 | colorator (~> 1.0)
20 | em-websocket (~> 0.5)
21 | i18n (~> 1.0)
22 | jekyll-sass-converter (~> 2.0)
23 | jekyll-watch (~> 2.0)
24 | kramdown (~> 2.3)
25 | kramdown-parser-gfm (~> 1.0)
26 | liquid (~> 4.0)
27 | mercenary (~> 0.4.0)
28 | pathutil (~> 0.9)
29 | rouge (~> 3.0)
30 | safe_yaml (~> 1.0)
31 | terminal-table (~> 2.0)
32 | jekyll-feed (0.15.1)
33 | jekyll (>= 3.7, < 5.0)
34 | jekyll-sass-converter (2.1.0)
35 | sassc (> 2.0.1, < 3.0)
36 | jekyll-seo-tag (2.7.1)
37 | jekyll (>= 3.8, < 5.0)
38 | jekyll-watch (2.2.1)
39 | listen (~> 3.0)
40 | kramdown (2.3.1)
41 | rexml
42 | kramdown-parser-gfm (1.1.0)
43 | kramdown (~> 2.0)
44 | liquid (4.0.3)
45 | listen (3.6.0)
46 | rb-fsevent (~> 0.10, >= 0.10.3)
47 | rb-inotify (~> 0.9, >= 0.9.10)
48 | mercenary (0.4.0)
49 | minima (2.5.1)
50 | jekyll (>= 3.5, < 5.0)
51 | jekyll-feed (~> 0.9)
52 | jekyll-seo-tag (~> 2.1)
53 | pathutil (0.16.2)
54 | forwardable-extended (~> 2.6)
55 | public_suffix (4.0.6)
56 | rb-fsevent (0.11.0)
57 | rb-inotify (0.10.1)
58 | ffi (~> 1.0)
59 | rexml (3.2.5)
60 | rouge (3.26.0)
61 | safe_yaml (1.0.5)
62 | sassc (2.4.0)
63 | ffi (~> 1.9)
64 | terminal-table (2.0.0)
65 | unicode-display_width (~> 1.1, >= 1.1.1)
66 | tzinfo (2.0.4)
67 | concurrent-ruby (~> 1.0)
68 | tzinfo-data (1.2021.1)
69 | tzinfo (>= 1.0.0)
70 | unicode-display_width (1.7.0)
71 | wdm (0.1.1)
72 |
73 | PLATFORMS
74 | ruby
75 |
76 | DEPENDENCIES
77 | jekyll
78 | jekyll-feed
79 | minima
80 | tzinfo
81 | tzinfo-data
82 | wdm (~> 0.1.1)
83 |
84 | BUNDLED WITH
85 | 2.2.15
86 |
--------------------------------------------------------------------------------
/example/static/docs/_config.yml:
--------------------------------------------------------------------------------
1 | # Welcome to Jekyll!
2 | #
3 | # This config file is meant for settings that affect your whole blog, values
4 | # which you are expected to set up once and rarely edit after that. If you find
5 | # yourself editing this file very often, consider using Jekyll's data files
6 | # feature for the data you need to update frequently.
7 | #
8 | # For technical reasons, this file is *NOT* reloaded automatically when you use
9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10 | #
11 | # If you need help with YAML syntax, here are some quick references for you:
12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13 | # https://learnxinyminutes.com/docs/yaml/
14 | #
15 | # Site settings
16 | # These are used to personalize your new site. If you look in the HTML files,
17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18 | # You can create any custom variable you would like, and they will be accessible
19 | # in the templates via {{ site.myvariable }}.
20 |
21 | title: Your awesome title
22 | email: your-email@example.com
23 | description: >- # this means to ignore newlines until "baseurl:"
24 | Write an awesome description for your new site here. You can edit this
25 | line in _config.yml. It will appear in your document head meta (for
26 | Google search results) and in your feed.xml site description.
27 | baseurl: "/docs" # the subpath of your site, e.g. /blog
28 | url: "" # the base hostname & protocol for your site, e.g. http://example.com
29 | twitter_username: jekyllrb
30 | github_username: jekyll
31 |
32 | # Build settings
33 | theme: minima
34 | plugins:
35 | - jekyll-feed
36 |
37 | # Exclude from processing.
38 | # The following items will not be processed, by default.
39 | # Any item listed under the `exclude:` key here will be automatically added to
40 | # the internal "default list".
41 | #
42 | # Excluded items can be processed by explicitly listing the directories or
43 | # their entries' file path in the `include:` list.
44 | #
45 | # exclude:
46 | # - .sass-cache/
47 | # - .jekyll-cache/
48 | # - gemfiles/
49 | # - Gemfile
50 | # - Gemfile.lock
51 | # - node_modules/
52 | # - vendor/bundle/
53 | # - vendor/cache/
54 | # - vendor/gems/
55 | # - vendor/ruby/
56 |
--------------------------------------------------------------------------------
/example/static/docs/_posts/2020-04-27-welcome-to-jekyll.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: post
3 | title: "Welcome to Jekyll!"
4 | date: 2020-04-27 15:21:07 +0900
5 | categories: jekyll update
6 | ---
7 | You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
8 |
9 | Jekyll requires blog post files to be named according to the following format:
10 |
11 | `YEAR-MONTH-DAY-title.MARKUP`
12 |
13 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works.
14 |
15 | Jekyll also offers powerful support for code snippets:
16 |
17 | {% highlight ruby %}
18 | def print_hi(name)
19 | puts "Hi, #{name}"
20 | end
21 | print_hi('Tom')
22 | #=> prints 'Hi, Tom' to STDOUT.
23 | {% endhighlight %}
24 |
25 | Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
26 |
27 | [jekyll-docs]: https://jekyllrb.com/docs/home
28 | [jekyll-gh]: https://github.com/jekyll/jekyll
29 | [jekyll-talk]: https://talk.jekyllrb.com/
30 |
--------------------------------------------------------------------------------
/example/static/docs/about.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: About
4 | permalink: /about/
5 | ---
6 |
7 | This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/)
8 |
9 | You can find the source code for Minima at GitHub:
10 | [jekyll][jekyll-organization] /
11 | [minima](https://github.com/jekyll/minima)
12 |
13 | You can find the source code for Jekyll at GitHub:
14 | [jekyll][jekyll-organization] /
15 | [jekyll](https://github.com/jekyll/jekyll)
16 |
17 |
18 | [jekyll-organization]: https://github.com/jekyll
19 |
--------------------------------------------------------------------------------
/example/static/docs/assets/a_css.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: blue
3 | }
4 |
--------------------------------------------------------------------------------
/example/static/docs/assets/a_css.css.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/a_css.css.gz
--------------------------------------------------------------------------------
/example/static/docs/assets/a_html.html:
--------------------------------------------------------------------------------
1 | Neat
2 |
--------------------------------------------------------------------------------
/example/static/docs/assets/a_html.html.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/a_html.html.gz
--------------------------------------------------------------------------------
/example/static/docs/assets/a_javascript.js:
--------------------------------------------------------------------------------
1 | console.log('hello');
2 |
--------------------------------------------------------------------------------
/example/static/docs/assets/a_javascript.js.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/a_javascript.js.gz
--------------------------------------------------------------------------------
/example/static/docs/assets/a_json.json:
--------------------------------------------------------------------------------
1 | {
2 | "stuff": ["things"]
3 | }
4 |
--------------------------------------------------------------------------------
/example/static/docs/assets/a_json.json.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/a_json.json.gz
--------------------------------------------------------------------------------
/example/static/docs/assets/an_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/an_image.png
--------------------------------------------------------------------------------
/example/static/docs/assets/an_image.png.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/static/docs/assets/an_image.png.gz
--------------------------------------------------------------------------------
/example/static/docs/index.markdown:
--------------------------------------------------------------------------------
1 | ---
2 | # Feel free to add content and custom Front Matter to this file.
3 | # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
4 |
5 | layout: home
6 | ---
7 |
--------------------------------------------------------------------------------
/example/static/marketing/.gitignore:
--------------------------------------------------------------------------------
1 | /resources
2 | /public
3 |
--------------------------------------------------------------------------------
/example/static/marketing/archetypes/default.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "{{ replace .Name "-" " " | title }}"
3 | date: {{ .Date }}
4 | draft: true
5 | ---
6 |
7 |
--------------------------------------------------------------------------------
/example/static/marketing/config.toml:
--------------------------------------------------------------------------------
1 | baseURL = "http://example.com/marketing"
2 | languageCode = "en-us"
3 | title = "My Marketing Page"
4 | theme = "ultralight"
5 |
--------------------------------------------------------------------------------
/example/static/marketing/content/posts/my-first-post.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "My First Post"
3 | date: 2020-04-29T16:49:04+09:00
4 | ---
5 | h2 Hello
6 |
7 | I am a post
8 |
9 |
--------------------------------------------------------------------------------
/example/static/marketing/layouts/404.html:
--------------------------------------------------------------------------------
1 |
2 | I am a 404 page for /marketing
3 |
4 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/archetypes/default.md:
--------------------------------------------------------------------------------
1 | +++
2 | +++
3 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/js/index.js:
--------------------------------------------------------------------------------
1 | // JS can go here.
2 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/sass/code.css:
--------------------------------------------------------------------------------
1 | /* LIGHT MODE */
2 | /* hugo gen chromastyles --style autumn */
3 |
4 | /* Background */ .chroma { background-color: #ffffff }
5 | /* Error */ .chroma .err { color: #ff0000; background-color: #ffaaaa }
6 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
7 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
8 | /* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
9 | /* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
10 | /* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
11 | /* Keyword */ .chroma .k { color: #0000aa }
12 | /* KeywordConstant */ .chroma .kc { color: #0000aa }
13 | /* KeywordDeclaration */ .chroma .kd { color: #0000aa }
14 | /* KeywordNamespace */ .chroma .kn { color: #0000aa }
15 | /* KeywordPseudo */ .chroma .kp { color: #0000aa }
16 | /* KeywordReserved */ .chroma .kr { color: #0000aa }
17 | /* KeywordType */ .chroma .kt { color: #00aaaa }
18 | /* NameAttribute */ .chroma .na { color: #1e90ff }
19 | /* NameBuiltin */ .chroma .nb { color: #00aaaa }
20 | /* NameClass */ .chroma .nc { color: #00aa00; text-decoration: underline }
21 | /* NameConstant */ .chroma .no { color: #aa0000 }
22 | /* NameDecorator */ .chroma .nd { color: #888888 }
23 | /* NameEntity */ .chroma .ni { color: #880000; font-weight: bold }
24 | /* NameFunction */ .chroma .nf { color: #00aa00 }
25 | /* NameNamespace */ .chroma .nn { color: #00aaaa; text-decoration: underline }
26 | /* NameTag */ .chroma .nt { color: #1e90ff; font-weight: bold }
27 | /* NameVariable */ .chroma .nv { color: #aa0000 }
28 | /* LiteralString */ .chroma .s { color: #aa5500 }
29 | /* LiteralStringAffix */ .chroma .sa { color: #aa5500 }
30 | /* LiteralStringBacktick */ .chroma .sb { color: #aa5500 }
31 | /* LiteralStringChar */ .chroma .sc { color: #aa5500 }
32 | /* LiteralStringDelimiter */ .chroma .dl { color: #aa5500 }
33 | /* LiteralStringDoc */ .chroma .sd { color: #aa5500 }
34 | /* LiteralStringDouble */ .chroma .s2 { color: #aa5500 }
35 | /* LiteralStringEscape */ .chroma .se { color: #aa5500 }
36 | /* LiteralStringHeredoc */ .chroma .sh { color: #aa5500 }
37 | /* LiteralStringInterpol */ .chroma .si { color: #aa5500 }
38 | /* LiteralStringOther */ .chroma .sx { color: #aa5500 }
39 | /* LiteralStringRegex */ .chroma .sr { color: #009999 }
40 | /* LiteralStringSingle */ .chroma .s1 { color: #aa5500 }
41 | /* LiteralStringSymbol */ .chroma .ss { color: #0000aa }
42 | /* LiteralNumber */ .chroma .m { color: #009999 }
43 | /* LiteralNumberBin */ .chroma .mb { color: #009999 }
44 | /* LiteralNumberFloat */ .chroma .mf { color: #009999 }
45 | /* LiteralNumberHex */ .chroma .mh { color: #009999 }
46 | /* LiteralNumberInteger */ .chroma .mi { color: #009999 }
47 | /* LiteralNumberIntegerLong */ .chroma .il { color: #009999 }
48 | /* LiteralNumberOct */ .chroma .mo { color: #009999 }
49 | /* OperatorWord */ .chroma .ow { color: #0000aa }
50 | /* Comment */ .chroma .c { color: #aaaaaa; font-style: italic }
51 | /* CommentHashbang */ .chroma .ch { color: #aaaaaa; font-style: italic }
52 | /* CommentMultiline */ .chroma .cm { color: #aaaaaa; font-style: italic }
53 | /* CommentSingle */ .chroma .c1 { color: #aaaaaa; font-style: italic }
54 | /* CommentSpecial */ .chroma .cs { color: #0000aa; font-style: italic }
55 | /* CommentPreproc */ .chroma .cp { color: #4c8317 }
56 | /* CommentPreprocFile */ .chroma .cpf { color: #4c8317 }
57 | /* GenericDeleted */ .chroma .gd { color: #aa0000 }
58 | /* GenericEmph */ .chroma .ge { font-style: italic }
59 | /* GenericError */ .chroma .gr { color: #aa0000 }
60 | /* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold }
61 | /* GenericInserted */ .chroma .gi { color: #00aa00 }
62 | /* GenericOutput */ .chroma .go { color: #888888 }
63 | /* GenericPrompt */ .chroma .gp { color: #555555 }
64 | /* GenericStrong */ .chroma .gs { font-weight: bold }
65 | /* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold }
66 | /* GenericTraceback */ .chroma .gt { color: #aa0000 }
67 | /* GenericUnderline */ .chroma .gl { text-decoration: underline }
68 | /* TextWhitespace */ .chroma .w { color: #bbbbbb }
69 |
70 | /* LIGHT MODE OVERRIDES: */
71 | /* Background */ .chroma { background-color: #f6f6f6 }
72 |
73 |
74 | /* DARK MODE */
75 | @media (prefers-color-scheme: dark) {
76 | /* hugo gen chromastyles --style monokai */
77 | /* Background */ .chroma { color: #f8f8f2; background-color: #272822 }
78 | /* Error */ .chroma .err { color: #960050; background-color: #1e0010 }
79 | /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
80 | /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
81 | /* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc }
82 | /* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
83 | /* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
84 | /* Keyword */ .chroma .k { color: #66d9ef }
85 | /* KeywordConstant */ .chroma .kc { color: #66d9ef }
86 | /* KeywordDeclaration */ .chroma .kd { color: #66d9ef }
87 | /* KeywordNamespace */ .chroma .kn { color: #f92672 }
88 | /* KeywordPseudo */ .chroma .kp { color: #66d9ef }
89 | /* KeywordReserved */ .chroma .kr { color: #66d9ef }
90 | /* KeywordType */ .chroma .kt { color: #66d9ef }
91 | /* NameAttribute */ .chroma .na { color: #a6e22e }
92 | /* NameClass */ .chroma .nc { color: #a6e22e }
93 | /* NameConstant */ .chroma .no { color: #66d9ef }
94 | /* NameDecorator */ .chroma .nd { color: #a6e22e }
95 | /* NameException */ .chroma .ne { color: #a6e22e }
96 | /* NameFunction */ .chroma .nf { color: #a6e22e }
97 | /* NameOther */ .chroma .nx { color: #a6e22e }
98 | /* NameTag */ .chroma .nt { color: #f92672 }
99 | /* Literal */ .chroma .l { color: #ae81ff }
100 | /* LiteralDate */ .chroma .ld { color: #e6db74 }
101 | /* LiteralString */ .chroma .s { color: #e6db74 }
102 | /* LiteralStringAffix */ .chroma .sa { color: #e6db74 }
103 | /* LiteralStringBacktick */ .chroma .sb { color: #e6db74 }
104 | /* LiteralStringChar */ .chroma .sc { color: #e6db74 }
105 | /* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 }
106 | /* LiteralStringDoc */ .chroma .sd { color: #e6db74 }
107 | /* LiteralStringDouble */ .chroma .s2 { color: #e6db74 }
108 | /* LiteralStringEscape */ .chroma .se { color: #ae81ff }
109 | /* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 }
110 | /* LiteralStringInterpol */ .chroma .si { color: #e6db74 }
111 | /* LiteralStringOther */ .chroma .sx { color: #e6db74 }
112 | /* LiteralStringRegex */ .chroma .sr { color: #e6db74 }
113 | /* LiteralStringSingle */ .chroma .s1 { color: #e6db74 }
114 | /* LiteralStringSymbol */ .chroma .ss { color: #e6db74 }
115 | /* LiteralNumber */ .chroma .m { color: #ae81ff }
116 | /* LiteralNumberBin */ .chroma .mb { color: #ae81ff }
117 | /* LiteralNumberFloat */ .chroma .mf { color: #ae81ff }
118 | /* LiteralNumberHex */ .chroma .mh { color: #ae81ff }
119 | /* LiteralNumberInteger */ .chroma .mi { color: #ae81ff }
120 | /* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff }
121 | /* LiteralNumberOct */ .chroma .mo { color: #ae81ff }
122 | /* Operator */ .chroma .o { color: #f92672 }
123 | /* OperatorWord */ .chroma .ow { color: #f92672 }
124 | /* Comment */ .chroma .c { color: #75715e }
125 | /* CommentHashbang */ .chroma .ch { color: #75715e }
126 | /* CommentMultiline */ .chroma .cm { color: #75715e }
127 | /* CommentSingle */ .chroma .c1 { color: #75715e }
128 | /* CommentSpecial */ .chroma .cs { color: #75715e }
129 | /* CommentPreproc */ .chroma .cp { color: #75715e }
130 | /* CommentPreprocFile */ .chroma .cpf { color: #75715e }
131 | /* GenericDeleted */ .chroma .gd { color: #f92672 }
132 | /* GenericEmph */ .chroma .ge { font-style: italic }
133 | /* GenericInserted */ .chroma .gi { color: #a6e22e }
134 | /* GenericStrong */ .chroma .gs { font-weight: bold }
135 | /* GenericSubheading */ .chroma .gu { color: #75715e }
136 | }
137 |
138 |
139 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/sass/layout.sass:
--------------------------------------------------------------------------------
1 | *:root
2 | --content-margin: 6vw
3 |
4 | @media (min-width: $wider-display)
5 | *:root
6 | --content-margin: 70px
7 |
8 | html, body
9 | margin: 0
10 | width: 100%
11 | height: 100%
12 | background-color: var(--bgbg)
13 | color: var(--font-color)
14 |
15 | #wrap
16 | min-height: 100vh
17 | display: flex
18 | flex-direction: column
19 | justify-content: space-between
20 | align-items: center
21 |
22 | header, footer
23 | display: flex
24 | justify-content: center
25 | align-items: center
26 | width: 100%
27 | min-height: 60px
28 | background-color: var(--accent)
29 |
30 | #main
31 | width: 83.2ex // <-- ~80 chars
32 | max-width: 100%
33 | background-color: var(--bg)
34 |
35 | @media (min-width: $page-width)
36 | box-shadow: 0px 6px 12px 3px var(--shadow-color)
37 |
38 | > :first-child
39 | margin: 0 var(--content-margin)
40 | padding: 46px 0
41 |
42 | @media (min-width: $page-width)
43 | margin-top: 23px
44 | margin-bottom: 23px
45 |
46 | blockquote
47 | margin-left: 2px
48 | margin-right: 6px
49 | padding-left: 16px
50 | border-left: 3px solid var(--blockquote-accent)
51 | color: var(--blockquote)
52 |
53 | pre
54 | padding: 6px 8px
55 |
56 | img
57 | margin: 0.5em auto
58 | width: calc(100% + 2 * var(--content-margin))
59 | margin-inline-start: calc(-1 * var(--content-margin))
60 |
61 | display: block
62 | height: auto
63 |
64 | border: 1px solid var(--accent)
65 | border-left: none
66 | border-right: none
67 |
68 | hr
69 | border: 0;
70 | height: 1px;
71 | background-image: linear-gradient(to right, rgba(0, 0, 0, 0), var(--accent), rgba(0, 0, 0, 0));
72 |
73 | .aftermath
74 | text-align: center
75 |
76 | time
77 | font-weight: 100
78 | opacity: .3
79 |
80 | nav
81 | display: inline-block
82 | font-size: 1.4em
83 |
84 | ul
85 | display: inline-block
86 | list-style-type: none
87 | margin: 0
88 | padding: 0
89 |
90 | li
91 | display: inline-block
92 |
93 | &:not(:first-child)
94 | margin-left: 14px
95 |
96 | .posts-list
97 | .post
98 | margin-top: 44px
99 |
100 | &:last-of-type
101 | hr
102 | display: none
103 |
104 | .title
105 | width: 90%
106 | margin: 0 auto
107 | font-size: 1.25em
108 | font-weight: semibold
109 |
110 | time
111 | margin-top: 32px
112 | display: block
113 | text-align: center
114 |
115 |
116 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/sass/main.sass:
--------------------------------------------------------------------------------
1 | @import ../vendor/css/modern-normalize
2 |
3 | @import vars
4 | @import layout
5 | @import readability
6 | @import code
7 |
8 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/sass/readability.sass:
--------------------------------------------------------------------------------
1 | body
2 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol
3 | text-rendering: optimizeLegibility
4 | text-align: start
5 | word-wrap: break-word
6 | --webkit-font-smoothing: subpixel-antialiased
7 |
8 | header
9 | a
10 | color: white
11 | text-decoration: none
12 |
13 | nav
14 | a
15 | color: var(--realbg)
16 | text-decoration: none
17 |
18 | #main
19 | font-size: 18px
20 | line-height: 29px
21 |
22 | h1
23 | font-size: 1.5em
24 |
25 | h2
26 | font-size: 1.5em
27 |
28 | h3
29 | font-size: 1.25em
30 |
31 | h4, h5, h6
32 | font-size: 1em
33 | margin: 1em 0
34 |
35 | a
36 | color: var(--accent-bright)
37 | text-decoration: none
38 | font-weight: 400
39 |
40 | &:hover
41 | text-decoration: underline
42 | text-decoration-style: dotted
43 |
44 | article
45 | h1:first-of-type
46 | margin-top: 0
47 | margin-bottom: 0.5em
48 | font-weight: bold
49 | font-size: 1.95552em
50 | line-height: 1.2141em
51 |
52 | h2:first-of-type
53 | color: var(--subhead-color)
54 | font-size: 1.46664em
55 | margin-top: -0.35em
56 | line-height: 1.27275em
57 | font-weight: normal
58 |
59 |
60 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/sass/vars.sass:
--------------------------------------------------------------------------------
1 | $page-width: 800px
2 | $wider-display: 500px
3 |
4 | $accent: rgb(183, 64, 233)
5 |
6 | *:root
7 | --realbg: rgb(230, 230, 230)
8 | --bgbg: rgb(255, 255, 255)
9 | --bg: rgb(255, 255, 255)
10 | --accent: #{$accent}
11 | --accent-bright: #{$accent}
12 | --accent-light: #{lighten($accent, 20%)}
13 | --font-color: rgb(27, 27, 27)
14 | --subhead-color: rgba(27, 27, 27, 0.65)
15 | --shadow-color: rgba(0, 0, 0, 0.2)
16 | --blockquote: rgba(0, 0, 0, 0.65)
17 | --blockquote-accent: rgba(0, 0, 0, 0.1)
18 |
19 | @media (min-width: $page-width)
20 | *:root
21 | --bgbg: rgb(230, 230, 230)
22 |
23 | @media (prefers-color-scheme: dark)
24 | *:root
25 | --realbg: rgb(50, 50, 51)
26 | --bgbg: rgb(74, 74, 77)
27 | --bg: rgb(74, 74, 77)
28 | --accent: #{darken($accent, 28%)}
29 | --accent-bright: #{lighten($accent, 20%)}
30 | --accent-light: #{lighten($accent, 30%)}
31 | --font-color: rgba(255, 255, 255, 0.78)
32 | --subhead-color: rgba(255, 255, 255, 0.65)
33 | --blockquote: rgba(255, 255, 255, 0.6)
34 | --blockquote-accent: rgba(255, 255, 255, 0.15)
35 |
36 | @media (prefers-color-scheme: dark) and (min-width: $page-width)
37 | *:root
38 | --bgbg: rgb(50, 50, 51)
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/assets/vendor/css/modern-normalize.css:
--------------------------------------------------------------------------------
1 | /*! modern-normalize | MIT License | https://github.com/sindresorhus/modern-normalize */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * Use a better box model (opinionated).
8 | */
9 |
10 | html {
11 | box-sizing: border-box;
12 | }
13 |
14 | *,
15 | *::before,
16 | *::after {
17 | box-sizing: inherit;
18 | }
19 |
20 | /**
21 | * Use a more readable tab size (opinionated).
22 | */
23 |
24 | :root {
25 | -moz-tab-size: 4;
26 | tab-size: 4;
27 | }
28 |
29 | /**
30 | * 1. Correct the line height in all browsers.
31 | * 2. Prevent adjustments of font size after orientation changes in iOS.
32 | */
33 |
34 | html {
35 | line-height: 1.15; /* 1 */
36 | -webkit-text-size-adjust: 100%; /* 2 */
37 | }
38 |
39 | /* Sections
40 | ========================================================================== */
41 |
42 | /**
43 | * Remove the margin in all browsers.
44 | */
45 |
46 | body {
47 | margin: 0;
48 | }
49 |
50 | /**
51 | * Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
52 | */
53 |
54 | body {
55 | font-family:
56 | -apple-system,
57 | BlinkMacSystemFont,
58 | 'Segoe UI',
59 | Roboto,
60 | Helvetica,
61 | Arial,
62 | sans-serif,
63 | 'Apple Color Emoji',
64 | 'Segoe UI Emoji',
65 | 'Segoe UI Symbol';
66 | }
67 |
68 | /* Grouping content
69 | ========================================================================== */
70 |
71 | /**
72 | * Add the correct height in Firefox.
73 | */
74 |
75 | hr {
76 | height: 0;
77 | }
78 |
79 | /* Text-level semantics
80 | ========================================================================== */
81 |
82 | /**
83 | * Add the correct text decoration in Chrome, Edge, and Safari.
84 | */
85 |
86 | abbr[title] {
87 | text-decoration: underline dotted;
88 | }
89 |
90 | /**
91 | * Add the correct font weight in Chrome, Edge, and Safari.
92 | */
93 |
94 | b,
95 | strong {
96 | font-weight: bolder;
97 | }
98 |
99 | /**
100 | * 1. Improve consistency of default fonts in all browsers. (https://github.com/sindresorhus/modern-normalize/issues/3)
101 | * 2. Correct the odd `em` font sizing in all browsers.
102 | */
103 |
104 | code,
105 | kbd,
106 | samp,
107 | pre {
108 | font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in all browsers.
122 | */
123 |
124 | sub,
125 | sup {
126 | font-size: 75%;
127 | line-height: 0;
128 | position: relative;
129 | vertical-align: baseline;
130 | }
131 |
132 | sub {
133 | bottom: -0.25em;
134 | }
135 |
136 | sup {
137 | top: -0.5em;
138 | }
139 |
140 | /* Forms
141 | ========================================================================== */
142 |
143 | /**
144 | * 1. Change the font styles in all browsers.
145 | * 2. Remove the margin in Firefox and Safari.
146 | */
147 |
148 | button,
149 | input,
150 | optgroup,
151 | select,
152 | textarea {
153 | font-family: inherit; /* 1 */
154 | font-size: 100%; /* 1 */
155 | line-height: 1.15; /* 1 */
156 | margin: 0; /* 2 */
157 | }
158 |
159 | /**
160 | * Remove the inheritance of text transform in Edge and Firefox.
161 | * 1. Remove the inheritance of text transform in Firefox.
162 | */
163 |
164 | button,
165 | select { /* 1 */
166 | text-transform: none;
167 | }
168 |
169 | /**
170 | * Correct the inability to style clickable types in iOS and Safari.
171 | */
172 |
173 | button,
174 | [type='button'],
175 | [type='reset'],
176 | [type='submit'] {
177 | -webkit-appearance: button;
178 | }
179 |
180 | /**
181 | * Remove the inner border and padding in Firefox.
182 | */
183 |
184 | button::-moz-focus-inner,
185 | [type='button']::-moz-focus-inner,
186 | [type='reset']::-moz-focus-inner,
187 | [type='submit']::-moz-focus-inner {
188 | border-style: none;
189 | padding: 0;
190 | }
191 |
192 | /**
193 | * Restore the focus styles unset by the previous rule.
194 | */
195 |
196 | button:-moz-focusring,
197 | [type='button']:-moz-focusring,
198 | [type='reset']:-moz-focusring,
199 | [type='submit']:-moz-focusring {
200 | outline: 1px dotted ButtonText;
201 | }
202 |
203 | /**
204 | * Correct the padding in Firefox.
205 | */
206 |
207 | fieldset {
208 | padding: 0.35em 0.75em 0.625em;
209 | }
210 |
211 | /**
212 | * Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers.
213 | */
214 |
215 | legend {
216 | padding: 0;
217 | }
218 |
219 | /**
220 | * Add the correct vertical alignment in Chrome and Firefox.
221 | */
222 |
223 | progress {
224 | vertical-align: baseline;
225 | }
226 |
227 | /**
228 | * Correct the cursor style of increment and decrement buttons in Safari.
229 | */
230 |
231 | [type='number']::-webkit-inner-spin-button,
232 | [type='number']::-webkit-outer-spin-button {
233 | height: auto;
234 | }
235 |
236 | /**
237 | * 1. Correct the odd appearance in Chrome and Safari.
238 | * 2. Correct the outline style in Safari.
239 | */
240 |
241 | [type='search'] {
242 | -webkit-appearance: textfield; /* 1 */
243 | outline-offset: -2px; /* 2 */
244 | }
245 |
246 | /**
247 | * Remove the inner padding in Chrome and Safari on macOS.
248 | */
249 |
250 | [type='search']::-webkit-search-decoration {
251 | -webkit-appearance: none;
252 | }
253 |
254 | /**
255 | * 1. Correct the inability to style clickable types in iOS and Safari.
256 | * 2. Change font properties to `inherit` in Safari.
257 | */
258 |
259 | ::-webkit-file-upload-button {
260 | -webkit-appearance: button; /* 1 */
261 | font: inherit; /* 2 */
262 | }
263 |
264 | /* Interactive
265 | ========================================================================== */
266 |
267 | /*
268 | * Add the correct display in Chrome and Safari.
269 | */
270 |
271 | summary {
272 | display: list-item;
273 | }
274 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/404.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/_default/baseof.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{- partial "head.html" . -}}
4 |
5 |
6 | {{- partial "header.html" . -}}
7 |
8 | {{- block "main" . }}{{- end }}
9 |
10 | {{- partial "footer.html" . -}}
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/_default/list.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 |
6 |
7 | {{- partial "posts-list.html" . -}}
8 |
9 |
10 | {{ end }}
11 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/_default/single.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 | {{ .Title }}
4 | {{ .Content }}
5 |
6 |
7 | {{ if .Date }}
8 |
9 | {{ .Date.Format "2006.01.02" }}
10 |
11 | {{ end }}
12 |
13 |
14 | {{ end }}
15 |
16 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/index.html:
--------------------------------------------------------------------------------
1 | {{ define "main" }}
2 |
3 |
6 |
7 | {{ with .Site.GetPage "/posts" }}
8 | {{- partial "posts-list.html" . -}}
9 | {{ end }}
10 |
11 |
12 | {{ end }}
13 |
14 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/index.rss.xml:
--------------------------------------------------------------------------------
1 | {{ printf "" | safeHTML }}
2 |
3 |
4 | {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}
5 | {{ .Permalink }}
6 | Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}
7 | Hugo -- gohugo.io {{ with .Site.LanguageCode }}
8 | {{.}} {{end}}{{ with .Site.Author.email }}
9 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}{{ with .Site.Author.email }}
10 | {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}{{ with .Site.Copyright }}
11 | {{.}} {{end}}{{ if not .Date.IsZero }}
12 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} {{ end }}
13 | {{ with .OutputFormats.Get "RSS" }}
14 | {{ printf " " .Permalink .MediaType | safeHTML }}
15 | {{ end }}
16 | {{ range .Pages }}
17 | -
18 |
{{ .Title }}
19 | {{ .Permalink }}
20 | {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}
21 | {{ with .Site.Author.email }}{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}} {{end}}
22 | {{ .Permalink }}
23 | {{ .Content | html }}
24 |
25 | {{ end }}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/css.html:
--------------------------------------------------------------------------------
1 | {{ $bundleRaw := resources.Get "/sass/main.sass" | resources.ExecuteAsTemplate "/css/main.tmp.css" . }}
2 |
3 | {{ $cssOpts := (dict "targetPath" "/css/main.css" "enableSourceMap" true ) }}
4 | {{ $bundle := $bundleRaw | toCSS $cssOpts | minify | fingerprint }}
5 |
6 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ if .Site.Params.TwitterName }}
5 | tweet me
6 | {{ end }}
7 | {{ if .Site.Params.GithubName }}
8 | fork me
9 | {{ end }}
10 | {{ if .Site.Params.Email }}
11 | write me
12 | {{ end }}
13 |
14 |
15 | {{ partial "js.html" . }}
16 |
17 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/head.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ .Title }}
5 | {{ partial "social-preview.html" . }}
6 | {{ partial "css.html" . }}
7 | {{ range .AlternativeOutputFormats -}}
8 | {{ printf ` ` .Rel .MediaType.Type .RelPermalink $.Site.Title | safeHTML }}
9 | {{ end -}}
10 |
11 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/header.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/js.html:
--------------------------------------------------------------------------------
1 | {{ $index := resources.Get "/js/index.js" | minify }}
2 | {{ $scripts := slice $index | resources.Concat "/js/bundle.js" | fingerprint }}
3 |
4 |
5 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/posts-list.html:
--------------------------------------------------------------------------------
1 |
2 | {{ range .Pages.ByPublishDate.Reverse }}
3 |
4 |
7 |
8 | {{ .Date.Format "2006.01.02" }}
9 |
10 |
11 |
12 | {{ end }}
13 |
14 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/layouts/partials/social-preview.html:
--------------------------------------------------------------------------------
1 | {{ $description := .Params.Description | default "A thing @searls wrote for you." }}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/static/marketing/themes/ultralight/theme.toml:
--------------------------------------------------------------------------------
1 | # theme.toml template for a Hugo theme
2 | # See https://github.com/gohugoio/hugoThemes#themetoml for an example
3 |
4 | name = "Ultralight"
5 | license = "Unlicensed"
6 | description = "Private"
7 | homepage = "http://github.com/searls/hugo-theme-ultralight"
8 | tags = []
9 | features = []
10 | min_version = "0.54"
11 |
12 | [author]
13 | name = "Justin Searls"
14 | homepage = "https://twitter.com/searls"
15 |
--------------------------------------------------------------------------------
/example/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/storage/.keep
--------------------------------------------------------------------------------
/example/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/example/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
4 | # test "connects with cookies" do
5 | # cookies.signed[:user_id] = 42
6 | #
7 | # connect
8 | #
9 | # assert_equal connection.user_id, "42"
10 | # end
11 | end
12 |
--------------------------------------------------------------------------------
/example/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/controllers/.keep
--------------------------------------------------------------------------------
/example/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/fixtures/.keep
--------------------------------------------------------------------------------
/example/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/example/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/helpers/.keep
--------------------------------------------------------------------------------
/example/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/integration/.keep
--------------------------------------------------------------------------------
/example/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/mailers/.keep
--------------------------------------------------------------------------------
/example/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/models/.keep
--------------------------------------------------------------------------------
/example/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/test/system/.keep
--------------------------------------------------------------------------------
/example/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] ||= "test"
2 | require_relative "../config/environment"
3 | require "rails/test_help"
4 |
5 | class ActiveSupport::TestCase
6 | # Run tests in parallel with specified workers
7 | parallelize(workers: :number_of_processors)
8 |
9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
10 | fixtures :all
11 |
12 | # Add more helper methods to be used by all tests here...
13 | end
14 |
--------------------------------------------------------------------------------
/example/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/tmp/.keep
--------------------------------------------------------------------------------
/example/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/testdouble/static-rails/2d1a576370a634519b24a2175548abda0c71c58b/example/vendor/.keep
--------------------------------------------------------------------------------
/lib/generators/static_rails/initializer_generator.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | module Generators
3 | class InitializerGenerator < ::Rails::Generators::Base
4 | source_root File.expand_path("../../templates", __FILE__)
5 |
6 | desc "Creates a sample static-rails initializer."
7 | def copy_initializer
8 | copy_file "static.rb", "config/initializers/static.rb"
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/generators/templates/static.rb:
--------------------------------------------------------------------------------
1 | StaticRails.config do |config|
2 | # Control whether static-rails adds a middleware to proxy requests to your static site servers
3 | # config.proxy_requests = !Rails.env.production?
4 |
5 | # Control whether static-rails adds a middleware to serve your sites' compiled static assets with Static::Rack (has no effect if proxy_requests is enabled)
6 | # config.serve_compiled_assets = Rails.env.production?
7 |
8 | # Timeout in seconds to wait when proxying to a static server
9 | # (Applies when a site has both start_server and ping_server set to true)
10 | # config.ping_server_timeout = 5
11 |
12 | # When true, both the proxy & static asset middleware will set a cookie
13 | # named "_csrf_token" to the Rails CSRF token, allowing any client-side
14 | # API requests to take advantage of Rails' request forgery protection
15 | # config.set_csrf_token_cookie = false
16 |
17 | # The list of static sites you are hosting with static-rails.
18 | # Note that order matters! Request will be forwarded to the first site that
19 | # matches the subdomain and root path (this probably means you want any sites
20 | # with subdomains listed first)
21 | config.sites = [
22 | # {
23 | # # Unique name for the site
24 | # name: "blog",
25 | #
26 | # # File path to the static app relative to Rails root path
27 | # source_dir: "static/blog",
28 | #
29 | # # Constrain static app to the following subdomain (omit or leave nil
30 | # # if you aren't hosting the site on a subdomain)
31 | # url_subdomain: "blog",
32 | #
33 | # # Mount the static site web hosting to a certain sub-path (e.g. "/docs")
34 | # url_root_path: "/",
35 | #
36 | # # Don't serve/redirect routes whose paths start with these strings
37 | # url_skip_paths_starting_with: ["/api"],
38 | #
39 | # # Whether to run the local development/test server or not
40 | # start_server: !Rails.env.production?,
41 | #
42 | # # If start_server is true, wait to proxy requests to the server until it
43 | # # can connect to server_host over TCP on server_port
44 | # ping_server: true,
45 | #
46 | # # Any environment variables you need to pass to the server & compile
47 | # # commands as a hash (e.g. `env: {"BUNDLE_PATH" => "vendor/bundle"}`)
48 | # env: {},
49 | #
50 | # # The command to execute when running the static app in local
51 | # # development/test environments
52 | # server_command: "hugo server",
53 | #
54 | # # The host the local development/test server should be reached on
55 | # server_host: "localhost",
56 | #
57 | # # The port the local development/test server should be reached on
58 | # server_port: "1313",
59 | #
60 | # # The root path on the local development/test server to which requests
61 | # # should be forwarded
62 | # server_path: "/",
63 | #
64 | # # The command to execute when building the static app for production
65 | # compile_command: "hugo",
66 | #
67 | # # The destination of production-compiled assets, relative to Rails root
68 | # compile_dir: "static/blog/dist",
69 | #
70 | # # A 404 page to be sent when serving compiled assets and no file matches
71 | # compile_404_file_path: "404.html"
72 | # },
73 | ]
74 | end
75 |
--------------------------------------------------------------------------------
/lib/static-rails.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | end
3 |
4 | require_relative "static-rails/error"
5 | require_relative "static-rails/version"
6 | require_relative "static-rails/configuration"
7 | require_relative "static-rails/compile"
8 | require_relative "static-rails/railtie"
9 |
--------------------------------------------------------------------------------
/lib/static-rails/compile.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | def self.compile
3 | CompilesSites.new.call(StaticRails.config)
4 | end
5 |
6 | class CompilesSites
7 | def call(config)
8 | config.sites.each do |site|
9 | Dir.chdir(config.app.root.join(site.source_dir)) do
10 | Bundler.with_unbundled_env do
11 | puts "=> Compiling static site \"#{site.name}\" to #{site.compile_dir}"
12 | result = system(site.env, site.compile_command)
13 | unless result == true
14 | raise Error.new("Compilation of static site \"#{site.name}\" failed (in directory \"#{site.source_dir}\" with command: `#{site.compile_command}`)")
15 | end
16 | end
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/static-rails/configuration.rb:
--------------------------------------------------------------------------------
1 | require_relative "site"
2 |
3 | module StaticRails
4 | def self.config(&blk)
5 | @configuration ||= Configuration.new
6 |
7 | @configuration.tap do |config|
8 | blk&.call(config)
9 | end
10 | end
11 |
12 | class Configuration
13 | # When Rails invokes our Railtie, we'll save off a reference to the Rails app
14 | attr_accessor :app
15 |
16 | # When true, our middleware will proxy requests to static site servers
17 | attr_accessor :proxy_requests
18 |
19 | # When true, our middleware will serve sites' compiled asset files
20 | attr_accessor :serve_compiled_assets
21 |
22 | # Number of seconds to wait on sites to confirm servers are ready
23 | attr_accessor :ping_server_timeout
24 |
25 | # When true, a cookie named "_csrf_token" will be set by static-rails middleware
26 | attr_accessor :set_csrf_token_cookie
27 |
28 | def initialize
29 | @sites = []
30 | @proxy_requests = !Rails.env.production?
31 | @serve_compiled_assets = Rails.env.production?
32 | @ping_server_timeout = 5
33 | @set_csrf_token_cookie = false
34 | end
35 |
36 | attr_reader :sites
37 | def sites=(sites)
38 | @sites = Array.wrap(sites).map { |site|
39 | Site.new(**site)
40 | }
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/static-rails/determines_whether_to_handle_request.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | class DeterminesWhetherToHandleRequest
3 | def initialize
4 | @matches_request_to_static_site = MatchesRequestToStaticSite.new
5 | end
6 |
7 | def call(env)
8 | req = Rack::Request.new(env)
9 |
10 | (req.get? || req.head?) &&
11 | (StaticRails.config.proxy_requests || StaticRails.config.serve_compiled_assets) &&
12 | @matches_request_to_static_site.call(req)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/static-rails/error.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | class Error < StandardError; end
3 | end
4 |
--------------------------------------------------------------------------------
/lib/static-rails/file_handler.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | # This class was extracted from Ruby on Rails:
3 | #
4 | # - actionpack/lib/action_dispatch/middleware/static.rb
5 | #
6 | # Copyright (c) 2005-2020 David Heinemeier Hansson, Ryan Edward Hall, Jeremy Daer
7 | #
8 | # License here: https://github.com/rails/rails/blob/master/MIT-LICENSE
9 | #
10 | # This endpoint serves static files from disk using Rack::File.
11 | #
12 | # URL paths are matched with static files according to expected
13 | # conventions: +path+, +path+.html, +path+/index.html.
14 | #
15 | # Precompressed versions of these files are checked first. Brotli (.br)
16 | # and gzip (.gz) files are supported. If +path+.br exists, this
17 | # endpoint returns that file with a +Content-Encoding: br+ header.
18 | #
19 | # If no matching file is found, this endpoint responds 404 Not Found.
20 | #
21 | # Pass the +root+ directory to search for matching files, an optional
22 | # +index: "index"+ to change the default +path+/index.html, and optional
23 | # additional response headers.
24 | class FileHandler
25 | # Accept-Encoding value -> file extension
26 | PRECOMPRESSED = {
27 | "br" => ".br",
28 | "gzip" => ".gz",
29 | "identity" => nil
30 | }
31 |
32 | def initialize(root, index: "index", headers: {}, precompressed: %i[br gzip], compressible_content_types: /\A(?:text\/|application\/javascript)/)
33 | @root = root.chomp("/").b
34 | @index = index
35 |
36 | @precompressed = Array(precompressed).map(&:to_s) | %w[identity]
37 | @compressible_content_types = compressible_content_types
38 |
39 | @file_server = ::Rack::File.new(@root, headers)
40 | end
41 |
42 | def call(env)
43 | attempt(env) || @file_server.call(env)
44 | end
45 |
46 | def attempt(env)
47 | request = Rack::Request.new env
48 |
49 | if request.get? || request.head?
50 | if (found = find_file(request.path_info, accept_encoding: request.accept_encoding))
51 | serve request, *found
52 | end
53 | end
54 | end
55 |
56 | def serve(request, filepath, content_headers)
57 | original, request.path_info = request.path_info, ::Rack::Utils.escape_path(filepath).b
58 |
59 | @file_server.call(request.env).tap do |status, headers, body|
60 | # Omit Content-Encoding/Type/etc headers for 304 Not Modified
61 | if status != 304
62 | headers.update(content_headers)
63 | end
64 | end
65 | ensure
66 | request.path_info = original
67 | end
68 |
69 | # Match a URI path to a static file to be served.
70 | #
71 | # Used by the +Static+ class to negotiate a servable file in the
72 | # +public/+ directory (see Static#call).
73 | #
74 | # Checks for +path+, +path+.html, and +path+/index.html files,
75 | # in that order, including .br and .gzip compressed extensions.
76 | #
77 | # If a matching file is found, the path and necessary response headers
78 | # (Content-Type, Content-Encoding) are returned.
79 | def find_file(path_info, accept_encoding:)
80 | each_candidate_filepath(path_info) do |filepath, content_type|
81 | if (response = try_files(filepath, content_type, accept_encoding: accept_encoding))
82 | return response
83 | end
84 | end
85 | end
86 |
87 | private
88 |
89 | def try_files(filepath, content_type, accept_encoding:)
90 | headers = {"Content-Type" => content_type}
91 |
92 | if compressible? content_type
93 | try_precompressed_files filepath, headers, accept_encoding: accept_encoding
94 | elsif file_readable? filepath
95 | [filepath, headers]
96 | end
97 | end
98 |
99 | def try_precompressed_files(filepath, headers, accept_encoding:)
100 | each_precompressed_filepath(filepath) do |content_encoding, precompressed_filepath|
101 | if file_readable? precompressed_filepath
102 | # Identity encoding is default, so we skip Accept-Encoding
103 | # negotiation and needn't set Content-Encoding.
104 | #
105 | # Vary header is expected when we've found other available
106 | # encodings that Accept-Encoding ruled out.
107 | if content_encoding == "identity"
108 | return precompressed_filepath, headers
109 | else
110 | headers["Vary"] = "Accept-Encoding"
111 |
112 | if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) }
113 | headers["Content-Encoding"] = content_encoding
114 | return precompressed_filepath, headers
115 | end
116 | end
117 | end
118 | end
119 | end
120 |
121 | def file_readable?(path)
122 | file_stat = File.stat(File.join(@root, path.b))
123 | rescue SystemCallError
124 | false
125 | else
126 | file_stat.file? && file_stat.readable?
127 | end
128 |
129 | def compressible?(content_type)
130 | @compressible_content_types.match?(content_type)
131 | end
132 |
133 | def each_precompressed_filepath(filepath)
134 | @precompressed.each do |content_encoding|
135 | precompressed_ext = PRECOMPRESSED.fetch(content_encoding)
136 | yield content_encoding, "#{filepath}#{precompressed_ext}"
137 | end
138 |
139 | nil
140 | end
141 |
142 | def each_candidate_filepath(path_info)
143 | return unless (path = clean_path(path_info))
144 |
145 | ext = ::File.extname(path)
146 | content_type = ::Rack::Mime.mime_type(ext, nil)
147 | yield path, content_type || "text/plain"
148 |
149 | # Tack on .html and /index.html only for paths that don't have
150 | # an explicit, resolvable file extension. No need to check
151 | # for foo.js.html and foo.js/index.html.
152 | unless content_type
153 | default_ext = ::ActionController::Base.default_static_extension
154 | if ext != default_ext
155 | default_content_type = ::Rack::Mime.mime_type(default_ext, "text/plain")
156 |
157 | yield "#{path}#{default_ext}", default_content_type
158 | yield "#{path}/#{@index}#{default_ext}", default_content_type
159 | end
160 | end
161 |
162 | nil
163 | end
164 |
165 | def clean_path(path_info)
166 | path = ::Rack::Utils.unescape_path path_info.chomp("/")
167 | if ::Rack::Utils.valid_path? path
168 | ::Rack::Utils.clean_path_info path
169 | end
170 | end
171 | end
172 | end
173 |
--------------------------------------------------------------------------------
/lib/static-rails/gets_csrf_token.rb:
--------------------------------------------------------------------------------
1 | require_relative "request_forgery_protection_fallback"
2 |
3 | module StaticRails
4 | class GetsCsrfToken
5 | include RequestForgeryProtectionFallback
6 |
7 | def call(req)
8 | masked_authenticity_token(req.session)
9 | end
10 |
11 | private
12 |
13 | [
14 | :csrf_token_hmac,
15 | :mask_token,
16 | :xor_byte_strings
17 | ].each do |method|
18 | define_method method do |*args, **kwargs, &blk|
19 | ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
20 | end
21 | end
22 |
23 | def masked_authenticity_token(session, form_options: {})
24 | ActionController::RequestForgeryProtection.instance_method(:masked_authenticity_token).bind(self).call(session, form_options: form_options)
25 | end
26 |
27 | def global_csrf_token(session)
28 | ActionController::RequestForgeryProtection.instance_method(:global_csrf_token).bind(self).call(session)
29 | end
30 |
31 | def real_csrf_token(session)
32 | ActionController::RequestForgeryProtection.instance_method(:real_csrf_token).bind(self).call(session)
33 | end
34 |
35 | def per_form_csrf_tokens
36 | false
37 | end
38 |
39 | def urlsafe_csrf_tokens
40 | Rails.application.config.action_controller.urlsafe_csrf_tokens
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/static-rails/matches_request_to_static_site.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | class MatchesRequestToStaticSite
3 | def call(request)
4 | StaticRails.config.sites.find { |site|
5 | subdomain_match?(site, request) && path_match?(site, request) && !skip_path?(site, request)
6 | }
7 | end
8 |
9 | private
10 |
11 | def subdomain_match?(site, request)
12 | return true if site.url_subdomain.nil?
13 |
14 | expected = site.url_subdomain.split(".")
15 | actual = request.host.split(".")
16 |
17 | expected.enum_for.with_index.all? { |sub, i| actual[i] == sub }
18 | end
19 |
20 | def path_match?(site, request)
21 | request.path_info.start_with?(site.url_root_path)
22 | end
23 |
24 | def skip_path?(site, request)
25 | site.url_skip_paths_starting_with.any? { |path_start|
26 | request.path_info.start_with?(path_start)
27 | }
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/static-rails/proxy_middleware.rb:
--------------------------------------------------------------------------------
1 | require "rack-proxy"
2 |
3 | require_relative "matches_request_to_static_site"
4 | require_relative "server_store"
5 |
6 | module StaticRails
7 | class ProxyMiddleware < Rack::Proxy
8 | def initialize(app)
9 | @matches_request_to_static_site = MatchesRequestToStaticSite.new
10 | @app = app
11 | @servers = {}
12 | super
13 | end
14 |
15 | def perform_request(env)
16 | return @app.call(env) unless StaticRails.config.proxy_requests
17 |
18 | server_store = ServerStore.instance
19 | server_store.ensure_all_servers_are_started
20 |
21 | req = Rack::Request.new(env)
22 | if (req.get? || req.head?) && (site = @matches_request_to_static_site.call(req))
23 | if site.ping_server && (server = server_store.server_for(site))
24 | server.wait_until_ready
25 | end
26 |
27 | @backend = URI("http://#{site.server_host}:#{site.server_port}")
28 | env["HTTP_HOST"] = @backend.host
29 | env["PATH_INFO"] = forwarding_path(site, req)
30 |
31 | super(env)
32 | else
33 | @app.call(env)
34 | end
35 | end
36 |
37 | private
38 |
39 | def forwarding_path(site, req)
40 | req_path = req.path_info
41 |
42 | if req_path == site.url_root_path && !req_path.end_with?("/")
43 | req_path + "/" # <- Necessary for getting jekyll, possibly hugo to serve the root
44 | else
45 | req_path
46 | end
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/static-rails/rack_server_check.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | # Shamelessly ripped out of @danmeyer's Coverband: https://github.com/danmayer/coverband/blob/master/lib/coverband/integrations/rack_server_check.rb#L14
3 | #
4 | # Copyright (c) 2010-2018 Dan Mayer
5 | #
6 | # Distributed under the MIT License
7 | #
8 | # Details:
9 | # https://github.com/danmayer/coverband/blob/master/LICENSE.txt
10 | class RackServerCheck
11 | def self.running?
12 | new(Kernel.caller_locations).running?
13 | end
14 |
15 | def initialize(stack)
16 | @stack = stack
17 | end
18 |
19 | def running?
20 | rack_server? || rails_server?
21 | end
22 |
23 | def rack_server?
24 | @stack.any? { |line| line.path.include?("lib/rack/") }
25 | end
26 |
27 | def rails_server?
28 | @stack.any? do |location|
29 | (
30 | (location.path.include?("rails/commands/commands_tasks.rb") && location.label == "server") ||
31 | (location.path.include?("rails/commands/server/server_command.rb") && location.label == "perform")
32 | )
33 | end
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/static-rails/railtie.rb:
--------------------------------------------------------------------------------
1 | require_relative "rack_server_check"
2 | require_relative "server_store"
3 | require_relative "site_middleware"
4 | require_relative "site_plus_csrf_middleware"
5 |
6 | module StaticRails
7 | class Railtie < ::Rails::Railtie
8 | rake_tasks do
9 | load "tasks/static-rails.rake"
10 | end
11 |
12 | initializer "static_rails.middleware" do
13 | config.app_middleware.insert_after Rack::Sendfile, SiteMiddleware
14 | config.app_middleware.use SitePlusCsrfMiddleware
15 | end
16 |
17 | config.after_initialize do |app|
18 | static_rails_config = StaticRails.config
19 | static_rails_config.app = app
20 |
21 | if RackServerCheck.running?
22 | ServerStore.instance.ensure_all_servers_are_started
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/static-rails/request_forgery_protection_fallback.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | module RequestForgeryProtectionFallback
3 | def method_missing(method_name, *args, **kwargs, &blk)
4 | if respond_to?(method_name)
5 | ActionController::RequestForgeryProtection.instance_method(method_name).bind(self).call(*args, **kwargs, &blk)
6 | else
7 | super
8 | end
9 | end
10 |
11 | def respond_to?(method_name, *args)
12 | ActionController::RequestForgeryProtection.instance_method(method_name) || super
13 | end
14 |
15 | def respond_to_missing?(method_name, *args)
16 | ActionController::RequestForgeryProtection.instance_method(method_name) || super
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/static-rails/server.rb:
--------------------------------------------------------------------------------
1 | require_relative "waits_for_connection"
2 |
3 | module StaticRails
4 | class Server
5 | def initialize(site)
6 | @site = site
7 | @ready = false
8 | @waits_for_connection = WaitsForConnection.new
9 | end
10 |
11 | def start
12 | return if started?
13 | @pid = spawn_process
14 | set_at_exit_hook
15 | nil
16 | end
17 |
18 | def started?
19 | return false unless @pid.present?
20 |
21 | begin
22 | Process.getpgid(@pid)
23 | true
24 | rescue Errno::ESRCH
25 | @ready = false
26 | false
27 | end
28 | end
29 |
30 | def wait_until_ready
31 | return if @ready
32 | @waits_for_connection.call(@site)
33 | @ready = true
34 | end
35 |
36 | private
37 |
38 | def spawn_process
39 | options = {
40 | in: "/dev/null",
41 | out: "/dev/stdout",
42 | err: "/dev/stderr",
43 | close_others: true,
44 | chdir: StaticRails.config.app.root.join(@site.source_dir).to_s
45 | }
46 |
47 | Rails.logger.info "=> Starting #{@site.name} static server"
48 | Bundler.with_unbundled_env do
49 | Process.spawn(@site.env, @site.server_command, options).tap do |pid|
50 | Process.detach(pid)
51 | end
52 | end
53 | end
54 |
55 | def set_at_exit_hook
56 | return if @at_exit_hook_set
57 | at_exit do
58 | if started?
59 | Rails.logger.info "=> Stopping #{@site.name} static server"
60 | Process.kill("INT", @pid)
61 | end
62 | end
63 | @at_exit_hook_set = true
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/static-rails/server_store.rb:
--------------------------------------------------------------------------------
1 | require_relative "server"
2 |
3 | module StaticRails
4 | class ServerStore
5 | def self.instance
6 | @instance ||= new
7 | end
8 |
9 | def ensure_all_servers_are_started
10 | StaticRails.config.sites.select(&:start_server).each do |site|
11 | server_for(site).start
12 | end
13 | end
14 |
15 | def server_for(site)
16 | @servers[site] ||= Server.new(site)
17 | end
18 |
19 | private
20 |
21 | def initialize
22 | @servers = {}
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/static-rails/site.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | class Site < Struct.new(
3 | :name,
4 | :url_subdomain,
5 | :url_root_path,
6 | :url_skip_paths_starting_with,
7 | :source_dir,
8 | :start_server,
9 | :ping_server,
10 | :env,
11 | :server_command,
12 | :server_host,
13 | :server_port,
14 | :server_path,
15 | :compile_command,
16 | :compile_dir,
17 | :compile_404_file_path,
18 | keyword_init: true
19 | )
20 |
21 | def initialize(
22 | url_root_path: "/",
23 | url_skip_paths_starting_with: [],
24 | start_server: !Rails.env.production?,
25 | ping_server: true,
26 | env: {},
27 | server_host: "localhost",
28 | server_path: "/",
29 | **other_kwargs
30 | )
31 | @url_root_path = url_root_path
32 | @url_skip_paths_starting_with = url_skip_paths_starting_with
33 | @start_server = start_server
34 | @ping_server = ping_server
35 | @env = env
36 | @server_host = server_host
37 | @server_path = server_path
38 | super
39 | end
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/lib/static-rails/site_middleware.rb:
--------------------------------------------------------------------------------
1 | require_relative "proxy_middleware"
2 | require_relative "static_middleware"
3 | require_relative "determines_whether_to_handle_request"
4 |
5 | module StaticRails
6 | class SiteMiddleware
7 | PATH_INFO_OBFUSCATION = "__static-rails__"
8 |
9 | def initialize(app)
10 | @app = app
11 | @proxy_middleware = ProxyMiddleware.new(app)
12 | @static_middleware = StaticMiddleware.new(app)
13 | @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
14 | end
15 |
16 | def call(env)
17 | return @app.call(env) unless @determines_whether_to_handle_request.call(env)
18 |
19 | if require_csrf_before_processing_request?
20 | # You might be asking yourself what the hell is going on here. In short,
21 | # This middleware sits at the top of the stack, which is too early to
22 | # set a CSRF token in a cookie. Therefore, we've placed a subclass of
23 | # this middleware named SitePlusCsrfMiddleware near the bottom of the
24 | # middleware stack, which is slower but comes after Session::CookieStore
25 | # and therefore can write _csrf_token to the cookie. As a result, the
26 | # observable behavior to the user is identical, but the first request
27 | # to set the cookie will be marginally slower because it needs to go
28 | # deeper down the Rails middleware stack
29 | #
30 | # But! Between these two is ActionDispatch::Static. In the odd case that
31 | # a path that this middleware would serve happens to match the name of
32 | # a path in public/, kicking down the middleware stack would result in
33 | # that file being served instead of our deeper middleware being called.
34 | # So to work around this we're just making the PATH_INFO property so
35 | # ugly that there's no chance it'll match anything. When our subclass
36 | # gets its shot at this request, it'll know to remove the path
37 | # obfuscation from PATH_INFO and go about its business.
38 | #
39 | # See, easy!
40 | #
41 | # (By the way, this was all Matthew Draper's bright idea. You can
42 | # compliment him here: https://github.com/matthewd )
43 | @app.call(env.merge("PATH_INFO" => "/" + PATH_INFO_OBFUSCATION + env["PATH_INFO"]))
44 | elsif StaticRails.config.proxy_requests
45 | @proxy_middleware.call(env)
46 | elsif StaticRails.config.serve_compiled_assets
47 | @static_middleware.call(env)
48 | end
49 | end
50 |
51 | protected
52 |
53 | # Override this in subclass since it'll call super(env) and deal itself
54 | def require_csrf_before_processing_request?
55 | StaticRails.config.set_csrf_token_cookie
56 | end
57 | end
58 | end
59 |
--------------------------------------------------------------------------------
/lib/static-rails/site_plus_csrf_middleware.rb:
--------------------------------------------------------------------------------
1 | require_relative "site_middleware"
2 | require_relative "determines_whether_to_handle_request"
3 | require_relative "validates_csrf_token"
4 | require_relative "gets_csrf_token"
5 |
6 | module StaticRails
7 | class SitePlusCsrfMiddleware < SiteMiddleware
8 | def initialize(app)
9 | @determines_whether_to_handle_request = DeterminesWhetherToHandleRequest.new
10 | @validates_csrf_token = ValidatesCsrfToken.new
11 | @gets_csrf_token = GetsCsrfToken.new
12 | super
13 | end
14 |
15 | def call(env)
16 | return @app.call(env) unless env["PATH_INFO"]&.start_with?(/\/?#{PATH_INFO_OBFUSCATION}/o) || @determines_whether_to_handle_request.call(env)
17 |
18 | env = env.merge(
19 | "PATH_INFO" => env["PATH_INFO"].gsub(/^\/?#{PATH_INFO_OBFUSCATION}/o, "")
20 | )
21 | status, headers, body = super(env)
22 |
23 | if StaticRails.config.set_csrf_token_cookie
24 | req = Rack::Request.new(env)
25 | res = Rack::Response.new(body, status, headers)
26 | if needs_new_csrf_token?(req)
27 | res.set_cookie("_csrf_token", {
28 | value: @gets_csrf_token.call(req),
29 | path: "/"
30 | })
31 | end
32 | res.finish
33 | else
34 | [status, headers, body]
35 | end
36 | end
37 |
38 | protected
39 |
40 | def require_csrf_before_processing_request?
41 | false
42 | end
43 |
44 | private
45 |
46 | def needs_new_csrf_token?(req)
47 | !req.cookies.has_key?("_csrf_token") || !@validates_csrf_token.call(req)
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/lib/static-rails/static_middleware.rb:
--------------------------------------------------------------------------------
1 | require "rack-proxy"
2 |
3 | require_relative "file_handler"
4 | require_relative "matches_request_to_static_site"
5 |
6 | module StaticRails
7 | class StaticMiddleware
8 | def initialize(app)
9 | @matches_request_to_static_site = MatchesRequestToStaticSite.new
10 | @app = app
11 | @file_handlers = {}
12 | end
13 |
14 | def call(env)
15 | return @app.call(env) unless StaticRails.config.serve_compiled_assets
16 | req = Rack::Request.new env
17 |
18 | if (req.get? || req.head?) && (site = @matches_request_to_static_site.call(req))
19 | file_handler = file_handler_for(site)
20 | path = req.path_info.gsub(/^#{site.url_root_path}/, "").chomp("/")
21 | if (result = serve_file_for(file_handler, site, path, req))
22 | return result
23 | end
24 | end
25 |
26 | @app.call(req.env)
27 | end
28 |
29 | private
30 |
31 | # The same file handler used by Rails when serving up files from /public
32 | # See: actionpack/lib/action_dispatch/middleware/static.rb
33 | def file_handler_for(site)
34 | @file_handlers[site] ||= FileHandler.new(
35 | StaticRails.config.app.root.join(site.compile_dir).to_s,
36 | headers: {
37 | "cache-control" => "public; max-age=31536000"
38 | },
39 | compressible_content_types: /^text\/|[\/+](javascript|json|text|xml|css|yaml)$/i
40 | )
41 | end
42 |
43 | def serve_file_for(file_handler, site, path, req)
44 | if (found = file_handler.find_file(path, accept_encoding: req.accept_encoding))
45 | serve_file(file_handler, found, req)
46 | elsif site.compile_404_file_path.present?
47 | found = file_handler.find_file(site.compile_404_file_path, accept_encoding: req.accept_encoding)
48 | serve_file(file_handler, found, req, 404)
49 | end
50 | end
51 |
52 | def serve_file(file_handler, file, req, status_override = nil)
53 | return unless file
54 | file_handler.serve(req, *file).tap do |result|
55 | result[0] = status_override unless status_override.nil?
56 | override_cache_control_if_we_probably_shouldnt_cache!(file, result[1])
57 | end
58 | end
59 |
60 | PROBABLY_SHOULDNT_CACHE_MIME_TYPES = [
61 | "text/html", "application/xml", "application/rss+xml"
62 | ]
63 | def override_cache_control_if_we_probably_shouldnt_cache!(file, headers = {})
64 | mime_type = file[1]["Content-Type"]
65 | if PROBABLY_SHOULDNT_CACHE_MIME_TYPES.include?(mime_type)
66 | headers["cache-control"] = "no-cache, no-store"
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/lib/static-rails/validates_csrf_token.rb:
--------------------------------------------------------------------------------
1 | require_relative "request_forgery_protection_fallback"
2 |
3 | module StaticRails
4 | class ValidatesCsrfToken
5 | include RequestForgeryProtectionFallback
6 |
7 | def call(req)
8 | valid_authenticity_token?(req.session, req.cookies["_csrf_token"])
9 | end
10 |
11 | private
12 |
13 | [
14 | :compare_with_global_token,
15 | :global_csrf_token,
16 | :csrf_token_hmac,
17 | :valid_authenticity_token?,
18 | :unmask_token,
19 | :compare_with_real_token,
20 | :valid_per_form_csrf_token?,
21 | :xor_byte_strings,
22 | :real_csrf_token
23 | ].each do |method|
24 | define_method method do |*args, **kwargs, &blk|
25 | ActionController::RequestForgeryProtection.instance_method(method).bind(self).call(*args, **kwargs, &blk)
26 | end
27 | end
28 |
29 | def per_form_csrf_tokens
30 | false
31 | end
32 |
33 | def urlsafe_csrf_tokens
34 | Rails.application.config.action_controller.urlsafe_csrf_tokens
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/static-rails/version.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | VERSION = "0.1.0"
3 | end
4 |
--------------------------------------------------------------------------------
/lib/static-rails/waits_for_connection.rb:
--------------------------------------------------------------------------------
1 | module StaticRails
2 | class WaitsForConnection
3 | def call(site)
4 | timeout = StaticRails.config.ping_server_timeout
5 | start = Time.new
6 | wait_message_logged = false
7 |
8 | loop do
9 | Socket.tcp(site.server_host, site.server_port, connect_timeout: 5)
10 | break
11 | rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
12 | elapsed = Time.new - start
13 | if elapsed > timeout
14 | raise Error.new("Static site server \"#{site.name}\" failed to start within #{timeout} seconds. You can change the timeout with `StaticRails.config.ping_server_timeout = 42`")
15 | else
16 | unless wait_message_logged
17 | Rails.logger.info "=> Static site server \"#{site.name}\" is not yet accepting connections on #{site.server_host}:#{site.server_port}. Will try to connect for #{timeout} more seconds"
18 | wait_message_logged = true
19 | end
20 | sleep 0.3
21 | end
22 | end
23 | end
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/lib/tasks/static-rails.rake:
--------------------------------------------------------------------------------
1 | $stdout.sync = true
2 |
3 | def enhance_assets_precompile
4 | Rake::Task["assets:precompile"].enhance([]) do
5 | Rake::Task["static:compile"].invoke
6 | end
7 | end
8 |
9 | namespace :static do
10 | desc "Compile static sites configured with static-rails"
11 | task compile: :environment do
12 | StaticRails.compile
13 | end
14 | end
15 |
16 | if Rake::Task.task_defined?("assets:precompile")
17 | enhance_assets_precompile
18 | else
19 | Rake::Task.define_task("assets:precompile" => "static:compile")
20 | end
21 |
--------------------------------------------------------------------------------
/script/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | echo "--> Installing static-rails' Ruby deps"
6 | bundle install --path vendor/bundle
7 |
8 | echo "--> Installing example app's Ruby deps"
9 | cd example
10 | bundle install --path vendor/bundle
11 | cd ..
12 |
13 | echo "--> Installing example app's JS deps"
14 | cd example
15 | yarn install
16 | cd ..
17 |
18 | echo "--> Installing example app's Jekyll site's Ruby deps"
19 | cd example/static/docs
20 | bundle install --path vendor/bundle
21 | cd ../../..
22 |
23 | echo "--> Installing example app's Eleventy site's JS deps"
24 | cd example/static/blog-docs
25 | npm install
26 | cd ../../..
27 |
28 |
29 |
--------------------------------------------------------------------------------
/script/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | echo "--> Running example app's tests"
6 | cd example
7 | ./script/test
8 | cd ..
9 |
10 | echo "--> Running standard:fix"
11 | bundle exec rake standard:fix
12 |
13 |
--------------------------------------------------------------------------------
/static-rails.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/static-rails/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "static-rails"
5 | spec.version = StaticRails::VERSION
6 | spec.authors = ["Justin Searls"]
7 | spec.email = ["searls@gmail.com"]
8 |
9 | spec.summary = "Build & serve static sites (e.g. Jekyll, Hugo) from your Rails app"
10 | spec.homepage = "https://github.com/testdouble/static-rails"
11 | spec.license = "MIT"
12 | spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
13 |
14 | spec.metadata["homepage_uri"] = spec.homepage
15 |
16 | spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
17 | `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|example)/}) }
18 | end
19 | spec.bindir = "exe"
20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21 | spec.require_paths = ["lib"]
22 |
23 | spec.add_dependency "railties", ">= 5.0.0"
24 | spec.add_dependency "rack-proxy", "~> 0.6"
25 | end
26 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
2 | require "rails/static/site"
3 |
4 | require "minitest/autorun"
5 |
--------------------------------------------------------------------------------