mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
refactor(dynamic): centralize runtime dependency handling across frameworks, enhance manifest generation for Rust, Java, Python, Go, and PHP, and improve framework adapter integration
This commit is contained in:
parent
ed398e2834
commit
ed96f94bb5
12 changed files with 1202 additions and 50 deletions
|
|
@ -317,6 +317,14 @@ pub fn materialize_go(env: &Environment) -> RuntimeArtifacts {
|
|||
};
|
||||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
let mut versioned: Vec<crate::dynamic::framework::runtime_deps::VersionedPackage> = Vec::new();
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for dep in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).go_modules {
|
||||
if seen.insert(dep.name.to_owned()) {
|
||||
versioned.push(*dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_go_stdlib(d) {
|
||||
continue;
|
||||
|
|
@ -330,8 +338,11 @@ pub fn materialize_go(env: &Environment) -> RuntimeArtifacts {
|
|||
let mut body = String::with_capacity(128);
|
||||
body.push_str("module nyx_harness\n\n");
|
||||
body.push_str(&format!("go {go_version}\n"));
|
||||
if !deps.is_empty() {
|
||||
if !deps.is_empty() || !versioned.is_empty() {
|
||||
body.push_str("\nrequire (\n");
|
||||
for dep in &versioned {
|
||||
body.push_str(&format!("\t{} {}\n", dep.name, dep.version));
|
||||
}
|
||||
for d in &deps {
|
||||
body.push_str(&format!("\t{d} latest\n"));
|
||||
}
|
||||
|
|
@ -651,6 +662,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
// GraphQLResolver short-circuit (gqlgen).
|
||||
if let crate::evidence::EntryKind::GraphQLResolver { type_name, field } = &spec.entry_kind {
|
||||
return Ok(emit_graphql_resolver_harness(
|
||||
spec,
|
||||
&spec.entry_name,
|
||||
type_name,
|
||||
field,
|
||||
|
|
@ -660,7 +672,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
let entry_source = read_entry_source(&spec.entry_file);
|
||||
let shape = GoShape::detect(spec, &entry_source);
|
||||
let main_go = generate_main_go(spec, shape);
|
||||
let go_mod = generate_go_mod(shape);
|
||||
let go_mod = generate_go_mod_for_spec(shape, spec);
|
||||
|
||||
let mut extra_files = vec![("go.mod".to_owned(), go_mod)];
|
||||
// Phase 15: GinHandler shape stages a minimal gin stub package so
|
||||
|
|
@ -1356,23 +1368,56 @@ fn framework_route_invocation(
|
|||
}
|
||||
|
||||
fn generate_go_mod(shape: GoShape) -> String {
|
||||
let deps: &[(&str, &str)] = match shape {
|
||||
render_go_mod(shape_go_deps(shape), &[])
|
||||
}
|
||||
|
||||
fn generate_go_mod_for_spec(shape: GoShape, spec: &HarnessSpec) -> String {
|
||||
let adapter_deps = spec
|
||||
.framework
|
||||
.as_ref()
|
||||
.map(|binding| {
|
||||
crate::dynamic::framework::runtime_deps::deps_for_adapter(&binding.adapter).go_modules
|
||||
})
|
||||
.unwrap_or(&[]);
|
||||
render_go_mod(shape_go_deps(shape), adapter_deps)
|
||||
}
|
||||
|
||||
fn shape_go_deps(shape: GoShape) -> &'static [(&'static str, &'static str)] {
|
||||
match shape {
|
||||
GoShape::GinRoute => &[("github.com/gin-gonic/gin", "v1.10.0")],
|
||||
GoShape::EchoRoute => &[("github.com/labstack/echo/v4", "v4.12.0")],
|
||||
GoShape::FiberRoute => &[("github.com/gofiber/fiber/v2", "v2.52.5")],
|
||||
GoShape::ChiRoute => &[("github.com/go-chi/chi/v5", "v5.0.12")],
|
||||
_ => &[],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn render_go_mod(
|
||||
shape_deps: &[(&str, &str)],
|
||||
adapter_deps: &[crate::dynamic::framework::runtime_deps::VersionedPackage],
|
||||
) -> String {
|
||||
let mut out = "module nyx-harness\n\ngo 1.21\n".to_owned();
|
||||
if !deps.is_empty() {
|
||||
if !shape_deps.is_empty() || !adapter_deps.is_empty() {
|
||||
out.push_str("\nrequire (\n");
|
||||
for (module, version) in deps {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for (module, version) in shape_deps {
|
||||
seen.insert(*module);
|
||||
out.push('\t');
|
||||
out.push_str(module);
|
||||
out.push(' ');
|
||||
out.push_str(version);
|
||||
out.push('\n');
|
||||
}
|
||||
for dep in adapter_deps {
|
||||
if !seen.insert(dep.name) {
|
||||
continue;
|
||||
}
|
||||
out.push('\t');
|
||||
out.push_str(dep.name);
|
||||
out.push(' ');
|
||||
out.push_str(dep.version);
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(")\n");
|
||||
}
|
||||
out
|
||||
|
|
@ -2106,7 +2151,7 @@ var NyxAutoReceivers = map[string]interface{{}}{{
|
|||
/// default → Pub/Sub.
|
||||
fn emit_message_handler_harness(spec: &HarnessSpec, queue: &str) -> HarnessSource {
|
||||
let shim = probe_shim();
|
||||
let go_mod = generate_go_mod(GoShape::Generic);
|
||||
let go_mod = generate_go_mod_for_spec(GoShape::Generic, spec);
|
||||
let handler = &spec.entry_name;
|
||||
let broker = go_broker_for_adapter(spec);
|
||||
|
||||
|
|
@ -2259,9 +2304,14 @@ func main() {{
|
|||
/// map (mirrors the `NyxReceivers` / `NyxHandlers` contracts from
|
||||
/// Phase 19 / 20), constructs a synthetic `context.Background()`, and
|
||||
/// invokes the resolver with the payload positionally.
|
||||
fn emit_graphql_resolver_harness(handler: &str, type_name: &str, field: &str) -> HarnessSource {
|
||||
fn emit_graphql_resolver_harness(
|
||||
spec: &HarnessSpec,
|
||||
handler: &str,
|
||||
type_name: &str,
|
||||
field: &str,
|
||||
) -> HarnessSource {
|
||||
let shim = probe_shim();
|
||||
let go_mod = generate_go_mod(GoShape::Generic);
|
||||
let go_mod = generate_go_mod_for_spec(GoShape::Generic, spec);
|
||||
let source = format!(
|
||||
r##"// Nyx dynamic harness — GraphQL resolver (Phase 21 / Track M.3).
|
||||
package main
|
||||
|
|
|
|||
|
|
@ -498,6 +498,17 @@ pub fn materialize_java(env: &Environment) -> RuntimeArtifacts {
|
|||
.to_owned();
|
||||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
let mut maven_deps: Vec<crate::dynamic::framework::runtime_deps::MavenPackage> = Vec::new();
|
||||
let mut seen_maven: std::collections::HashSet<(&'static str, &'static str)> =
|
||||
std::collections::HashSet::new();
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for dep in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).maven_packages
|
||||
{
|
||||
if seen_maven.insert((dep.group_id, dep.artifact_id)) {
|
||||
maven_deps.push(*dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_java_stdlib(d) {
|
||||
continue;
|
||||
|
|
@ -523,8 +534,18 @@ pub fn materialize_java(env: &Environment) -> RuntimeArtifacts {
|
|||
" <maven.compiler.target>{java_version}</maven.compiler.target>\n"
|
||||
));
|
||||
body.push_str(" </properties>\n");
|
||||
if !deps.is_empty() {
|
||||
if !deps.is_empty() || !maven_deps.is_empty() {
|
||||
body.push_str(" <dependencies>\n");
|
||||
for dep in &maven_deps {
|
||||
body.push_str(" <dependency>\n");
|
||||
body.push_str(&format!(" <groupId>{}</groupId>\n", dep.group_id));
|
||||
body.push_str(&format!(
|
||||
" <artifactId>{}</artifactId>\n",
|
||||
dep.artifact_id
|
||||
));
|
||||
body.push_str(&format!(" <version>{}</version>\n", dep.version));
|
||||
body.push_str(" </dependency>\n");
|
||||
}
|
||||
for d in &deps {
|
||||
body.push_str(" <dependency>\n");
|
||||
body.push_str(&format!(" <groupId>{d}</groupId>\n"));
|
||||
|
|
@ -3924,7 +3945,11 @@ public class NyxHarness {{
|
|||
".".to_owned(),
|
||||
"NyxHarness".to_owned(),
|
||||
],
|
||||
extra_files: message_handler_annotation_stubs(),
|
||||
extra_files: {
|
||||
let mut files = message_handler_annotation_stubs();
|
||||
files.extend(framework_dependency_files(spec));
|
||||
files
|
||||
},
|
||||
entry_subpath: Some(format!("{entry_class}.java")),
|
||||
}
|
||||
}
|
||||
|
|
@ -3969,6 +3994,52 @@ public @interface RabbitListener {
|
|||
]
|
||||
}
|
||||
|
||||
fn framework_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)> {
|
||||
if spec.expected_cap != crate::labels::Cap::CODE_EXEC {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let deps = crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter);
|
||||
if deps.maven_packages.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let java_version = spec
|
||||
.toolchain_id
|
||||
.strip_prefix("java-")
|
||||
.and_then(|v| v.parse::<u32>().ok())
|
||||
.unwrap_or(21);
|
||||
let mut body = String::from("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
body.push_str("<project xmlns=\"http://maven.apache.org/POM/4.0.0\">\n");
|
||||
body.push_str(" <modelVersion>4.0.0</modelVersion>\n");
|
||||
body.push_str(" <groupId>nyx</groupId>\n");
|
||||
body.push_str(" <artifactId>harness-framework</artifactId>\n");
|
||||
body.push_str(" <version>0.0.1</version>\n");
|
||||
body.push_str(" <properties>\n");
|
||||
body.push_str(&format!(
|
||||
" <maven.compiler.source>{java_version}</maven.compiler.source>\n"
|
||||
));
|
||||
body.push_str(&format!(
|
||||
" <maven.compiler.target>{java_version}</maven.compiler.target>\n"
|
||||
));
|
||||
body.push_str(" </properties>\n");
|
||||
body.push_str(" <dependencies>\n");
|
||||
for dep in deps.maven_packages {
|
||||
body.push_str(" <dependency>\n");
|
||||
body.push_str(&format!(" <groupId>{}</groupId>\n", dep.group_id));
|
||||
body.push_str(&format!(
|
||||
" <artifactId>{}</artifactId>\n",
|
||||
dep.artifact_id
|
||||
));
|
||||
body.push_str(&format!(" <version>{}</version>\n", dep.version));
|
||||
body.push_str(" </dependency>\n");
|
||||
}
|
||||
body.push_str(" </dependencies>\n");
|
||||
body.push_str("</project>\n");
|
||||
vec![("pom.xml".to_owned(), body)]
|
||||
}
|
||||
|
||||
// ── Phase 21 (Track M.3) — synthetic entry-kind harnesses ─────────────────────
|
||||
|
||||
fn emit_scheduled_job_harness(
|
||||
|
|
@ -4047,7 +4118,7 @@ public class NyxHarness {{
|
|||
".".to_owned(),
|
||||
"NyxHarness".to_owned(),
|
||||
],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(format!("{entry_class}.java")),
|
||||
}
|
||||
}
|
||||
|
|
@ -4123,7 +4194,7 @@ public class NyxHarness {{
|
|||
".".to_owned(),
|
||||
"NyxHarness".to_owned(),
|
||||
],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(format!("{entry_class}.java")),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -380,6 +380,14 @@ pub fn materialize_node(env: &Environment) -> RuntimeArtifacts {
|
|||
let mut deps: Vec<(String, &'static str)> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for dep in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).node_packages
|
||||
{
|
||||
if seen.insert(dep.name.to_owned()) {
|
||||
deps.push((dep.name.to_owned(), dep.version));
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_node_builtin(d) {
|
||||
continue;
|
||||
|
|
@ -1039,7 +1047,7 @@ if (_h == null) {{
|
|||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
command: vec!["node".to_owned(), "harness.js".to_owned()],
|
||||
extra_files: Vec::new(),
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(entry_subpath),
|
||||
}
|
||||
}
|
||||
|
|
@ -1081,7 +1089,7 @@ if (_h == null) {{
|
|||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
command: vec!["node".to_owned(), "harness.js".to_owned()],
|
||||
extra_files: Vec::new(),
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(entry_subpath),
|
||||
}
|
||||
}
|
||||
|
|
@ -1125,7 +1133,7 @@ if (_h == null) {{
|
|||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
command: vec!["node".to_owned(), "harness.js".to_owned()],
|
||||
extra_files: Vec::new(),
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(entry_subpath),
|
||||
}
|
||||
}
|
||||
|
|
@ -1161,7 +1169,7 @@ const _res = {{ statusCode: 200, headers: {{}}, end: function(d){{ if (d != null
|
|||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
command: vec!["node".to_owned(), "harness.js".to_owned()],
|
||||
extra_files: Vec::new(),
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(entry_subpath),
|
||||
}
|
||||
}
|
||||
|
|
@ -1222,7 +1230,7 @@ const _prisma = {{
|
|||
source: body,
|
||||
filename: "harness.js".to_owned(),
|
||||
command: vec!["node".to_owned(), "harness.js".to_owned()],
|
||||
extra_files: Vec::new(),
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some(entry_subpath),
|
||||
}
|
||||
}
|
||||
|
|
@ -2527,10 +2535,19 @@ fn message_handler_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)>
|
|||
return Vec::new();
|
||||
}
|
||||
let source = read_entry_source(&spec.entry_file);
|
||||
let deps = js_message_handler_deps(&source);
|
||||
let mut deps = js_message_handler_deps(&source);
|
||||
if let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) {
|
||||
for dep in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).node_packages
|
||||
{
|
||||
if !deps.iter().any(|(name, _)| *name == dep.name) {
|
||||
deps.push((dep.name, dep.version));
|
||||
}
|
||||
}
|
||||
}
|
||||
if deps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
deps.sort_by(|a, b| a.0.cmp(b.0));
|
||||
vec![
|
||||
(
|
||||
"package.json".to_owned(),
|
||||
|
|
@ -2543,6 +2560,36 @@ fn message_handler_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)>
|
|||
]
|
||||
}
|
||||
|
||||
fn framework_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)> {
|
||||
if spec.expected_cap != crate::labels::Cap::CODE_EXEC {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut deps: Vec<(&'static str, &'static str)> =
|
||||
crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter)
|
||||
.node_packages
|
||||
.iter()
|
||||
.map(|dep| (dep.name, dep.version))
|
||||
.collect();
|
||||
if deps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
deps.sort_by(|a, b| a.0.cmp(b.0));
|
||||
deps.dedup_by(|a, b| a.0 == b.0);
|
||||
vec![
|
||||
(
|
||||
"package.json".to_owned(),
|
||||
package_json_multi("nyx-harness-framework", &deps),
|
||||
),
|
||||
(
|
||||
"package-lock.json".to_owned(),
|
||||
package_lock_skeleton("nyx-harness-framework"),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn js_message_handler_deps(source: &str) -> Vec<(&'static str, &'static str)> {
|
||||
let mut deps = Vec::new();
|
||||
for raw_line in source.lines() {
|
||||
|
|
|
|||
|
|
@ -301,11 +301,31 @@ pub fn materialize_php(env: &Environment) -> RuntimeArtifacts {
|
|||
} else {
|
||||
php_ver
|
||||
};
|
||||
let adapter_deps = env
|
||||
.framework_adapter
|
||||
.as_deref()
|
||||
.map(crate::dynamic::framework::runtime_deps::deps_for_adapter);
|
||||
let composer_deps = adapter_deps
|
||||
.as_ref()
|
||||
.map(|deps| deps.composer_packages)
|
||||
.unwrap_or(&[]);
|
||||
let mut body = String::with_capacity(128);
|
||||
body.push_str("{\n");
|
||||
body.push_str(" \"name\": \"nyx/harness\",\n");
|
||||
body.push_str(" \"require\": {\n");
|
||||
body.push_str(&format!(" \"php\": \">={php_ver}\"\n"));
|
||||
body.push_str(&format!(" \"php\": \">={php_ver}\""));
|
||||
if !composer_deps.is_empty() {
|
||||
body.push_str(",\n");
|
||||
for (i, dep) in composer_deps.iter().enumerate() {
|
||||
body.push_str(&format!(" \"{}\": \"{}\"", dep.name, dep.version));
|
||||
if i + 1 != composer_deps.len() {
|
||||
body.push(',');
|
||||
}
|
||||
body.push('\n');
|
||||
}
|
||||
} else {
|
||||
body.push('\n');
|
||||
}
|
||||
body.push_str(" }\n");
|
||||
body.push_str("}\n");
|
||||
artifacts.push("composer.json", body);
|
||||
|
|
@ -567,12 +587,12 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
|
||||
// Phase 21 (Track M.3): Middleware short-circuit (Laravel handle()).
|
||||
if let crate::evidence::EntryKind::Middleware { name } = &spec.entry_kind {
|
||||
return Ok(emit_middleware_harness(&spec.entry_name, name));
|
||||
return Ok(emit_middleware_harness(spec, name));
|
||||
}
|
||||
|
||||
// Phase 21 (Track M.3): Migration short-circuit (Laravel up()).
|
||||
if let crate::evidence::EntryKind::Migration { version } = &spec.entry_kind {
|
||||
return Ok(emit_migration_harness(&spec.entry_name, version.as_deref()));
|
||||
return Ok(emit_migration_harness(spec, version.as_deref()));
|
||||
}
|
||||
|
||||
let entry_source = read_entry_source(&spec.entry_file);
|
||||
|
|
@ -3084,8 +3104,9 @@ echo "__NYX_SINK_HIT__\n";
|
|||
)
|
||||
}
|
||||
|
||||
fn emit_middleware_harness(handler: &str, name: &str) -> HarnessSource {
|
||||
fn emit_middleware_harness(spec: &HarnessSpec, name: &str) -> HarnessSource {
|
||||
let preamble = nyx_php_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
echo "__NYX_MIDDLEWARE__: " . {name:?} . "\n";
|
||||
|
|
@ -3130,13 +3151,14 @@ if (class_exists({handler:?})) {{
|
|||
source: body,
|
||||
filename: "harness.php".to_owned(),
|
||||
command: vec!["php".to_owned(), "harness.php".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.php".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_migration_harness(handler: &str, version: Option<&str>) -> HarnessSource {
|
||||
fn emit_migration_harness(spec: &HarnessSpec, version: Option<&str>) -> HarnessSource {
|
||||
let preamble = nyx_php_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let version_repr = version.unwrap_or("<no-version>");
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
|
|
@ -3175,11 +3197,35 @@ if (class_exists({handler:?})) {{
|
|||
source: body,
|
||||
filename: "harness.php".to_owned(),
|
||||
command: vec!["php".to_owned(), "harness.php".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.php".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn framework_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)> {
|
||||
if spec.expected_cap != crate::labels::Cap::CODE_EXEC {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let deps = crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter);
|
||||
if deps.composer_packages.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut body = String::from("{\n \"name\": \"nyx/harness-framework\",\n \"require\": {\n");
|
||||
body.push_str(" \"php\": \">=8.1\",\n");
|
||||
for (i, dep) in deps.composer_packages.iter().enumerate() {
|
||||
body.push_str(&format!(" \"{}\": \"{}\"", dep.name, dep.version));
|
||||
if i + 1 != deps.composer_packages.len() {
|
||||
body.push(',');
|
||||
}
|
||||
body.push('\n');
|
||||
}
|
||||
body.push_str(" }\n}\n");
|
||||
vec![("composer.json".to_owned(), body)]
|
||||
}
|
||||
|
||||
fn build_call_expr(spec: &HarnessSpec, shape: PhpShape, func: &str) -> String {
|
||||
match shape {
|
||||
PhpShape::TopLevelScript => "null".to_owned(),
|
||||
|
|
|
|||
|
|
@ -468,6 +468,15 @@ pub fn materialize_python(env: &Environment) -> RuntimeArtifacts {
|
|||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for d in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).python_packages
|
||||
{
|
||||
let canonical = canonical_python_pkg_name(d);
|
||||
if seen.insert(canonical.clone()) {
|
||||
deps.push(canonical);
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_python_stdlib(d) {
|
||||
continue;
|
||||
|
|
@ -918,7 +927,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1089,7 +1098,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1147,7 +1156,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1194,7 +1203,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1250,7 +1259,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -1299,7 +1308,7 @@ except Exception as _e:
|
|||
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||
filename: "harness.py".to_owned(),
|
||||
command: vec!["python3".to_owned(), "harness.py".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: None,
|
||||
}
|
||||
}
|
||||
|
|
@ -3129,10 +3138,44 @@ fn message_handler_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)>
|
|||
return Vec::new();
|
||||
}
|
||||
let source = read_entry_source(&spec.entry_file);
|
||||
let deps = python_message_handler_deps(&source);
|
||||
let mut deps = python_message_handler_deps(&source);
|
||||
if let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) {
|
||||
for &dep in
|
||||
crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).python_packages
|
||||
{
|
||||
if !deps.contains(&dep) {
|
||||
deps.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
if deps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
deps.sort_unstable();
|
||||
let mut body = String::new();
|
||||
for dep in deps {
|
||||
body.push_str(dep);
|
||||
body.push('\n');
|
||||
}
|
||||
vec![("requirements.txt".to_owned(), body)]
|
||||
}
|
||||
|
||||
fn framework_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)> {
|
||||
if spec.expected_cap != crate::labels::Cap::CODE_EXEC {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut deps: Vec<&'static str> =
|
||||
crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter)
|
||||
.python_packages
|
||||
.to_vec();
|
||||
if deps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
deps.sort_unstable();
|
||||
deps.dedup();
|
||||
let mut body = String::new();
|
||||
for dep in deps {
|
||||
body.push_str(dep);
|
||||
|
|
|
|||
|
|
@ -385,6 +385,13 @@ pub fn materialize_ruby(env: &Environment) -> RuntimeArtifacts {
|
|||
let mut artifacts = RuntimeArtifacts::new();
|
||||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for d in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).ruby_gems {
|
||||
if seen.insert((*d).to_owned()) {
|
||||
deps.push((*d).to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_ruby_stdlib(d) {
|
||||
continue;
|
||||
|
|
@ -495,25 +502,22 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
|
||||
// Phase 21 (Track M.3): ScheduledJob short-circuit (Sidekiq workers).
|
||||
if let crate::evidence::EntryKind::ScheduledJob { schedule } = &spec.entry_kind {
|
||||
return Ok(emit_scheduled_job_harness(
|
||||
&spec.entry_name,
|
||||
schedule.as_deref(),
|
||||
));
|
||||
return Ok(emit_scheduled_job_harness(spec, schedule.as_deref()));
|
||||
}
|
||||
|
||||
// Phase 21 (Track M.3): WebSocket short-circuit (ActionCable channels).
|
||||
if let crate::evidence::EntryKind::WebSocket { path } = &spec.entry_kind {
|
||||
return Ok(emit_websocket_handler_harness(&spec.entry_name, path));
|
||||
return Ok(emit_websocket_handler_harness(spec, path));
|
||||
}
|
||||
|
||||
// Phase 21 (Track M.3): Middleware short-circuit (Rack-shape).
|
||||
if let crate::evidence::EntryKind::Middleware { name } = &spec.entry_kind {
|
||||
return Ok(emit_middleware_harness(&spec.entry_name, name));
|
||||
return Ok(emit_middleware_harness(spec, name));
|
||||
}
|
||||
|
||||
// Phase 21 (Track M.3): Migration short-circuit (ActiveRecord up/down).
|
||||
if let crate::evidence::EntryKind::Migration { version } = &spec.entry_kind {
|
||||
return Ok(emit_migration_harness(&spec.entry_name, version.as_deref()));
|
||||
return Ok(emit_migration_harness(spec, version.as_deref()));
|
||||
}
|
||||
|
||||
let entry_source = read_entry_source(&spec.entry_file);
|
||||
|
|
@ -727,8 +731,9 @@ puts "__NYX_SINK_HIT__"
|
|||
)
|
||||
}
|
||||
|
||||
fn emit_scheduled_job_harness(handler: &str, schedule: Option<&str>) -> HarnessSource {
|
||||
fn emit_scheduled_job_harness(spec: &HarnessSpec, schedule: Option<&str>) -> HarnessSource {
|
||||
let preamble = nyx_ruby_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let sched = schedule.unwrap_or("<unscheduled>");
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
|
|
@ -773,13 +778,14 @@ end
|
|||
source: body,
|
||||
filename: "harness.rb".to_owned(),
|
||||
command: vec!["ruby".to_owned(), "harness.rb".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.rb".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_websocket_handler_harness(handler: &str, path: &str) -> HarnessSource {
|
||||
fn emit_websocket_handler_harness(spec: &HarnessSpec, path: &str) -> HarnessSource {
|
||||
let preamble = nyx_ruby_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
puts "__NYX_WEBSOCKET__: " + {path:?}
|
||||
|
|
@ -823,13 +829,14 @@ end
|
|||
source: body,
|
||||
filename: "harness.rb".to_owned(),
|
||||
command: vec!["ruby".to_owned(), "harness.rb".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.rb".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_middleware_harness(handler: &str, name: &str) -> HarnessSource {
|
||||
fn emit_middleware_harness(spec: &HarnessSpec, name: &str) -> HarnessSource {
|
||||
let preamble = nyx_ruby_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
puts "__NYX_MIDDLEWARE__: " + {name:?}
|
||||
|
|
@ -879,13 +886,14 @@ end
|
|||
source: body,
|
||||
filename: "harness.rb".to_owned(),
|
||||
command: vec!["ruby".to_owned(), "harness.rb".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.rb".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_migration_harness(handler: &str, version: Option<&str>) -> HarnessSource {
|
||||
fn emit_migration_harness(spec: &HarnessSpec, version: Option<&str>) -> HarnessSource {
|
||||
let preamble = nyx_ruby_preamble();
|
||||
let handler = &spec.entry_name;
|
||||
let ver = version.unwrap_or("<no-version>");
|
||||
let body = format!(
|
||||
r#"{preamble}
|
||||
|
|
@ -932,11 +940,34 @@ end
|
|||
source: body,
|
||||
filename: "harness.rb".to_owned(),
|
||||
command: vec!["ruby".to_owned(), "harness.rb".to_owned()],
|
||||
extra_files: vec![],
|
||||
extra_files: framework_dependency_files(spec),
|
||||
entry_subpath: Some("entry.rb".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
fn framework_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)> {
|
||||
if spec.expected_cap != crate::labels::Cap::CODE_EXEC {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(adapter) = spec.framework.as_ref().map(|b| b.adapter.as_str()) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut deps: Vec<&'static str> =
|
||||
crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter)
|
||||
.ruby_gems
|
||||
.to_vec();
|
||||
if deps.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
deps.sort_unstable();
|
||||
deps.dedup();
|
||||
let mut body = String::from("source 'https://rubygems.org'\n");
|
||||
for dep in deps {
|
||||
body.push_str(&format!("gem '{dep}'\n"));
|
||||
}
|
||||
vec![("Gemfile".to_owned(), body)]
|
||||
}
|
||||
|
||||
/// Phase 03 — Track J.1 deserialize harness for Ruby.
|
||||
///
|
||||
/// Wraps a call to `Marshal.load(input)` with a const-lookup
|
||||
|
|
|
|||
|
|
@ -147,6 +147,14 @@ pub fn materialize_rust(env: &Environment) -> RuntimeArtifacts {
|
|||
let mut artifacts = RuntimeArtifacts::new();
|
||||
let mut deps: Vec<String> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
let mut versioned: Vec<crate::dynamic::framework::runtime_deps::VersionedPackage> = Vec::new();
|
||||
if let Some(adapter) = env.framework_adapter.as_deref() {
|
||||
for dep in crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter).rust_crates {
|
||||
if seen.insert(dep.name.to_owned()) {
|
||||
versioned.push(*dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
for d in &env.direct_deps {
|
||||
if is_rust_stdlib(d) {
|
||||
continue;
|
||||
|
|
@ -166,6 +174,12 @@ pub fn materialize_rust(env: &Environment) -> RuntimeArtifacts {
|
|||
body.push_str("name = \"nyx_harness\"\n");
|
||||
body.push_str("path = \"src/main.rs\"\n\n");
|
||||
body.push_str("[dependencies]\n");
|
||||
for dep in &versioned {
|
||||
body.push_str(dep.name);
|
||||
body.push_str(" = \"");
|
||||
body.push_str(dep.version);
|
||||
body.push_str("\"\n");
|
||||
}
|
||||
for d in &deps {
|
||||
body.push_str(d);
|
||||
body.push_str(" = \"*\"\n");
|
||||
|
|
@ -2023,7 +2037,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
|||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
||||
}
|
||||
|
||||
let cargo_toml = generate_cargo_toml_for_shape(spec.expected_cap, shape);
|
||||
let cargo_toml = generate_cargo_toml_for_spec(spec.expected_cap, shape, spec);
|
||||
let main_rs = generate_main_rs(spec, shape);
|
||||
|
||||
Ok(HarnessSource {
|
||||
|
|
@ -2348,7 +2362,7 @@ fn emit_graphql_resolver_harness(
|
|||
field: &str,
|
||||
) -> HarnessSource {
|
||||
let shim = probe_shim();
|
||||
let cargo_toml = generate_cargo_toml(spec.expected_cap);
|
||||
let cargo_toml = generate_cargo_toml_for_spec(spec.expected_cap, RustShape::Generic, spec);
|
||||
let handler = &spec.entry_name;
|
||||
let label = format!("{type_name}.{field}");
|
||||
let body = format!(
|
||||
|
|
@ -2571,6 +2585,36 @@ fn generate_cargo_toml_for_shape(cap: Cap, shape: RustShape) -> String {
|
|||
cargo
|
||||
}
|
||||
|
||||
fn generate_cargo_toml_for_spec(cap: Cap, shape: RustShape, spec: &HarnessSpec) -> String {
|
||||
let mut cargo = generate_cargo_toml_for_shape(cap, shape);
|
||||
let Some(adapter) = spec
|
||||
.framework
|
||||
.as_ref()
|
||||
.map(|binding| binding.adapter.as_str())
|
||||
else {
|
||||
return cargo;
|
||||
};
|
||||
let deps = crate::dynamic::framework::runtime_deps::deps_for_adapter(adapter);
|
||||
if deps.rust_crates.is_empty() {
|
||||
return cargo;
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for line in cargo.lines() {
|
||||
if let Some((name, _)) = line.split_once(" = ") {
|
||||
seen.insert(name.trim().to_owned());
|
||||
}
|
||||
}
|
||||
for dep in deps.rust_crates {
|
||||
if seen.insert(dep.name.to_owned()) {
|
||||
cargo.push_str(dep.name);
|
||||
cargo.push_str(" = \"");
|
||||
cargo.push_str(dep.version);
|
||||
cargo.push_str("\"\n");
|
||||
}
|
||||
}
|
||||
cargo
|
||||
}
|
||||
|
||||
/// Variant of [`generate_cargo_toml`] that conditionally pulls in
|
||||
/// `percent-encoding` for the HEADER_INJECTION benign control fixture
|
||||
/// (it routes the value through `utf8_percent_encode` to land CRLF as
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue