mod action_executor;
mod app;
mod app_metadata;
mod authoring;
mod bootstrap;
mod fixture;
mod host;
mod inspection;
mod launcher;
mod lint;
mod memory;
mod pack;
mod planner;
mod preset;
mod preset_migration;
mod profile;
mod schema;
mod shell;
mod shell_action_executor;
mod sys;
mod sys_action_executor;
mod sys_bootstrap;
mod sys_manifest;
mod sys_model;
mod sys_profile;
mod trust;
mod validation;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
pub use action_executor::{
APP_OPERATION_JOURNAL_FILE, AppOperationExecutionV1, AppRecoveryReportV1,
};
pub use authoring::{
PRESET_AUTHORING_PLAN_SCHEMA_VERSION, PresetAuthoringPlanAssumptionsV1,
PresetAuthoringPlanReportV1, PresetAuthoringPlanSectionV1, plan_preset_path,
};
pub use fixture::{
PRESET_TEST_FIXTURE_FILE, PRESET_TEST_SCHEMA_VERSION, PresetTestCaseResultV1,
PresetTestReportV1, PresetTestSummaryV1, test_preset_path,
};
pub use host::{
FileKind, FileMetadata, FileSystemHost, FileSystemObservationHost, HostError, HostOperation,
NullObserver, PrivilegedFileSystemHost, PrivilegedOperationGuard, ProcessHost, ProcessIo,
ProcessOutput, ProcessRequest, RealHost, RuntimeEvent, RuntimeInteraction, RuntimeObserver,
SplitDnsHost, SplitDnsObservationHost, SplitDnsRequest, SplitDnsState,
};
pub(crate) use inspection::JournalInspection;
pub use inspection::{
AppFileInspection, DomainInspectionReport, InspectionChange, InspectionFileStatus,
ShellFileInspection,
};
pub use launcher::{
LinkConflict, LinkConflictKind, LinkReport, LinkSpec, UnlinkReport, command_path_for_name,
link_executables_with_host, link_is_current_with_host, link_stem,
unlink_managed_command_with_host,
};
pub use lint::{
PRESET_LINT_SCHEMA_VERSION, PresetLintDiagnosticV1, PresetLintReportV1, PresetLintSeverity,
PresetLintSummaryV1, lint_preset_path,
};
pub use memory::InMemoryHost;
pub use pack::{
PRESET_BUNDLE_SCHEMA_VERSION, PresetPackArtifactV1, PresetPackReportV1, pack_preset_path,
};
pub use planner::{
AppApprovedUpgradeOptions, AppArtifactPlanRequest, AppPlanRequest, AppRefreshPlanRequest,
OpaqueSecretVersion, PlanningInputVersions, ShellPlanRequest, SysBootstrapPlanRequest,
SysManagedPlanRequest, SysProfilePlanRequest,
};
pub use preset::{
PresetFile, PresetFileOrigin, PresetSnapshot, PresetSourceKind, PresetValidationIssue,
PresetValidationReport,
};
pub use preset_migration::{
PRESET_MIGRATION_SCHEMA_VERSION, PresetMigrationActionV1, PresetMigrationBaseline,
PresetMigrationDiagnosticV1, PresetMigrationEdit, PresetMigrationFileV1, PresetMigrationPlan,
PresetMigrationReportV1, PresetMigrationSeverityV1, PresetMigrationStatusV1,
PresetMigrationSummaryV1, plan_preset_migration, sha256,
};
pub use profile::{
PathUpdateStatus, SHELL_SENTINEL_END, SHELL_SENTINEL_START, ShellConfigUpdate,
ShellProfileRemoval, managed_profile_snippet, managed_shell_profile_path,
powershell_bin_assignment, powershell_quote, remove_shell_sentinel, shell_config_snippet,
shell_source_command, supports_completion_registration,
};
pub use schema::{
PRESET_SCHEMA_REFERENCE_VERSION, PresetSchemaReferenceV1, preset_schema_reference_v1,
};
pub use shell::{
BunDependencyMode, BunRuntimeSpec, ExternalShellMode, LinkRuntime, SHELL_MANIFEST_FILE,
SHELL_MANIFEST_SCHEMA_VERSION, ShellCacheReport, ShellCacheRequest, ShellCategory,
ShellCompletionReport, ShellFile, ShellLifecycleReport, ShellLifecycleRequest, ShellManifest,
ShellManifestEntry, ShellManifestUpdateScope, ShellScriptTemplate, ShellTarget,
ShellTemplateReport, ShellType, ShellUninstallReport, ShellUninstallRequest,
ShellUpgradeLifecycleReport, ShellUpgradeRequest, parse_shell_lifecycle_target,
};
pub use shell_action_executor::{
SHELL_OPERATION_JOURNAL_FILE, ShellOperationExecutionV1, ShellRecoveryReportV1,
};
pub use sys::{
ManagedFileReceipt, ManagedFileRemoveRequest, ManagedFileRequest, RECEIPT_VERSION,
ResourceConflict, ResourceOutcome, ResourcePlan, SYS_MANIFEST_FILE,
SYS_MANIFEST_SCHEMA_VERSION, SplitDnsDomainRequest, SplitDnsReceipt, SysDriverKind,
SysItemStatus, SysManagedAction, SysManagedReport, SysManagedRequest, SysRunEntry,
SysRunManifest, SystemReceipt, remove_split_dns_with_host, split_dns_receipt,
};
pub use sys_action_executor::{
SYS_OPERATION_JOURNAL_FILE, SysOperationExecutionV1, SysRecoveryReportV1,
};
pub use sys_bootstrap::{
SysBootstrapBatchReport, SysBootstrapBatchRequest, SysBootstrapReport, SysBootstrapRequest,
sys_install_requires_admin,
};
pub use sys_manifest::{parse_sys_manifest, validate_sys_manifest};
pub use sys_model::{
LoadedSysPreset, ResolvedSelection, SYS_PROFILE_PHASES, SelectionSource,
ShellProfileBlockPosition, SysDetection, SysDetectionProbe, SysInstall, SysInstalledRow,
SysItem, SysItemMode, SysItemOutcome, SysManifest, SysPackageProvider, SysProfile,
SysProfilePhase, SysShellIntegration, SysShellKind, SysUpdateRow, SysUpgradeReport,
};
pub use sys_profile::{SysProfileStateReport, SysProfileStateRequest};
pub use trust::ExternalCodeRequirementReport;
pub use validation::{
PRESET_VALIDATION_SCHEMA_VERSION, PresetCategoryValidation, PresetDiagnostic,
PresetDiagnosticSeverity, PresetValidationReportV1, PresetValidationSummary,
validate_preset_path,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RuntimeContext {
pub home_dir: PathBuf,
pub shine_dir: PathBuf,
pub presets_dir: PathBuf,
pub bin_dir: PathBuf,
pub cache_dir: PathBuf,
pub data_dir: PathBuf,
pub app_default_dest_root: PathBuf,
pub overlay_dir: Option<PathBuf>,
pub platform: RuntimePlatform,
pub shell: ShellType,
pub shell_config_paths: Vec<PathBuf>,
pub external_shell_mode: ExternalShellMode,
pub is_external_presets: bool,
pub trust_grants: Vec<crate::trust::TrustGrantV1>,
pub linux_split_dns_ready: bool,
pub running_as_admin: bool,
pub captured_unix_time: u64,
pub env: BTreeMap<String, String>,
pub path_env: Option<String>,
pub proxy_env: BTreeMap<String, String>,
}
impl RuntimeContext {
pub fn isolated(
home_dir: PathBuf,
shine_dir: PathBuf,
presets_dir: PathBuf,
bin_dir: PathBuf,
platform: RuntimePlatform,
) -> Self {
Self {
cache_dir: shine_dir.join("cache"),
data_dir: home_dir.join(".local/share"),
app_default_dest_root: home_dir.join(".config"),
home_dir: home_dir.clone(),
shine_dir,
presets_dir,
bin_dir,
overlay_dir: None,
platform,
shell: ShellType::default(),
shell_config_paths: vec![home_dir.join(".zshrc")],
external_shell_mode: ExternalShellMode::Snapshot,
is_external_presets: false,
trust_grants: Vec::new(),
linux_split_dns_ready: true,
running_as_admin: false,
captured_unix_time: 0,
env: BTreeMap::new(),
path_env: None,
proxy_env: BTreeMap::new(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum RuntimePlatform {
Macos,
Linux,
Windows,
}
impl RuntimePlatform {
pub const ALL: [Self; 3] = [Self::Macos, Self::Linux, Self::Windows];
pub fn current() -> Self {
if cfg!(target_os = "macos") {
Self::Macos
} else if cfg!(target_os = "windows") {
Self::Windows
} else {
Self::Linux
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Macos => "macos",
Self::Linux => "linux",
Self::Windows => "windows",
}
}
pub const fn is_unix(self) -> bool {
matches!(self, Self::Macos | Self::Linux)
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct LifecycleRequest {
pub target: Option<String>,
pub dry_run: bool,
pub force: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ResourceInspection {
pub logical_path: String,
pub installed: bool,
pub matches_snapshot: bool,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct RuntimeInspection {
pub resources: Vec<ResourceInspection>,
}
pub struct CoreRuntime<H> {
host: H,
context: RuntimeContext,
presets: PresetSnapshot,
}
impl<H> CoreRuntime<H> {
pub fn new(host: H, context: RuntimeContext, presets: PresetSnapshot) -> Self {
Self {
host,
context,
presets,
}
}
pub fn context(&self) -> &RuntimeContext {
&self.context
}
pub fn context_mut_for_cli(&mut self) -> &mut RuntimeContext {
&mut self.context
}
pub fn presets(&self) -> &PresetSnapshot {
&self.presets
}
pub fn host(&self) -> &H {
&self.host
}
pub fn into_host(self) -> H {
self.host
}
pub fn validate(&self) -> PresetValidationReport {
self.presets.validate()
}
}
impl<H: FileSystemHost> CoreRuntime<H> {
pub async fn inspect_snapshot(
&self,
installed_root: &Path,
) -> anyhow::Result<RuntimeInspection> {
let mut inspection = RuntimeInspection::default();
for (logical_path, expected) in self.presets.files() {
let installed_path = installed_root.join(logical_path);
let current = self.host.read(&installed_path).await;
let (installed, matches_snapshot) = match current {
Ok(current) => (true, current == *expected),
Err(error) if error.is_not_found() => (false, false),
Err(error) => return Err(error.into_anyhow("reading snapshot resource")),
};
inspection.resources.push(ResourceInspection {
logical_path: logical_path.clone(),
installed,
matches_snapshot,
});
}
Ok(inspection)
}
}
pub use app::{
AppArtifact, AppArtifactAction, AppArtifactRequest, AppCacheRequest, AppCategory,
AppDestinationRoot, AppFile, AppFileAction, AppFileLifecycleReport, AppGenerator,
AppGeneratorRequest, AppHook, AppHookAction, AppHookPhase, AppHookReport, AppHookRequest,
AppInspectionOptions, AppLifecycleReport, AppLifecycleRequest, AppListMode, AppRefreshRequest,
AppUninstallLifecycleRequest, AppUpgradeLifecycleReport, AppUpgradeRequest, ArtifactRuntime,
};
pub use bootstrap::{
PresetSnapshotRequest, PresetSnapshotSource, capture_embedded_preset_snapshot,
capture_preset_snapshot,
};
#[cfg(test)]
mod tests {
use super::*;
fn context() -> RuntimeContext {
RuntimeContext::isolated(
PathBuf::from("/home/test"),
PathBuf::from("/home/test/.shine"),
PathBuf::from("/home/test/.shine/presets"),
PathBuf::from("/home/test/.shine/bin"),
RuntimePlatform::Linux,
)
}
#[tokio::test]
async fn core_only_harness_validates_and_inspects_without_real_host_access() {
let host = InMemoryHost::new();
host.put_file("/installed/app/demo/config.toml", b"current".to_vec());
let presets = PresetSnapshot::builder(PresetSourceKind::External)
.file(
"app/demo/shine.toml",
b"[[files]]\nsource = \"config.toml\"\n".to_vec(),
)
.file("app/demo/config.toml", b"desired".to_vec())
.build();
let runtime = CoreRuntime::new(host, context(), presets);
assert!(runtime.validate().valid);
let inspection = runtime
.inspect_snapshot(Path::new("/installed"))
.await
.unwrap();
assert_eq!(inspection.resources.len(), 2);
assert!(inspection.resources.iter().any(|row| {
row.logical_path == "app/demo/config.toml" && row.installed && !row.matches_snapshot
}));
}
}