Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,30 @@ jobs:
env:
RUSTDOCFLAGS: "-D warnings"

wasm:
# std::time panics on wasm32-unknown-unknown, so bashkit routes clock reads
# through src/time_compat.rs (web-time on wasm32). This job keeps the crate
# compiling for browser/wasm-bindgen consumers; the feature list is the
# supported wasm32-unknown-unknown surface (python is excluded: monty pulls
# getrandom 0.3 without its wasm_js feature).
name: WASM (wasm32-unknown-unknown)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
with:
targets: wasm32-unknown-unknown

- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2

- name: Check bashkit for wasm32-unknown-unknown
run: cargo check -p bashkit --target wasm32-unknown-unknown --features scripted_tool,jq
env:
# getrandom needs an explicit JS backend opt-in on this target.
RUSTFLAGS: --cfg getrandom_backend="wasm_js" -D warnings

audit:
name: Audit
runs-on: ubuntu-latest
Expand Down
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ zeroize = "1"
# CSPRNG primitive and is already transitively in the dep tree via reqwest/rustls.
getrandom = { version = "0.4", features = ["wasm_js"] }

# Drop-in std::time::{Instant,SystemTime} replacement for wasm32-unknown-unknown,
# where std::time is a hard panic ("time not implemented on this platform" — no
# extension point, see library/std/src/sys/time/unsupported.rs). Backed by
# Performance.now()/Date.now() via web-sys on wasm32; re-exports std::time
# unchanged everywhere else. See crates/bashkit/src/time_compat.rs.
web-time = "1"

# CLI
# Intentionally NOT enabling clap's `env` feature: it makes `Arg::env(...)`
# read defaults from `std::env`, which would tunnel host process state into
Expand Down
6 changes: 6 additions & 0 deletions crates/bashkit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,9 @@ required-features = ["sqlite"]
# Additional tokio features needed only on native (not WASM)
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "fs"] }

