mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
feat: adapt artifacts to new packaging and 2.5.6 (#14947)
This commit is contained in:
@@ -13,8 +13,8 @@ pub enum ArtifactRuntimeError {
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("invalid manifest at {path}")]
|
||||
InvalidManifest {
|
||||
#[error("invalid package metadata at {path}")]
|
||||
InvalidPackageMetadata {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: serde_json::Error,
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
use super::ArtifactRuntimeError;
|
||||
use super::ArtifactRuntimePlatform;
|
||||
use super::ExtractedRuntimeManifest;
|
||||
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,
|
||||
@@ -36,10 +38,7 @@ pub struct InstalledArtifactRuntime {
|
||||
root_dir: PathBuf,
|
||||
runtime_version: String,
|
||||
platform: ArtifactRuntimePlatform,
|
||||
manifest: ExtractedRuntimeManifest,
|
||||
node_path: PathBuf,
|
||||
build_js_path: PathBuf,
|
||||
render_cli_path: PathBuf,
|
||||
}
|
||||
|
||||
impl InstalledArtifactRuntime {
|
||||
@@ -48,19 +47,13 @@ impl InstalledArtifactRuntime {
|
||||
root_dir: PathBuf,
|
||||
runtime_version: String,
|
||||
platform: ArtifactRuntimePlatform,
|
||||
manifest: ExtractedRuntimeManifest,
|
||||
node_path: PathBuf,
|
||||
build_js_path: PathBuf,
|
||||
render_cli_path: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
root_dir,
|
||||
runtime_version,
|
||||
platform,
|
||||
manifest,
|
||||
node_path,
|
||||
build_js_path,
|
||||
render_cli_path,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,35 +62,16 @@ impl InstalledArtifactRuntime {
|
||||
root_dir: PathBuf,
|
||||
platform: ArtifactRuntimePlatform,
|
||||
) -> Result<Self, ArtifactRuntimeError> {
|
||||
let manifest_path = root_dir.join("manifest.json");
|
||||
let manifest_bytes =
|
||||
std::fs::read(&manifest_path).map_err(|source| ArtifactRuntimeError::Io {
|
||||
context: format!("failed to read {}", manifest_path.display()),
|
||||
source,
|
||||
})?;
|
||||
let manifest = serde_json::from_slice::<ExtractedRuntimeManifest>(&manifest_bytes)
|
||||
.map_err(|source| ArtifactRuntimeError::InvalidManifest {
|
||||
path: manifest_path,
|
||||
source,
|
||||
})?;
|
||||
let node_path = resolve_relative_runtime_path(&root_dir, &manifest.node.relative_path)?;
|
||||
let package_metadata = load_package_metadata(&root_dir)?;
|
||||
let build_js_path =
|
||||
resolve_relative_runtime_path(&root_dir, &manifest.entrypoints.build_js.relative_path)?;
|
||||
let render_cli_path = resolve_relative_runtime_path(
|
||||
&root_dir,
|
||||
&manifest.entrypoints.render_cli.relative_path,
|
||||
)?;
|
||||
resolve_relative_runtime_path(&root_dir, &package_metadata.build_js_relative_path)?;
|
||||
verify_required_runtime_path(&build_js_path)?;
|
||||
verify_required_runtime_path(&render_cli_path)?;
|
||||
|
||||
Ok(Self::new(
|
||||
root_dir,
|
||||
manifest.runtime_version.clone(),
|
||||
package_metadata.version,
|
||||
platform,
|
||||
manifest,
|
||||
node_path,
|
||||
build_js_path,
|
||||
render_cli_path,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -106,7 +80,7 @@ impl InstalledArtifactRuntime {
|
||||
&self.root_dir
|
||||
}
|
||||
|
||||
/// Returns the runtime version recorded in the extracted manifest.
|
||||
/// Returns the runtime version recorded in `package.json`.
|
||||
pub fn runtime_version(&self) -> &str {
|
||||
&self.runtime_version
|
||||
}
|
||||
@@ -116,33 +90,17 @@ impl InstalledArtifactRuntime {
|
||||
self.platform
|
||||
}
|
||||
|
||||
/// Returns the parsed extracted-runtime manifest.
|
||||
pub fn manifest(&self) -> &ExtractedRuntimeManifest {
|
||||
&self.manifest
|
||||
}
|
||||
|
||||
/// Returns the bundled Node executable path advertised by the runtime manifest.
|
||||
pub fn node_path(&self) -> &Path {
|
||||
&self.node_path
|
||||
}
|
||||
|
||||
/// Returns the artifact build entrypoint path.
|
||||
pub fn build_js_path(&self) -> &Path {
|
||||
&self.build_js_path
|
||||
}
|
||||
|
||||
/// Returns the artifact render CLI entrypoint path.
|
||||
pub fn render_cli_path(&self) -> &Path {
|
||||
&self.render_cli_path
|
||||
}
|
||||
|
||||
/// Resolves the best executable to use for artifact commands.
|
||||
///
|
||||
/// Preference order is the bundled Node path, then a machine Node install,
|
||||
/// then Electron from the machine or a Codex desktop app bundle.
|
||||
/// 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(
|
||||
Some(self.node_path()),
|
||||
system_node_runtime(),
|
||||
system_electron_runtime(),
|
||||
codex_app_runtime_candidates(),
|
||||
@@ -198,3 +156,128 @@ fn verify_required_runtime_path(path: &Path) -> Result<(), ArtifactRuntimeError>
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ pub fn can_manage_artifact_runtime() -> bool {
|
||||
|
||||
pub(crate) fn resolve_machine_js_runtime() -> Option<JsRuntime> {
|
||||
resolve_js_runtime_from_candidates(
|
||||
/*preferred_node_path*/ None,
|
||||
system_node_runtime(),
|
||||
system_electron_runtime(),
|
||||
codex_app_runtime_candidates(),
|
||||
@@ -82,20 +81,15 @@ pub(crate) fn resolve_machine_js_runtime() -> Option<JsRuntime> {
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_js_runtime_from_candidates(
|
||||
preferred_node_path: Option<&Path>,
|
||||
node_runtime: Option<JsRuntime>,
|
||||
electron_runtime: Option<JsRuntime>,
|
||||
codex_app_candidates: Vec<PathBuf>,
|
||||
) -> Option<JsRuntime> {
|
||||
preferred_node_path
|
||||
.and_then(node_runtime_from_path)
|
||||
.or(node_runtime)
|
||||
.or(electron_runtime)
|
||||
.or_else(|| {
|
||||
codex_app_candidates
|
||||
.into_iter()
|
||||
.find_map(|candidate| electron_runtime_from_path(&candidate))
|
||||
})
|
||||
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> {
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -79,12 +80,9 @@ impl ArtifactRuntimeReleaseLocator {
|
||||
/// Returns the default GitHub-release locator for a runtime version.
|
||||
pub fn default(runtime_version: impl Into<String>) -> Self {
|
||||
Self::new(
|
||||
match Url::parse(DEFAULT_RELEASE_BASE_URL) {
|
||||
Ok(url) => url,
|
||||
Err(error) => {
|
||||
panic!("hard-coded artifact runtime release base URL must be valid: {error}")
|
||||
}
|
||||
},
|
||||
Url::parse(DEFAULT_RELEASE_BASE_URL).unwrap_or_else(|error| {
|
||||
panic!("hard-coded artifact runtime release base URL must be valid: {error}")
|
||||
}),
|
||||
runtime_version,
|
||||
)
|
||||
}
|
||||
@@ -250,4 +248,8 @@ impl ManagedPackage for ArtifactRuntimePackage {
|
||||
) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,25 +13,3 @@ pub struct ReleaseManifest {
|
||||
pub node_version: Option<String>,
|
||||
pub platforms: BTreeMap<String, PackageReleaseArchive>,
|
||||
}
|
||||
|
||||
/// Manifest shipped inside the extracted runtime payload.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct ExtractedRuntimeManifest {
|
||||
pub schema_version: u32,
|
||||
pub runtime_version: String,
|
||||
pub node: RuntimePathEntry,
|
||||
pub entrypoints: RuntimeEntrypoints,
|
||||
}
|
||||
|
||||
/// A relative path entry inside an extracted runtime manifest.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct RuntimePathEntry {
|
||||
pub relative_path: String,
|
||||
}
|
||||
|
||||
/// Entrypoints required to build and render artifacts.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct RuntimeEntrypoints {
|
||||
pub build_js: RuntimePathEntry,
|
||||
pub render_cli: RuntimePathEntry,
|
||||
}
|
||||
|
||||
@@ -18,12 +18,10 @@ 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::ExtractedRuntimeManifest;
|
||||
pub use manifest::ReleaseManifest;
|
||||
pub use manifest::RuntimeEntrypoints;
|
||||
pub use manifest::RuntimePathEntry;
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user