Add new runtime for tsx
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
All checks were successful
timw4mail/scroll/pipeline/head This commit looks good
This commit is contained in:
parent
e656ad3112
commit
8d2ba868b0
16
justfile
16
justfile
@ -9,7 +9,7 @@ coverage: bun-test deno-coverage
|
|||||||
check: deno-check bun-check
|
check: deno-check bun-check
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
deno doc --html --unstable-ffi --name="Scroll" ./src/scroll.ts ./src/common/mod.ts ./src/deno/mod.ts ./src/bun/mod.ts
|
deno doc --html --unstable-ffi --name="Scroll" ./src/scroll.ts ./src/common/*.ts ./src/deno/mod.ts ./src/bun/mod.ts
|
||||||
|
|
||||||
# Reformat the code
|
# Reformat the code
|
||||||
fmt:
|
fmt:
|
||||||
@ -23,6 +23,7 @@ quality: check test
|
|||||||
|
|
||||||
# Clean up any generated files
|
# Clean up any generated files
|
||||||
clean:
|
clean:
|
||||||
|
rm -f test.file
|
||||||
rm -rf .deno-cover
|
rm -rf .deno-cover
|
||||||
rm -rf coverage
|
rm -rf coverage
|
||||||
rm -rf docs
|
rm -rf docs
|
||||||
@ -66,3 +67,16 @@ deno-coverage:
|
|||||||
# Run with deno
|
# Run with deno
|
||||||
deno-run file="":
|
deno-run file="":
|
||||||
deno run --allow-all --allow-ffi --deny-hrtime --unstable-ffi ./src/scroll.ts {{file}}
|
deno run --allow-all --allow-ffi --deny-hrtime --unstable-ffi ./src/scroll.ts {{file}}
|
||||||
|
|
||||||
|
##########################################################################################
|
||||||
|
# tsx(Node JS)-specific commands
|
||||||
|
##########################################################################################
|
||||||
|
|
||||||
|
# Test with tsx (NodeJS)
|
||||||
|
tsx-test:
|
||||||
|
npx tsx --test './src/common/all_test.ts'
|
||||||
|
|
||||||
|
# Run with tsx (NodeJS)
|
||||||
|
tsx-run file="":
|
||||||
|
npx tsx ./src/scroll.ts {{file}}
|
||||||
|
|
||||||
|
@ -2,5 +2,8 @@
|
|||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.0.11"
|
"bun-types": "^1.0.11"
|
||||||
}
|
},
|
||||||
|
"scripts": {
|
||||||
|
},
|
||||||
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,11 @@ import { IRuntime, RunTimeType } from '../common/runtime.ts';
|
|||||||
import BunTerminalIO from './terminal_io.ts';
|
import BunTerminalIO from './terminal_io.ts';
|
||||||
import BunFileIO from './file_io.ts';
|
import BunFileIO from './file_io.ts';
|
||||||
|
|
||||||
import * as process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Bun Runtime implementation
|
||||||
|
*/
|
||||||
const BunRuntime: IRuntime = {
|
const BunRuntime: IRuntime = {
|
||||||
name: RunTimeType.Bun,
|
name: RunTimeType.Bun,
|
||||||
file: BunFileIO,
|
file: BunFileIO,
|
||||||
|
@ -28,7 +28,7 @@ export function readKey(raw: Uint8Array): string {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some keycodes have multiple potential inputs
|
// Some key codes have multiple potential inputs
|
||||||
switch (parsed) {
|
switch (parsed) {
|
||||||
case '\x1b[1~':
|
case '\x1b[1~':
|
||||||
case '\x1b[7~':
|
case '\x1b[7~':
|
||||||
|
@ -13,7 +13,7 @@ enum OptionType {
|
|||||||
|
|
||||||
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
|
const isOption = <T>(v: any): v is Option<T> => v instanceof Option;
|
||||||
|
|
||||||
class OptionInnerNone<T> {
|
class OptionInnerNone {
|
||||||
public type: OptionType = OptionType.None;
|
public type: OptionType = OptionType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ class OptionInnerSome<T> {
|
|||||||
constructor(public value: T) {}
|
constructor(public value: T) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionInnerType<T> = OptionInnerNone<T> | OptionInnerSome<T>;
|
type OptionInnerType<T> = OptionInnerNone | OptionInnerSome<T>;
|
||||||
|
|
||||||
const isSome = <T>(v: OptionInnerType<T>): v is OptionInnerSome<T> =>
|
const isSome = <T>(v: OptionInnerType<T>): v is OptionInnerSome<T> =>
|
||||||
'value' in v && v.type === OptionType.Some;
|
'value' in v && v.type === OptionType.Some;
|
||||||
@ -50,6 +50,9 @@ export class Option<T> {
|
|||||||
: new OptionInnerNone();
|
: new OptionInnerNone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The equivalent of the Rust `Option`.`None` type
|
||||||
|
*/
|
||||||
public static get None(): Option<any> {
|
public static get None(): Option<any> {
|
||||||
return Option._None;
|
return Option._None;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Functions/Methods that depend on the current runtime to function
|
||||||
|
*/
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
import { IRuntime, ITestBase } from './types.ts';
|
import { IRuntime, ITestBase } from './types.ts';
|
||||||
import { noop } from './fns.ts';
|
import { noop } from './fns.ts';
|
||||||
@ -11,9 +14,13 @@ export type { IFileIO, IRuntime, ITerminal } from './types.ts';
|
|||||||
export enum RunTimeType {
|
export enum RunTimeType {
|
||||||
Bun = 'bun',
|
Bun = 'bun',
|
||||||
Deno = 'deno',
|
Deno = 'deno',
|
||||||
|
Tsx = 'tsx',
|
||||||
Unknown = 'common',
|
Unknown = 'common',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for type/severity of the log entry
|
||||||
|
*/
|
||||||
export enum LogLevel {
|
export enum LogLevel {
|
||||||
Debug = 'Debug',
|
Debug = 'Debug',
|
||||||
Info = 'Info',
|
Info = 'Info',
|
||||||
@ -62,7 +69,7 @@ export function die(s: string | Error): void {
|
|||||||
* Determine which Typescript runtime we are operating under
|
* Determine which Typescript runtime we are operating under
|
||||||
*/
|
*/
|
||||||
export function runtimeType(): RunTimeType {
|
export function runtimeType(): RunTimeType {
|
||||||
let runtime = RunTimeType.Unknown;
|
let runtime = RunTimeType.Tsx;
|
||||||
|
|
||||||
if ('Deno' in globalThis) {
|
if ('Deno' in globalThis) {
|
||||||
runtime = RunTimeType.Deno;
|
runtime = RunTimeType.Deno;
|
||||||
|
@ -7,6 +7,9 @@ import DenoFileIO from './file_io.ts';
|
|||||||
|
|
||||||
import * as node_process from 'node:process';
|
import * as node_process from 'node:process';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Deno Runtime implementation
|
||||||
|
*/
|
||||||
const DenoRuntime: IRuntime = {
|
const DenoRuntime: IRuntime = {
|
||||||
name: RunTimeType.Deno,
|
name: RunTimeType.Deno,
|
||||||
file: DenoFileIO,
|
file: DenoFileIO,
|
||||||
|
25
src/tsx/file_io.ts
Normal file
25
src/tsx/file_io.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { appendFile, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
|
|
||||||
|
import { IFileIO } from '../common/runtime.ts';
|
||||||
|
|
||||||
|
const TsxFileIO: IFileIO = {
|
||||||
|
openFile: async function (path: string): Promise<string> {
|
||||||
|
const filePath = resolve(path);
|
||||||
|
const contents = await readFile(filePath, { encoding: 'utf8' });
|
||||||
|
|
||||||
|
return contents;
|
||||||
|
},
|
||||||
|
appendFile: async function (path: string, contents: string): Promise<void> {
|
||||||
|
const filePath = resolve(path);
|
||||||
|
|
||||||
|
await appendFile(filePath, contents);
|
||||||
|
},
|
||||||
|
saveFile: async function (path: string, contents: string): Promise<void> {
|
||||||
|
const filePath = resolve(path);
|
||||||
|
|
||||||
|
await writeFile(filePath, contents);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TsxFileIO;
|
26
src/tsx/mod.ts
Normal file
26
src/tsx/mod.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* The main entrypoint when using Tsx as the runtime
|
||||||
|
*/
|
||||||
|
import { IRuntime, RunTimeType } from '../common/runtime.ts';
|
||||||
|
import TsxTerminalIO from './terminal_io.ts';
|
||||||
|
import TsxFileIO from './file_io.ts';
|
||||||
|
|
||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tsx Runtime implementation
|
||||||
|
*/
|
||||||
|
const TsxRuntime: IRuntime = {
|
||||||
|
name: RunTimeType.Tsx,
|
||||||
|
file: TsxFileIO,
|
||||||
|
term: TsxTerminalIO,
|
||||||
|
onEvent: (eventName: string, handler) => process.on(eventName, handler),
|
||||||
|
onExit: (cb: () => void): void => {
|
||||||
|
process.on('beforeExit', cb);
|
||||||
|
process.on('exit', cb);
|
||||||
|
process.on('SIGINT', cb);
|
||||||
|
},
|
||||||
|
exit: (code?: number) => process.exit(code),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TsxRuntime;
|
42
src/tsx/terminal_io.ts
Normal file
42
src/tsx/terminal_io.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import process from 'node:process';
|
||||||
|
|
||||||
|
import { readKey } from '../common/fns.ts';
|
||||||
|
import { ITerminal, ITerminalSize } from '../common/types.ts';
|
||||||
|
|
||||||
|
const TsxTerminalIO: ITerminal = {
|
||||||
|
argv: (process.argv.length > 2) ? process.argv.slice(2) : [],
|
||||||
|
inputLoop: async function* (): AsyncGenerator<Uint8Array, null> {
|
||||||
|
yield (await TsxTerminalIO.readStdinRaw()) ?? new Uint8Array(0);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getTerminalSize: function getTerminalSize(): Promise<ITerminalSize> {
|
||||||
|
const [cols, rows] = process.stdout.getWindowSize();
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
rows,
|
||||||
|
cols,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
readStdin: async function (): Promise<string | null> {
|
||||||
|
const raw = await TsxTerminalIO.readStdinRaw();
|
||||||
|
return readKey(raw ?? new Uint8Array(0));
|
||||||
|
},
|
||||||
|
readStdinRaw: function (): Promise<Uint8Array | null> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
process.stdin.setRawMode(true).resume().once(
|
||||||
|
'data',
|
||||||
|
(buffer: Uint8Array) => {
|
||||||
|
resolve(buffer);
|
||||||
|
process.stdin.setRawMode(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
writeStdout: function (s: string): Promise<void> {
|
||||||
|
process.stdout.write(s);
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TsxTerminalIO;
|
41
src/tsx/test_base.ts
Normal file
41
src/tsx/test_base.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Adapt the node test interface to the shared testing interface
|
||||||
|
*/
|
||||||
|
// @ts-ignore: Only in newer versions of node
|
||||||
|
import {
|
||||||
|
deepStrictEqual,
|
||||||
|
notStrictEqual,
|
||||||
|
strictEqual,
|
||||||
|
} from 'node:assert/strict';
|
||||||
|
// @ts-ignore: Only in newer versions of node
|
||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import { ITestBase } from '../common/types.ts';
|
||||||
|
|
||||||
|
export function testSuite(testObj: any) {
|
||||||
|
Object.keys(testObj).forEach((group) => {
|
||||||
|
describe(group, () => {
|
||||||
|
const groupObj = testObj[group];
|
||||||
|
Object.keys(groupObj).forEach((testName) => {
|
||||||
|
it(testName, groupObj[testName]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const TsxTestBase: ITestBase = {
|
||||||
|
assertEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
deepStrictEqual(actual, expected),
|
||||||
|
assertExists: (actual: unknown) => notStrictEqual(actual, undefined),
|
||||||
|
assertFalse: (actual: boolean) => strictEqual(actual, false),
|
||||||
|
assertInstanceOf: (actual: unknown, expectedType: any) =>
|
||||||
|
strictEqual(actual instanceof expectedType, true),
|
||||||
|
assertNotEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
notStrictEqual(actual, expected),
|
||||||
|
assertNull: (actual: unknown) => strictEqual(actual, null),
|
||||||
|
assertStrictEquals: (actual: unknown, expected: unknown) =>
|
||||||
|
strictEqual(actual, expected),
|
||||||
|
assertTrue: (actual: boolean) => strictEqual(actual, true),
|
||||||
|
testSuite,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TsxTestBase;
|
Loading…
Reference in New Issue
Block a user