# std::time::{Instant,SystemTime} panic on wasm32-unknown-unknown; see
# time_compat.rs. Only pulled in for wasm32 — everywhere else time_compat
# re-exports std::time directly with zero overhead.
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = { workspace = true }
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ async fn add_file_to_tar(
// Mtime (12 bytes, octal)
let mtime = metadata
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
write_octal(&mut header[136..148], mtime, 11);
Expand Down Expand Up @@ -403,7 +403,7 @@ fn add_directory_to_tar<'a>(
// Mtime
let mtime = metadata
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
write_octal(&mut header[136..148], mtime, 11);
Expand Down
6 changes: 3 additions & 3 deletions crates/bashkit/src/builtins/curl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,8 +513,8 @@ async fn execute_curl_request(
let multipart_body: Option<Vec<u8>> = if !form_fields.is_empty() {
let boundary = format!(
"----bashkit{:016x}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
crate::time_compat::SystemTime::now()
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
);
Expand Down Expand Up @@ -1758,7 +1758,7 @@ mod tests {
async fn set_modified_time(
&self,
path: &std::path::Path,
time: std::time::SystemTime,
time: crate::time_compat::SystemTime,
) -> Result<()> {
self.inner.set_modified_time(path, time).await
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ impl Builtin for Date {
let path = resolve_path(ctx.cwd, file);
match ctx.fs.stat(&path).await {
Ok(meta) => {
dt_utc = meta.modified.into();
dt_utc = crate::time_compat::to_chrono_utc(meta.modified);
epoch_input = false;
}
Err(_) => {
Expand Down
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/fileops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Decision: touch delegates mtime changes to the filesystem layer so `touch`
// and `touch -t` stay consistent across in-memory, overlay, and realfs backends.

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use chrono::{Datelike, Local, LocalResult, NaiveDate, TimeZone};
use std::path::Path;
use std::time::SystemTime;

use super::limits::MKTEMP_MAX_ATTEMPTS;
use super::{Builtin, Context, resolve_path};
Expand Down Expand Up @@ -376,7 +376,7 @@ fn parse_touch_timestamp(raw: &str) -> std::result::Result<SystemTime, String> {
LocalResult::None => return Err(format!("touch: invalid date format '{}'\n", raw)),
};

Ok(local.into())
Ok(crate::time_compat::from_chrono(local))
}

#[async_trait]
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ fn format_permissions(metadata: &crate::fs::Metadata) -> String {
fn default_stat_format(name: &str, metadata: &crate::fs::Metadata) -> String {
let modified = metadata
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/ls/find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ fn find_printf_format(
chars.next();
let secs = metadata
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.ok()
.map(|d| d.as_secs())
.unwrap_or(0);
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/ls/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ pub(super) fn format_long_entry(name: &str, metadata: &crate::fs::Metadata, huma
// Format modified time
let modified = metadata
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| {
let secs = d.as_secs();
// Simple date formatting: YYYY-MM-DD HH:MM
Expand Down
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,15 +400,15 @@ pub struct ExecutionExtensions {
#[derive(Debug, Clone)]
pub(crate) struct ExecutionDeadline {
#[allow(dead_code)]
started_at: std::time::Instant,
started_at: crate::time_compat::Instant,
timeout: std::time::Duration,
}

impl ExecutionDeadline {
/// Create a deadline anchored at the start of `Bash::exec*`.
pub(crate) fn new(timeout: std::time::Duration) -> Self {
Self {
started_at: std::time::Instant::now(),
started_at: crate::time_compat::Instant::now(),
timeout,
}
}
Expand Down
5 changes: 3 additions & 2 deletions crates/bashkit/src/builtins/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//!
//! Supports: `python -c "code"`, `python script.py`, stdin piping.

use crate::time_compat::Instant;
use async_trait::async_trait;
use chrono::{Datelike, Timelike};
use monty::{
Expand All @@ -30,7 +31,7 @@ use std::future::Future;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::time::Duration;

use super::{Builtin, Context, ExecutionDeadline, RuntimeLimits, resolve_path};
use crate::error::Result;
Expand Down Expand Up @@ -833,7 +834,7 @@ async fn handle_os_call(
Ok(meta) => {
let mtime = meta
.modified
.duration_since(std::time::UNIX_EPOCH)
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_secs_f64())
.unwrap_or(0.0);
let stat_obj = match meta.file_type {
Expand Down
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/rg/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7138,7 +7138,7 @@ mod tests {
fs_trait.write_file(p, content).await.unwrap();
}
for (path, secs) in mtimes {
let time = std::time::UNIX_EPOCH + std::time::Duration::from_secs(*secs);
let time = crate::time_compat::UNIX_EPOCH + std::time::Duration::from_secs(*secs);
fs_trait
.set_modified_time(Path::new(path), time)
.await
Expand Down Expand Up @@ -12593,7 +12593,7 @@ mod tests {
}
for (path, secs) in case.mtimes {
let host_path = tempdir.path().join(path.trim_start_matches('/'));
let time = std::time::UNIX_EPOCH + std::time::Duration::from_secs(*secs);
let time = crate::time_compat::UNIX_EPOCH + std::time::Duration::from_secs(*secs);
std::fs::File::open(host_path)
.expect("open timed rg fixture file")
.set_modified(time)
Expand Down
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/shuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ struct SmallRng {

impl SmallRng {
fn seed_from_now() -> Self {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
let nanos = crate::time_compat::SystemTime::now()
.duration_since(crate::time_compat::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0x123_4567_89AB_CDEF);
// SystemTime::now can return a tiny duration in tight loops; XOR
Expand Down
4 changes: 2 additions & 2 deletions crates/bashkit/src/builtins/sleep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ mod tests {

#[tokio::test]
async fn test_sleep_zero() {
let start = std::time::Instant::now();
let start = crate::time_compat::Instant::now();
let result = run_sleep(&["0"]).await;
let elapsed = start.elapsed();

Expand All @@ -105,7 +105,7 @@ mod tests {

#[tokio::test]
async fn test_sleep_fractional() {
let start = std::time::Instant::now();
let start = crate::time_compat::Instant::now();
let result = run_sleep(&["0.1"]).await;
let elapsed = start.elapsed();

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/builtins/sqlite/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
//! Both expose the same query API. The builtin layer above is agnostic to
//! which backend is active.

use crate::time_compat::Instant;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;

use turso_core::{Connection, Database, IO, MemoryIO, Numeric, OpenFlags, StepResult, Value};

Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@
//!
//! See `examples/custom_backend.rs` for a complete working example.

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, Metadata};
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
// while holding lock). This is intentional - corrupted state should not propagate.
#![allow(clippy::unwrap_used)]

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::collections::HashMap;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, FileSystem, FileSystemExt, FileType, Metadata};
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,9 @@ pub use search::{
pub use traits::{DirEntry, FileSystem, FileSystemExt, FileType, Metadata, fs_errors};

use crate::error::Result;
use crate::time_compat::SystemTime;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};
use std::time::SystemTime;

/// Filesystem implementation for logic-only shells.
///
Expand Down
6 changes: 3 additions & 3 deletions crates/bashkit/src/fs/mountable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
// while holding lock). This is intentional - corrupted state should not propagate.
#![allow(clippy::unwrap_used)]

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::collections::BTreeMap;
use std::io::Error as IoError;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, FileSystem, FileSystemExt, FileType, Metadata};
Expand Down Expand Up @@ -420,8 +420,8 @@ impl FileSystem for MountableFs {
file_type: FileType::Directory,
size: 0,
mode: 0o755,
modified: std::time::SystemTime::now(),
created: std::time::SystemTime::now(),
modified: crate::time_compat::SystemTime::now(),
created: crate::time_compat::SystemTime::now(),
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@
// while holding lock). This is intentional - corrupted state should not propagate.
#![allow(clippy::unwrap_used)]

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::collections::HashSet;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use std::time::SystemTime;

use super::limits::{FsLimits, FsUsage};
use super::memory::InMemoryFs;
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@
//!
//! See [`FsBackend`](super::FsBackend) for how to implement a backend.

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::io::Error as IoError;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;

use super::backend::FsBackend;
use super::limits::{FsLimits, FsUsage};
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/readonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
//! embedder wants a session that can inspect data but cannot persist or stage
//! any filesystem changes, including copies into the in-memory VFS.

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;

use super::limits::{FsLimits, FsUsage};
use super::traits::{DirEntry, FileSystem, FileSystemExt, Metadata};
Expand Down
2 changes: 1 addition & 1 deletion crates/bashkit/src/fs/realfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@
//! bashkit --mount-rw /path/to/out:/mnt/out -c 'echo hi > /mnt/out/result.txt'
//! ```

use crate::time_compat::SystemTime;
use async_trait::async_trait;
use std::io::{Error as IoError, ErrorKind};
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use super::backend::FsBackend;
use super::limits::{FsLimits, FsUsage};
Expand Down
Loading
Loading