From b80f2cacd06e81bed035b830be65e0fcef38422e Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 17 Sep 2026 19:41:06 -0700 Subject: [PATCH] Bound development snapshot retention while preserving live and edited builds --- easel/dev/README.md | 2 +- easel/dev/sync.mjs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/easel/dev/README.md b/easel/dev/README.md index b6716a3eff..904e7ee886 100644 --- a/easel/dev/README.md +++ b/easel/dev/README.md @@ -32,7 +32,7 @@ node easel/dev/install.mjs --repo ~/aesthetic-computer \ The installer creates `~/Applications/Aesel Dev.app`, `~/.local/bin/aesel-dev`, and the launch agent `computer.aesthetic.aesel-dev-sync`. State, logs, immutable source snapshots, and the atomic `current` link live in -`~/.local/share/aesel-dev/`. The native Electron shell is not replaced by source +`~/.local/share/aesel-dev/`. The last three snapshots, any live PTY snapshot, and locally edited snapshots are retained. The native Electron shell is not replaced by source sync. If Electron or npm dependency requirements change, syncing stops visibly until this installer is rerun with matching dependencies. No signing credentials are copied; the development shell is signed locally with an ad-hoc identity. diff --git a/easel/dev/sync.mjs b/easel/dev/sync.mjs index 6217ffc410..6860448924 100644 --- a/easel/dev/sync.mjs +++ b/easel/dev/sync.mjs @@ -26,6 +26,17 @@ export function modifiedFiles(directory, manifest) { return [...new Set([...Object.keys(actual),...Object.keys(expected)])].filter(name=>actual[name]!==expected[name]); } export function dependencyKey(pkg) { return digest(JSON.stringify({dependencies:pkg.dependencies,electron:pkg.devDependencies?.electron})); } +export function pruneBuilds(home, retain=3) { + // Keep recent builds, live PTY roots, and any locally edited snapshot. + let processes;try{processes=execFileSync('/bin/ps',['-axo','command='],{encoding:'utf8',maxBuffer:4*1024*1024});}catch{return;} + const current=fs.realpathSync(path.join(home,'current')); + const versions=path.join(home,'versions'); + const builds=fs.readdirSync(versions).filter(name=>/^[a-f0-9]{40}$/.test(name)).map(name=>path.join(versions,name)).filter(dir=>fs.existsSync(path.join(dir,'build.json'))).sort((a,b)=>fs.statSync(b).mtimeMs-fs.statSync(a).mtimeMs); + for(const dir of builds.slice(retain)){ + if(dir===current||processes.includes(dir))continue; + try{const manifest=JSON.parse(fs.readFileSync(path.join(dir,'build.json')));if(!modifiedFiles(path.join(dir,'easel'),manifest).length)fs.rmSync(dir,{recursive:true});}catch{} + } +} function writeJSON(file,value){const temp=file+'.tmp';fs.writeFileSync(temp,JSON.stringify(value,null,2)+'\n');fs.renameSync(temp,file);} export function sync({home=path.join(os.homedir(),'.local/share/aesel-dev'),fetch=true,build=source=>execFileSync('/bin/bash',[path.join(source,'desktop/scripts/build-credit-label.sh')],{timeout:120000,stdio:['ignore','pipe','pipe']})}={}) { const config=JSON.parse(fs.readFileSync(path.join(home,'config.json'))); @@ -66,7 +77,7 @@ export function sync({home=path.join(os.homedir(),'.local/share/aesel-dev'),fetc const next=path.join(home,`current-${process.pid}`);fs.symlinkSync(target,next);fs.renameSync(next,current); installed=manifest; } - state.installed={revision:installed.revision,tree:installed.tree};state.state='current';writeJSON(stateFile,state);return state; + state.installed={revision:installed.revision,tree:installed.tree};state.state='current';writeJSON(stateFile,state);pruneBuilds(home);return state; } catch(error) { const state={...previous,channel:'dev',state:'error',online:false,attemptedAt:new Date().toISOString(),error:String(error.message).split('\n')[0].slice(0,180)}; writeJSON(stateFile,state);throw error; -- 2.51.2