1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//! Alchemy infrastructure-as-code plugin.
//!
//! Alchemy reads a Stack from `alchemy.run.ts` in the working directory, or
//! from the file that a script passes to the CLI (`alchemy deploy
//! ./stacks/app.ts`). Nothing in the project imports a stack file, and a root
//! stack imports the Worker modules that it deploys. Thus each
//! `alchemy.run.*` file is an entry point. A stack runs at deploy time, not in
//! production, so the role is support. A script file argument is an entry
//! point through the shared script reader.
//!
//! The bundle entry that a resource names in its `main` property is read
//! during extraction (`fallow_extract::alchemy`), because a Worker module
//! declares it as often as a stack file does.
//!
//! The Alchemy CLI loads `@effect/platform-node` or `@effect/platform-bun`
//! through a dynamic `import()` from its own code, so a project that uses
//! Alchemy declares one of them without an import of its own.
use super::{Plugin, PluginResult};
const ENABLERS: &[&str] = &["alchemy"];
const STACK_PATTERNS: &[&str] = &["**/alchemy.run.{ts,mts,cts,js,mjs,cjs}"];
/// `alchemy` is the CLI. The two Effect platform packages are optional peers
/// that the CLI loads at runtime for the JavaScript runtime it runs on.
const TOOLING_DEPENDENCIES: &[&str] = &["alchemy", "@effect/platform-node", "@effect/platform-bun"];
define_plugin! {
struct AlchemyPlugin => "alchemy",
enablers: ENABLERS,
entry_patterns: STACK_PATTERNS,
config_patterns: STACK_PATTERNS,
always_used: STACK_PATTERNS,
tooling_dependencies: TOOLING_DEPENDENCIES,
resolve_config: imports_only,
}
#[cfg(test)]
mod tests {
use fallow_config::EntryPointRole;
use super::*;
#[test]
fn stack_files_are_support_entry_points_at_any_depth() {
let plugin = AlchemyPlugin;
assert_eq!(plugin.entry_point_role(), EntryPointRole::Support);
let matcher = globset::Glob::new(STACK_PATTERNS[0])
.expect("valid stack pattern")
.compile_matcher();
for path in [
"alchemy.run.ts",
"apps/api/alchemy.run.mts",
"infra/alchemy.run.js",
] {
assert!(matcher.is_match(path), "{path} should match");
}
assert!(!matcher.is_match("src/alchemy.ts"));
}
#[test]
fn cli_and_platform_peers_are_tooling_dependencies() {
let plugin = AlchemyPlugin;
for dependency in ["alchemy", "@effect/platform-node", "@effect/platform-bun"] {
assert!(plugin.tooling_dependencies().contains(&dependency));
}
}
}