158 lines
6.1 KiB
Text
158 lines
6.1 KiB
Text
# Cache Core — reads from the shared plugin cache directory.
|
|
# Written by ncl-sync daemon; read by this module and nu_plugin_nickel.
|
|
# Single writer principle: Nu NEVER writes to the cache dir directly.
|
|
|
|
# cache/metadata star-import was dead — dropped (ADR-025 Phase 3 Layer 2).
|
|
|
|
# Check if a directory has workspace markers.
|
|
def is-ws-dir [path: string]: nothing -> bool {
|
|
if ($path | is-empty) or (not ($path | path exists)) { return false }
|
|
let has_infra = ($path | path join "infra" | path exists)
|
|
let has_config = ($path | path join "config" "provisioning.ncl" | path exists)
|
|
let has_onto = ($path | path join ".ontology" | path exists)
|
|
$has_infra or $has_config or $has_onto
|
|
}
|
|
|
|
# Walk up from PWD to find workspace root (recursive).
|
|
def find-ws-up [path: string]: nothing -> string {
|
|
if ($path | is-empty) or $path == "/" { return "" }
|
|
if (is-ws-dir $path) { return $path }
|
|
let parent = ($path | path dirname)
|
|
if $parent == $path { return "" }
|
|
find-ws-up $parent
|
|
}
|
|
|
|
# Global cache directory (shared across workspaces, for files under $PROVISIONING).
|
|
def get-global-cache-dir []: nothing -> string {
|
|
let home = ($env.HOME? | default "~" | path expand)
|
|
let host_info = (do { sys host } | complete)
|
|
let is_mac = if $host_info.exit_code == 0 {
|
|
($host_info.stdout | get name | str downcase | str contains "darwin")
|
|
or ($host_info.stdout | get name | str downcase | str contains "macos")
|
|
} else {
|
|
($home | path join "Library" | path exists)
|
|
}
|
|
if $is_mac {
|
|
$home | path join "Library" "Caches" "provisioning" "config-cache"
|
|
} else {
|
|
$home | path join ".cache" "provisioning" "config-cache"
|
|
}
|
|
}
|
|
|
|
# Resolve cache directory FOR A SPECIFIC FILE. Priority:
|
|
# 1. $NCL_CACHE_DIR (explicit override, for CI/tests)
|
|
# 2. File under $PROVISIONING → global cache (extensions, schemas — shared)
|
|
# 3. File under a workspace (walk up from file path) → <ws>/.ncl-cache/
|
|
# 4. Fallback: global cache
|
|
#
|
|
# Must match resolve_cache_dir_for_file() in ncl-sync + plugin.
|
|
def get-cache-dir-for-file [file_path: string]: nothing -> string {
|
|
if ($env.NCL_CACHE_DIR? | is-not-empty) {
|
|
return $env.NCL_CACHE_DIR
|
|
}
|
|
# File under $PROVISIONING → global cache
|
|
let prov = ($env.PROVISIONING? | default "")
|
|
if ($prov | is-not-empty) and ($file_path | str starts-with $prov) {
|
|
return (get-global-cache-dir)
|
|
}
|
|
# File under a workspace → workspace-local cache
|
|
let ws = (find-ws-up ($file_path | path dirname))
|
|
if ($ws | is-not-empty) {
|
|
return ($ws | path join ".ncl-cache")
|
|
}
|
|
get-global-cache-dir
|
|
}
|
|
|
|
# Legacy helper (CWD-based) — kept for backwards compat in code paths that don't have
|
|
# the file path at hand. Prefer get-cache-dir-for-file.
|
|
def get-cache-base-dir []: nothing -> string {
|
|
if ($env.NCL_CACHE_DIR? | is-not-empty) { return $env.NCL_CACHE_DIR }
|
|
let ws = (find-ws-up $env.PWD)
|
|
if ($ws | is-not-empty) { return ($ws | path join ".ncl-cache") }
|
|
get-global-cache-dir
|
|
}
|
|
|
|
# Lookup a cache entry by pre-computed key.
|
|
# Only "nickel" type is backed by the shared plugin cache.
|
|
# Returns: { valid: bool, reason: string, data: any }
|
|
export def cache-lookup [
|
|
cache_type: string
|
|
cache_key: string
|
|
--ttl: int = 0
|
|
]: nothing -> record {
|
|
if $cache_type != "nickel" {
|
|
return { valid: false, reason: "type_not_supported", data: null }
|
|
}
|
|
let cache_file = ((get-cache-base-dir) | path join $"($cache_key).json")
|
|
if not ($cache_file | path exists) {
|
|
return { valid: false, reason: "cache_miss", data: null }
|
|
}
|
|
let result = (do { open $cache_file } | complete)
|
|
if $result.exit_code != 0 {
|
|
return { valid: false, reason: "read_error", data: null }
|
|
}
|
|
{ valid: true, reason: "hit", data: ($result.stdout | from json) }
|
|
}
|
|
|
|
# Signal ncl-sync daemon to (re-)export a list of NCL files.
|
|
# Nu never writes to the cache directly — only signals the daemon.
|
|
# Uses pid-unique sidecar + atomic rename to prevent concurrent-write corruption.
|
|
export def cache-write [
|
|
cache_type: string
|
|
cache_key: string
|
|
data: any
|
|
source_files: list
|
|
--ttl: int = 0
|
|
]: nothing -> nothing {
|
|
if $cache_type != "nickel" { return }
|
|
write-sync-request ($source_files | each {|f| { path: $f, import_paths: [] }})
|
|
}
|
|
|
|
# Write a sync-request sidecar file for ncl-sync to process.
|
|
# Each Nu process writes .sync-<pid>.tmp then renames to .sync-<pid>.json atomically.
|
|
export def write-sync-request [
|
|
requests: list # list of {path: string, import_paths: list}
|
|
]: nothing -> nothing {
|
|
let cache_dir = (get-cache-base-dir)
|
|
if not ($cache_dir | path exists) { return }
|
|
let pid = $nu.pid
|
|
let tmp_file = ($cache_dir | path join $".sync-($pid).tmp")
|
|
let json_file = ($cache_dir | path join $".sync-($pid).json")
|
|
$requests | to json | save --force $tmp_file
|
|
^mv $tmp_file $json_file
|
|
}
|
|
|
|
# Cache stats — count entries and total size in the shared cache dir.
|
|
export def get-cache-stats []: nothing -> record {
|
|
let cache_dir = (get-cache-base-dir)
|
|
if not ($cache_dir | path exists) {
|
|
return { total_entries: 0, total_size_mb: 0.0, by_type: {} }
|
|
}
|
|
let files = (do { ls $cache_dir } | complete)
|
|
if $files.exit_code != 0 {
|
|
return { total_entries: 0, total_size_mb: 0.0, by_type: {} }
|
|
}
|
|
let entries = ($files.stdout | where name =~ '\.json$' | where name !~ 'manifest' | length)
|
|
let size_bytes = ($files.stdout | where name =~ '\.json$' | get size | math sum)
|
|
{
|
|
total_entries: $entries,
|
|
total_size_mb: ($size_bytes / 1_048_576 | math round -p 2),
|
|
by_type: { nickel: $entries }
|
|
}
|
|
}
|
|
|
|
# Clear the shared cache directory (removes all .json files except manifest).
|
|
export def cache-clear-type [cache_type: string]: nothing -> nothing {
|
|
if $cache_type != "nickel" { return }
|
|
let cache_dir = (get-cache-base-dir)
|
|
if not ($cache_dir | path exists) { return }
|
|
do {
|
|
ls $cache_dir
|
|
| where name =~ '\.json$'
|
|
| where name !~ 'manifest'
|
|
| each {|f| rm $f.name}
|
|
} | ignore
|
|
}
|
|
|
|
# No-op — eviction is handled by ncl-sync daemon.
|
|
export def cleanup-expired-cache [max_size_mb: int = 100]: nothing -> nothing {}
|