import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import {execFileSync} from 'node:child_process'; import {fileURLToPath} from 'node:url'; export const skillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); export function workspace(start = process.cwd()) { if (process.env.CHLOE_OS_ROOT) return checked(process.env.CHLOE_OS_ROOT); for (const seed of [start, skillDir]) { let p = path.resolve(seed); while (true) { if (fs.existsSync(path.join(p, 'chloe-workspace.json'))) return checked(p); const next = path.dirname(p); if (next === p) break; p = next; } } return checked(path.join(os.homedir(), 'chloe-os')); } function checked(p) { const root = fs.realpathSync(p); const c = JSON.parse(fs.readFileSync(path.join(root, 'chloe-workspace.json'), 'utf8')); if (c.schema !== 1 || c.deviceModel !== 'FiiO JM21') throw new Error('Not a CHLOE//OS workspace. Run the bootstrap first.'); return root; } export function run(command, args = [], options = {}) { return execFileSync(command, args, {encoding:'utf8', maxBuffer:32*1024*1024, ...options}); } export function sdk(root) { const candidates = [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT, path.join(root,'android-sdk'), path.join(os.homedir(),'Library/Android/sdk'), path.join(os.homedir(),'Android/Sdk')].filter(Boolean); return candidates.find(p => fs.existsSync(path.join(p,'platforms'))) || candidates[0]; } export function sdkTool(root, name, version = '35.0.0') { const exe = process.platform === 'win32' ? name + (name==='apksigner'?'.bat':'.exe') : name; const p = path.join(sdk(root),'build-tools',version,exe); if (!fs.existsSync(p)) throw new Error(`Install Android build-tools ${version}: missing ${p}`); return p; } export function adbBinary(root) { if (process.env.CHLOE_ADB) return process.env.CHLOE_ADB; const p = root && path.join(sdk(root),'platform-tools',process.platform==='win32'?'adb.exe':'adb'); return p && fs.existsSync(p) ? p : 'adb'; } export function javaEnv(root) { const candidates=[process.env.JAVA_HOME,'/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home','/usr/lib/jvm/java-17-openjdk-amd64'].filter(Boolean); const home=candidates.find(p=>fs.existsSync(path.join(p,'bin/java'))); if(!home) throw new Error('Set JAVA_HOME to Java 17.'); return {...process.env,JAVA_HOME:home,ANDROID_HOME:sdk(root),PATH:path.join(home,'bin')+path.delimiter+process.env.PATH}; } export function writeJSON(file, value) { fs.mkdirSync(path.dirname(file),{recursive:true});fs.writeFileSync(file,JSON.stringify(value,null,2)+'\n'); } export function slug(value) { if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value||'') || value.length>40) throw new Error('Use a short slug like moon-notes (letters, digits and hyphens).'); return value; } export function packageName(value) { if(!/^dev\.chloe\.creations\.[a-z][a-z0-9]*$/.test(value||''))throw new Error('New apps use dev.chloe.creations.NAME.'); return value; }