Add more typings (#6082)

This commit is contained in:
Matt Godbolt
2024-02-04 13:33:19 -06:00
committed by GitHub
parent 31dd08be80
commit c26a2924fc
4 changed files with 52 additions and 38 deletions

View File

@@ -36,18 +36,19 @@ import type {FilenameTransformFunc, UnprocessedExecResult} from '../types/execut
import {logger} from './logger.js'; import {logger} from './logger.js';
import {propsFor} from './properties.js'; import {propsFor} from './properties.js';
import {Graceful} from './node-graceful.js'; import {Graceful} from './node-graceful.js';
import {unwrapString} from './assert.js'; import {unwrapString, unwrap, assert} from './assert.js';
import os from 'os'; import os from 'os';
import {Stream} from 'stream';
type NsJailOptions = { type NsJailOptions = {
args: string[]; args: string[];
options: ExecutionOptions; options: ExecutionOptions;
filenameTransform: FilenameTransformFunc; filenameTransform: FilenameTransformFunc | undefined;
}; };
const execProps = propsFor('execution'); const execProps = propsFor('execution');
function setupOnError(stream, name) { function setupOnError(stream: Stream, name: string) {
if (stream === undefined) return; if (stream === undefined) return;
stream.on('error', err => { stream.on('error', err => {
logger.error(`Error with ${name} stream:`, err); logger.error(`Error with ${name} stream:`, err);
@@ -116,7 +117,7 @@ export function executeDirect(
streams.stderr += '\nKilled - processing time exceeded\n'; streams.stderr += '\nKilled - processing time exceeded\n';
}, timeoutMs); }, timeoutMs);
function setupStream(stream, name) { function setupStream(stream: Stream, name: string) {
if (stream === undefined) return; if (stream === undefined) return;
stream.on('data', data => { stream.on('data', data => {
if (streams.truncated) return; if (streams.truncated) return;
@@ -222,7 +223,7 @@ export function getNsJailOptions(
} }
const homeDir = '/app'; const homeDir = '/app';
let filenameTransform; let filenameTransform: FilenameTransformFunc | undefined;
if (options.customCwd) { if (options.customCwd) {
let replacement = options.customCwd; let replacement = options.customCwd;
if (options.appHome) { if (options.appHome) {
@@ -314,28 +315,28 @@ export function getExecuteCEWrapperOptions(command: string, args: string[], opti
return getCeWrapperOptions('execute', command, args, options); return getCeWrapperOptions('execute', command, args, options);
} }
function sandboxNsjail(command, args, options) { function sandboxNsjail(command: string, args: string[], options: ExecutionOptions) {
logger.info('Sandbox execution via nsjail', {command, args}); logger.info('Sandbox execution via nsjail', {command, args});
const nsOpts = getSandboxNsjailOptions(command, args, options); const nsOpts = getSandboxNsjailOptions(command, args, options);
return executeDirect(execProps<string>('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); return executeDirect(execProps<string>('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform);
} }
function executeNsjail(command, args, options) { function executeNsjail(command: string, args: string[], options: ExecutionOptions) {
const nsOpts = getNsJailOptions('execute', command, args, options); const nsOpts = getNsJailOptions('execute', command, args, options);
return executeDirect(execProps<string>('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); return executeDirect(execProps<string>('nsjail'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform);
} }
function sandboxCEWrapper(command, args, options) { function sandboxCEWrapper(command: string, args: string[], options: ExecutionOptions) {
const nsOpts = getSandboxCEWrapperOptions(command, args, options); const nsOpts = getSandboxCEWrapperOptions(command, args, options);
return executeDirect(execProps<string>('cewrapper'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); return executeDirect(execProps<string>('cewrapper'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform);
} }
function executeCEWrapper(command, args, options) { function executeCEWrapper(command: string, args: string[], options: ExecutionOptions) {
const nsOpts = getExecuteCEWrapperOptions(command, args, options); const nsOpts = getExecuteCEWrapperOptions(command, args, options);
return executeDirect(execProps<string>('cewrapper'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform); return executeDirect(execProps<string>('cewrapper'), nsOpts.args, nsOpts.options, nsOpts.filenameTransform);
} }
function withFirejailTimeout(args: string[], options?) { function withFirejailTimeout(args: string[], options?: ExecutionOptions) {
if (options && options.timeoutMs) { if (options && options.timeoutMs) {
// const ExtraWallClockLeewayMs = 1000; // const ExtraWallClockLeewayMs = 1000;
const ExtraCpuLeewayMs = 1500; const ExtraCpuLeewayMs = 1500;
@@ -344,7 +345,7 @@ function withFirejailTimeout(args: string[], options?) {
return args; return args;
} }
function sandboxFirejail(command: string, args: string[], options) { function sandboxFirejail(command: string, args: string[], options: ExecutionOptions) {
logger.info('Sandbox execution via firejail', {command, args}); logger.info('Sandbox execution via firejail', {command, args});
const execPath = path.dirname(command); const execPath = path.dirname(command);
const execName = path.basename(command); const execName = path.basename(command);
@@ -362,8 +363,9 @@ function sandboxFirejail(command: string, args: string[], options) {
delete options.ldPath; delete options.ldPath;
} }
for (const key of Object.keys(options.env || {})) { const env = options.env || {};
jailingOptions.push(`--env=${key}=${options.env[key]}`); for (const key of Object.keys(env)) {
jailingOptions.push(`--env=${key}=${env[key]}`);
} }
delete options.env; delete options.env;
@@ -371,7 +373,7 @@ function sandboxFirejail(command: string, args: string[], options) {
} }
const sandboxDispatchTable = { const sandboxDispatchTable = {
none: (command, args, options) => { none: (command: string, args: string[], options: ExecutionOptions) => {
logger.info('Sandbox execution (sandbox disabled)', {command, args}); logger.info('Sandbox execution (sandbox disabled)', {command, args});
if (needsWine(command)) { if (needsWine(command)) {
return executeWineDirect(command, args, options); return executeWineDirect(command, args, options);
@@ -412,7 +414,7 @@ export function startWineInit() {
const executionType = execProps('executionType', 'none'); const executionType = execProps('executionType', 'none');
// We need to fire up a firejail wine server even in nsjail world (for now). // We need to fire up a firejail wine server even in nsjail world (for now).
const firejail = executionType === 'firejail' || executionType === 'nsjail' ? execProps<string>('firejail') : null; const firejail = executionType === 'firejail' || executionType === 'nsjail' ? execProps<string>('firejail') : null;
const env = applyWineEnv({PATH: process.env.PATH}); const env = applyWineEnv({PATH: unwrapString(process.env.PATH)});
const prefix = env.WINEPREFIX; const prefix = env.WINEPREFIX;
logger.info(`Initialising WINE in ${prefix}`); logger.info(`Initialising WINE in ${prefix}`);
@@ -433,7 +435,7 @@ export function startWineInit() {
// We wait until the process has printed out some known good text, but don't wait // We wait until the process has printed out some known good text, but don't wait
// for it to exit (it won't, on purpose). // for it to exit (it won't, on purpose).
let wineServer; let wineServer: child_process.ChildProcess | undefined;
if (firejail) { if (firejail) {
logger.info(`Starting a new, firejailed, long-lived wineserver complex`); logger.info(`Starting a new, firejailed, long-lived wineserver complex`);
wineServer = child_process.spawn( wineServer = child_process.spawn(
@@ -462,7 +464,7 @@ export function startWineInit() {
Graceful.on('exit', () => { Graceful.on('exit', () => {
const waitingPromises: Promise<void>[] = []; const waitingPromises: Promise<void>[] = [];
function waitForExit(process, name): Promise<void> { function waitForExit(process: child_process.ChildProcess, name: string): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
process.on('close', () => { process.on('close', () => {
logger.info(`Process '${name}' closed`); logger.info(`Process '${name}' closed`);
@@ -477,29 +479,33 @@ export function startWineInit() {
if (wineServer.killed) { if (wineServer.killed) {
waitingPromises.push(waitForExit(wineServer, 'WINE server')); waitingPromises.push(waitForExit(wineServer, 'WINE server'));
} }
wineServer = null; wineServer = undefined;
} }
return Promise.all(waitingPromises); return Promise.all(waitingPromises);
}); });
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
setupOnError(wineServer.stdin, 'stdin'); assert(wineServer);
setupOnError(wineServer.stdout, 'stdout'); const [stdin, stdout, stderr] = [
setupOnError(wineServer.stderr, 'stderr'); unwrap(wineServer.stdin),
unwrap(wineServer.stdout),
unwrap(wineServer.stderr),
];
setupOnError(stdin, 'stdin');
setupOnError(stdout, 'stdout');
setupOnError(stderr, 'stderr');
const magicString = '!!EVERYTHING IS WORKING!!'; const magicString = '!!EVERYTHING IS WORKING!!';
wineServer.stdin.write(`echo ${magicString}`); stdin.write(`echo ${magicString}`);
let output = ''; let output = '';
wineServer.stdout.on('data', data => { stdout.on('data', data => {
logger.info(`Output from wine server complex: ${data.toString().trim()}`); logger.info(`Output from wine server complex: ${data.toString().trim()}`);
output += data; output += data;
if (output.includes(magicString)) { if (output.includes(magicString)) {
resolve(); resolve();
} }
}); });
wineServer.stderr.on('data', data => stderr.on('data', data => logger.info(`stderr output from wine server complex: ${data.toString().trim()}`));
logger.info(`stderr output from wine server complex: ${data.toString().trim()}`),
);
wineServer.on('error', e => { wineServer.on('error', e => {
logger.error(`WINE server complex exited with error ${e}`); logger.error(`WINE server complex exited with error ${e}`);
reject(e); reject(e);
@@ -513,29 +519,29 @@ export function startWineInit() {
wineInitPromise = asyncSetup(); wineInitPromise = asyncSetup();
} }
function applyWineEnv(env) { function applyWineEnv(env: Record<string, string>): Record<string, string> {
return { return {
...env, ...env,
// Force use of wine vcruntime (See change 45106c382) // Force use of wine vcruntime (See change 45106c382)
WINEDLLOVERRIDES: 'vcruntime140=b', WINEDLLOVERRIDES: 'vcruntime140=b',
WINEDEBUG: '-all', WINEDEBUG: '-all',
WINEPREFIX: execProps('winePrefix'), WINEPREFIX: execProps<string>('winePrefix'),
}; };
} }
function needsWine(command) { function needsWine(command: string) {
return command.match(/\.exe$/i) && process.platform === 'linux' && !process.env.wsl; return command.match(/\.exe$/i) && process.platform === 'linux' && !process.env.wsl;
} }
async function executeWineDirect(command, args, options) { async function executeWineDirect(command: string, args: string[], options: ExecutionOptions) {
options = _.clone(options) || {}; options = _.clone(options) || {};
options.env = applyWineEnv(options.env); options.env = applyWineEnv(options.env || {});
args = [command, ...args]; args = [command, ...args];
await wineInitPromise; await wineInitPromise;
return await executeDirect(unwrapString(execProps<string>('wine')), args, options); return await executeDirect(unwrapString(execProps<string>('wine')), args, options);
} }
async function executeFirejail(command, args, options) { async function executeFirejail(command: string, args: string[], options: ExecutionOptions) {
options = _.clone(options) || {}; options = _.clone(options) || {};
const firejail = execProps<string>('firejail'); const firejail = execProps<string>('firejail');
const baseOptions = withFirejailTimeout( const baseOptions = withFirejailTimeout(
@@ -544,9 +550,9 @@ async function executeFirejail(command, args, options) {
); );
if (needsWine(command)) { if (needsWine(command)) {
logger.debug('WINE execution via firejail', {command, args}); logger.debug('WINE execution via firejail', {command, args});
options.env = applyWineEnv(options.env); options.env = applyWineEnv(options.env || {});
args = [command, ...args]; args = [command, ...args];
command = execProps('wine'); command = execProps<string>('wine');
baseOptions.push('--profile=' + getFirejailProfileFilePath('wine'), `--join=${wineSandboxName}`); baseOptions.push('--profile=' + getFirejailProfileFilePath('wine'), `--join=${wineSandboxName}`);
delete options.customCwd; delete options.customCwd;
baseOptions.push(command); baseOptions.push(command);
@@ -562,7 +568,7 @@ async function executeFirejail(command, args, options) {
delete options.ldPath; delete options.ldPath;
} }
let filenameTransform; let filenameTransform: FilenameTransformFunc | undefined;
if (options.customCwd) { if (options.customCwd) {
baseOptions.push(`--private=${options.customCwd}`); baseOptions.push(`--private=${options.customCwd}`);
const replacement = options.customCwd; const replacement = options.customCwd;
@@ -579,7 +585,7 @@ async function executeFirejail(command, args, options) {
return await executeDirect(firejail, baseOptions.concat(args), options, filenameTransform); return await executeDirect(firejail, baseOptions.concat(args), options, filenameTransform);
} }
async function executeNone(command, args, options) { async function executeNone(command: string, args: string[], options: ExecutionOptions) {
if (needsWine(command)) { if (needsWine(command)) {
return await executeWineDirect(command, args, options); return await executeWineDirect(command, args, options);
} }
@@ -589,7 +595,7 @@ async function executeNone(command, args, options) {
const executeDispatchTable = { const executeDispatchTable = {
none: executeNone, none: executeNone,
firejail: executeFirejail, firejail: executeFirejail,
nsjail: (command, args, options) => nsjail: (command: string, args: string[], options: ExecutionOptions) =>
needsWine(command) ? executeFirejail(command, args, options) : executeNsjail(command, args, options), needsWine(command) ? executeFirejail(command, args, options) : executeNsjail(command, args, options),
cewrapper: executeCEWrapper, cewrapper: executeCEWrapper,
}; };

View File

@@ -183,7 +183,7 @@
"format": "prettier --write .", "format": "prettier --write .",
"format-files": "prettier --write --ignore-unknown", "format-files": "prettier --write --ignore-unknown",
"ts-compile": "tsc", "ts-compile": "tsc",
"ts-check": "tsc -p ./tsconfig.backend.json --noEmit && tsc -p ./tsconfig.frontend.json --noEmit", "ts-check": "tsc -p ./tsconfig.backend.json --noEmit && tsc -p ./tsconfig.frontend.json --noEmit && tsc -p ./tsconfig.tests.json --noEmit",
"webpack": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./node_modules/webpack-cli/bin/cli.js --node-env=production --config webpack.config.esm.ts" "webpack": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./node_modules/webpack-cli/bin/cli.js --node-env=production --config webpack.config.esm.ts"
}, },
"license": "BSD-2-Clause" "license": "BSD-2-Clause"

View File

@@ -24,6 +24,7 @@
import path from 'path'; import path from 'path';
import {assert} from '../lib/assert.js';
import * as exec from '../lib/exec.js'; import * as exec from '../lib/exec.js';
import * as props from '../lib/properties.js'; import * as props from '../lib/properties.js';
import {UnprocessedExecResult} from '../types/execution/execution.interfaces.js'; import {UnprocessedExecResult} from '../types/execution/execution.interfaces.js';
@@ -230,6 +231,7 @@ describe('Execution tests', () => {
]); ]);
options.should.deep.equals({}); options.should.deep.equals({});
expect(filenameTransform).to.not.be.undefined; expect(filenameTransform).to.not.be.undefined;
assert(filenameTransform);
filenameTransform('moo').should.equal('moo'); filenameTransform('moo').should.equal('moo');
filenameTransform('/some/custom/cwd/file').should.equal('/app/file'); filenameTransform('/some/custom/cwd/file').should.equal('/app/file');
}); });

6
tsconfig.tests.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.backend.json",
"files": [],
"include": ["test/**/*.js", "test/**/*.ts"],
"exclude": []
}