@@ -39,6 +39,10 @@ import {
3939 writeTextFileIfChanged ,
4040} from "../lib/fs.ts" ;
4141import { resolveHackInvocation } from "../lib/hack-cli.ts" ;
42+ import {
43+ ensureBundledMutagenInstalled ,
44+ getMutagenPath ,
45+ } from "../lib/mutagen.ts" ;
4246import { isMac } from "../lib/os.ts" ;
4347import {
4448 findProjectContext ,
@@ -139,6 +143,7 @@ const handleDoctor: CommandHandlerFor<typeof doctorSpec> = async ({
139143 checkTool ( { name : "zellij (sessions)" , cmd : "zellij" , optional : true } )
140144 )
141145 ) ;
146+ results . push ( await runCheck ( s , "mutagen" , ( ) => checkMutagenBinary ( ) ) ) ;
142147 results . push (
143148 await runCheck ( s , "go" , ( ) =>
144149 checkTool ( { name : "go (optional)" , cmd : "go" , optional : true } )
@@ -488,6 +493,33 @@ function checkOptionalFzf(): CheckResult {
488493 return { name : "fzf (optional)" , status : "ok" , message : fzf } ;
489494}
490495
496+ async function checkMutagenBinary ( ) : Promise < CheckResult > {
497+ const mutagen = getMutagenPath ( ) ;
498+ if ( ! mutagen ) {
499+ return {
500+ name : "mutagen" ,
501+ status : "warn" ,
502+ message : "Not found (run: hack doctor --fix)" ,
503+ } ;
504+ }
505+
506+ const version = await exec ( [ mutagen , "version" ] , { stdin : "ignore" } ) ;
507+ if ( version . exitCode !== 0 ) {
508+ return {
509+ name : "mutagen" ,
510+ status : "warn" ,
511+ message : `${ mutagen } (version probe failed)` ,
512+ } ;
513+ }
514+
515+ const firstLine = version . stdout . trim ( ) . split ( "\n" ) [ 0 ] ?. trim ( ) ;
516+ return {
517+ name : "mutagen" ,
518+ status : "ok" ,
519+ message : firstLine && firstLine . length > 0 ? firstLine : mutagen ,
520+ } ;
521+ }
522+
491523async function checkDockerRunning ( ) : Promise < CheckResult > {
492524 const res = await exec ( [ "docker" , "info" ] , { stdin : "ignore" } ) ;
493525 return {
@@ -1408,6 +1440,8 @@ async function runDoctorFix(): Promise<void> {
14081440 return ;
14091441 }
14101442
1443+ await maybeInstallMutagenForDoctorFix ( ) ;
1444+
14111445 const dockerOk = await dockerInfoOk ( ) ;
14121446 if ( ! dockerOk ) {
14131447 note ( "Docker is not reachable; cannot apply fixes." , "doctor" ) ;
@@ -1440,6 +1474,34 @@ async function runDoctorFix(): Promise<void> {
14401474 await maybeMigrateDnsmasq ( ) ;
14411475}
14421476
1477+ async function maybeInstallMutagenForDoctorFix ( ) : Promise < void > {
1478+ if ( getMutagenPath ( ) ) {
1479+ return ;
1480+ }
1481+
1482+ const okMutagen = await confirmOrThrow ( {
1483+ message : "Install managed mutagen at ~/.hack/bin/mutagen?" ,
1484+ initialValue : true ,
1485+ } ) ;
1486+ if ( ! okMutagen ) {
1487+ return ;
1488+ }
1489+
1490+ const installed = await ensureBundledMutagenInstalled ( ) ;
1491+ if ( installed . ok ) {
1492+ note (
1493+ installed . installed
1494+ ? `Installed mutagen at ${ installed . mutagenPath } `
1495+ : `Mutagen already installed at ${ installed . mutagenPath } ` ,
1496+ "doctor"
1497+ ) ;
1498+ return ;
1499+ }
1500+
1501+ const detail = installed . message ? `: ${ installed . message } ` : "" ;
1502+ note ( `Mutagen install failed (${ installed . reason } ${ detail } )` , "doctor" ) ;
1503+ }
1504+
14431505async function confirmOrThrow ( opts : {
14441506 readonly message : string ;
14451507 readonly initialValue : boolean ;
0 commit comments