frontman-ssg 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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