Skip to content

Commit 6f4feda

Browse files
authored
Support globbing of runtime config target HTML files. (#71)
* Support globbing of runtime config target HTML files. * Upgrade github-actions/setup-pack to v5.11.0
1 parent f41c2bd commit 6f4feda

File tree

10 files changed

+183
-17
lines changed

10 files changed

+183
-17
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ jobs:
5656
- name: Rust Cache
5757
uses: Swatinem/rust-cache@v2.7.3
5858
- name: Install Pack CLI
59-
uses: buildpacks/github-actions/setup-pack@v5.7.4
59+
uses: buildpacks/github-actions/setup-pack@v5.11.0
6060
- name: Run integration tests
6161
run: cargo test --locked -- --ignored --test-threads 16

Cargo.lock

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

buildpacks/static-web-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ regex = "1.12.2"
2222
bullet_stream = "0.10.0"
2323
tracing = "0.1.44"
2424
const_format = "0.2.35"
25+
glob = "0.3.3"
2526

2627
[dev-dependencies]
2728
libcnb-test = "=0.30.2"

buildpacks/static-web-server/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ The files must be located within the [document root](#document-root), `public/`
162162
html_files = ["index.html", "subsection/index.html"]
163163
```
164164

165+
`*` wildcards (globbing) are supported for websites that include many HTML files.
166+
167+
```toml
168+
[com.heroku.static-web-server.runtime_config]
169+
html_files = ["*.html"]
170+
```
171+
172+
Recursive globbing is also supported, for websites that include many HTML files nested within subdirectories.
173+
174+
```toml
175+
[com.heroku.static-web-server.runtime_config]
176+
html_files = ["**/*.html"]
177+
```
178+
179+
If a website contains an extremely large number (thousands) of globbed filenames, it's possible that the runtime configuration process could cause noticeable delays launching the web process.
180+
165181
### Document Root
166182

167183
*Default: `public`*

buildpacks/static-web-server/src/config_web_server.rs

Lines changed: 98 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::heroku_web_server_config::{
44
};
55
use crate::o11y::*;
66
use crate::{StaticWebServerBuildpack, StaticWebServerBuildpackError, BUILD_PLAN_ID};
7+
use glob::glob;
78
use libcnb::additional_buildpack_binary_path;
89
use libcnb::data::layer_name;
910
use libcnb::layer::LayerRef;
@@ -12,7 +13,7 @@ use libcnb::{build::BuildContext, layer::UncachedLayerDefinition};
1213
use libherokubuildpack::log::log_info;
1314
use static_web_server_utils::read_project_config;
1415
use std::fs;
15-
use std::path::PathBuf;
16+
use std::path::{Path, PathBuf};
1617
use std::process::{Command, Stdio};
1718
use toml::Table;
1819

@@ -108,13 +109,12 @@ pub(crate) fn config_web_server(
108109
Scope::Process("web".to_string()),
109110
ModificationBehavior::Override,
110111
"ENV_AS_HTML_DATA_TARGET_FILES",
111-
runtime_config
112-
.html_files
113-
.unwrap_or(vec![doc_index])
114-
.iter()
115-
.map(|doc| format!("{}/{}", doc_root_path.to_string_lossy(), doc))
116-
.collect::<Vec<_>>()
117-
.join(", "),
112+
list_runtime_config_target_files(
113+
&context.app_dir,
114+
&doc_root_path,
115+
&doc_index,
116+
&runtime_config,
117+
),
118118
);
119119
configuration_layer.write_env(configuration_layer_env)?;
120120
} else {
@@ -183,6 +183,44 @@ fn generate_config_with_inheritance(
183183
Ok(heroku_config)
184184
}
185185

186+
fn list_runtime_config_target_files(
187+
real_root_path: &Path,
188+
doc_root_path: &Path,
189+
doc_index: &str,
190+
runtime_config: &RuntimeConfig,
191+
) -> String {
192+
runtime_config
193+
.html_files
194+
.clone()
195+
.unwrap_or(vec![doc_index.to_owned()])
196+
.iter()
197+
.fold(vec![], |mut acc: Vec<String>, doc| {
198+
let doc_path = format!("{}/{}", doc_root_path.to_string_lossy(), doc);
199+
if doc_path.contains('*') {
200+
for glob_matched_path in glob(&format!(
201+
"{}/{}",
202+
real_root_path.to_string_lossy(),
203+
doc_path
204+
))
205+
.expect("glob pattern should match files for runtime config")
206+
.flatten()
207+
{
208+
let relative_path = glob_matched_path
209+
.to_string_lossy()
210+
.into_owned()
211+
.replace::<&String>(&format!("{}/", real_root_path.to_string_lossy()), "");
212+
acc.append(&mut vec![relative_path]);
213+
}
214+
} else {
215+
acc.append(&mut vec![doc_path]);
216+
}
217+
acc
218+
})
219+
.into_iter()
220+
.collect::<Vec<_>>()
221+
.join(", ")
222+
}
223+
186224
#[cfg(test)]
187225
mod tests {
188226
use libcnb::{
@@ -195,11 +233,18 @@ mod tests {
195233
generic::GenericPlatform,
196234
Env, Target,
197235
};
198-
use std::{collections::HashSet, path::PathBuf};
236+
use std::{
237+
collections::HashSet,
238+
path::{Path, PathBuf},
239+
};
199240
use toml::toml;
200241

201242
use crate::{
202-
config_web_server::{generate_build_plan_config, generate_config_with_inheritance},
243+
config_web_server::{
244+
generate_build_plan_config, generate_config_with_inheritance,
245+
list_runtime_config_target_files,
246+
},
247+
heroku_web_server_config::{RuntimeConfig, DEFAULT_DOC_INDEX, DEFAULT_DOC_ROOT},
203248
StaticWebServerBuildpack, BUILD_PLAN_ID,
204249
};
205250

@@ -411,4 +456,47 @@ mod tests {
411456
};
412457
test_context
413458
}
459+
460+
#[test]
461+
fn list_runtime_config_target_files_custom() {
462+
let real_root_path = Path::new("tests/fixtures/runtime_config")
463+
.to_path_buf()
464+
.canonicalize()
465+
.expect("real root path exists in test/fixtures");
466+
let doc_root_path = Path::new(&DEFAULT_DOC_ROOT.to_string()).to_path_buf();
467+
let result = list_runtime_config_target_files(
468+
&real_root_path,
469+
&doc_root_path,
470+
DEFAULT_DOC_INDEX,
471+
&RuntimeConfig {
472+
enabled: Some(true),
473+
html_files: Some(vec![
474+
"index.html".to_string(),
475+
"subsection/index.html".to_string(),
476+
"non-existent.html".to_string(),
477+
"subsection/subsubsection/**/*.html".to_string(),
478+
]),
479+
},
480+
);
481+
assert_eq!("public/index.html, public/subsection/index.html, public/non-existent.html, public/subsection/subsubsection/index.html, public/subsection/subsubsection/second.html, public/subsection/subsubsection/subsubsubsection/index.html", result);
482+
}
483+
484+
#[test]
485+
fn list_runtime_config_target_files_none() {
486+
let real_root_path = Path::new("tests/fixtures/runtime_config")
487+
.to_path_buf()
488+
.canonicalize()
489+
.expect("real root path exists in test/fixtures");
490+
let doc_root_path = Path::new(&DEFAULT_DOC_ROOT.to_string()).to_path_buf();
491+
let result = list_runtime_config_target_files(
492+
&real_root_path,
493+
&doc_root_path,
494+
DEFAULT_DOC_INDEX,
495+
&RuntimeConfig {
496+
enabled: Some(true),
497+
html_files: Some(vec![]),
498+
},
499+
);
500+
assert_eq!("", result);
501+
}
414502
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[com.heroku.static-web-server.runtime_config]
2-
html_files = ["index.html", "subsection/index.html", "non-existent.html"]
2+
html_files = ["index.html", "subsection/index.html", "non-existent.html", "subsection/subsubsection/**/*.html"]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>CNB Static Web Server Runtime Config Test: Subsubsection</title>
8+
</head>
9+
10+
<body>
11+
<h1>Welcome to CNB Static Web Server Runtime Config Test: Subsubsection!</h1>
12+
</body>
13+
14+
</html>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>CNB Static Web Server Runtime Config Test: Subsubsection Second</title>
8+
</head>
9+
10+
<body>
11+
<h1>Welcome to CNB Static Web Server Runtime Config Test: Subsubsection Second!</h1>
12+
</body>
13+
14+
</html>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>CNB Static Web Server Runtime Config Test: Subsubsubsection</title>
8+
</head>
9+
10+
<body>
11+
<h1>Welcome to CNB Static Web Server Runtime Config Test: Subsubsubsection!</h1>
12+
</body>
13+
14+
</html>

buildpacks/static-web-server/tests/integration_test.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,18 @@ fn runtime_configuration_custom() {
303303
log_output.stderr,
304304
"Runtime configuration skipping 'public/non-existent.html'"
305305
);
306+
assert_contains!(
307+
log_output.stderr,
308+
"Runtime configuration written into 'public/subsection/subsubsection/index.html'"
309+
);
310+
assert_contains!(
311+
log_output.stderr,
312+
"Runtime configuration written into 'public/subsection/subsubsection/second.html'"
313+
);
314+
assert_contains!(
315+
log_output.stderr,
316+
"Runtime configuration written into 'public/subsection/subsubsection/subsubsubsection/index.html'"
317+
);
306318
},
307319
);
308320
});

0 commit comments

Comments
 (0)