import which from "which" ; import { access as fs_access , constants as fs_constaints } from "fs" ; import { exec as child_process_exec } from "child_process" ; import { promisify } from "util" ; const exec = promisify ( child_process_exec ) ; import { APPS } from "@cocalc/frontend/frame-editors/x11-editor/apps" ; import { ConfigurationAspect } from "@cocalc/frontend/project_configuration" ; import { Configuration , Capabilities , MainCapabilities , LIBRARY_INDEX_FILE , } from "@cocalc/frontend/project_configuration" ; import { syntax2tool , Tool as FormatTool } from "@cocalc/util/code-formatter" ; import { copy } from "@cocalc/util/misc" ; function construct_path ( ) { const env = process . env ; const entries = env . PATH ! . split ( ":" ) ; const home = env . HOME ?? "/home/user" ; entries . unshift ( `${ home }/.local/bin` ) ; entries . unshift ( `${ home }/bin` ) ; return entries . join ( ":" ) ; } const PATH = construct_path ( ) ; async function have ( name ) { return new Promise < boolean > ( ( resolve ) => { which ( name , { path : PATH } , function ( error , path ) { resolve ( error == null && path != null ) ; } ) ; } ) ; } const conf = { } ; async function x11_apps ( ) { const status = [ ] ; const KEYS = Object . keys ( APPS ) ; for ( const key of KEYS ) { const app = APPS [ key ] ; status . push ( have ( app . command != null ? app . command : key ) ) ; } const results = await Promise . all ( status ) ; const ret = { } ; KEYS . map ( ( name , idx ) => ( ret [ name ] = results [ idx ] ) ) ; return ret ; } async function get_x11 ( ) { return await have ( "xpra" ) ; } async function get_sage_info ( ) { const exists = await have ( "sage" ) ; let version = undefined ; if ( exists ) { try { const env = copy ( process . env ) ; env . PATH = PATH ; const info = ( await exec ( "sage --version" , { env } ) ) . stdout . trim ( ) ; const m = info . match ( /version ([\d+.]+[\d+])/ ) ; if ( m != null ) { const v = m [ 1 ] ; if ( v != null && v . length > 1 ) { version = v . split ( "." ) . map ( ( x ) => parseInt ( x ) ) ; } } } catch ( err ) { console . log ( "Problem fetching sage version info -- ignoring" , err ) ; } } return { exists , version } ; } async function get_jupyter ( ) { if ( await have ( "jupyter" ) ) { return { lab : await have ( "jupyter-lab" ) , notebook : await have ( "jupyter-notebook" ) , kernelspec : await have ( "jupyter-kernelspec" ) , } ; } else { return false ; } } async function get_latex ( hashsums ) { const prereq = [ "pdflatex" , "latexmk" , "synctex" ] ; const have_prereq = ( await Promise . all ( prereq . map ( have ) ) ) . every ( ( p ) => p ) ; return hashsums . sha1sum && have_prereq ; } async function get_spellcheck ( ) { return await have ( "aspell" ) ; } async function get_sshd ( ) { return await have ( "/usr/sbin/sshd" ) ; } async function get_html2pdf ( ) { return ( await have ( "chromium-browser" ) ) || ( await have ( "google-chrome" ) ) ; } async function get_pandoc ( ) { return await have ( "pandoc" ) ; } async function get_rmd ( ) { return await have ( "R" ) ; } async function get_jq ( ) { return await have ( "jq" ) ; } async function get_library ( ) { return new Promise < boolean > ( ( resolve ) => { fs_access ( LIBRARY_INDEX_FILE , fs_constaints . R_OK , ( err ) => { resolve ( err ? false : true ) ; } ) ; } ) ; } async function get_formatting ( ) { const status = [ ] ; const tools = new Array ( ... new Set ( Object . keys ( syntax2tool ) . map ( ( k ) => syntax2tool [ k ] ) ) ) ; tools . push ( "yapf3" , "black" , "autopep8" ) ; const tidy = have ( "tidy" ) ; for ( const tool of tools ) { if ( tool === ( "formatR" as FormatTool ) ) { status . push ( have ( "R" ) ) ; } else if ( tool == ( "bib-biber" as FormatTool ) ) { status . push ( have ( "biber" ) ) ; } else if ( tool === ( "html-tidy" as FormatTool ) || tool === ( "xml-tidy" as FormatTool ) ) { } else { status . push ( have ( tool ) ) ; } } const results = await Promise . all ( status ) ; const ret = { } ; tools . map ( ( tool , idx ) => ( ret [ tool ] = results [ idx ] ) ) ; ret [ "tidy" ] = await tidy ; ret [ "prettier" ] = true ; return ret ; } async function get_hashsums ( ) { return { sha1sum : await have ( "sha1sum" ) , sha256sum : await have ( "sha256sum" ) , md5sum : await have ( "md5sum" ) , } ; } async function capabilities ( ) { const sage_info_future = get_sage_info ( ) ; const hashsums = await get_hashsums ( ) ; const [ formatting , latex , jupyter , spellcheck , html2pdf , pandoc , sshd , library , x11 , rmd , ] = await Promise . all ( [ get_formatting ( ) , get_latex ( hashsums ) , get_jupyter ( ) , get_spellcheck ( ) , get_html2pdf ( ) , get_pandoc ( ) , get_sshd ( ) , get_library ( ) , get_x11 ( ) , get_rmd ( ) , ] ) ; const caps = { jupyter , formatting , hashsums , latex , sage : false , sage_version : undefined , x11 , rmd , jq : await get_jq ( ) , spellcheck , library , sshd , html2pdf , pandoc , } ; const sage = await sage_info_future ; caps . sage = sage . exists ; if ( caps . sage ) { caps . sage_version = sage . version ; } return caps ; } export async function get_configuration ( aspect , no_cache = false ) { const cached = conf [ aspect ] ; if ( cached != null && ! no_cache ) return cached ; const t0 = new Date ( ) . getTime ( ) ; const new_conf = ( async function ( ) { switch ( aspect ) { case "main" : return { timestamp : new Date ( ) , capabilities : await capabilities ( ) , } ; case "x11" : return { timestamp : new Date ( ) , capabilities : await x11_apps ( ) , } ; } } ) ( ) ; new_conf . timing_s = ( new Date ( ) . getTime ( ) - t0 ) / 1000 ; conf [ aspect ] = await new_conf ; return new_conf ; }