/** * Functions/Methods that depend on the current runtime to function */ import process from 'node:process'; import Ansi from './ansi.ts'; import { IRuntime, ITerminalSize, ITestBase } from './types.ts'; import { noop } from './fns.ts'; import { defaultTerminalSize, SCROLL_LOG_FILE_PREFIX, SCROLL_LOG_FILE_SUFFIX, } from './config.ts'; export type { IFileIO, IRuntime, ITerminal } from './types.ts'; /** * Which Typescript runtime is currently being used */ export enum RunTimeType { Bun = 'bun', Deno = 'deno', Tsx = 'tsx', Unknown = 'common', } /** * The label for type/severity of the log entry */ export enum LogLevel { Debug = 'Debug', Info = 'Info', Notice = 'Notice', Warning = 'Warning', Error = 'Error', } let scrollRuntime: IRuntime | null = null; // ---------------------------------------------------------------------------- // Misc runtime functions // ---------------------------------------------------------------------------- /** * Get the size of the terminal window via ANSI codes * @see https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html#window-size-the-hard-way */ async function _getTerminalSizeFromAnsi(): Promise { const { term } = await getRuntime(); // Tell the cursor to move to Row 999 and Column 999 // Since this command specifically doesn't go off the screen // When we ask where the cursor is, we should get the size of the screen await term.writeStdout( Ansi.moveCursorForward(999) + Ansi.moveCursorDown(999), ); // Ask where the cursor is await term.writeStdout(Ansi.GetCursorLocation); // Get the first chunk from stdin // The response is \x1b[(rows);(cols)R.. const chunk = await term.readStdinRaw(); if (chunk === null) { return defaultTerminalSize; } const rawCode = (new TextDecoder()).decode(chunk); const res = rawCode.trim().replace(/^.\[([0-9]+;[0-9]+)R$/, '$1'); const [srows, scols] = res.split(';'); const rows = parseInt(srows, 10) ?? 24; const cols = parseInt(scols, 10) ?? 80; // Clear the screen await term.writeStdout(Ansi.ClearScreen + Ansi.ResetCursor); return { rows, cols, }; } /** * Basic logging - */ export function log( s: unknown, level: LogLevel = LogLevel.Notice, data?: any, ): void { getRuntime().then(({ file }) => { const rawS = JSON.stringify(s, null, 2); const rawData = JSON.stringify(data, null, 2); const output = (typeof data !== 'undefined') ? `${rawS}\n${rawData}\n\n` : `${rawS}\n`; const outputFile = `${SCROLL_LOG_FILE_PREFIX}-${level.toLowerCase()}${SCROLL_LOG_FILE_SUFFIX}`; file.appendFile(outputFile, output).then(noop); }); } export const logDebug = (s: unknown, data?: any) => log(s, LogLevel.Debug, data); export const logInfo = (s: unknown, data?: any) => log(s, LogLevel.Info, data); export const logNotice = (s: unknown, data?: any) => log(s, LogLevel.Notice, data); export const logWarning = (s: unknown, data?: any) => log(s, LogLevel.Warning, data); export const logError = (s: unknown, data?: any) => log(s, LogLevel.Warning, data); /** * Kill program, displaying an error message * @param s */ export function die(s: string | Error): void { logError(s); process.stdin.setRawMode(false); console.error(s); getRuntime().then((r) => r.exit()); } /** * Determine which Typescript runtime we are operating under */ export function runtimeType(): RunTimeType { let runtime = RunTimeType.Tsx; if ('Deno' in globalThis) { runtime = RunTimeType.Deno; } if ('Bun' in globalThis) { runtime = RunTimeType.Bun; } return runtime; } /** * Get the adapter object for the current Runtime */ export async function getRuntime(): Promise { if (scrollRuntime === null) { const runtime = runtimeType(); const path = `../${runtime}/mod.ts`; const pkg = await import(path); if ('default' in pkg) { scrollRuntime = pkg.default; } if (scrollRuntime !== null) { return Promise.resolve(scrollRuntime); } return Promise.reject('Missing default import'); } return Promise.resolve(scrollRuntime); } /** * Get the common test interface object */ export async function getTestRunner(): Promise { const runtime = runtimeType(); const path = `../${runtime}/test_base.ts`; const pkg = await import(path); if ('default' in pkg) { return pkg.default; } return pkg; } /** * Import a runtime-specific module * * e.g. to load "src/bun/mod.ts", if the runtime is bun, * you can use like so `await importForRuntime('index')`; * * @param path - the path within the runtime module */ export const importForRuntime = async (path: string) => { const runtime = runtimeType(); const suffix = '.ts'; const base = `../${runtime}/`; const pathParts = path .split('/') .filter((part) => part !== '' && part !== '.' && part !== suffix) .map((part) => part.replace(suffix, '')); const cleanedPath = pathParts.join('/'); const importPath = base + cleanedPath + suffix; const pkg = await import(importPath); if ('default' in pkg) { return pkg.default; } return pkg; };