#!/usr/bin/env node import fs from 'node:fs/promises'; import path from 'node:path'; import {pathToFileURL} from 'node:url'; import {parseArgs,OpsError,withLock,run,npmInvocation,publicResult} from './ops/core.mjs'; import {resolveContext,gitState,pull} from './ops/git.mjs'; import {build} from './ops/build.mjs'; async function dev(ctx, action) { const {runDev}=await import('./ops/dev.mjs'); const result=await runDev(action,{repoRoot:ctx.root,timeoutMs:Math.min(ctx.timeoutMs,120000),stateDir:ctx.stateDir,logDir:ctx.logDir,prepare:false}); if(result.code==='TIMEOUT'||result.evidence?.possibleOrphans)ctx.keepLock=true; return result; } export async function execute(args) { let options,ctx; try { options=parseArgs(args);ctx=await resolveContext(options); const initial=await gitState(ctx); let result; if(options.action==='status'||options.action==='doctor') { let lock=null;try{lock=JSON.parse(await fs.readFile(path.join(ctx.stateDir,'workspace.lock'),'utf8'));}catch(error){if(error.code!=='ENOENT')lock={unreadable:true};} const runtime=await dev(ctx,'status'); result={status:'PASS',code:'STATUS',summary:'Read-only workspace status; no start, fetch or repair attempted.',git:initial,evidence:{lock,dev:runtime}}; if(options.action==='doctor') { let npm;try {const invocation=await npmInvocation();npm=await run(invocation.command,[...invocation.prefix,'--version'],{cwd:ctx.root,timeoutMs:10000});}catch(error){npm={error:error.message};} const checks={node:process.version,platform:process.platform,arch:process.arch,npm:npm.stdout?.trim()||null,npmError:npm.error||null,dependencies:await fs.access(path.join(ctx.root,'node_modules','electron-vite')).then(()=>true,()=>false),dev:runtime}; result={...result,status:npm.exitCode===0&&checks.dependencies?'PASS':'BLOCKED',code:npm.exitCode===0&&checks.dependencies?'DOCTOR_OK':'PREREQUISITES_MISSING',summary:'Read-only prerequisite checks; no installation or repair attempted.',evidence:{lock,...checks}}; } } else result=await withLock(ctx,options.action,async()=>{ if(['pull','build','package'].includes(options.action)&&process.platform==='win32') { const running=await dev(ctx,'status'); if(running.status!=='BLOCKED'||running.code!=='DEV_NOT_RUNNING')throw new OpsError('DEV_STATE_BLOCKS_MUTATION','Target dev is running or its ownership is unverifiable; stop it explicitly before changing shared build/worktree files.',{dev:running}); } if(options.action==='pull')return pull(ctx); if(options.action==='build'||options.action==='package')return build(ctx,options.action); let sync; if(options.action==='dev-latest') { if(process.platform!=='win32')throw new OpsError('WINDOWS_REQUIRED','Native dev-latest requires Windows; no fetch or checkout change was attempted.',{},'UNSUPPORTED'); // Do not rewrite a checkout underneath an existing or unverifiable dev process. const running=await dev(ctx,'status'); if(running.status!=='BLOCKED' || running.code!=='DEV_NOT_RUNNING')throw new OpsError('DEV_STATE_BLOCKS_SYNC','Stop the verified dev instance before synchronizing latest; no implicit process termination.',{dev:running}); sync=await pull(ctx); } const started=await dev(ctx,options.action==='restart-dev'?'restart':options.action==='stop-dev'?'stop':'dev'); return {...started,git:await gitState(ctx),...(sync?{sync:sync.evidence}:{})}; }); return {schemaVersion:1,action:options.action,root:ctx.root,...result,ok:result.status==='PASS',exitCode:{PASS:0,BLOCKED:2,FAIL:3,UNSUPPORTED:4}[result.status]??3}; } catch(error) { const status=error instanceof OpsError?error.status:'FAIL'; return {schemaVersion:1,action:options?.action||args[0]||null,root:ctx?.root||null,status,ok:false,code:error.code||'INTERNAL_ERROR',summary:error.message,evidence:error.evidence||{},exitCode:{BLOCKED:2,FAIL:3,UNSUPPORTED:4}[status]||3}; } } if(process.argv[1]&&import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href) { const result=publicResult(await execute(process.argv.slice(2))); console.log(process.argv.includes('--json')?JSON.stringify(result):`${result.status} ${result.code}: ${result.summary}\n${JSON.stringify(result,null,2)}`); process.exitCode=result.exitCode; }