frontman-ssg 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +42 -0
- data/.github/CODE_OF_CONDUCT.md +9 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +88 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +42 -0
- data/Gemfile +5 -0
- data/LICENSE.md +21 -0
- data/Rakefile +94 -0
- data/SECURITY.md +6 -0
- data/bin/frontman +6 -0
- data/frontman-ssg.gemspec +48 -0
- data/frontman.svg +2 -0
- data/lib/frontman.rb +15 -0
- data/lib/frontman/app.rb +175 -0
- data/lib/frontman/bootstrapper.rb +70 -0
- data/lib/frontman/builder/asset_pipeline.rb +55 -0
- data/lib/frontman/builder/builder.rb +193 -0
- data/lib/frontman/builder/file.rb +55 -0
- data/lib/frontman/builder/mapping.rb +54 -0
- data/lib/frontman/builder/statistics_collector.rb +37 -0
- data/lib/frontman/cli.rb +6 -0
- data/lib/frontman/commands/build.rb +76 -0
- data/lib/frontman/commands/init.rb +58 -0
- data/lib/frontman/commands/serve.rb +110 -0
- data/lib/frontman/concerns/dispatch_events.rb +56 -0
- data/lib/frontman/concerns/forward_calls_to_app.rb +28 -0
- data/lib/frontman/config.rb +52 -0
- data/lib/frontman/context.rb +125 -0
- data/lib/frontman/custom_struct.rb +44 -0
- data/lib/frontman/data_store.rb +106 -0
- data/lib/frontman/data_store_file.rb +60 -0
- data/lib/frontman/helpers/app_helper.rb +18 -0
- data/lib/frontman/helpers/link_helper.rb +35 -0
- data/lib/frontman/helpers/render_helper.rb +76 -0
- data/lib/frontman/helpers/url_helper.rb +11 -0
- data/lib/frontman/iterator.rb +48 -0
- data/lib/frontman/process/chain.rb +43 -0
- data/lib/frontman/process/processor.rb +11 -0
- data/lib/frontman/renderers/erb_renderer.rb +21 -0
- data/lib/frontman/renderers/haml_renderer.rb +22 -0
- data/lib/frontman/renderers/markdown_renderer.rb +26 -0
- data/lib/frontman/renderers/renderer.rb +26 -0
- data/lib/frontman/renderers/renderer_resolver.rb +26 -0
- data/lib/frontman/resource.rb +279 -0
- data/lib/frontman/sitemap_tree.rb +211 -0
- data/lib/frontman/toolbox/timer.rb +49 -0
- data/lib/frontman/version.rb +6 -0
- data/project-templates/default/.gitignore +2 -0
- data/project-templates/default/Gemfile +3 -0
- data/project-templates/default/config.rb +17 -0
- data/project-templates/default/data/site.yml +4 -0
- data/project-templates/default/helpers/site_helper.rb +7 -0
- data/project-templates/default/public/code.css +77 -0
- data/project-templates/default/public/frontman-logo.svg +2 -0
- data/project-templates/default/public/main.css +27 -0
- data/project-templates/default/public/main.js +1 -0
- data/project-templates/default/source/index.html.md.erb +7 -0
- data/project-templates/default/source/sitemap.xml.erb +11 -0
- data/project-templates/default/views/layouts/main.erb +19 -0
- data/project-templates/default/views/layouts/main.haml +15 -0
- data/project-templates/default/views/partials/menu.erb +7 -0
- data/project-templates/webpack/.gitignore +4 -0
- data/project-templates/webpack/Gemfile +3 -0
- data/project-templates/webpack/README.md +54 -0
- data/project-templates/webpack/assets/css/code.css +77 -0
- data/project-templates/webpack/assets/css/style.css +27 -0
- data/project-templates/webpack/assets/images/.gitkeep +0 -0
- data/project-templates/webpack/assets/images/frontman_logo.svg +2 -0
- data/project-templates/webpack/assets/js/index.js +1 -0
- data/project-templates/webpack/config.rb +24 -0
- data/project-templates/webpack/data/site.yml +4 -0
- data/project-templates/webpack/helpers/assets_helper.rb +24 -0
- data/project-templates/webpack/helpers/site_helper.rb +7 -0
- data/project-templates/webpack/package-lock.json +7603 -0
- data/project-templates/webpack/package.json +34 -0
- data/project-templates/webpack/source/index.html.md.erb +7 -0
- data/project-templates/webpack/source/sitemap.xml.erb +11 -0
- data/project-templates/webpack/views/layouts/main.erb +20 -0
- data/project-templates/webpack/views/layouts/main.haml +14 -0
- data/project-templates/webpack/views/partials/menu.erb +7 -0
- data/project-templates/webpack/views/partials/script_with_vendors.haml +5 -0
- data/project-templates/webpack/webpack/base.config.js +51 -0
- data/project-templates/webpack/webpack/dev.config.js +6 -0
- data/project-templates/webpack/webpack/prod.config.js +30 -0
- data/readme.md +80 -0
- data/sorbet/config +2 -0
- data/sorbet/rbi/hidden-definitions/errors.txt +27259 -0
- data/sorbet/rbi/hidden-definitions/hidden.rbi +45122 -0
- data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
- data/sorbet/rbi/todo.rbi +6 -0
- data/spec/frontman/app_spec.rb +48 -0
- data/spec/frontman/bootstrapper_spec.rb +26 -0
- data/spec/frontman/builder/builder_spec.rb +79 -0
- data/spec/frontman/builder/file_spec.rb +45 -0
- data/spec/frontman/builder/mapping_spec.rb +8 -0
- data/spec/frontman/concerns/dispatch_events_spec.rb +70 -0
- data/spec/frontman/concerns/forward_calls_to_app_spec.rb +21 -0
- data/spec/frontman/config_spec.rb +54 -0
- data/spec/frontman/context_spec.rb +48 -0
- data/spec/frontman/custom_struct_spec.rb +51 -0
- data/spec/frontman/data_store_file_spec.rb +9 -0
- data/spec/frontman/data_store_spec.rb +36 -0
- data/spec/frontman/frontman_ssg_spec.rb +7 -0
- data/spec/frontman/helpers/app_helper_spec.rb +24 -0
- data/spec/frontman/helpers/link_helper_spec.rb +37 -0
- data/spec/frontman/helpers/render_helper_spec.rb +55 -0
- data/spec/frontman/helpers/url_helper_spec.rb +21 -0
- data/spec/frontman/iterator_spec.rb +47 -0
- data/spec/frontman/mocks/asset.css +3 -0
- data/spec/frontman/mocks/config.rb +0 -0
- data/spec/frontman/mocks/helpers/formatting_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/language_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/link_helper.rb +5 -0
- data/spec/frontman/mocks/helpers/test_command.rb +5 -0
- data/spec/frontman/mocks/html_file.html +8 -0
- data/spec/frontman/mocks/html_file.html.md.erb +9 -0
- data/spec/frontman/mocks/html_file.md.html +8 -0
- data/spec/frontman/mocks/info.yml +4 -0
- data/spec/frontman/mocks/layouts/raw_without_body.haml +1 -0
- data/spec/frontman/mocks/nested/data.yml +4 -0
- data/spec/frontman/mocks/nested/more_data.yml +4 -0
- data/spec/frontman/mocks/partials/paragraph.haml +2 -0
- data/spec/frontman/mocks/snippet/html_file.html +8 -0
- data/spec/frontman/mocks/snippet/html_file.yml +670 -0
- data/spec/frontman/mocks/test.html +8 -0
- data/spec/frontman/mocks/wrap.haml +3 -0
- data/spec/frontman/process/chain_spec.rb +56 -0
- data/spec/frontman/renderers/erb_renderer_spec.rb +22 -0
- data/spec/frontman/renderers/haml_renderer_spec.rb +12 -0
- data/spec/frontman/renderers/markdown_renderer_spec.rb +12 -0
- data/spec/frontman/renderers/renderer_spec.rb +16 -0
- data/spec/frontman/resource_spec.rb +151 -0
- data/spec/frontman/sitemap_tree_spec.rb +128 -0
- data/spec/frontman/toolbox/timer_spec.rb +34 -0
- data/spec/spec_setup.rb +19 -0
- metadata +507 -0
data/frontman.svg
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 344.97 369.41" width="344.97" height="369.41"><defs><style>.cls-1{fill:#5468ff;}</style></defs><title>logo-frontman-blue</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M15.36,175.35q5.2-1.64,10.72-3.16L22,147.43H322.24l-4.12,24.76q5.5,1.51,10.72,3.16l5.41-32.55a5.55,5.55,0,0,0-5.47-6.46H243.51L238.92,0l-8.3,26a16.68,16.68,0,0,1-22.41,10.29s-24.75-8.55-38.62-8.55S131,36.28,131,36.28A16.69,16.69,0,0,1,108.56,26L100.27,0,95.66,136.34H15.42A5.55,5.55,0,0,0,10,142.8Zm214.22-72-19.2,17.26a16.15,16.15,0,0,1-19.68,1.48l-11.28-7.41Zm-69.82,11.33-11.28,7.41a16.15,16.15,0,0,1-19.68-1.48l-19.2-17.26Z"></path><path class="cls-1" d="M50.79,320.77l-6.72-40.41q-5.52,1.25-10.81,2.6l7.15,43a5.54,5.54,0,0,0,3.85,4.39l92.07,28.15-6.74-13.66Z"></path><path class="cls-1" d="M293.42,320.77l-79.77,24.38L206.9,358.8l93-28.44a5.54,5.54,0,0,0,3.85-4.39l7.15-43q-5.28-1.36-10.8-2.6Z"></path><path class="cls-1" d="M147.35,302V259.67q-6.36.34-12.7.85-5.13,13.38-10.23,26.88l-.55.05q-5.13-12.42-10.24-24.72-6.31.81-12.62,1.81v42.28c3.29-.52,6.58-1,9.88-1.45V278.68l.52-.08q4.71,11.14,9.41,22.37c2.22-.25,4.43-.48,6.65-.7q4.69-12.22,9.4-24.34l.53,0v26.69Q142.37,302.21,147.35,302Z"></path><path class="cls-1" d="M196.14,302.08Q189,280.52,181.9,259.21q-6.6-.16-13.19-.14-7.12,21.17-14.23,42.56c3.57-.13,7.13-.21,10.7-.25l2.73-9.32q6.93,0,13.84.14c.88,3.12,1.77,6.23,2.65,9.36Q190.27,301.74,196.14,302.08ZM170,284.32q2.33-7.86,4.64-15.71h.56q2.28,7.89,4.56,15.81Q174.84,284.32,170,284.32Z"></path><path class="cls-1" d="M213.44,279.39l.56.06q8.82,12.87,17.63,26.11c2.83.39,5.65.81,8.48,1.26V264.54q-5.07-.8-10.15-1.49v23.79l-.55-.07q-8.78-13.06-17.58-25.78-4.29-.4-8.59-.72v42.28q5.1.38,10.2.87Z"></path><path class="cls-1" d="M44.44,218.83q-13.51,3.18-26.9,7V209.81q14.75-4.26,29.66-7.69V187.71Q23.39,193.2,0,200.79v70.46q8.73-2.83,17.54-5.38V239.6q13.38-3.87,26.9-7Z"></path><path class="cls-1" d="M117.8,199.4c0-14.52-10-22.39-26.5-20q-15.57,2.29-31,5.46v70.46q8.92-1.83,17.91-3.37V227.3c3.09-.53,6.19-1,9.3-1.51q6,11.39,12.12,22.92,10.11-1.34,20.27-2.29-7.09-13-14.18-25.77A24.05,24.05,0,0,0,117.8,199.4Zm-29,13.54c-3.55.53-7.09,1.11-10.62,1.71V195q5.28-.9,10.57-1.71c6.42-1,10.6,2.36,10.6,8.36S95.42,211.94,88.8,212.94Z"></path><path class="cls-1" d="M164,171.63c-21.46.36-35.17,15.9-35.17,38.76S142.45,246.26,164,245.9s35.14-13.58,35.14-36.58C199.15,186.47,185.42,171.34,164,171.63Zm0,59.43c-10.14.16-16.71-8.18-16.71-21.81S154,186.64,164,186.48s16.66,8.69,16.66,22.26S174,230.91,164,231.06Z"></path><path class="cls-1" d="M256.56,219.49Q241.83,197.43,227,176.14q-7.22-.7-14.45-1.22v70.45q8.58.63,17.16,1.51v-40l.93.09q14.85,21.58,29.62,43.95,7.13,1.14,14.22,2.46V182.88q-8.49-1.59-17-2.9v39.65Z"></path><path class="cls-1" d="M287.23,185.39V199.8q10.13,2.13,20.17,4.63v56.05q8.85,2.2,17.63,4.7V209.13Q335,212,345,215.2V200.79A559.87,559.87,0,0,0,287.23,185.39Z"></path><polygon class="cls-1" points="156.87 331.36 145.94 342.94 156.87 342.94 156.87 331.36"></polygon><polygon class="cls-1" points="159.36 340.38 168.54 330.65 159.36 330.65 159.36 340.38"></polygon><polygon class="cls-1" points="171.71 330.64 160.25 342.94 183.11 342.94 171.71 330.64"></polygon><polygon class="cls-1" points="159.92 345.42 171.71 369.41 183.43 345.42 159.92 345.42"></polygon><polygon class="cls-1" points="146.01 345.42 167.93 366.91 157.05 345.42 146.01 345.42"></polygon><polygon class="cls-1" points="186.54 342.94 197.47 342.94 186.54 331.36 186.54 342.94"></polygon><polygon class="cls-1" points="184.06 330.65 174.88 330.65 184.06 340.38 184.06 330.65"></polygon><polygon class="cls-1" points="175.49 366.91 197.41 345.42 186.36 345.42 175.49 366.91"></polygon></g></g></svg>
|
data/lib/frontman.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'frontman/app'
|
4
|
+
require 'frontman/context'
|
5
|
+
require 'frontman/config'
|
6
|
+
require 'frontman/custom_struct'
|
7
|
+
require 'frontman/data_store'
|
8
|
+
require 'frontman/data_store_file'
|
9
|
+
require 'frontman/sitemap_tree'
|
10
|
+
require 'frontman/resource'
|
11
|
+
|
12
|
+
require 'frontman/renderers/renderer'
|
13
|
+
require 'frontman/renderers/erb_renderer'
|
14
|
+
require 'frontman/renderers/haml_renderer'
|
15
|
+
require 'frontman/renderers/markdown_renderer'
|
data/lib/frontman/app.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# typed: ignore
|
2
|
+
# frozen_string_literal: false
|
3
|
+
|
4
|
+
require 'frontman/sitemap_tree'
|
5
|
+
require 'frontman/data_store'
|
6
|
+
require 'singleton'
|
7
|
+
require 'sorbet-runtime'
|
8
|
+
|
9
|
+
module Frontman
|
10
|
+
class App
|
11
|
+
extend T::Sig
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
attr_accessor :current_page, :current_tree, :view_data, :refresh_data_files,
|
15
|
+
:asset_pipelines
|
16
|
+
attr_reader :layouts, :redirects, :assets_manifest, :data_dirs
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def initialize
|
20
|
+
@current_page = nil
|
21
|
+
@current_tree = nil
|
22
|
+
@view_data = []
|
23
|
+
@layouts = []
|
24
|
+
@redirects = {}
|
25
|
+
@assets_manifest = {}
|
26
|
+
@data_dirs = {}
|
27
|
+
@refresh_data_files = false
|
28
|
+
@asset_pipelines = []
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { returns(Frontman::SitemapTree) }
|
32
|
+
def sitemap_tree
|
33
|
+
@sitemap_tree ||= Frontman::SitemapTree.new(nil)
|
34
|
+
end
|
35
|
+
|
36
|
+
sig { returns(T.self_type) }
|
37
|
+
def app
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(config: String).void }
|
42
|
+
def run(config)
|
43
|
+
instance_eval config
|
44
|
+
end
|
45
|
+
|
46
|
+
sig { params(glob: String, layout_name: T.nilable(String)).void }
|
47
|
+
def register_layout(glob, layout_name)
|
48
|
+
layout_dir = Frontman::Config.get(:layout_dir, fallback: 'views/layouts')
|
49
|
+
layout = glob, layout_name.nil? ? nil : File.join(layout_dir, layout_name)
|
50
|
+
|
51
|
+
@layouts.push(layout)
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { params(helpers: T::Array[T::Hash[Symbol, String]]).void }
|
55
|
+
def register_helpers(helpers)
|
56
|
+
helpers.each do |helper|
|
57
|
+
require T.must(helper[:path])
|
58
|
+
singleton_class.send(
|
59
|
+
:include,
|
60
|
+
Object.const_get(T.must(helper[:name]).to_sym)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
sig { params(dir: String).void }
|
66
|
+
def register_helper_dir(dir)
|
67
|
+
Frontman::Config.set(:helpers_dir, dir)
|
68
|
+
register_helpers(
|
69
|
+
Frontman::Bootstrapper.find_helpers_in(
|
70
|
+
File.join('.', dir)
|
71
|
+
)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { params(from: String, to: String).returns(String) }
|
76
|
+
def add_redirect(from, to)
|
77
|
+
from = format_url(from)
|
78
|
+
|
79
|
+
resource = sitemap_tree.from_url(from)&.resource
|
80
|
+
raise ExistingResourceError.create(from, resource) if resource
|
81
|
+
|
82
|
+
@redirects[from] = to
|
83
|
+
end
|
84
|
+
|
85
|
+
sig { params(url: String).returns(T.nilable(String)) }
|
86
|
+
def get_redirect(url)
|
87
|
+
url = format_url(url)
|
88
|
+
|
89
|
+
@redirects[url]
|
90
|
+
end
|
91
|
+
|
92
|
+
sig { params(key: String, value: String).returns(String) }
|
93
|
+
def add_to_manifest(key, value)
|
94
|
+
@assets_manifest[key] = '/' + value.sub(%r{^/}, '')
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(dirs: Array).void }
|
98
|
+
def register_data_dirs(dirs)
|
99
|
+
dirs.each do |dir|
|
100
|
+
define_singleton_method dir.to_sym do
|
101
|
+
@data_dirs[dir] ||= DataStore.new(File.join(Dir.pwd, dir))
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
sig do
|
107
|
+
params(
|
108
|
+
command: String,
|
109
|
+
name: T.nilable(String),
|
110
|
+
source_dir: T.nilable(String),
|
111
|
+
timing: Symbol,
|
112
|
+
mode: Symbol,
|
113
|
+
delay: T.any(Integer, String)
|
114
|
+
).returns(Array)
|
115
|
+
end
|
116
|
+
def add_asset_pipeline(
|
117
|
+
command:, name: nil, source_dir: nil,
|
118
|
+
timing: :before, mode: :all, delay: 0
|
119
|
+
)
|
120
|
+
@asset_pipelines.push(
|
121
|
+
name: name || command,
|
122
|
+
source_dir: source_dir,
|
123
|
+
command: command,
|
124
|
+
timing: timing == :after ? :after : :before,
|
125
|
+
mode: mode,
|
126
|
+
delay: delay
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def method_missing(method_id, *_)
|
131
|
+
from_view_data = get_from_view_data(method_id)
|
132
|
+
return from_view_data unless from_view_data.nil?
|
133
|
+
|
134
|
+
from_current_page = get_from_current_page(method_id)
|
135
|
+
return from_current_page unless from_current_page.nil?
|
136
|
+
|
137
|
+
return true if method_id == :render_toc
|
138
|
+
|
139
|
+
super
|
140
|
+
end
|
141
|
+
|
142
|
+
def respond_to_missing?(method_name, include_private = false)
|
143
|
+
public_send(method_name)
|
144
|
+
true
|
145
|
+
rescue StandardError
|
146
|
+
super
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
sig { params(key: T.any(String, Symbol)).returns(T.untyped) }
|
152
|
+
def get_from_current_page(key)
|
153
|
+
return unless current_page && current_page.data.respond_to?(key)
|
154
|
+
|
155
|
+
current_page.data[key]
|
156
|
+
end
|
157
|
+
|
158
|
+
sig { params(key: T.any(String, Symbol)).returns(T.untyped) }
|
159
|
+
def get_from_view_data(key)
|
160
|
+
return if @view_data.empty? || @view_data.last.nil?
|
161
|
+
|
162
|
+
return @view_data.last[key] unless @view_data.last[key].nil?
|
163
|
+
|
164
|
+
local_data = @view_data.last[:locals]
|
165
|
+
local_data[key] unless local_data.nil?
|
166
|
+
end
|
167
|
+
|
168
|
+
class ExistingResourceError < StandardError
|
169
|
+
def self.create(url, resource)
|
170
|
+
new("Unable to redirect for #{url},
|
171
|
+
the resource #{resource.file_path} already exists on this URL")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: ignore
|
4
|
+
|
5
|
+
require 'sorbet-runtime'
|
6
|
+
require 'frontman/app'
|
7
|
+
require 'frontman/resource'
|
8
|
+
|
9
|
+
module Frontman
|
10
|
+
class Bootstrapper
|
11
|
+
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(path: String).returns(T::Array[T::Hash[Symbol, String]]) }
|
15
|
+
def find_helpers_in(path)
|
16
|
+
Dir.entries(path).map do |file|
|
17
|
+
next unless file.end_with?('_helper.rb')
|
18
|
+
|
19
|
+
{
|
20
|
+
path: File.join(path, file),
|
21
|
+
name: file.split('_').collect(&:capitalize).join.gsub('.rb', '')
|
22
|
+
}
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(app: Frontman::App).returns(Frontman::App) }
|
27
|
+
def bootstrap_app(app)
|
28
|
+
unless bootstrapped?
|
29
|
+
register_default_helpers(app)
|
30
|
+
|
31
|
+
config_path = Frontman::Config.get(
|
32
|
+
:config_path,
|
33
|
+
fallback: './config.rb'
|
34
|
+
)
|
35
|
+
app.run(File.read(config_path)) if File.exist?(config_path)
|
36
|
+
|
37
|
+
@@bootstrapped = true
|
38
|
+
end
|
39
|
+
|
40
|
+
app
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { params(content_root: String).returns(T::Array[Frontman::Resource]) }
|
44
|
+
def resources_from_dir(content_root)
|
45
|
+
files = Dir.glob(
|
46
|
+
File.join(content_root, '**/*')
|
47
|
+
)
|
48
|
+
|
49
|
+
files.reject! { |f| File.directory?(f) || File.fnmatch('**.yml', f) }
|
50
|
+
files.sort! { |a, b| a <=> b || 0 }
|
51
|
+
files.map do |path|
|
52
|
+
Frontman::Resource.from_path(path, path.sub(/^#{content_root}/, ''))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bootstrapped?
|
57
|
+
@@bootstrapped ||= false
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
sig { params(app: Frontman::App).void }
|
63
|
+
def register_default_helpers(app)
|
64
|
+
app.register_helpers(
|
65
|
+
find_helpers_in(File.join(__dir__, 'helpers'))
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'sorbet-runtime'
|
5
|
+
|
6
|
+
module Frontman
|
7
|
+
module Builder
|
8
|
+
class AssetPipeline
|
9
|
+
extend T::Sig
|
10
|
+
|
11
|
+
attr_reader :pipelines
|
12
|
+
|
13
|
+
sig { params(external_pipelines: T::Array[Hash]).void }
|
14
|
+
def initialize(external_pipelines)
|
15
|
+
@pipelines = external_pipelines
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(timing: Symbol).returns(T::Array[Hash]) }
|
19
|
+
def run!(timing)
|
20
|
+
pipelines = get_by_timing(timing)
|
21
|
+
|
22
|
+
pipelines.each do |pipeline|
|
23
|
+
p "Running external asset pipeline: #{pipeline[:name]}"
|
24
|
+
|
25
|
+
`#{pipeline[:command]}`
|
26
|
+
sleep(pipeline[:delay]) if pipeline[:delay]
|
27
|
+
|
28
|
+
p "Finished: #{pipeline[:name]}"
|
29
|
+
end
|
30
|
+
|
31
|
+
pipelines
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(timing: Symbol).returns(T::Array[T.nilable(Integer)]) }
|
35
|
+
def run_in_background!(timing)
|
36
|
+
pipelines = get_by_timing(timing)
|
37
|
+
|
38
|
+
pipelines.map do |pipeline|
|
39
|
+
p "Running external asset pipeline: #{pipeline[:name]}"
|
40
|
+
pid = fork { exec(pipeline[:command]) }
|
41
|
+
sleep(pipeline[:delay]) if pipeline[:delay]
|
42
|
+
|
43
|
+
pid
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
sig { params(timing: Symbol).returns(T::Array[Hash]) }
|
50
|
+
def get_by_timing(timing)
|
51
|
+
@pipelines.filter { |pipeline| pipeline[:timing] == timing }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'frontman/builder/file'
|
5
|
+
require 'frontman/config'
|
6
|
+
require 'frontman/concerns/dispatch_events'
|
7
|
+
require 'frontman/iterator'
|
8
|
+
require 'sorbet-runtime'
|
9
|
+
|
10
|
+
module Frontman
|
11
|
+
module Builder
|
12
|
+
class Builder
|
13
|
+
extend T::Sig
|
14
|
+
include Frontman::DispatchEvents
|
15
|
+
|
16
|
+
attr_accessor :build_directory, :current_build_files, :public_dir
|
17
|
+
|
18
|
+
sig { void }
|
19
|
+
def initialize
|
20
|
+
@emit_events = true
|
21
|
+
@build_directory = Dir.pwd + '/build/'
|
22
|
+
@public_dir = Frontman::Config.get(:public_dir, fallback: 'public/')
|
23
|
+
@current_build_files = []
|
24
|
+
end
|
25
|
+
|
26
|
+
sig do
|
27
|
+
params(resource: Frontman::Resource).returns(Frontman::Builder::File)
|
28
|
+
end
|
29
|
+
def build_resource(resource)
|
30
|
+
build_from_content(create_path_from_resource(resource), resource.render)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(resources: T::Array[Frontman::Resource])
|
35
|
+
.returns(T::Array[String])
|
36
|
+
end
|
37
|
+
def build_from_resources(resources)
|
38
|
+
# Disable emitting of events, this causes problems when done in parallel
|
39
|
+
@emit_events = false
|
40
|
+
opts = { in_processes: (Frontman::Iterator.processor_count / 2).ceil }
|
41
|
+
resources = Frontman::Iterator.map(resources, opts) do |resource|
|
42
|
+
build_resource(resource)
|
43
|
+
end
|
44
|
+
|
45
|
+
# After the resources are built, we emit all events synchronously
|
46
|
+
@emit_events = true
|
47
|
+
resources.map do |build_file|
|
48
|
+
fire_build_event(build_file)
|
49
|
+
build_file.path
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
sig do
|
54
|
+
params(assets_to_build: T::Array[String]).returns(T::Array[String])
|
55
|
+
end
|
56
|
+
def build_assets(assets_to_build)
|
57
|
+
# We need to go through ERB files at the end so assets_manifest filled
|
58
|
+
assets_to_build.sort_by! { |f| f.end_with?('.erb') ? 1 : 0 }
|
59
|
+
assets_to_build.map do |path|
|
60
|
+
public_dir = ::File.join(@public_dir, '')
|
61
|
+
if path.end_with?('.erb')
|
62
|
+
build_file = build_from_erb(
|
63
|
+
path,
|
64
|
+
path.sub(public_dir, '').gsub('.erb', '')
|
65
|
+
)
|
66
|
+
else
|
67
|
+
build_file = build_from_asset(
|
68
|
+
path,
|
69
|
+
path.sub(public_dir, '')
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
build_file.path
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
sig do
|
78
|
+
params(path: String, manifest_path: String)
|
79
|
+
.returns(Frontman::Builder::File)
|
80
|
+
end
|
81
|
+
def build_from_asset(path, manifest_path)
|
82
|
+
target_path = create_target_path(manifest_path)
|
83
|
+
|
84
|
+
build_from_content(target_path, ::File.read(path))
|
85
|
+
end
|
86
|
+
|
87
|
+
sig do
|
88
|
+
params(path: String, manifest_path: String)
|
89
|
+
.returns(Frontman::Builder::File)
|
90
|
+
end
|
91
|
+
def build_from_erb(path, manifest_path)
|
92
|
+
build_resource(Resource.from_path(path, manifest_path))
|
93
|
+
end
|
94
|
+
|
95
|
+
sig { returns(T::Array[String]) }
|
96
|
+
def build_redirects
|
97
|
+
Frontman::App.instance.redirects.map do |from, to|
|
98
|
+
build_file = build_redirect("#{from}index.html", to)
|
99
|
+
build_file.path
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { params(files_to_delete: T::Array[String]).void }
|
104
|
+
def delete_files(files_to_delete)
|
105
|
+
files_to_delete.each do |path|
|
106
|
+
delete_from_path(path)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
sig do
|
113
|
+
params(destination_path: String, redirect_to: String)
|
114
|
+
.returns(Frontman::Builder::File)
|
115
|
+
end
|
116
|
+
def build_redirect(destination_path, redirect_to)
|
117
|
+
content = <<~REDIRECT
|
118
|
+
<!DOCTYPE html>
|
119
|
+
<link rel="canonical" href="#{redirect_to}" />
|
120
|
+
<meta http-equiv=refresh content="0; url=#{redirect_to}" />
|
121
|
+
<meta name="robots" content="noindex,follow" />
|
122
|
+
<meta http-equiv="cache-control" content="no-cache" />
|
123
|
+
</html>
|
124
|
+
REDIRECT
|
125
|
+
|
126
|
+
build_from_content(create_target_path(destination_path), content)
|
127
|
+
end
|
128
|
+
|
129
|
+
sig do
|
130
|
+
params(build_file: Frontman::Builder::File)
|
131
|
+
.returns(Frontman::Builder::File)
|
132
|
+
end
|
133
|
+
def fire_build_event(build_file)
|
134
|
+
emit(build_file.status, build_file) if @emit_events
|
135
|
+
build_file
|
136
|
+
end
|
137
|
+
|
138
|
+
sig { params(path: String).returns(Frontman::Builder::File) }
|
139
|
+
def delete_from_path(path)
|
140
|
+
FileUtils.rm_f(path)
|
141
|
+
fire_build_event(File.deleted(path))
|
142
|
+
end
|
143
|
+
|
144
|
+
sig { params(file_path: String, content: String).returns(T::Boolean) }
|
145
|
+
def content_changed?(file_path, content)
|
146
|
+
::File.read(file_path) != content
|
147
|
+
end
|
148
|
+
|
149
|
+
sig { params(file_path: String).returns(T::Boolean) }
|
150
|
+
def exists_in_current_build?(file_path)
|
151
|
+
current_build_files.include?(file_path)
|
152
|
+
end
|
153
|
+
|
154
|
+
sig do
|
155
|
+
params(file_path: String, content: String)
|
156
|
+
.returns(Frontman::Builder::File)
|
157
|
+
end
|
158
|
+
def build_from_content(file_path, content)
|
159
|
+
file_exists = exists_in_current_build?(file_path)
|
160
|
+
|
161
|
+
if file_exists && !content_changed?(file_path, content)
|
162
|
+
return fire_build_event(File.unchanged(file_path))
|
163
|
+
end
|
164
|
+
|
165
|
+
::File.write(file_path, content)
|
166
|
+
|
167
|
+
fire_build_event(
|
168
|
+
file_exists ? File.updated(file_path) : File.created(file_path)
|
169
|
+
)
|
170
|
+
end
|
171
|
+
|
172
|
+
sig { params(destination_path: String).returns(String) }
|
173
|
+
def create_target_path(destination_path)
|
174
|
+
# dir = destination_path.split('/')[0..-2].join('/')
|
175
|
+
dir = ::File.dirname(destination_path)
|
176
|
+
destination = build_directory + dir
|
177
|
+
FileUtils.mkdir_p(destination)
|
178
|
+
|
179
|
+
build_directory + destination_path.sub(%r{^/}, '')
|
180
|
+
end
|
181
|
+
|
182
|
+
sig { params(resource: Frontman::Resource).returns(String) }
|
183
|
+
def create_path_from_resource(resource)
|
184
|
+
create_target_path(resource.destination_path)
|
185
|
+
end
|
186
|
+
|
187
|
+
sig { params(path: String).returns(String) }
|
188
|
+
def digest(path)
|
189
|
+
::Digest::SHA1.file(path).hexdigest[0..7]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|