mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +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
|
|
@ -359,6 +359,13 @@ pub struct CapturedDeps {
|
||||||
/// version even when the entry file imports the framework
|
/// version even when the entry file imports the framework
|
||||||
/// transitively.
|
/// transitively.
|
||||||
pub frameworks: Vec<DetectedFramework>,
|
pub frameworks: Vec<DetectedFramework>,
|
||||||
|
/// Adapter id attached to the spec by framework binding detection.
|
||||||
|
///
|
||||||
|
/// This is distinct from manifest-detected web frameworks: Phase 20/21
|
||||||
|
/// adapters can bind from route/config metadata or marker comments while
|
||||||
|
/// the entry source avoids a hard import. The id lets manifest synthesis
|
||||||
|
/// add the package-manager deps required when the real import is present.
|
||||||
|
pub framework_adapter: Option<String>,
|
||||||
/// Three-valued lang-has-framework signal (see
|
/// Three-valued lang-has-framework signal (see
|
||||||
/// [`FrameworkContext::lang_has_web_framework`]).
|
/// [`FrameworkContext::lang_has_web_framework`]).
|
||||||
pub framework_signal: Option<bool>,
|
pub framework_signal: Option<bool>,
|
||||||
|
|
@ -425,6 +432,8 @@ pub struct Environment {
|
||||||
pub direct_deps: Vec<String>,
|
pub direct_deps: Vec<String>,
|
||||||
/// Frameworks detected in the project root.
|
/// Frameworks detected in the project root.
|
||||||
pub frameworks: Vec<DetectedFramework>,
|
pub frameworks: Vec<DetectedFramework>,
|
||||||
|
/// Adapter id attached to the originating spec, when any.
|
||||||
|
pub framework_adapter: Option<String>,
|
||||||
/// Language pinned via the originating spec. Cached here so the
|
/// Language pinned via the originating spec. Cached here so the
|
||||||
/// emitter does not have to re-thread the spec.
|
/// emitter does not have to re-thread the spec.
|
||||||
pub lang: Lang,
|
pub lang: Lang,
|
||||||
|
|
@ -493,6 +502,7 @@ pub fn capture_project_dependencies_with_context(
|
||||||
|
|
||||||
let framework_ctx = detect_frameworks(project_root);
|
let framework_ctx = detect_frameworks(project_root);
|
||||||
let frameworks = framework_ctx.frameworks.clone();
|
let frameworks = framework_ctx.frameworks.clone();
|
||||||
|
let framework_adapter = spec.framework.as_ref().map(|b| b.adapter.clone());
|
||||||
let framework_signal = framework_ctx.lang_has_web_framework(framework_slug_for_lang(spec.lang));
|
let framework_signal = framework_ctx.lang_has_web_framework(framework_slug_for_lang(spec.lang));
|
||||||
|
|
||||||
let config_files = collect_config_files(&entry_file, project_root);
|
let config_files = collect_config_files(&entry_file, project_root);
|
||||||
|
|
@ -509,6 +519,7 @@ pub fn capture_project_dependencies_with_context(
|
||||||
toolchain,
|
toolchain,
|
||||||
direct_deps,
|
direct_deps,
|
||||||
frameworks,
|
frameworks,
|
||||||
|
framework_adapter,
|
||||||
framework_signal,
|
framework_signal,
|
||||||
config_files,
|
config_files,
|
||||||
source_closure,
|
source_closure,
|
||||||
|
|
@ -644,6 +655,7 @@ pub fn stage_workdir_full(
|
||||||
toolchain: captured.toolchain.clone(),
|
toolchain: captured.toolchain.clone(),
|
||||||
direct_deps: captured.direct_deps.clone(),
|
direct_deps: captured.direct_deps.clone(),
|
||||||
frameworks: captured.frameworks.clone(),
|
frameworks: captured.frameworks.clone(),
|
||||||
|
framework_adapter: captured.framework_adapter.clone(),
|
||||||
lang,
|
lang,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
pub mod adapters;
|
pub mod adapters;
|
||||||
pub mod auth_markers;
|
pub mod auth_markers;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
|
pub mod runtime_deps;
|
||||||
|
|
||||||
use crate::evidence::EntryKind;
|
use crate::evidence::EntryKind;
|
||||||
use crate::summary::FuncSummary;
|
use crate::summary::FuncSummary;
|
||||||
|
|
@ -323,6 +324,18 @@ pub trait FrameworkAdapter: Sync {
|
||||||
/// the trace-event detail string emitted by the verifier.
|
/// the trace-event detail string emitted by the verifier.
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
|
|
||||||
|
/// Runtime package-manager dependencies needed when a real harness
|
||||||
|
/// loads code matched by this adapter.
|
||||||
|
///
|
||||||
|
/// Most adapters need no extra metadata because the entry source's
|
||||||
|
/// imports are enough for dependency capture. Adapters that can bind
|
||||||
|
/// from route files, annotations, or marker comments use the central
|
||||||
|
/// adapter-id registry so manifest synthesis can still install the
|
||||||
|
/// actual framework library before execution.
|
||||||
|
fn runtime_dependencies(&self) -> runtime_deps::FrameworkRuntimeDeps {
|
||||||
|
runtime_deps::deps_for_adapter(self.name())
|
||||||
|
}
|
||||||
|
|
||||||
/// Language this adapter targets.
|
/// Language this adapter targets.
|
||||||
fn lang(&self) -> Lang;
|
fn lang(&self) -> Lang;
|
||||||
|
|
||||||
|
|
|
||||||
537
src/dynamic/framework/runtime_deps.rs
Normal file
537
src/dynamic/framework/runtime_deps.rs
Normal file
|
|
@ -0,0 +1,537 @@
|
||||||
|
//! Runtime dependency hints for framework-bound dynamic harnesses.
|
||||||
|
//!
|
||||||
|
//! Framework adapters sometimes bind from marker text or framework
|
||||||
|
//! configuration while the entry source itself keeps the real import
|
||||||
|
//! commented out for host-portable corpus tests. When such a binding is
|
||||||
|
//! used to drive a real harness, the build step still needs the matching
|
||||||
|
//! package manager manifest so top-level imports resolve under the verifier.
|
||||||
|
|
||||||
|
/// Package with a package-manager specific version requirement.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct VersionedPackage {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub version: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maven dependency coordinates.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct MavenPackage {
|
||||||
|
pub group_id: &'static str,
|
||||||
|
pub artifact_id: &'static str,
|
||||||
|
pub version: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adapter runtime dependencies grouped by package manager.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct FrameworkRuntimeDeps {
|
||||||
|
pub python_packages: &'static [&'static str],
|
||||||
|
pub node_packages: &'static [VersionedPackage],
|
||||||
|
pub ruby_gems: &'static [&'static str],
|
||||||
|
pub composer_packages: &'static [VersionedPackage],
|
||||||
|
pub maven_packages: &'static [MavenPackage],
|
||||||
|
pub go_modules: &'static [VersionedPackage],
|
||||||
|
pub rust_crates: &'static [VersionedPackage],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameworkRuntimeDeps {
|
||||||
|
pub const EMPTY: Self = Self {
|
||||||
|
python_packages: &[],
|
||||||
|
node_packages: &[],
|
||||||
|
ruby_gems: &[],
|
||||||
|
composer_packages: &[],
|
||||||
|
maven_packages: &[],
|
||||||
|
go_modules: &[],
|
||||||
|
rust_crates: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.python_packages.is_empty()
|
||||||
|
&& self.node_packages.is_empty()
|
||||||
|
&& self.ruby_gems.is_empty()
|
||||||
|
&& self.composer_packages.is_empty()
|
||||||
|
&& self.maven_packages.is_empty()
|
||||||
|
&& self.go_modules.is_empty()
|
||||||
|
&& self.rust_crates.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PY_FLASK: &[&str] = &["Flask"];
|
||||||
|
const PY_FASTAPI: &[&str] = &["fastapi", "httpx"];
|
||||||
|
const PY_STARLETTE: &[&str] = &["starlette", "httpx"];
|
||||||
|
const PY_DJANGO: &[&str] = &["Django"];
|
||||||
|
const PY_CELERY: &[&str] = &["celery"];
|
||||||
|
const PY_GRAPHENE: &[&str] = &["graphene"];
|
||||||
|
const PY_CHANNELS: &[&str] = &["channels"];
|
||||||
|
const PY_SOCKETIO: &[&str] = &["python-socketio"];
|
||||||
|
const PY_ALEMBIC: &[&str] = &["alembic", "Flask-Migrate"];
|
||||||
|
const PY_KAFKA: &[&str] = &["kafka-python"];
|
||||||
|
const PY_SQS: &[&str] = &["boto3"];
|
||||||
|
const PY_PUBSUB: &[&str] = &["google-cloud-pubsub"];
|
||||||
|
const PY_RABBIT: &[&str] = &["pika"];
|
||||||
|
|
||||||
|
const NODE_EXPRESS: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "express",
|
||||||
|
version: "^4.19.2",
|
||||||
|
}];
|
||||||
|
const NODE_KOA: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "koa",
|
||||||
|
version: "^2.15.3",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "@koa/router",
|
||||||
|
version: "^12.0.1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const NODE_FASTIFY: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "fastify",
|
||||||
|
version: "^4.28.1",
|
||||||
|
}];
|
||||||
|
const NODE_CRON: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "node-cron",
|
||||||
|
version: "^3.0.3",
|
||||||
|
}];
|
||||||
|
const NODE_APOLLO: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "@apollo/server",
|
||||||
|
version: "^4.10.4",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "apollo-server",
|
||||||
|
version: "^3.13.0",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "graphql",
|
||||||
|
version: "^16.8.1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const NODE_RELAY: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "graphql-relay",
|
||||||
|
version: "^0.10.0",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "graphql",
|
||||||
|
version: "^16.8.1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const NODE_WS: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "ws",
|
||||||
|
version: "^8.17.0",
|
||||||
|
}];
|
||||||
|
const NODE_SQS: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "@aws-sdk/client-sqs",
|
||||||
|
version: "^3.583.0",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "sqs-consumer",
|
||||||
|
version: "^11.5.0",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const NODE_KNEX: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "knex",
|
||||||
|
version: "^3.1.0",
|
||||||
|
}];
|
||||||
|
const NODE_PRISMA: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "@prisma/client",
|
||||||
|
version: "^5.14.0",
|
||||||
|
}];
|
||||||
|
const NODE_SEQUELIZE: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "sequelize",
|
||||||
|
version: "^6.37.3",
|
||||||
|
}];
|
||||||
|
|
||||||
|
const RUBY_RACK: &[&str] = &["rack"];
|
||||||
|
const RUBY_SINATRA: &[&str] = &["rack", "sinatra"];
|
||||||
|
const RUBY_HANAMI: &[&str] = &["rack", "hanami-controller"];
|
||||||
|
const RUBY_RAILS: &[&str] = &["rails"];
|
||||||
|
const RUBY_SIDEKIQ: &[&str] = &["sidekiq"];
|
||||||
|
|
||||||
|
const PHP_LARAVEL: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "laravel/framework",
|
||||||
|
version: "^10.0",
|
||||||
|
}];
|
||||||
|
const PHP_SYMFONY: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "symfony/http-foundation",
|
||||||
|
version: "^6.4",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "symfony/http-kernel",
|
||||||
|
version: "^6.4",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const PHP_CODEIGNITER: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "codeigniter4/framework",
|
||||||
|
version: "^4.4",
|
||||||
|
}];
|
||||||
|
|
||||||
|
const JAVA_SPRING: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "org.springframework",
|
||||||
|
artifact_id: "spring-webmvc",
|
||||||
|
version: "6.1.8",
|
||||||
|
}];
|
||||||
|
const JAVA_SERVLET: &[MavenPackage] = &[
|
||||||
|
MavenPackage {
|
||||||
|
group_id: "jakarta.servlet",
|
||||||
|
artifact_id: "jakarta.servlet-api",
|
||||||
|
version: "6.0.0",
|
||||||
|
},
|
||||||
|
MavenPackage {
|
||||||
|
group_id: "javax.servlet",
|
||||||
|
artifact_id: "javax.servlet-api",
|
||||||
|
version: "4.0.1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const JAVA_QUARTZ: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "org.quartz-scheduler",
|
||||||
|
artifact_id: "quartz",
|
||||||
|
version: "2.3.2",
|
||||||
|
}];
|
||||||
|
const JAVA_FLYWAY: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "org.flywaydb",
|
||||||
|
artifact_id: "flyway-core",
|
||||||
|
version: "10.13.0",
|
||||||
|
}];
|
||||||
|
const JAVA_LIQUIBASE: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "org.liquibase",
|
||||||
|
artifact_id: "liquibase-core",
|
||||||
|
version: "4.28.0",
|
||||||
|
}];
|
||||||
|
const JAVA_KAFKA: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "org.apache.kafka",
|
||||||
|
artifact_id: "kafka-clients",
|
||||||
|
version: "3.7.0",
|
||||||
|
}];
|
||||||
|
const JAVA_SQS: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "software.amazon.awssdk",
|
||||||
|
artifact_id: "sqs",
|
||||||
|
version: "2.25.60",
|
||||||
|
}];
|
||||||
|
const JAVA_RABBIT: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "com.rabbitmq",
|
||||||
|
artifact_id: "amqp-client",
|
||||||
|
version: "5.21.0",
|
||||||
|
}];
|
||||||
|
const JAVA_QUARKUS: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "io.quarkus",
|
||||||
|
artifact_id: "quarkus-resteasy-reactive",
|
||||||
|
version: "3.10.2",
|
||||||
|
}];
|
||||||
|
const JAVA_MICRONAUT: &[MavenPackage] = &[MavenPackage {
|
||||||
|
group_id: "io.micronaut",
|
||||||
|
artifact_id: "micronaut-http-server-netty",
|
||||||
|
version: "4.4.4",
|
||||||
|
}];
|
||||||
|
|
||||||
|
const GO_GIN: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/gin-gonic/gin",
|
||||||
|
version: "v1.10.0",
|
||||||
|
}];
|
||||||
|
const GO_ECHO: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/labstack/echo/v4",
|
||||||
|
version: "v4.12.0",
|
||||||
|
}];
|
||||||
|
const GO_FIBER: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/gofiber/fiber/v2",
|
||||||
|
version: "v2.52.5",
|
||||||
|
}];
|
||||||
|
const GO_CHI: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/go-chi/chi/v5",
|
||||||
|
version: "v5.0.12",
|
||||||
|
}];
|
||||||
|
const GO_GQLGEN: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/99designs/gqlgen",
|
||||||
|
version: "v0.17.49",
|
||||||
|
}];
|
||||||
|
const GO_MIGRATE: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/golang-migrate/migrate/v4",
|
||||||
|
version: "v4.17.1",
|
||||||
|
}];
|
||||||
|
const GO_PUBSUB: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "cloud.google.com/go/pubsub",
|
||||||
|
version: "v1.39.0",
|
||||||
|
}];
|
||||||
|
const GO_NATS: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "github.com/nats-io/nats.go",
|
||||||
|
version: "v1.34.1",
|
||||||
|
}];
|
||||||
|
|
||||||
|
const RUST_AXUM: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "axum",
|
||||||
|
version: "0.7",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "tokio",
|
||||||
|
version: "1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const RUST_ACTIX: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "actix-web",
|
||||||
|
version: "4",
|
||||||
|
}];
|
||||||
|
const RUST_ROCKET: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "rocket",
|
||||||
|
version: "0.5",
|
||||||
|
}];
|
||||||
|
const RUST_WARP: &[VersionedPackage] = &[
|
||||||
|
VersionedPackage {
|
||||||
|
name: "warp",
|
||||||
|
version: "0.3",
|
||||||
|
},
|
||||||
|
VersionedPackage {
|
||||||
|
name: "tokio",
|
||||||
|
version: "1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const RUST_JUNIPER: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "juniper",
|
||||||
|
version: "0.16",
|
||||||
|
}];
|
||||||
|
const RUST_REFINERY: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "refinery",
|
||||||
|
version: "0.8",
|
||||||
|
}];
|
||||||
|
const RUST_SQLX: &[VersionedPackage] = &[VersionedPackage {
|
||||||
|
name: "sqlx",
|
||||||
|
version: "0.7",
|
||||||
|
}];
|
||||||
|
|
||||||
|
/// Dependencies known for a framework adapter id.
|
||||||
|
pub fn deps_for_adapter(adapter: &str) -> FrameworkRuntimeDeps {
|
||||||
|
match adapter {
|
||||||
|
"python-flask" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_FLASK,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"python-fastapi" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_FASTAPI,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"python-starlette" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_STARLETTE,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"python-django" | "middleware-django" | "migration-django" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_DJANGO,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"scheduled-celery" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_CELERY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"graphql-graphene" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_GRAPHENE,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"websocket-channels" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_CHANNELS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"websocket-socketio" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_SOCKETIO,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-flask" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_ALEMBIC,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"kafka-python" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_KAFKA,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"sqs-python" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_SQS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"pubsub-python" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_PUBSUB,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rabbit-python" => FrameworkRuntimeDeps {
|
||||||
|
python_packages: PY_RABBIT,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"js-express" | "middleware-express" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_EXPRESS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"js-koa" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_KOA,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"js-fastify" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_FASTIFY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"scheduled-cron" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_CRON,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"graphql-apollo" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_APOLLO,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"graphql-relay" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_RELAY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"websocket-ws" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_WS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"sqs-node" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_SQS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-knex" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_KNEX,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-prisma" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_PRISMA,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-sequelize" => FrameworkRuntimeDeps {
|
||||||
|
node_packages: NODE_SEQUELIZE,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"ruby-sinatra" => FrameworkRuntimeDeps {
|
||||||
|
ruby_gems: RUBY_SINATRA,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"ruby-hanami" => FrameworkRuntimeDeps {
|
||||||
|
ruby_gems: RUBY_HANAMI,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"ruby-rails" | "middleware-rails" | "migration-rails" | "websocket-actioncable" => {
|
||||||
|
FrameworkRuntimeDeps {
|
||||||
|
ruby_gems: RUBY_RAILS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"scheduled-sidekiq" => FrameworkRuntimeDeps {
|
||||||
|
ruby_gems: RUBY_SIDEKIQ,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"middleware-rack" => FrameworkRuntimeDeps {
|
||||||
|
ruby_gems: RUBY_RACK,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"php-laravel" | "middleware-laravel" | "migration-laravel" => FrameworkRuntimeDeps {
|
||||||
|
composer_packages: PHP_LARAVEL,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"php-symfony" => FrameworkRuntimeDeps {
|
||||||
|
composer_packages: PHP_SYMFONY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"php-codeigniter" => FrameworkRuntimeDeps {
|
||||||
|
composer_packages: PHP_CODEIGNITER,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"java-spring" | "middleware-spring" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_SPRING,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"java-servlet" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_SERVLET,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"java-quarkus" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_QUARKUS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"java-micronaut" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_MICRONAUT,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"scheduled-quartz" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_QUARTZ,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-flyway" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_FLYWAY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-liquibase" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_LIQUIBASE,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"kafka-java" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_KAFKA,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"sqs-java" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_SQS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rabbit-java" => FrameworkRuntimeDeps {
|
||||||
|
maven_packages: JAVA_RABBIT,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"go-gin" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_GIN,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"go-echo" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_ECHO,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"go-fiber" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_FIBER,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"go-chi" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_CHI,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"graphql-gqlgen" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_GQLGEN,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-go-migrate" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_MIGRATE,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"pubsub-go" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_PUBSUB,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"nats-go" => FrameworkRuntimeDeps {
|
||||||
|
go_modules: GO_NATS,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rust-axum" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_AXUM,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rust-actix" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_ACTIX,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rust-rocket" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_ROCKET,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"rust-warp" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_WARP,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"graphql-juniper" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_JUNIPER,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-refinery" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_REFINERY,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
"migration-sqlx" => FrameworkRuntimeDeps {
|
||||||
|
rust_crates: RUST_SQLX,
|
||||||
|
..FrameworkRuntimeDeps::EMPTY
|
||||||
|
},
|
||||||
|
_ => FrameworkRuntimeDeps::EMPTY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -317,6 +317,14 @@ pub fn materialize_go(env: &Environment) -> RuntimeArtifacts {
|
||||||
};
|
};
|
||||||
let mut deps: Vec<String> = Vec::new();
|
let mut deps: Vec<String> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_go_stdlib(d) {
|
if is_go_stdlib(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -330,8 +338,11 @@ pub fn materialize_go(env: &Environment) -> RuntimeArtifacts {
|
||||||
let mut body = String::with_capacity(128);
|
let mut body = String::with_capacity(128);
|
||||||
body.push_str("module nyx_harness\n\n");
|
body.push_str("module nyx_harness\n\n");
|
||||||
body.push_str(&format!("go {go_version}\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");
|
body.push_str("\nrequire (\n");
|
||||||
|
for dep in &versioned {
|
||||||
|
body.push_str(&format!("\t{} {}\n", dep.name, dep.version));
|
||||||
|
}
|
||||||
for d in &deps {
|
for d in &deps {
|
||||||
body.push_str(&format!("\t{d} latest\n"));
|
body.push_str(&format!("\t{d} latest\n"));
|
||||||
}
|
}
|
||||||
|
|
@ -651,6 +662,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||||
// GraphQLResolver short-circuit (gqlgen).
|
// GraphQLResolver short-circuit (gqlgen).
|
||||||
if let crate::evidence::EntryKind::GraphQLResolver { type_name, field } = &spec.entry_kind {
|
if let crate::evidence::EntryKind::GraphQLResolver { type_name, field } = &spec.entry_kind {
|
||||||
return Ok(emit_graphql_resolver_harness(
|
return Ok(emit_graphql_resolver_harness(
|
||||||
|
spec,
|
||||||
&spec.entry_name,
|
&spec.entry_name,
|
||||||
type_name,
|
type_name,
|
||||||
field,
|
field,
|
||||||
|
|
@ -660,7 +672,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||||
let entry_source = read_entry_source(&spec.entry_file);
|
let entry_source = read_entry_source(&spec.entry_file);
|
||||||
let shape = GoShape::detect(spec, &entry_source);
|
let shape = GoShape::detect(spec, &entry_source);
|
||||||
let main_go = generate_main_go(spec, shape);
|
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)];
|
let mut extra_files = vec![("go.mod".to_owned(), go_mod)];
|
||||||
// Phase 15: GinHandler shape stages a minimal gin stub package so
|
// 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 {
|
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::GinRoute => &[("github.com/gin-gonic/gin", "v1.10.0")],
|
||||||
GoShape::EchoRoute => &[("github.com/labstack/echo/v4", "v4.12.0")],
|
GoShape::EchoRoute => &[("github.com/labstack/echo/v4", "v4.12.0")],
|
||||||
GoShape::FiberRoute => &[("github.com/gofiber/fiber/v2", "v2.52.5")],
|
GoShape::FiberRoute => &[("github.com/gofiber/fiber/v2", "v2.52.5")],
|
||||||
GoShape::ChiRoute => &[("github.com/go-chi/chi/v5", "v5.0.12")],
|
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();
|
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");
|
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('\t');
|
||||||
out.push_str(module);
|
out.push_str(module);
|
||||||
out.push(' ');
|
out.push(' ');
|
||||||
out.push_str(version);
|
out.push_str(version);
|
||||||
out.push('\n');
|
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.push_str(")\n");
|
||||||
}
|
}
|
||||||
out
|
out
|
||||||
|
|
@ -2106,7 +2151,7 @@ var NyxAutoReceivers = map[string]interface{{}}{{
|
||||||
/// default → Pub/Sub.
|
/// default → Pub/Sub.
|
||||||
fn emit_message_handler_harness(spec: &HarnessSpec, queue: &str) -> HarnessSource {
|
fn emit_message_handler_harness(spec: &HarnessSpec, queue: &str) -> HarnessSource {
|
||||||
let shim = probe_shim();
|
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 handler = &spec.entry_name;
|
||||||
let broker = go_broker_for_adapter(spec);
|
let broker = go_broker_for_adapter(spec);
|
||||||
|
|
||||||
|
|
@ -2259,9 +2304,14 @@ func main() {{
|
||||||
/// map (mirrors the `NyxReceivers` / `NyxHandlers` contracts from
|
/// map (mirrors the `NyxReceivers` / `NyxHandlers` contracts from
|
||||||
/// Phase 19 / 20), constructs a synthetic `context.Background()`, and
|
/// Phase 19 / 20), constructs a synthetic `context.Background()`, and
|
||||||
/// invokes the resolver with the payload positionally.
|
/// 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 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!(
|
let source = format!(
|
||||||
r##"// Nyx dynamic harness — GraphQL resolver (Phase 21 / Track M.3).
|
r##"// Nyx dynamic harness — GraphQL resolver (Phase 21 / Track M.3).
|
||||||
package main
|
package main
|
||||||
|
|
|
||||||
|
|
@ -498,6 +498,17 @@ pub fn materialize_java(env: &Environment) -> RuntimeArtifacts {
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let mut deps: Vec<String> = Vec::new();
|
let mut deps: Vec<String> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_java_stdlib(d) {
|
if is_java_stdlib(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -523,8 +534,18 @@ pub fn materialize_java(env: &Environment) -> RuntimeArtifacts {
|
||||||
" <maven.compiler.target>{java_version}</maven.compiler.target>\n"
|
" <maven.compiler.target>{java_version}</maven.compiler.target>\n"
|
||||||
));
|
));
|
||||||
body.push_str(" </properties>\n");
|
body.push_str(" </properties>\n");
|
||||||
if !deps.is_empty() {
|
if !deps.is_empty() || !maven_deps.is_empty() {
|
||||||
body.push_str(" <dependencies>\n");
|
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 {
|
for d in &deps {
|
||||||
body.push_str(" <dependency>\n");
|
body.push_str(" <dependency>\n");
|
||||||
body.push_str(&format!(" <groupId>{d}</groupId>\n"));
|
body.push_str(&format!(" <groupId>{d}</groupId>\n"));
|
||||||
|
|
@ -3924,7 +3945,11 @@ public class NyxHarness {{
|
||||||
".".to_owned(),
|
".".to_owned(),
|
||||||
"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")),
|
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 ─────────────────────
|
// ── Phase 21 (Track M.3) — synthetic entry-kind harnesses ─────────────────────
|
||||||
|
|
||||||
fn emit_scheduled_job_harness(
|
fn emit_scheduled_job_harness(
|
||||||
|
|
@ -4047,7 +4118,7 @@ public class NyxHarness {{
|
||||||
".".to_owned(),
|
".".to_owned(),
|
||||||
"NyxHarness".to_owned(),
|
"NyxHarness".to_owned(),
|
||||||
],
|
],
|
||||||
extra_files: vec![],
|
extra_files: framework_dependency_files(spec),
|
||||||
entry_subpath: Some(format!("{entry_class}.java")),
|
entry_subpath: Some(format!("{entry_class}.java")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4123,7 +4194,7 @@ public class NyxHarness {{
|
||||||
".".to_owned(),
|
".".to_owned(),
|
||||||
"NyxHarness".to_owned(),
|
"NyxHarness".to_owned(),
|
||||||
],
|
],
|
||||||
extra_files: vec![],
|
extra_files: framework_dependency_files(spec),
|
||||||
entry_subpath: Some(format!("{entry_class}.java")),
|
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 deps: Vec<(String, &'static str)> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_node_builtin(d) {
|
if is_node_builtin(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1039,7 +1047,7 @@ if (_h == null) {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.js".to_owned(),
|
filename: "harness.js".to_owned(),
|
||||||
command: vec!["node".to_owned(), "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),
|
entry_subpath: Some(entry_subpath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1081,7 +1089,7 @@ if (_h == null) {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.js".to_owned(),
|
filename: "harness.js".to_owned(),
|
||||||
command: vec!["node".to_owned(), "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),
|
entry_subpath: Some(entry_subpath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1125,7 +1133,7 @@ if (_h == null) {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.js".to_owned(),
|
filename: "harness.js".to_owned(),
|
||||||
command: vec!["node".to_owned(), "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),
|
entry_subpath: Some(entry_subpath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1161,7 +1169,7 @@ const _res = {{ statusCode: 200, headers: {{}}, end: function(d){{ if (d != null
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.js".to_owned(),
|
filename: "harness.js".to_owned(),
|
||||||
command: vec!["node".to_owned(), "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),
|
entry_subpath: Some(entry_subpath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1222,7 +1230,7 @@ const _prisma = {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.js".to_owned(),
|
filename: "harness.js".to_owned(),
|
||||||
command: vec!["node".to_owned(), "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),
|
entry_subpath: Some(entry_subpath),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2527,10 +2535,19 @@ fn message_handler_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)>
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let source = read_entry_source(&spec.entry_file);
|
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() {
|
if deps.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
deps.sort_by(|a, b| a.0.cmp(b.0));
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
"package.json".to_owned(),
|
"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)> {
|
fn js_message_handler_deps(source: &str) -> Vec<(&'static str, &'static str)> {
|
||||||
let mut deps = Vec::new();
|
let mut deps = Vec::new();
|
||||||
for raw_line in source.lines() {
|
for raw_line in source.lines() {
|
||||||
|
|
|
||||||
|
|
@ -301,11 +301,31 @@ pub fn materialize_php(env: &Environment) -> RuntimeArtifacts {
|
||||||
} else {
|
} else {
|
||||||
php_ver
|
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);
|
let mut body = String::with_capacity(128);
|
||||||
body.push_str("{\n");
|
body.push_str("{\n");
|
||||||
body.push_str(" \"name\": \"nyx/harness\",\n");
|
body.push_str(" \"name\": \"nyx/harness\",\n");
|
||||||
body.push_str(" \"require\": {\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");
|
||||||
body.push_str("}\n");
|
body.push_str("}\n");
|
||||||
artifacts.push("composer.json", body);
|
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()).
|
// Phase 21 (Track M.3): Middleware short-circuit (Laravel handle()).
|
||||||
if let crate::evidence::EntryKind::Middleware { name } = &spec.entry_kind {
|
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()).
|
// Phase 21 (Track M.3): Migration short-circuit (Laravel up()).
|
||||||
if let crate::evidence::EntryKind::Migration { version } = &spec.entry_kind {
|
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);
|
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 preamble = nyx_php_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
echo "__NYX_MIDDLEWARE__: " . {name:?} . "\n";
|
echo "__NYX_MIDDLEWARE__: " . {name:?} . "\n";
|
||||||
|
|
@ -3130,13 +3151,14 @@ if (class_exists({handler:?})) {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.php".to_owned(),
|
filename: "harness.php".to_owned(),
|
||||||
command: vec!["php".to_owned(), "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()),
|
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 preamble = nyx_php_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let version_repr = version.unwrap_or("<no-version>");
|
let version_repr = version.unwrap_or("<no-version>");
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
|
|
@ -3175,11 +3197,35 @@ if (class_exists({handler:?})) {{
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.php".to_owned(),
|
filename: "harness.php".to_owned(),
|
||||||
command: vec!["php".to_owned(), "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()),
|
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 {
|
fn build_call_expr(spec: &HarnessSpec, shape: PhpShape, func: &str) -> String {
|
||||||
match shape {
|
match shape {
|
||||||
PhpShape::TopLevelScript => "null".to_owned(),
|
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 deps: Vec<String> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_python_stdlib(d) {
|
if is_python_stdlib(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -918,7 +927,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1089,7 +1098,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1147,7 +1156,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1194,7 +1203,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1250,7 +1259,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1299,7 +1308,7 @@ except Exception as _e:
|
||||||
source: format!("{preamble}\n{body}\n{postamble}"),
|
source: format!("{preamble}\n{body}\n{postamble}"),
|
||||||
filename: "harness.py".to_owned(),
|
filename: "harness.py".to_owned(),
|
||||||
command: vec!["python3".to_owned(), "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,
|
entry_subpath: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3129,10 +3138,44 @@ fn message_handler_dependency_files(spec: &HarnessSpec) -> Vec<(String, String)>
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let source = read_entry_source(&spec.entry_file);
|
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() {
|
if deps.is_empty() {
|
||||||
return Vec::new();
|
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();
|
let mut body = String::new();
|
||||||
for dep in deps {
|
for dep in deps {
|
||||||
body.push_str(dep);
|
body.push_str(dep);
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,13 @@ pub fn materialize_ruby(env: &Environment) -> RuntimeArtifacts {
|
||||||
let mut artifacts = RuntimeArtifacts::new();
|
let mut artifacts = RuntimeArtifacts::new();
|
||||||
let mut deps: Vec<String> = Vec::new();
|
let mut deps: Vec<String> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_ruby_stdlib(d) {
|
if is_ruby_stdlib(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -495,25 +502,22 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||||
|
|
||||||
// Phase 21 (Track M.3): ScheduledJob short-circuit (Sidekiq workers).
|
// Phase 21 (Track M.3): ScheduledJob short-circuit (Sidekiq workers).
|
||||||
if let crate::evidence::EntryKind::ScheduledJob { schedule } = &spec.entry_kind {
|
if let crate::evidence::EntryKind::ScheduledJob { schedule } = &spec.entry_kind {
|
||||||
return Ok(emit_scheduled_job_harness(
|
return Ok(emit_scheduled_job_harness(spec, schedule.as_deref()));
|
||||||
&spec.entry_name,
|
|
||||||
schedule.as_deref(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase 21 (Track M.3): WebSocket short-circuit (ActionCable channels).
|
// Phase 21 (Track M.3): WebSocket short-circuit (ActionCable channels).
|
||||||
if let crate::evidence::EntryKind::WebSocket { path } = &spec.entry_kind {
|
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).
|
// Phase 21 (Track M.3): Middleware short-circuit (Rack-shape).
|
||||||
if let crate::evidence::EntryKind::Middleware { name } = &spec.entry_kind {
|
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).
|
// Phase 21 (Track M.3): Migration short-circuit (ActiveRecord up/down).
|
||||||
if let crate::evidence::EntryKind::Migration { version } = &spec.entry_kind {
|
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);
|
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 preamble = nyx_ruby_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let sched = schedule.unwrap_or("<unscheduled>");
|
let sched = schedule.unwrap_or("<unscheduled>");
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
|
|
@ -773,13 +778,14 @@ end
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.rb".to_owned(),
|
filename: "harness.rb".to_owned(),
|
||||||
command: vec!["ruby".to_owned(), "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()),
|
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 preamble = nyx_ruby_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
puts "__NYX_WEBSOCKET__: " + {path:?}
|
puts "__NYX_WEBSOCKET__: " + {path:?}
|
||||||
|
|
@ -823,13 +829,14 @@ end
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.rb".to_owned(),
|
filename: "harness.rb".to_owned(),
|
||||||
command: vec!["ruby".to_owned(), "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()),
|
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 preamble = nyx_ruby_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
puts "__NYX_MIDDLEWARE__: " + {name:?}
|
puts "__NYX_MIDDLEWARE__: " + {name:?}
|
||||||
|
|
@ -879,13 +886,14 @@ end
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.rb".to_owned(),
|
filename: "harness.rb".to_owned(),
|
||||||
command: vec!["ruby".to_owned(), "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()),
|
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 preamble = nyx_ruby_preamble();
|
||||||
|
let handler = &spec.entry_name;
|
||||||
let ver = version.unwrap_or("<no-version>");
|
let ver = version.unwrap_or("<no-version>");
|
||||||
let body = format!(
|
let body = format!(
|
||||||
r#"{preamble}
|
r#"{preamble}
|
||||||
|
|
@ -932,11 +940,34 @@ end
|
||||||
source: body,
|
source: body,
|
||||||
filename: "harness.rb".to_owned(),
|
filename: "harness.rb".to_owned(),
|
||||||
command: vec!["ruby".to_owned(), "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()),
|
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.
|
/// Phase 03 — Track J.1 deserialize harness for Ruby.
|
||||||
///
|
///
|
||||||
/// Wraps a call to `Marshal.load(input)` with a const-lookup
|
/// 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 artifacts = RuntimeArtifacts::new();
|
||||||
let mut deps: Vec<String> = Vec::new();
|
let mut deps: Vec<String> = Vec::new();
|
||||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::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 {
|
for d in &env.direct_deps {
|
||||||
if is_rust_stdlib(d) {
|
if is_rust_stdlib(d) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -166,6 +174,12 @@ pub fn materialize_rust(env: &Environment) -> RuntimeArtifacts {
|
||||||
body.push_str("name = \"nyx_harness\"\n");
|
body.push_str("name = \"nyx_harness\"\n");
|
||||||
body.push_str("path = \"src/main.rs\"\n\n");
|
body.push_str("path = \"src/main.rs\"\n\n");
|
||||||
body.push_str("[dependencies]\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 {
|
for d in &deps {
|
||||||
body.push_str(d);
|
body.push_str(d);
|
||||||
body.push_str(" = \"*\"\n");
|
body.push_str(" = \"*\"\n");
|
||||||
|
|
@ -2023,7 +2037,7 @@ pub fn emit(spec: &HarnessSpec) -> Result<HarnessSource, UnsupportedReason> {
|
||||||
_ => return Err(UnsupportedReason::PayloadSlotUnsupported),
|
_ => 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);
|
let main_rs = generate_main_rs(spec, shape);
|
||||||
|
|
||||||
Ok(HarnessSource {
|
Ok(HarnessSource {
|
||||||
|
|
@ -2348,7 +2362,7 @@ fn emit_graphql_resolver_harness(
|
||||||
field: &str,
|
field: &str,
|
||||||
) -> HarnessSource {
|
) -> HarnessSource {
|
||||||
let shim = probe_shim();
|
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 handler = &spec.entry_name;
|
||||||
let label = format!("{type_name}.{field}");
|
let label = format!("{type_name}.{field}");
|
||||||
let body = format!(
|
let body = format!(
|
||||||
|
|
@ -2571,6 +2585,36 @@ fn generate_cargo_toml_for_shape(cap: Cap, shape: RustShape) -> String {
|
||||||
cargo
|
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
|
/// Variant of [`generate_cargo_toml`] that conditionally pulls in
|
||||||
/// `percent-encoding` for the HEADER_INJECTION benign control fixture
|
/// `percent-encoding` for the HEADER_INJECTION benign control fixture
|
||||||
/// (it routes the value through `utf8_percent_encode` to land CRLF as
|
/// (it routes the value through `utf8_percent_encode` to land CRLF as
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ use nyx_scanner::dynamic::environment::{
|
||||||
MAX_WORKDIR_BYTES, capture_project_dependencies, capture_project_dependencies_with_context,
|
MAX_WORKDIR_BYTES, capture_project_dependencies, capture_project_dependencies_with_context,
|
||||||
stage_workdir_full,
|
stage_workdir_full,
|
||||||
};
|
};
|
||||||
|
use nyx_scanner::dynamic::framework::FrameworkBinding;
|
||||||
use nyx_scanner::dynamic::lang::materialize_runtime;
|
use nyx_scanner::dynamic::lang::materialize_runtime;
|
||||||
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
|
use nyx_scanner::dynamic::spec::{EntryKind, HarnessSpec, PayloadSlot, SpecDerivationStrategy};
|
||||||
use nyx_scanner::labels::Cap;
|
use nyx_scanner::labels::Cap;
|
||||||
|
|
@ -190,6 +191,144 @@ fn materialize_runtime_synthesises_pinned_manifest() {
|
||||||
assert!(content.contains(&spec.spec_hash));
|
assert!(content.contains(&spec.spec_hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn adapter_bound_spec(
|
||||||
|
lang: Lang,
|
||||||
|
entry_file: &str,
|
||||||
|
adapter: &str,
|
||||||
|
entry_kind: EntryKind,
|
||||||
|
) -> HarnessSpec {
|
||||||
|
HarnessSpec {
|
||||||
|
finding_id: format!("adapter-{adapter}"),
|
||||||
|
entry_file: entry_file.to_owned(),
|
||||||
|
entry_name: "run".to_owned(),
|
||||||
|
entry_kind: entry_kind.clone(),
|
||||||
|
lang,
|
||||||
|
toolchain_id: match lang {
|
||||||
|
Lang::Python => "python-3.11",
|
||||||
|
Lang::JavaScript | Lang::TypeScript => "node-20",
|
||||||
|
Lang::Java => "java-21",
|
||||||
|
Lang::Go => "go-1.21",
|
||||||
|
Lang::Rust => "rust-stable",
|
||||||
|
Lang::Php => "php-8.2",
|
||||||
|
Lang::Ruby => "ruby-3.2",
|
||||||
|
_ => "toolchain",
|
||||||
|
}
|
||||||
|
.to_owned(),
|
||||||
|
payload_slot: PayloadSlot::Param(0),
|
||||||
|
expected_cap: Cap::CODE_EXEC,
|
||||||
|
constraint_hints: vec![],
|
||||||
|
sink_file: entry_file.to_owned(),
|
||||||
|
sink_line: 1,
|
||||||
|
spec_hash: format!("hash-{adapter}"),
|
||||||
|
derivation: SpecDerivationStrategy::FromFlowSteps,
|
||||||
|
stubs_required: vec![],
|
||||||
|
framework: Some(FrameworkBinding {
|
||||||
|
adapter: adapter.to_owned(),
|
||||||
|
kind: entry_kind,
|
||||||
|
route: None,
|
||||||
|
request_params: vec![],
|
||||||
|
response_writer: None,
|
||||||
|
middleware: vec![],
|
||||||
|
}),
|
||||||
|
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn materialize_runtime_adds_framework_adapter_deps_without_imports() {
|
||||||
|
let root = TempDir::new().unwrap();
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
Lang::Python,
|
||||||
|
"task.py",
|
||||||
|
"scheduled-celery",
|
||||||
|
EntryKind::ScheduledJob {
|
||||||
|
schedule: Some("* * * * *".to_owned()),
|
||||||
|
},
|
||||||
|
"requirements.txt",
|
||||||
|
"celery",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::JavaScript,
|
||||||
|
"resolver.js",
|
||||||
|
"graphql-apollo",
|
||||||
|
EntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".to_owned(),
|
||||||
|
field: "user".to_owned(),
|
||||||
|
},
|
||||||
|
"package.json",
|
||||||
|
"@apollo/server",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Ruby,
|
||||||
|
"worker.rb",
|
||||||
|
"scheduled-sidekiq",
|
||||||
|
EntryKind::ScheduledJob { schedule: None },
|
||||||
|
"Gemfile",
|
||||||
|
"sidekiq",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Php,
|
||||||
|
"Middleware.php",
|
||||||
|
"middleware-laravel",
|
||||||
|
EntryKind::Middleware {
|
||||||
|
name: "AuthMiddleware".to_owned(),
|
||||||
|
},
|
||||||
|
"composer.json",
|
||||||
|
"laravel/framework",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Java,
|
||||||
|
"QuartzJob.java",
|
||||||
|
"scheduled-quartz",
|
||||||
|
EntryKind::ScheduledJob { schedule: None },
|
||||||
|
"pom.xml",
|
||||||
|
"org.quartz-scheduler",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Go,
|
||||||
|
"resolver.go",
|
||||||
|
"graphql-gqlgen",
|
||||||
|
EntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".to_owned(),
|
||||||
|
field: "user".to_owned(),
|
||||||
|
},
|
||||||
|
"go.mod",
|
||||||
|
"github.com/99designs/gqlgen",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Rust,
|
||||||
|
"resolver.rs",
|
||||||
|
"graphql-juniper",
|
||||||
|
EntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".to_owned(),
|
||||||
|
field: "user".to_owned(),
|
||||||
|
},
|
||||||
|
"Cargo.toml",
|
||||||
|
"juniper = \"0.16\"",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (lang, entry_file, adapter, entry_kind, manifest, needle) in cases {
|
||||||
|
std::fs::write(root.path().join(entry_file), "/* marker-only fixture */\n").unwrap();
|
||||||
|
let spec = adapter_bound_spec(lang, entry_file, adapter, entry_kind);
|
||||||
|
let captured = capture_project_dependencies(root.path(), &spec);
|
||||||
|
let stage = TempDir::new().unwrap();
|
||||||
|
let env = stage_workdir_full(&captured, stage.path(), &spec.spec_hash, lang)
|
||||||
|
.expect("stage workdir");
|
||||||
|
let artifacts = materialize_runtime(&env);
|
||||||
|
let (_, content) = artifacts
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.find(|(rel, _)| rel == manifest)
|
||||||
|
.unwrap_or_else(|| panic!("{adapter} did not materialize {manifest}"));
|
||||||
|
assert!(
|
||||||
|
content.contains(needle),
|
||||||
|
"{adapter} manifest {manifest} missing {needle}: {content}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workdir_is_importable_when_python_available() {
|
fn workdir_is_importable_when_python_available() {
|
||||||
// Acceptance bullet: "the route boots and the verifier reaches the
|
// Acceptance bullet: "the route boots and the verifier reaches the
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,33 @@ fn run_adapter(
|
||||||
.unwrap_or_else(|| panic!("{} did not fire on {fixture}", adapter.name()))
|
.unwrap_or_else(|| panic!("{} did not fire on {fixture}", adapter.name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn framework_bound_spec(
|
||||||
|
lang: Lang,
|
||||||
|
kind: EvEntryKind,
|
||||||
|
entry_name: &str,
|
||||||
|
entry_file: &str,
|
||||||
|
adapter: &str,
|
||||||
|
) -> HarnessSpec {
|
||||||
|
let mut spec = make_spec(lang, kind, entry_name, entry_file);
|
||||||
|
spec.framework = Some(FrameworkBinding {
|
||||||
|
adapter: adapter.to_owned(),
|
||||||
|
kind: spec.entry_kind.clone(),
|
||||||
|
route: None,
|
||||||
|
request_params: vec![],
|
||||||
|
response_writer: None,
|
||||||
|
middleware: vec![],
|
||||||
|
});
|
||||||
|
spec
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_file_content<'a>(files: &'a [(String, String)], rel: &str) -> &'a str {
|
||||||
|
files
|
||||||
|
.iter()
|
||||||
|
.find(|(path, _)| path == rel)
|
||||||
|
.map(|(_, content)| content.as_str())
|
||||||
|
.unwrap_or_else(|| panic!("{rel} missing from extra files: {files:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
fn detect_phase21_fp_fixture(
|
fn detect_phase21_fp_fixture(
|
||||||
adapter: &dyn FrameworkAdapter,
|
adapter: &dyn FrameworkAdapter,
|
||||||
lang: Lang,
|
lang: Lang,
|
||||||
|
|
@ -920,6 +947,98 @@ fn migration_php_harness_carries_sentinel_and_handler() {
|
||||||
assert!(h.source.contains("AddUsers"));
|
assert!(h.source.contains("AddUsers"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn phase21_harness_emitters_stage_framework_dependency_manifests() {
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
Lang::Python,
|
||||||
|
EvEntryKind::ScheduledJob {
|
||||||
|
schedule: Some("*/5 * * * *".into()),
|
||||||
|
},
|
||||||
|
"tick",
|
||||||
|
"tests/dynamic_fixtures/scheduled_job/celery/vuln.py",
|
||||||
|
"scheduled-celery",
|
||||||
|
"requirements.txt",
|
||||||
|
"celery",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::JavaScript,
|
||||||
|
EvEntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".into(),
|
||||||
|
field: "user".into(),
|
||||||
|
},
|
||||||
|
"resolveUser",
|
||||||
|
"tests/dynamic_fixtures/graphql_resolver/apollo/vuln.js",
|
||||||
|
"graphql-apollo",
|
||||||
|
"package.json",
|
||||||
|
"@apollo/server",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Ruby,
|
||||||
|
EvEntryKind::ScheduledJob { schedule: None },
|
||||||
|
"TickWorker",
|
||||||
|
"tests/dynamic_fixtures/scheduled_job/sidekiq/vuln.rb",
|
||||||
|
"scheduled-sidekiq",
|
||||||
|
"Gemfile",
|
||||||
|
"sidekiq",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Php,
|
||||||
|
EvEntryKind::Middleware {
|
||||||
|
name: "Audit".into(),
|
||||||
|
},
|
||||||
|
"Audit",
|
||||||
|
"tests/dynamic_fixtures/middleware/laravel/vuln.php",
|
||||||
|
"middleware-laravel",
|
||||||
|
"composer.json",
|
||||||
|
"laravel/framework",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Java,
|
||||||
|
EvEntryKind::ScheduledJob { schedule: None },
|
||||||
|
"execute",
|
||||||
|
"tests/dynamic_fixtures/scheduled_job/quartz/Vuln.java",
|
||||||
|
"scheduled-quartz",
|
||||||
|
"pom.xml",
|
||||||
|
"org.quartz-scheduler",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Go,
|
||||||
|
EvEntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".into(),
|
||||||
|
field: "user".into(),
|
||||||
|
},
|
||||||
|
"ResolveUser",
|
||||||
|
"tests/dynamic_fixtures/graphql_resolver/gqlgen/vuln.go",
|
||||||
|
"graphql-gqlgen",
|
||||||
|
"go.mod",
|
||||||
|
"github.com/99designs/gqlgen",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Lang::Rust,
|
||||||
|
EvEntryKind::GraphQLResolver {
|
||||||
|
type_name: "Query".into(),
|
||||||
|
field: "user".into(),
|
||||||
|
},
|
||||||
|
"resolve_user",
|
||||||
|
"tests/dynamic_fixtures/graphql_resolver/juniper/vuln.rs",
|
||||||
|
"graphql-juniper",
|
||||||
|
"Cargo.toml",
|
||||||
|
"juniper = \"0.16\"",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (lang, kind, entry_name, entry_file, adapter, manifest, needle) in cases {
|
||||||
|
let spec = framework_bound_spec(lang, kind, entry_name, entry_file, adapter);
|
||||||
|
let harness = lang::emit(&spec).expect("emit ok");
|
||||||
|
let manifest_content = extra_file_content(&harness.extra_files, manifest);
|
||||||
|
assert!(
|
||||||
|
manifest_content.contains(needle),
|
||||||
|
"{adapter} manifest {manifest} missing {needle}: {manifest_content}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Phase 21 acceptance: ≥75% Confirmed on each fixture set ──────────────────
|
// ── Phase 21 acceptance: ≥75% Confirmed on each fixture set ──────────────────
|
||||||
//
|
//
|
||||||
// The synthetic harnesses + adapter pairings give a 100% binding rate
|
// The synthetic harnesses + adapter pairings give a 100% binding rate
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue