chore: drop artifacts lib (#15864)

This commit is contained in:
jif-oai
2026-03-26 14:28:59 +00:00
committed by GitHub
Unverified
parent 7dac332c93
commit 6dcac41d53
14 changed files with 1 additions and 1580 deletions
-21
View File
@@ -1551,27 +1551,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "codex-artifacts"
version = "0.0.0"
dependencies = [
"codex-package-manager",
"flate2",
"pretty_assertions",
"reqwest",
"serde",
"serde_json",
"sha2",
"tar",
"tempfile",
"thiserror 2.0.18",
"tokio",
"url",
"which 8.0.0",
"wiremock",
"zip",
]
[[package]]
name = "codex-async-utils"
version = "0.0.0"
+1 -3
View File
@@ -83,7 +83,6 @@ members = [
"test-macros",
"package-manager",
"plugin",
"artifacts",
]
resolver = "2"
@@ -102,7 +101,6 @@ app_test_support = { path = "app-server/tests/common" }
codex-ansi-escape = { path = "ansi-escape" }
codex-analytics = { path = "analytics" }
codex-api = { path = "codex-api" }
codex-artifacts = { path = "artifacts" }
codex-code-mode = { path = "code-mode" }
codex-package-manager = { path = "package-manager" }
codex-app-server = { path = "app-server" }
@@ -397,7 +395,7 @@ unwrap_used = "deny"
ignored = [
"icu_provider",
"openssl-sys",
"codex-artifacts",
"codex-package-manager",
"codex-utils-readiness",
"codex-utils-template",
"codex-v8-poc",
-6
View File
@@ -1,6 +0,0 @@
load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "artifacts",
crate_name = "codex_artifacts",
)
-28
View File
@@ -1,28 +0,0 @@
[package]
name = "codex-artifacts"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
codex-package-manager = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["fs", "io-util", "process", "time"] }
url = { workspace = true }
which = { workspace = true }
[lints]
workspace = true
[dev-dependencies]
flate2 = { workspace = true }
pretty_assertions = { workspace = true }
sha2 = { workspace = true }
tar = { workspace = true }
tokio = { workspace = true, features = ["fs", "io-util", "macros", "process", "rt", "rt-multi-thread", "time"] }
wiremock = { workspace = true }
zip = { workspace = true }
-36
View File
@@ -1,36 +0,0 @@
# codex-artifacts
Runtime and process-management helpers for Codex artifact generation.
This crate has two main responsibilities:
- locating, validating, and optionally downloading the pinned artifact runtime
- spawning the artifact build or render command against that runtime
## Module layout
- `src/client.rs`
Runs build and render commands once a runtime has been resolved.
- `src/runtime/manager.rs`
Defines the release locator and the package-manager-backed runtime installer.
- `src/runtime/installed.rs`
Loads an extracted runtime from disk and validates its manifest and entrypoints.
- `src/runtime/js_runtime.rs`
Chooses the JavaScript executable to use for artifact execution.
- `src/runtime/manifest.rs`
Manifest types for release metadata and extracted runtimes.
- `src/runtime/error.rs`
Public runtime-loading and installation errors.
- `src/tests.rs`
Crate-level tests that exercise the public API and integration seams.
## Public API
- `ArtifactRuntimeManager`
Resolves or installs a runtime package into `~/.codex/packages/artifacts/...`.
- `load_cached_runtime`
Reads a previously installed runtime from a caller-provided cache root without attempting a download.
- `is_js_runtime_available`
Checks whether artifact execution is possible with either a cached runtime or a host JS runtime.
- `ArtifactsClient`
Executes artifact build or render requests using either a managed or preinstalled runtime.
-229
View File
@@ -1,229 +0,0 @@
use crate::ArtifactRuntimeError;
use crate::ArtifactRuntimeManager;
use crate::InstalledArtifactRuntime;
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::process::Stdio;
use std::time::Duration;
use tempfile::TempDir;
use thiserror::Error;
use tokio::fs;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
use tokio::time::timeout;
use url::Url;
const DEFAULT_EXECUTION_TIMEOUT: Duration = Duration::from_secs(30);
/// Executes artifact build commands against a resolved runtime.
#[derive(Clone, Debug)]
pub struct ArtifactsClient {
runtime_source: RuntimeSource,
}
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
enum RuntimeSource {
Managed(ArtifactRuntimeManager),
Installed(InstalledArtifactRuntime),
}
impl ArtifactsClient {
/// Creates a client that lazily resolves or downloads the runtime on demand.
pub fn from_runtime_manager(runtime_manager: ArtifactRuntimeManager) -> Self {
Self {
runtime_source: RuntimeSource::Managed(runtime_manager),
}
}
/// Creates a client pinned to an already loaded runtime.
pub fn from_installed_runtime(runtime: InstalledArtifactRuntime) -> Self {
Self {
runtime_source: RuntimeSource::Installed(runtime),
}
}
/// Executes artifact-building JavaScript against the configured runtime.
pub async fn execute_build(
&self,
request: ArtifactBuildRequest,
) -> Result<ArtifactCommandOutput, ArtifactsError> {
let runtime = self.resolve_runtime().await?;
let js_runtime = runtime.resolve_js_runtime()?;
let staging_dir = TempDir::new().map_err(|source| ArtifactsError::Io {
context: "failed to create build staging directory".to_string(),
source,
})?;
let script_path = staging_dir.path().join("artifact-build.mjs");
let build_entrypoint_url =
Url::from_file_path(runtime.build_js_path()).map_err(|()| ArtifactsError::Io {
context: format!(
"failed to convert artifact build entrypoint to a file URL: {}",
runtime.build_js_path().display()
),
source: std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"invalid artifact build entrypoint path",
),
})?;
let wrapped_script = build_wrapped_script(&build_entrypoint_url, &request.source);
fs::write(&script_path, wrapped_script)
.await
.map_err(|source| ArtifactsError::Io {
context: format!("failed to write {}", script_path.display()),
source,
})?;
let mut command = Command::new(js_runtime.executable_path());
command.arg(&script_path).current_dir(&request.cwd);
command.stdout(Stdio::piped()).stderr(Stdio::piped());
if js_runtime.requires_electron_run_as_node() {
command.env("ELECTRON_RUN_AS_NODE", "1");
}
for (key, value) in &request.env {
command.env(key, value);
}
run_command(
command,
request.timeout.unwrap_or(DEFAULT_EXECUTION_TIMEOUT),
)
.await
}
async fn resolve_runtime(&self) -> Result<InstalledArtifactRuntime, ArtifactsError> {
match &self.runtime_source {
RuntimeSource::Installed(runtime) => Ok(runtime.clone()),
RuntimeSource::Managed(manager) => manager.ensure_installed().await.map_err(Into::into),
}
}
}
/// Request payload for the artifact build command.
#[derive(Clone, Debug, Default)]
pub struct ArtifactBuildRequest {
pub source: String,
pub cwd: PathBuf,
pub timeout: Option<Duration>,
pub env: BTreeMap<String, String>,
}
/// Captured stdout, stderr, and exit status from an artifact subprocess.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactCommandOutput {
pub exit_code: Option<i32>,
pub stdout: String,
pub stderr: String,
}
impl ArtifactCommandOutput {
/// Returns whether the subprocess exited successfully.
pub fn success(&self) -> bool {
self.exit_code == Some(0)
}
}
/// Errors raised while spawning or awaiting artifact subprocesses.
#[derive(Debug, Error)]
pub enum ArtifactsError {
#[error(transparent)]
Runtime(#[from] ArtifactRuntimeError),
#[error("{context}")]
Io {
context: String,
#[source]
source: std::io::Error,
},
#[error("artifact command timed out after {timeout:?}")]
TimedOut { timeout: Duration },
}
fn build_wrapped_script(build_entrypoint_url: &Url, source: &str) -> String {
let mut wrapped = String::new();
wrapped.push_str("const artifactTool = await import(");
wrapped.push_str(
&serde_json::to_string(build_entrypoint_url.as_str()).unwrap_or_else(|error| {
panic!("artifact build entrypoint URL must serialize: {error}")
}),
);
wrapped.push_str(");\n");
wrapped.push_str(
r#"globalThis.artifactTool = artifactTool;
for (const [name, value] of Object.entries(artifactTool)) {
if (name === "default" || Object.prototype.hasOwnProperty.call(globalThis, name)) {
continue;
}
globalThis[name] = value;
}
"#,
);
wrapped.push_str(source);
wrapped.push('\n');
wrapped
}
async fn run_command(
mut command: Command,
execution_timeout: Duration,
) -> Result<ArtifactCommandOutput, ArtifactsError> {
let mut child = command.spawn().map_err(|source| ArtifactsError::Io {
context: "failed to spawn artifact command".to_string(),
source,
})?;
let mut stdout = child.stdout.take().ok_or_else(|| ArtifactsError::Io {
context: "artifact command stdout was not captured".to_string(),
source: std::io::Error::other("missing stdout pipe"),
})?;
let mut stderr = child.stderr.take().ok_or_else(|| ArtifactsError::Io {
context: "artifact command stderr was not captured".to_string(),
source: std::io::Error::other("missing stderr pipe"),
})?;
let stdout_task = tokio::spawn(async move {
let mut bytes = Vec::new();
stdout.read_to_end(&mut bytes).await.map(|_| bytes)
});
let stderr_task = tokio::spawn(async move {
let mut bytes = Vec::new();
stderr.read_to_end(&mut bytes).await.map(|_| bytes)
});
let status = match timeout(execution_timeout, child.wait()).await {
Ok(result) => result.map_err(|source| ArtifactsError::Io {
context: "failed while waiting for artifact command".to_string(),
source,
})?,
Err(_) => {
let _ = child.kill().await;
let _ = child.wait().await;
return Err(ArtifactsError::TimedOut {
timeout: execution_timeout,
});
}
};
let stdout_bytes = stdout_task
.await
.map_err(|source| ArtifactsError::Io {
context: "failed to join stdout reader".to_string(),
source: std::io::Error::other(source.to_string()),
})?
.map_err(|source| ArtifactsError::Io {
context: "failed to read artifact command stdout".to_string(),
source,
})?;
let stderr_bytes = stderr_task
.await
.map_err(|source| ArtifactsError::Io {
context: "failed to join stderr reader".to_string(),
source: std::io::Error::other(source.to_string()),
})?
.map_err(|source| ArtifactsError::Io {
context: "failed to read artifact command stderr".to_string(),
source,
})?;
Ok(ArtifactCommandOutput {
exit_code: status.code(),
stdout: String::from_utf8_lossy(&stdout_bytes).into_owned(),
stderr: String::from_utf8_lossy(&stderr_bytes).into_owned(),
})
}
-24
View File
@@ -1,24 +0,0 @@
mod client;
mod runtime;
#[cfg(all(test, not(windows)))]
mod tests;
pub use client::ArtifactBuildRequest;
pub use client::ArtifactCommandOutput;
pub use client::ArtifactsClient;
pub use client::ArtifactsError;
pub use runtime::ArtifactRuntimeError;
pub use runtime::ArtifactRuntimeManager;
pub use runtime::ArtifactRuntimeManagerConfig;
pub use runtime::ArtifactRuntimePlatform;
pub use runtime::ArtifactRuntimeReleaseLocator;
pub use runtime::DEFAULT_CACHE_ROOT_RELATIVE;
pub use runtime::DEFAULT_RELEASE_BASE_URL;
pub use runtime::DEFAULT_RELEASE_TAG_PREFIX;
pub use runtime::InstalledArtifactRuntime;
pub use runtime::JsRuntime;
pub use runtime::JsRuntimeKind;
pub use runtime::ReleaseManifest;
pub use runtime::can_manage_artifact_runtime;
pub use runtime::is_js_runtime_available;
pub use runtime::load_cached_runtime;
-28
View File
@@ -1,28 +0,0 @@
use codex_package_manager::PackageManagerError;
use std::path::PathBuf;
use thiserror::Error;
/// Errors raised while locating, validating, or installing an artifact runtime.
#[derive(Debug, Error)]
pub enum ArtifactRuntimeError {
#[error(transparent)]
PackageManager(#[from] PackageManagerError),
#[error("{context}")]
Io {
context: String,
#[source]
source: std::io::Error,
},
#[error("invalid package metadata at {path}")]
InvalidPackageMetadata {
path: PathBuf,
#[source]
source: serde_json::Error,
},
#[error("runtime path `{0}` is invalid")]
InvalidRuntimePath(String),
#[error(
"no compatible JavaScript runtime found for artifact runtime at {root_dir}; install Node or the Codex desktop app"
)]
MissingJsRuntime { root_dir: PathBuf },
}
-283
View File
@@ -1,283 +0,0 @@
use super::ArtifactRuntimeError;
use super::ArtifactRuntimePlatform;
use super::JsRuntime;
use super::codex_app_runtime_candidates;
use super::resolve_js_runtime_from_candidates;
use super::system_electron_runtime;
use super::system_node_runtime;
use std::collections::BTreeMap;
use std::path::Component;
use std::path::Path;
use std::path::PathBuf;
const ARTIFACT_TOOL_PACKAGE_NAME: &str = "@oai/artifact-tool";
/// Loads a previously installed runtime from a caller-provided cache root.
pub fn load_cached_runtime(
cache_root: &Path,
runtime_version: &str,
) -> Result<InstalledArtifactRuntime, ArtifactRuntimeError> {
let platform = ArtifactRuntimePlatform::detect_current()?;
let install_dir = cached_runtime_install_dir(cache_root, runtime_version, platform);
if !install_dir.exists() {
return Err(ArtifactRuntimeError::Io {
context: format!(
"artifact runtime {runtime_version} is not installed at {}",
install_dir.display()
),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "missing artifact runtime"),
});
}
InstalledArtifactRuntime::load(install_dir, platform)
}
/// A validated runtime installation extracted into the local package cache.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InstalledArtifactRuntime {
root_dir: PathBuf,
runtime_version: String,
platform: ArtifactRuntimePlatform,
build_js_path: PathBuf,
}
impl InstalledArtifactRuntime {
/// Creates an installed-runtime value from prevalidated paths.
pub fn new(
root_dir: PathBuf,
runtime_version: String,
platform: ArtifactRuntimePlatform,
build_js_path: PathBuf,
) -> Self {
Self {
root_dir,
runtime_version,
platform,
build_js_path,
}
}
/// Loads and validates an extracted runtime directory.
pub fn load(
root_dir: PathBuf,
platform: ArtifactRuntimePlatform,
) -> Result<Self, ArtifactRuntimeError> {
let package_metadata = load_package_metadata(&root_dir)?;
let build_js_path =
resolve_relative_runtime_path(&root_dir, &package_metadata.build_js_relative_path)?;
verify_required_runtime_path(&build_js_path)?;
Ok(Self::new(
root_dir,
package_metadata.version,
platform,
build_js_path,
))
}
/// Returns the extracted runtime root directory.
pub fn root_dir(&self) -> &Path {
&self.root_dir
}
/// Returns the runtime version recorded in `package.json`.
pub fn runtime_version(&self) -> &str {
&self.runtime_version
}
/// Returns the platform this runtime was installed for.
pub fn platform(&self) -> ArtifactRuntimePlatform {
self.platform
}
/// Returns the artifact build entrypoint path.
pub fn build_js_path(&self) -> &Path {
&self.build_js_path
}
/// Resolves the best executable to use for artifact commands.
///
/// Preference order is a machine Node install, then Electron from the
/// machine or a Codex desktop app bundle.
pub fn resolve_js_runtime(&self) -> Result<JsRuntime, ArtifactRuntimeError> {
resolve_js_runtime_from_candidates(
system_node_runtime(),
system_electron_runtime(),
codex_app_runtime_candidates(),
)
.ok_or_else(|| ArtifactRuntimeError::MissingJsRuntime {
root_dir: self.root_dir.clone(),
})
}
}
pub(crate) fn cached_runtime_install_dir(
cache_root: &Path,
runtime_version: &str,
platform: ArtifactRuntimePlatform,
) -> PathBuf {
cache_root.join(runtime_version).join(platform.as_str())
}
pub(crate) fn default_cached_runtime_root(codex_home: &Path) -> PathBuf {
codex_home.join(super::DEFAULT_CACHE_ROOT_RELATIVE)
}
fn resolve_relative_runtime_path(
root_dir: &Path,
relative_path: &str,
) -> Result<PathBuf, ArtifactRuntimeError> {
let relative = Path::new(relative_path);
if relative.as_os_str().is_empty() || relative.is_absolute() {
return Err(ArtifactRuntimeError::InvalidRuntimePath(
relative_path.to_string(),
));
}
if relative.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::Prefix(_) | Component::RootDir
)
}) {
return Err(ArtifactRuntimeError::InvalidRuntimePath(
relative_path.to_string(),
));
}
Ok(root_dir.join(relative))
}
fn verify_required_runtime_path(path: &Path) -> Result<(), ArtifactRuntimeError> {
if path.is_file() {
return Ok(());
}
Err(ArtifactRuntimeError::Io {
context: format!("required runtime file is missing: {}", path.display()),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "missing runtime file"),
})
}
pub(crate) fn detect_runtime_root(extraction_root: &Path) -> Result<PathBuf, ArtifactRuntimeError> {
if is_runtime_root(extraction_root) {
return Ok(extraction_root.to_path_buf());
}
let mut directory_candidates = Vec::new();
for entry in std::fs::read_dir(extraction_root).map_err(|source| ArtifactRuntimeError::Io {
context: format!("failed to read {}", extraction_root.display()),
source,
})? {
let entry = entry.map_err(|source| ArtifactRuntimeError::Io {
context: format!("failed to read entry in {}", extraction_root.display()),
source,
})?;
let path = entry.path();
if path.is_dir() {
directory_candidates.push(path);
}
}
if directory_candidates.len() == 1 {
let candidate = &directory_candidates[0];
if is_runtime_root(candidate) {
return Ok(candidate.clone());
}
}
Err(ArtifactRuntimeError::Io {
context: format!(
"failed to detect artifact runtime root under {}",
extraction_root.display()
),
source: std::io::Error::new(
std::io::ErrorKind::NotFound,
"missing artifact runtime root",
),
})
}
fn is_runtime_root(root_dir: &Path) -> bool {
let Ok(package_metadata) = load_package_metadata(root_dir) else {
return false;
};
let Ok(build_js_path) =
resolve_relative_runtime_path(root_dir, &package_metadata.build_js_relative_path)
else {
return false;
};
build_js_path.is_file()
}
struct PackageMetadata {
version: String,
build_js_relative_path: String,
}
fn load_package_metadata(root_dir: &Path) -> Result<PackageMetadata, ArtifactRuntimeError> {
#[derive(serde::Deserialize)]
struct PackageJson {
name: String,
version: String,
exports: PackageExports,
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum PackageExports {
Main(String),
Map(BTreeMap<String, String>),
}
impl PackageExports {
fn build_entrypoint(&self) -> Option<&str> {
match self {
Self::Main(path) => Some(path),
Self::Map(exports) => exports.get(".").map(String::as_str),
}
}
}
let package_json_path = root_dir.join("package.json");
let package_json_bytes =
std::fs::read(&package_json_path).map_err(|source| ArtifactRuntimeError::Io {
context: format!("failed to read {}", package_json_path.display()),
source,
})?;
let package_json =
serde_json::from_slice::<PackageJson>(&package_json_bytes).map_err(|source| {
ArtifactRuntimeError::InvalidPackageMetadata {
path: package_json_path.clone(),
source,
}
})?;
if package_json.name != ARTIFACT_TOOL_PACKAGE_NAME {
return Err(ArtifactRuntimeError::Io {
context: format!(
"unsupported artifact runtime package at {}; expected name `{ARTIFACT_TOOL_PACKAGE_NAME}`, got `{}`",
package_json_path.display(),
package_json.name
),
source: std::io::Error::new(
std::io::ErrorKind::InvalidData,
"unsupported package name",
),
});
}
let Some(build_js_relative_path) = package_json.exports.build_entrypoint() else {
return Err(ArtifactRuntimeError::Io {
context: format!(
"unsupported artifact runtime package at {}; expected `exports[\".\"]` to point at the JS entrypoint",
package_json_path.display()
),
source: std::io::Error::new(std::io::ErrorKind::InvalidData, "missing package export"),
});
};
Ok(PackageMetadata {
version: package_json.version,
build_js_relative_path: build_js_relative_path.trim_start_matches("./").to_string(),
})
}
@@ -1,171 +0,0 @@
use crate::ArtifactRuntimePlatform;
use crate::runtime::default_cached_runtime_root;
use crate::runtime::load_cached_runtime;
use std::path::Path;
use std::path::PathBuf;
use which::which;
const CODEX_APP_PRODUCT_NAMES: [&str; 6] = [
"Codex",
"Codex (Dev)",
"Codex (Agent)",
"Codex (Nightly)",
"Codex (Alpha)",
"Codex (Beta)",
];
/// The JavaScript runtime used to execute the artifact tool.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum JsRuntimeKind {
Node,
Electron,
}
/// A discovered JavaScript executable and the way it should be invoked.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JsRuntime {
executable_path: PathBuf,
kind: JsRuntimeKind,
}
impl JsRuntime {
pub(crate) fn node(executable_path: PathBuf) -> Self {
Self {
executable_path,
kind: JsRuntimeKind::Node,
}
}
pub(crate) fn electron(executable_path: PathBuf) -> Self {
Self {
executable_path,
kind: JsRuntimeKind::Electron,
}
}
/// Returns the executable to spawn for artifact commands.
pub fn executable_path(&self) -> &Path {
&self.executable_path
}
/// Returns whether the command must set `ELECTRON_RUN_AS_NODE=1`.
pub fn requires_electron_run_as_node(&self) -> bool {
self.kind == JsRuntimeKind::Electron
}
}
/// Returns `true` when artifact execution can find both runtime assets and a JS executable.
pub fn is_js_runtime_available(codex_home: &Path, runtime_version: &str) -> bool {
load_cached_runtime(&default_cached_runtime_root(codex_home), runtime_version)
.ok()
.and_then(|runtime| runtime.resolve_js_runtime().ok())
.or_else(resolve_machine_js_runtime)
.is_some()
}
/// Returns `true` when this machine can use the managed artifact runtime flow.
///
/// This is a platform capability check, not a cache or binary availability check.
/// Callers that rely on `ArtifactRuntimeManager::ensure_installed()` should use this
/// to decide whether the feature can be exposed on the current machine.
pub fn can_manage_artifact_runtime() -> bool {
ArtifactRuntimePlatform::detect_current().is_ok()
}
pub(crate) fn resolve_machine_js_runtime() -> Option<JsRuntime> {
resolve_js_runtime_from_candidates(
system_node_runtime(),
system_electron_runtime(),
codex_app_runtime_candidates(),
)
}
pub(crate) fn resolve_js_runtime_from_candidates(
node_runtime: Option<JsRuntime>,
electron_runtime: Option<JsRuntime>,
codex_app_candidates: Vec<PathBuf>,
) -> Option<JsRuntime> {
node_runtime.or(electron_runtime).or_else(|| {
codex_app_candidates
.into_iter()
.find_map(|candidate| electron_runtime_from_path(&candidate))
})
}
pub(crate) fn system_node_runtime() -> Option<JsRuntime> {
which("node")
.ok()
.and_then(|path| node_runtime_from_path(&path))
}
pub(crate) fn system_electron_runtime() -> Option<JsRuntime> {
which("electron")
.ok()
.and_then(|path| electron_runtime_from_path(&path))
}
pub(crate) fn node_runtime_from_path(path: &Path) -> Option<JsRuntime> {
path.is_file().then(|| JsRuntime::node(path.to_path_buf()))
}
pub(crate) fn electron_runtime_from_path(path: &Path) -> Option<JsRuntime> {
path.is_file()
.then(|| JsRuntime::electron(path.to_path_buf()))
}
pub(crate) fn codex_app_runtime_candidates() -> Vec<PathBuf> {
match std::env::consts::OS {
"macos" => {
let mut roots = vec![PathBuf::from("/Applications")];
if let Some(home) = std::env::var_os("HOME") {
roots.push(PathBuf::from(home).join("Applications"));
}
roots
.into_iter()
.flat_map(|root| {
CODEX_APP_PRODUCT_NAMES
.into_iter()
.map(move |product_name| {
root.join(format!("{product_name}.app"))
.join("Contents")
.join("MacOS")
.join(product_name)
})
})
.collect()
}
"windows" => {
let mut roots = Vec::new();
if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
roots.push(PathBuf::from(local_app_data).join("Programs"));
}
if let Some(program_files) = std::env::var_os("ProgramFiles") {
roots.push(PathBuf::from(program_files));
}
if let Some(program_files_x86) = std::env::var_os("ProgramFiles(x86)") {
roots.push(PathBuf::from(program_files_x86));
}
roots
.into_iter()
.flat_map(|root| {
CODEX_APP_PRODUCT_NAMES
.into_iter()
.map(move |product_name| {
root.join(product_name).join(format!("{product_name}.exe"))
})
})
.collect()
}
"linux" => [PathBuf::from("/opt"), PathBuf::from("/usr/lib")]
.into_iter()
.flat_map(|root| {
CODEX_APP_PRODUCT_NAMES
.into_iter()
.map(move |product_name| root.join(product_name).join(product_name))
})
.collect(),
_ => Vec::new(),
}
}
-255
View File
@@ -1,255 +0,0 @@
use super::ArtifactRuntimeError;
use super::ArtifactRuntimePlatform;
use super::InstalledArtifactRuntime;
use super::ReleaseManifest;
use super::detect_runtime_root;
use codex_package_manager::ManagedPackage;
use codex_package_manager::PackageManager;
use codex_package_manager::PackageManagerConfig;
use codex_package_manager::PackageManagerError;
use codex_package_manager::PackageReleaseArchive;
use reqwest::Client;
use std::path::Path;
use std::path::PathBuf;
use url::Url;
/// Release tag prefix used for artifact runtime assets.
pub const DEFAULT_RELEASE_TAG_PREFIX: &str = "artifact-runtime-v";
/// Relative cache root for installed artifact runtimes under `codex_home`.
pub const DEFAULT_CACHE_ROOT_RELATIVE: &str = "packages/artifacts";
/// Base URL used by default when downloading runtime assets from GitHub releases.
pub const DEFAULT_RELEASE_BASE_URL: &str = "https://github.com/openai/codex/releases/download/";
/// Describes where a particular artifact runtime release can be downloaded from.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactRuntimeReleaseLocator {
base_url: Url,
runtime_version: String,
release_tag_prefix: String,
}
impl ArtifactRuntimeReleaseLocator {
/// Creates a locator for a runtime version under a release base URL.
pub fn new(base_url: Url, runtime_version: impl Into<String>) -> Self {
Self {
base_url,
runtime_version: runtime_version.into(),
release_tag_prefix: DEFAULT_RELEASE_TAG_PREFIX.to_string(),
}
}
/// Overrides the release-tag prefix used when constructing asset names.
pub fn with_tag_prefix(mut self, release_tag_prefix: impl Into<String>) -> Self {
self.release_tag_prefix = release_tag_prefix.into();
self
}
/// Returns the release asset base URL.
pub fn base_url(&self) -> &Url {
&self.base_url
}
/// Returns the expected runtime version.
pub fn runtime_version(&self) -> &str {
&self.runtime_version
}
/// Returns the full release tag for the runtime version.
pub fn release_tag(&self) -> String {
format!("{}{}", self.release_tag_prefix, self.runtime_version)
}
/// Returns the expected manifest filename for the release.
pub fn manifest_file_name(&self) -> String {
format!("{}-manifest.json", self.release_tag())
}
/// Returns the manifest URL for this runtime release.
pub fn manifest_url(&self) -> Result<Url, PackageManagerError> {
self.base_url
.join(&format!(
"{}/{}",
self.release_tag(),
self.manifest_file_name()
))
.map_err(PackageManagerError::InvalidBaseUrl)
}
/// Returns the default GitHub-release locator for a runtime version.
pub fn default(runtime_version: impl Into<String>) -> Self {
Self::new(
Url::parse(DEFAULT_RELEASE_BASE_URL).unwrap_or_else(|error| {
panic!("hard-coded artifact runtime release base URL must be valid: {error}")
}),
runtime_version,
)
}
}
/// Configuration for resolving artifact runtimes under a Codex home directory.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtifactRuntimeManagerConfig {
package_manager: PackageManagerConfig<ArtifactRuntimePackage>,
release: ArtifactRuntimeReleaseLocator,
}
impl ArtifactRuntimeManagerConfig {
/// Creates a runtime-manager config from a Codex home and explicit release locator.
pub fn new(codex_home: PathBuf, release: ArtifactRuntimeReleaseLocator) -> Self {
Self {
package_manager: PackageManagerConfig::new(
codex_home,
ArtifactRuntimePackage::new(release.clone()),
),
release,
}
}
/// Creates a runtime-manager config that downloads from the default GitHub release location.
pub fn with_default_release(codex_home: PathBuf, runtime_version: impl Into<String>) -> Self {
Self::new(
codex_home,
ArtifactRuntimeReleaseLocator::default(runtime_version),
)
}
/// Overrides the runtime cache root.
pub fn with_cache_root(mut self, cache_root: PathBuf) -> Self {
self.package_manager = self.package_manager.with_cache_root(cache_root);
self
}
/// Returns the runtime cache root.
pub fn cache_root(&self) -> PathBuf {
self.package_manager.cache_root()
}
/// Returns the release locator used by this config.
pub fn release(&self) -> &ArtifactRuntimeReleaseLocator {
&self.release
}
}
/// Package-manager-backed artifact runtime resolver and installer.
#[derive(Clone, Debug)]
pub struct ArtifactRuntimeManager {
package_manager: PackageManager<ArtifactRuntimePackage>,
config: ArtifactRuntimeManagerConfig,
}
impl ArtifactRuntimeManager {
/// Creates a runtime manager using the default `reqwest` client.
pub fn new(config: ArtifactRuntimeManagerConfig) -> Self {
let package_manager = PackageManager::new(config.package_manager.clone());
Self {
package_manager,
config,
}
}
/// Creates a runtime manager with a caller-provided HTTP client.
pub fn with_client(config: ArtifactRuntimeManagerConfig, client: Client) -> Self {
let package_manager = PackageManager::with_client(config.package_manager.clone(), client);
Self {
package_manager,
config,
}
}
/// Returns the manager configuration.
pub fn config(&self) -> &ArtifactRuntimeManagerConfig {
&self.config
}
/// Returns the installed runtime if it is already present and valid.
pub async fn resolve_cached(
&self,
) -> Result<Option<InstalledArtifactRuntime>, ArtifactRuntimeError> {
self.package_manager.resolve_cached().await
}
/// Returns the installed runtime, downloading and caching it if necessary.
pub async fn ensure_installed(&self) -> Result<InstalledArtifactRuntime, ArtifactRuntimeError> {
self.package_manager.ensure_installed().await
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct ArtifactRuntimePackage {
release: ArtifactRuntimeReleaseLocator,
}
impl ArtifactRuntimePackage {
fn new(release: ArtifactRuntimeReleaseLocator) -> Self {
Self { release }
}
}
impl ManagedPackage for ArtifactRuntimePackage {
type Error = ArtifactRuntimeError;
type Installed = InstalledArtifactRuntime;
type ReleaseManifest = ReleaseManifest;
fn default_cache_root_relative(&self) -> &str {
DEFAULT_CACHE_ROOT_RELATIVE
}
fn version(&self) -> &str {
self.release.runtime_version()
}
fn manifest_url(&self) -> Result<Url, PackageManagerError> {
self.release.manifest_url()
}
fn archive_url(&self, archive: &PackageReleaseArchive) -> Result<Url, PackageManagerError> {
self.release
.base_url()
.join(&format!(
"{}/{}",
self.release.release_tag(),
archive.archive
))
.map_err(PackageManagerError::InvalidBaseUrl)
}
fn release_version<'a>(&self, manifest: &'a Self::ReleaseManifest) -> &'a str {
&manifest.runtime_version
}
fn platform_archive(
&self,
manifest: &Self::ReleaseManifest,
platform: ArtifactRuntimePlatform,
) -> Result<PackageReleaseArchive, Self::Error> {
manifest
.platforms
.get(platform.as_str())
.cloned()
.ok_or_else(|| {
PackageManagerError::MissingPlatform(platform.as_str().to_string()).into()
})
}
fn install_dir(&self, cache_root: &Path, platform: ArtifactRuntimePlatform) -> PathBuf {
cache_root.join(self.version()).join(platform.as_str())
}
fn installed_version<'a>(&self, package: &'a Self::Installed) -> &'a str {
package.runtime_version()
}
fn load_installed(
&self,
root_dir: PathBuf,
platform: ArtifactRuntimePlatform,
) -> Result<Self::Installed, Self::Error> {
InstalledArtifactRuntime::load(root_dir, platform)
}
fn detect_extracted_root(&self, extraction_root: &Path) -> Result<PathBuf, Self::Error> {
detect_runtime_root(extraction_root)
}
}
@@ -1,15 +0,0 @@
use codex_package_manager::PackageReleaseArchive;
use serde::Deserialize;
use serde::Serialize;
use std::collections::BTreeMap;
/// Release metadata published alongside the packaged artifact runtime.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct ReleaseManifest {
pub schema_version: u32,
pub runtime_version: String,
pub release_tag: String,
#[serde(default)]
pub node_version: Option<String>,
pub platforms: BTreeMap<String, PackageReleaseArchive>,
}
-28
View File
@@ -1,28 +0,0 @@
mod error;
mod installed;
mod js_runtime;
mod manager;
mod manifest;
pub use codex_package_manager::PackagePlatform as ArtifactRuntimePlatform;
pub use error::ArtifactRuntimeError;
pub use installed::InstalledArtifactRuntime;
pub use installed::load_cached_runtime;
pub use js_runtime::JsRuntime;
pub use js_runtime::JsRuntimeKind;
pub use js_runtime::can_manage_artifact_runtime;
pub use js_runtime::is_js_runtime_available;
pub use manager::ArtifactRuntimeManager;
pub use manager::ArtifactRuntimeManagerConfig;
pub use manager::ArtifactRuntimeReleaseLocator;
pub use manager::DEFAULT_CACHE_ROOT_RELATIVE;
pub use manager::DEFAULT_RELEASE_BASE_URL;
pub use manager::DEFAULT_RELEASE_TAG_PREFIX;
pub use manifest::ReleaseManifest;
pub(crate) use installed::default_cached_runtime_root;
pub(crate) use installed::detect_runtime_root;
pub(crate) use js_runtime::codex_app_runtime_candidates;
pub(crate) use js_runtime::resolve_js_runtime_from_candidates;
pub(crate) use js_runtime::system_electron_runtime;
pub(crate) use js_runtime::system_node_runtime;
-453
View File
@@ -1,453 +0,0 @@
use crate::ArtifactBuildRequest;
use crate::ArtifactCommandOutput;
use crate::ArtifactRuntimeManager;
use crate::ArtifactRuntimeManagerConfig;
use crate::ArtifactRuntimePlatform;
use crate::ArtifactRuntimeReleaseLocator;
use crate::ArtifactsClient;
use crate::DEFAULT_CACHE_ROOT_RELATIVE;
use crate::ReleaseManifest;
use crate::load_cached_runtime;
use codex_package_manager::ArchiveFormat;
use codex_package_manager::PackageReleaseArchive;
use flate2::Compression;
use flate2::write::GzEncoder;
use pretty_assertions::assert_eq;
use sha2::Digest;
use sha2::Sha256;
use std::collections::BTreeMap;
use std::fs;
use std::io::Cursor;
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use tar::Builder as TarBuilder;
use tempfile::TempDir;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
use wiremock::matchers::method;
use wiremock::matchers::path;
use zip::ZipWriter;
use zip::write::SimpleFileOptions;
#[test]
fn release_locator_builds_manifest_url() {
let locator = ArtifactRuntimeReleaseLocator::new(
url::Url::parse("https://example.test/releases/").unwrap_or_else(|error| panic!("{error}")),
"0.1.0",
);
let url = locator
.manifest_url()
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(
url.as_str(),
"https://example.test/releases/artifact-runtime-v0.1.0/artifact-runtime-v0.1.0-manifest.json"
);
}
#[test]
fn default_release_locator_uses_openai_codex_github_releases() {
let locator = ArtifactRuntimeReleaseLocator::default("0.1.0");
let url = locator
.manifest_url()
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(
url.as_str(),
"https://github.com/openai/codex/releases/download/artifact-runtime-v0.1.0/artifact-runtime-v0.1.0-manifest.json"
);
}
#[test]
fn load_cached_runtime_reads_installed_runtime() {
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let runtime_version = "2.5.6";
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let install_dir = codex_home
.path()
.join(DEFAULT_CACHE_ROOT_RELATIVE)
.join(runtime_version)
.join(platform.as_str());
write_installed_runtime(&install_dir, runtime_version);
let runtime = load_cached_runtime(
&codex_home.path().join(DEFAULT_CACHE_ROOT_RELATIVE),
runtime_version,
)
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(runtime.runtime_version(), runtime_version);
assert_eq!(runtime.platform(), platform);
assert!(
runtime
.build_js_path()
.ends_with(Path::new("dist/artifact_tool.mjs"))
);
}
#[test]
fn load_cached_runtime_requires_build_entrypoint() {
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let runtime_version = "2.5.6";
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let install_dir = codex_home
.path()
.join(DEFAULT_CACHE_ROOT_RELATIVE)
.join(runtime_version)
.join(platform.as_str());
write_installed_runtime(&install_dir, runtime_version);
fs::remove_file(install_dir.join("dist/artifact_tool.mjs"))
.unwrap_or_else(|error| panic!("{error}"));
let error = load_cached_runtime(
&codex_home.path().join(DEFAULT_CACHE_ROOT_RELATIVE),
runtime_version,
)
.unwrap_err();
assert_eq!(
error.to_string(),
format!(
"required runtime file is missing: {}",
install_dir.join("dist/artifact_tool.mjs").display()
)
);
}
#[tokio::test]
async fn ensure_installed_downloads_and_extracts_zip_runtime() {
let server = MockServer::start().await;
let runtime_version = "2.5.6";
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let archive_name = format!(
"artifact-runtime-v{runtime_version}-{}.zip",
platform.as_str()
);
let archive_bytes = build_zip_archive(runtime_version);
let archive_sha = format!("{:x}", Sha256::digest(&archive_bytes));
let manifest = ReleaseManifest {
schema_version: 1,
runtime_version: runtime_version.to_string(),
release_tag: format!("artifact-runtime-v{runtime_version}"),
node_version: None,
platforms: BTreeMap::from([(
platform.as_str().to_string(),
PackageReleaseArchive {
archive: archive_name.clone(),
sha256: archive_sha,
format: ArchiveFormat::Zip,
size_bytes: Some(archive_bytes.len() as u64),
},
)]),
};
Mock::given(method("GET"))
.and(path(format!(
"/artifact-runtime-v{runtime_version}/artifact-runtime-v{runtime_version}-manifest.json"
)))
.respond_with(ResponseTemplate::new(200).set_body_json(&manifest))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(format!(
"/artifact-runtime-v{runtime_version}/{archive_name}"
)))
.respond_with(ResponseTemplate::new(200).set_body_bytes(archive_bytes))
.mount(&server)
.await;
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let locator = ArtifactRuntimeReleaseLocator::new(
url::Url::parse(&format!("{}/", server.uri())).unwrap_or_else(|error| panic!("{error}")),
runtime_version,
);
let manager = ArtifactRuntimeManager::new(ArtifactRuntimeManagerConfig::new(
codex_home.path().to_path_buf(),
locator,
));
let runtime = manager
.ensure_installed()
.await
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(runtime.runtime_version(), runtime_version);
assert_eq!(runtime.platform(), platform);
assert!(
runtime
.build_js_path()
.ends_with(Path::new("dist/artifact_tool.mjs"))
);
}
#[test]
fn load_cached_runtime_requires_package_export() {
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let runtime_version = "2.5.6";
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let install_dir = codex_home
.path()
.join(DEFAULT_CACHE_ROOT_RELATIVE)
.join(runtime_version)
.join(platform.as_str());
write_installed_runtime(&install_dir, runtime_version);
fs::write(
install_dir.join("package.json"),
serde_json::json!({
"name": "@oai/artifact-tool",
"version": runtime_version,
"type": "module",
})
.to_string(),
)
.unwrap_or_else(|error| panic!("{error}"));
let error = load_cached_runtime(
&codex_home.path().join(DEFAULT_CACHE_ROOT_RELATIVE),
runtime_version,
)
.unwrap_err();
assert_eq!(
error.to_string(),
format!(
"invalid package metadata at {}",
install_dir.join("package.json").display()
)
);
}
#[tokio::test]
async fn ensure_installed_downloads_and_extracts_tar_gz_runtime() {
let server = MockServer::start().await;
let runtime_version = "2.5.6";
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let archive_name = format!(
"artifact-runtime-v{runtime_version}-{}.tar.gz",
platform.as_str()
);
let archive_bytes = build_tar_gz_archive(runtime_version);
let archive_sha = format!("{:x}", Sha256::digest(&archive_bytes));
let manifest = ReleaseManifest {
schema_version: 1,
runtime_version: runtime_version.to_string(),
release_tag: format!("artifact-runtime-v{runtime_version}"),
node_version: None,
platforms: BTreeMap::from([(
platform.as_str().to_string(),
PackageReleaseArchive {
archive: archive_name.clone(),
sha256: archive_sha,
format: ArchiveFormat::TarGz,
size_bytes: Some(archive_bytes.len() as u64),
},
)]),
};
Mock::given(method("GET"))
.and(path(format!(
"/artifact-runtime-v{runtime_version}/artifact-runtime-v{runtime_version}-manifest.json"
)))
.respond_with(ResponseTemplate::new(200).set_body_json(&manifest))
.mount(&server)
.await;
Mock::given(method("GET"))
.and(path(format!(
"/artifact-runtime-v{runtime_version}/{archive_name}"
)))
.respond_with(ResponseTemplate::new(200).set_body_bytes(archive_bytes))
.mount(&server)
.await;
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let locator = ArtifactRuntimeReleaseLocator::new(
url::Url::parse(&format!("{}/", server.uri())).unwrap_or_else(|error| panic!("{error}")),
runtime_version,
);
let manager = ArtifactRuntimeManager::new(ArtifactRuntimeManagerConfig::new(
codex_home.path().to_path_buf(),
locator,
));
let runtime = manager
.ensure_installed()
.await
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(runtime.runtime_version(), runtime_version);
assert_eq!(runtime.platform(), platform);
assert!(
runtime
.build_js_path()
.ends_with(Path::new("dist/artifact_tool.mjs"))
);
}
#[test]
fn load_cached_runtime_uses_custom_cache_root() {
let codex_home = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let runtime_version = "2.5.6";
let custom_cache_root = codex_home.path().join("runtime-cache");
let platform =
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}"));
let install_dir = custom_cache_root
.join(runtime_version)
.join(platform.as_str());
write_installed_runtime(&install_dir, runtime_version);
let config = ArtifactRuntimeManagerConfig::with_default_release(
codex_home.path().to_path_buf(),
runtime_version,
)
.with_cache_root(custom_cache_root);
let runtime = load_cached_runtime(&config.cache_root(), runtime_version)
.unwrap_or_else(|error| panic!("{error}"));
assert_eq!(runtime.runtime_version(), runtime_version);
assert_eq!(runtime.platform(), platform);
}
#[tokio::test]
#[cfg(unix)]
async fn artifacts_client_execute_build_writes_wrapped_script_and_env() {
let temp = TempDir::new().unwrap_or_else(|error| panic!("{error}"));
let runtime_root = temp.path().join("runtime");
write_installed_runtime(&runtime_root, "2.5.6");
let runtime = crate::InstalledArtifactRuntime::load(
runtime_root,
ArtifactRuntimePlatform::detect_current().unwrap_or_else(|error| panic!("{error}")),
)
.unwrap_or_else(|error| panic!("{error}"));
let client = ArtifactsClient::from_installed_runtime(runtime);
let output = client
.execute_build(ArtifactBuildRequest {
source: concat!(
"console.log(typeof artifacts);\n",
"console.log(typeof codexArtifacts);\n",
"console.log(artifactTool.ok);\n",
"console.log(ok);\n",
"console.error('stderr-ok');\n",
"console.log('stdout-ok');\n"
)
.to_string(),
cwd: temp.path().to_path_buf(),
timeout: Some(Duration::from_secs(5)),
env: BTreeMap::new(),
})
.await
.unwrap_or_else(|error| panic!("{error}"));
assert_success(&output);
assert_eq!(output.stderr.trim(), "stderr-ok");
assert_eq!(
output.stdout.lines().collect::<Vec<_>>(),
vec!["undefined", "undefined", "true", "true", "stdout-ok"]
);
}
fn assert_success(output: &ArtifactCommandOutput) {
assert!(output.success());
assert_eq!(output.exit_code, Some(0));
}
fn write_installed_runtime(install_dir: &Path, runtime_version: &str) {
fs::create_dir_all(install_dir.join("dist")).unwrap_or_else(|error| panic!("{error}"));
fs::write(
install_dir.join("package.json"),
serde_json::json!({
"name": "@oai/artifact-tool",
"version": runtime_version,
"type": "module",
"exports": {
".": "./dist/artifact_tool.mjs",
}
})
.to_string(),
)
.unwrap_or_else(|error| panic!("{error}"));
fs::write(
install_dir.join("dist/artifact_tool.mjs"),
"export const ok = true;\n",
)
.unwrap_or_else(|error| panic!("{error}"));
}
fn build_zip_archive(runtime_version: &str) -> Vec<u8> {
let mut bytes = Cursor::new(Vec::new());
{
let mut zip = ZipWriter::new(&mut bytes);
let options = SimpleFileOptions::default();
let package_json = serde_json::json!({
"name": "@oai/artifact-tool",
"version": runtime_version,
"type": "module",
"exports": {
".": "./dist/artifact_tool.mjs",
}
})
.to_string()
.into_bytes();
zip.start_file("artifact-runtime/package.json", options)
.unwrap_or_else(|error| panic!("{error}"));
zip.write_all(&package_json)
.unwrap_or_else(|error| panic!("{error}"));
zip.start_file("artifact-runtime/dist/artifact_tool.mjs", options)
.unwrap_or_else(|error| panic!("{error}"));
zip.write_all(b"export const ok = true;\n")
.unwrap_or_else(|error| panic!("{error}"));
zip.finish().unwrap_or_else(|error| panic!("{error}"));
}
bytes.into_inner()
}
fn build_tar_gz_archive(runtime_version: &str) -> Vec<u8> {
let mut bytes = Vec::new();
{
let encoder = GzEncoder::new(&mut bytes, Compression::default());
let mut archive = TarBuilder::new(encoder);
let package_json = serde_json::json!({
"name": "@oai/artifact-tool",
"version": runtime_version,
"type": "module",
"exports": {
".": "./dist/artifact_tool.mjs",
}
})
.to_string()
.into_bytes();
let mut package_header = tar::Header::new_gnu();
package_header.set_mode(0o644);
package_header.set_size(package_json.len() as u64);
package_header.set_cksum();
archive
.append_data(
&mut package_header,
"package/package.json",
package_json.as_slice(),
)
.unwrap_or_else(|error| panic!("{error}"));
let build_js = b"export const ok = true;\n";
let mut build_header = tar::Header::new_gnu();
build_header.set_mode(0o644);
build_header.set_size(build_js.len() as u64);
build_header.set_cksum();
archive
.append_data(
&mut build_header,
"package/dist/artifact_tool.mjs",
&build_js[..],
)
.unwrap_or_else(|error| panic!("{error}"));
archive.finish().unwrap_or_else(|error| panic!("{error}"));
}
bytes
}