mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 11:24:35 +00:00
163 lines
4.2 KiB
TypeScript
163 lines
4.2 KiB
TypeScript
import path from 'path'
|
|
import { WriteStream, createWriteStream } from 'fs'
|
|
import { execFile } from 'child_process'
|
|
import { once } from 'stream'
|
|
import { createFile, moveFile } from '@sasjs/utils'
|
|
import { PreProgramVars, Session } from '../../types'
|
|
import { RunTimeType } from '../../utils'
|
|
import {
|
|
ExecutionVars,
|
|
createSASProgram,
|
|
createJSProgram,
|
|
createPythonProgram,
|
|
createRProgram
|
|
} from './'
|
|
|
|
export const processProgram = async (
|
|
program: string,
|
|
preProgramVariables: PreProgramVars,
|
|
vars: ExecutionVars,
|
|
session: Session,
|
|
weboutPath: string,
|
|
headersPath: string,
|
|
tokenFile: string,
|
|
runTime: RunTimeType,
|
|
logPath: string,
|
|
otherArgs?: any
|
|
) => {
|
|
if (runTime === RunTimeType.SAS) {
|
|
program = await createSASProgram(
|
|
program,
|
|
preProgramVariables,
|
|
vars,
|
|
session,
|
|
weboutPath,
|
|
headersPath,
|
|
tokenFile,
|
|
otherArgs
|
|
)
|
|
|
|
const codePath = path.join(session.path, 'code.sas')
|
|
|
|
// Creating this file in a RUNNING session will break out
|
|
// the autoexec loop and actually execute the program
|
|
// but - given it will take several milliseconds to create
|
|
// (which can mean SAS trying to run a partial program, or
|
|
// failing due to file lock) we first create the file THEN
|
|
// we rename it.
|
|
await createFile(codePath + '.bkp', program)
|
|
await moveFile(codePath + '.bkp', codePath)
|
|
|
|
// we now need to poll the session status
|
|
while (!session.completed) {
|
|
await delay(50)
|
|
}
|
|
} else {
|
|
let codePath: string
|
|
let executablePath: string
|
|
switch (runTime) {
|
|
case RunTimeType.JS:
|
|
program = await createJSProgram(
|
|
program,
|
|
preProgramVariables,
|
|
vars,
|
|
session,
|
|
weboutPath,
|
|
headersPath,
|
|
tokenFile,
|
|
otherArgs
|
|
)
|
|
codePath = path.join(session.path, 'code.js')
|
|
executablePath = process.nodeLoc!
|
|
|
|
break
|
|
case RunTimeType.PY:
|
|
program = await createPythonProgram(
|
|
program,
|
|
preProgramVariables,
|
|
vars,
|
|
session,
|
|
weboutPath,
|
|
headersPath,
|
|
tokenFile,
|
|
otherArgs
|
|
)
|
|
codePath = path.join(session.path, 'code.py')
|
|
executablePath = process.pythonLoc!
|
|
|
|
break
|
|
case RunTimeType.R:
|
|
program = await createRProgram(
|
|
program,
|
|
preProgramVariables,
|
|
vars,
|
|
session,
|
|
weboutPath,
|
|
headersPath,
|
|
tokenFile,
|
|
otherArgs
|
|
)
|
|
codePath = path.join(session.path, 'code.r')
|
|
executablePath = process.rLoc!
|
|
|
|
break
|
|
default:
|
|
throw new Error('Invalid runtime!')
|
|
}
|
|
|
|
await createFile(codePath, program)
|
|
|
|
// create a stream that will write to console outputs to log file
|
|
const writeStream = createWriteStream(logPath)
|
|
// waiting for the open event so that we can have underlying file descriptor
|
|
await once(writeStream, 'open')
|
|
|
|
await execFilePromise(executablePath, [codePath], writeStream)
|
|
.then(() => {
|
|
session.completed = true
|
|
process.logger.info('session completed', session)
|
|
})
|
|
.catch((err) => {
|
|
session.completed = true
|
|
session.crashed = err.toString()
|
|
process.logger.error('session crashed', session.id, session.crashed)
|
|
})
|
|
|
|
// copy the code file to log and end write stream
|
|
writeStream.end(program)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Promisified child_process.execFile
|
|
*
|
|
* @param file - The name or path of the executable file to run.
|
|
* @param args - List of string arguments.
|
|
* @param writeStream - Child process stdout and stderr will be piped to it.
|
|
*
|
|
* @returns {Promise<{ stdout: string, stderr: string }>}
|
|
*/
|
|
const execFilePromise = (
|
|
file: string,
|
|
args: string[],
|
|
writeStream: WriteStream
|
|
): Promise<{ stdout: string; stderr: string }> => {
|
|
return new Promise((resolve, reject) => {
|
|
const child = execFile(file, args, (err, stdout, stderr) => {
|
|
if (err) reject(err)
|
|
|
|
resolve({ stdout, stderr })
|
|
})
|
|
|
|
child.stdout?.on('data', (data) => {
|
|
writeStream.write(data)
|
|
})
|
|
|
|
child.stderr?.on('data', (data) => {
|
|
writeStream.write(data)
|
|
})
|
|
})
|
|
}
|
|
|
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|