Files
ycode/scripts/ops.mjs
T

59 lines
4.3 KiB
JavaScript

#!/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;
}