feat(wfe-yaml): add HTTP ops, module loader, and npm support via esm.sh
Phase 4 — Permission-gated HTTP fetch op: - op_fetch with net permission check on every request - globalThis.fetch() wrapper with .json()/.text() methods - Supports GET/POST/PUT/DELETE with headers and body Phase 5 — Module loader: - WfeModuleLoader resolving npm: → esm.sh, https://, file://, relative paths - All resolution paths permission-checked - Bare path resolution (/) for esm.sh sub-module redirects - Dynamic import rejection unless permissions.dynamic_import: true - esm.sh auto-added to net allowlist when modules declared Mandatory npm integration test (is-number via esm.sh). 25 new tests. 133 total deno tests, 326 total workspace tests.
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::RuntimeOptions;
|
||||
use wfe_core::WfeError;
|
||||
|
||||
use super::config::DenoConfig;
|
||||
use super::module_loader::WfeModuleLoader;
|
||||
use super::ops::workflow::{wfe_ops, StepMeta, StepOutputs, WorkflowInputs};
|
||||
use super::permissions::PermissionChecker;
|
||||
|
||||
@@ -16,8 +19,18 @@ pub fn create_runtime(
|
||||
) -> Result<JsRuntime, WfeError> {
|
||||
let ext = wfe_ops::init();
|
||||
|
||||
// Build permissions, auto-adding esm.sh if modules are declared.
|
||||
let mut permissions = config.permissions.clone();
|
||||
if !config.modules.is_empty() && !permissions.net.iter().any(|h| h == "esm.sh") {
|
||||
permissions.net.push("esm.sh".to_string());
|
||||
}
|
||||
|
||||
let checker = Rc::new(RefCell::new(PermissionChecker::from_config(&permissions)));
|
||||
let module_loader = WfeModuleLoader::new(checker.clone());
|
||||
|
||||
let runtime = JsRuntime::new(RuntimeOptions {
|
||||
extensions: vec![ext],
|
||||
module_loader: Some(Rc::new(module_loader)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -34,12 +47,18 @@ pub fn create_runtime(
|
||||
state.put(StepMeta {
|
||||
name: step_name.to_string(),
|
||||
});
|
||||
state.put(PermissionChecker::from_config(&config.permissions));
|
||||
state.put(PermissionChecker::from_config(&permissions));
|
||||
}
|
||||
|
||||
Ok(runtime)
|
||||
}
|
||||
|
||||
/// Returns whether esm.sh would be auto-added for the given config.
|
||||
/// Exposed for testing.
|
||||
pub fn would_auto_add_esm_sh(config: &DenoConfig) -> bool {
|
||||
!config.modules.is_empty() && !config.permissions.net.iter().any(|h| h == "esm.sh")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -78,4 +97,46 @@ mod tests {
|
||||
let meta = state.borrow::<StepMeta>();
|
||||
assert_eq!(meta.name, "my-step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_add_esm_sh_when_modules_declared() {
|
||||
let config = DenoConfig {
|
||||
script: Some("1".to_string()),
|
||||
file: None,
|
||||
permissions: DenoPermissions::default(),
|
||||
modules: vec!["npm:lodash@4".to_string()],
|
||||
env: HashMap::new(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
assert!(would_auto_add_esm_sh(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_auto_add_esm_sh_when_no_modules() {
|
||||
let config = DenoConfig {
|
||||
script: Some("1".to_string()),
|
||||
file: None,
|
||||
permissions: DenoPermissions::default(),
|
||||
modules: vec![],
|
||||
env: HashMap::new(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
assert!(!would_auto_add_esm_sh(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_auto_add_esm_sh_when_already_present() {
|
||||
let config = DenoConfig {
|
||||
script: Some("1".to_string()),
|
||||
file: None,
|
||||
permissions: DenoPermissions {
|
||||
net: vec!["esm.sh".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
modules: vec!["npm:lodash@4".to_string()],
|
||||
env: HashMap::new(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
assert!(!would_auto_add_esm_sh(&config));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user