frontman-ssg 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +42 -0
  3. data/.github/CODE_OF_CONDUCT.md +9 -0
  4. data/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  6. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  7. data/.gitignore +5 -0
  8. data/.rubocop.yml +88 -0
  9. data/CHANGELOG.md +11 -0
  10. data/CONTRIBUTING.md +42 -0
  11. data/Gemfile +5 -0
  12. data/LICENSE.md +21 -0
  13. data/Rakefile +94 -0
  14. data/SECURITY.md +6 -0
  15. data/bin/frontman +6 -0
  16. data/frontman-ssg.gemspec +48 -0
  17. data/frontman.svg +2 -0
  18. data/lib/frontman.rb +15 -0
  19. data/lib/frontman/app.rb +175 -0
  20. data/lib/frontman/bootstrapper.rb +70 -0
  21. data/lib/frontman/builder/asset_pipeline.rb +55 -0
  22. data/lib/frontman/builder/builder.rb +193 -0
  23. data/lib/frontman/builder/file.rb +55 -0
  24. data/lib/frontman/builder/mapping.rb +54 -0
  25. data/lib/frontman/builder/statistics_collector.rb +37 -0
  26. data/lib/frontman/cli.rb +6 -0
  27. data/lib/frontman/commands/build.rb +76 -0
  28. data/lib/frontman/commands/init.rb +58 -0
  29. data/lib/frontman/commands/serve.rb +110 -0
  30. data/lib/frontman/concerns/dispatch_events.rb +56 -0
  31. data/lib/frontman/concerns/forward_calls_to_app.rb +28 -0
  32. data/lib/frontman/config.rb +52 -0
  33. data/lib/frontman/context.rb +125 -0
  34. data/lib/frontman/custom_struct.rb +44 -0
  35. data/lib/frontman/data_store.rb +106 -0
  36. data/lib/frontman/data_store_file.rb +60 -0
  37. data/lib/frontman/helpers/app_helper.rb +18 -0
  38. data/lib/frontman/helpers/link_helper.rb +35 -0
  39. data/lib/frontman/helpers/render_helper.rb +76 -0
  40. data/lib/frontman/helpers/url_helper.rb +11 -0
  41. data/lib/frontman/iterator.rb +48 -0
  42. data/lib/frontman/process/chain.rb +43 -0
  43. data/lib/frontman/process/processor.rb +11 -0
  44. data/lib/frontman/renderers/erb_renderer.rb +21 -0
  45. data/lib/frontman/renderers/haml_renderer.rb +22 -0
  46. data/lib/frontman/renderers/markdown_renderer.rb +26 -0
  47. data/lib/frontman/renderers/renderer.rb +26 -0
  48. data/lib/frontman/renderers/renderer_resolver.rb +26 -0
  49. data/lib/frontman/resource.rb +279 -0
  50. data/lib/frontman/sitemap_tree.rb +211 -0
  51. data/lib/frontman/toolbox/timer.rb +49 -0
  52. data/lib/frontman/version.rb +6 -0
  53. data/project-templates/default/.gitignore +2 -0
  54. data/project-templates/default/Gemfile +3 -0
  55. data/project-templates/default/config.rb +17 -0
  56. data/project-templates/default/data/site.yml +4 -0
  57. data/project-templates/default/helpers/site_helper.rb +7 -0
  58. data/project-templates/default/public/code.css +77 -0
  59. data/project-templates/default/public/frontman-logo.svg +2 -0
  60. data/project-templates/default/public/main.css +27 -0
  61. data/project-templates/default/public/main.js +1 -0
  62. data/project-templates/default/source/index.html.md.erb +7 -0
  63. data/project-templates/default/source/sitemap.xml.erb +11 -0
  64. data/project-templates/default/views/layouts/main.erb +19 -0
  65. data/project-templates/default/views/layouts/main.haml +15 -0
  66. data/project-templates/default/views/partials/menu.erb +7 -0
  67. data/project-templates/webpack/.gitignore +4 -0
  68. data/project-templates/webpack/Gemfile +3 -0
  69. data/project-templates/webpack/README.md +54 -0
  70. data/project-templates/webpack/assets/css/code.css +77 -0
  71. data/project-templates/webpack/assets/css/style.css +27 -0
  72. data/project-templates/webpack/assets/images/.gitkeep +0 -0
  73. data/project-templates/webpack/assets/images/frontman_logo.svg +2 -0
  74. data/project-templates/webpack/assets/js/index.js +1 -0
  75. data/project-templates/webpack/config.rb +24 -0
  76. data/project-templates/webpack/data/site.yml +4 -0
  77. data/project-templates/webpack/helpers/assets_helper.rb +24 -0
  78. data/project-templates/webpack/helpers/site_helper.rb +7 -0
  79. data/project-templates/webpack/package-lock.json +7603 -0
  80. data/project-templates/webpack/package.json +34 -0
  81. data/project-templates/webpack/source/index.html.md.erb +7 -0
  82. data/project-templates/webpack/source/sitemap.xml.erb +11 -0
  83. data/project-templates/webpack/views/layouts/main.erb +20 -0
  84. data/project-templates/webpack/views/layouts/main.haml +14 -0
  85. data/project-templates/webpack/views/partials/menu.erb +7 -0
  86. data/project-templates/webpack/views/partials/script_with_vendors.haml +5 -0
  87. data/project-templates/webpack/webpack/base.config.js +51 -0
  88. data/project-templates/webpack/webpack/dev.config.js +6 -0
  89. data/project-templates/webpack/webpack/prod.config.js +30 -0
  90. data/readme.md +80 -0
  91. data/sorbet/config +2 -0
  92. data/sorbet/rbi/hidden-definitions/errors.txt +27259 -0
  93. data/sorbet/rbi/hidden-definitions/hidden.rbi +45122 -0
  94. data/sorbet/rbi/sorbet-typed/lib/rainbow/all/rainbow.rbi +276 -0
  95. data/sorbet/rbi/todo.rbi +6 -0
  96. data/spec/frontman/app_spec.rb +48 -0
  97. data/spec/frontman/bootstrapper_spec.rb +26 -0
  98. data/spec/frontman/builder/builder_spec.rb +79 -0
  99. data/spec/frontman/builder/file_spec.rb +45 -0
  100. data/spec/frontman/builder/mapping_spec.rb +8 -0
  101. data/spec/frontman/concerns/dispatch_events_spec.rb +70 -0
  102. data/spec/frontman/concerns/forward_calls_to_app_spec.rb +21 -0
  103. data/spec/frontman/config_spec.rb +54 -0
  104. data/spec/frontman/context_spec.rb +48 -0
  105. data/spec/frontman/custom_struct_spec.rb +51 -0
  106. data/spec/frontman/data_store_file_spec.rb +9 -0
  107. data/spec/frontman/data_store_spec.rb +36 -0
  108. data/spec/frontman/frontman_ssg_spec.rb +7 -0
  109. data/spec/frontman/helpers/app_helper_spec.rb +24 -0
  110. data/spec/frontman/helpers/link_helper_spec.rb +37 -0
  111. data/spec/frontman/helpers/render_helper_spec.rb +55 -0
  112. data/spec/frontman/helpers/url_helper_spec.rb +21 -0
  113. data/spec/frontman/iterator_spec.rb +47 -0
  114. data/spec/frontman/mocks/asset.css +3 -0
  115. data/spec/frontman/mocks/config.rb +0 -0
  116. data/spec/frontman/mocks/helpers/formatting_helper.rb +5 -0
  117. data/spec/frontman/mocks/helpers/language_helper.rb +5 -0
  118. data/spec/frontman/mocks/helpers/link_helper.rb +5 -0
  119. data/spec/frontman/mocks/helpers/test_command.rb +5 -0
  120. data/spec/frontman/mocks/html_file.html +8 -0
  121. data/spec/frontman/mocks/html_file.html.md.erb +9 -0
  122. data/spec/frontman/mocks/html_file.md.html +8 -0
  123. data/spec/frontman/mocks/info.yml +4 -0
  124. data/spec/frontman/mocks/layouts/raw_without_body.haml +1 -0
  125. data/spec/frontman/mocks/nested/data.yml +4 -0
  126. data/spec/frontman/mocks/nested/more_data.yml +4 -0
  127. data/spec/frontman/mocks/partials/paragraph.haml +2 -0
  128. data/spec/frontman/mocks/snippet/html_file.html +8 -0
  129. data/spec/frontman/mocks/snippet/html_file.yml +670 -0
  130. data/spec/frontman/mocks/test.html +8 -0
  131. data/spec/frontman/mocks/wrap.haml +3 -0
  132. data/spec/frontman/process/chain_spec.rb +56 -0
  133. data/spec/frontman/renderers/erb_renderer_spec.rb +22 -0
  134. data/spec/frontman/renderers/haml_renderer_spec.rb +12 -0
  135. data/spec/frontman/renderers/markdown_renderer_spec.rb +12 -0
  136. data/spec/frontman/renderers/renderer_spec.rb +16 -0
  137. data/spec/frontman/resource_spec.rb +151 -0
  138. data/spec/frontman/sitemap_tree_spec.rb +128 -0
  139. data/spec/frontman/toolbox/timer_spec.rb +34 -0
  140. data/spec/spec_setup.rb +19 -0
  141. metadata +507 -0
@@ -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>
@@ -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'
@@ -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