spiegel-keyman/developer/src/common/web/test-helpers/TestCompilerCallbacks.ts
Marc Durdin ac6eb0b767 chore: use explicit node: prefix for node imports
This makes it easier for us to enumerate modules that have node
dependencies. There may be others I have not yet picked up.
2025-04-03 08:01:08 +07:00

173 lines
4.6 KiB
TypeScript

import * as fs from 'node:fs';
import * as path from 'node:path';
import { CompilerEvent, CompilerCallbacks, CompilerPathCallbacks, CompilerFileSystemCallbacks,
CompilerError, CompilerNetAsyncCallbacks, DefaultCompilerFileSystemAsyncCallbacks,
CompilerFileSystemAsyncCallbacks,
CompilerErrorSeverity} from '@keymanapp/developer-utils';
import { fileURLToPath } from 'url';
const { TEST_SAVE_FIXTURES } = process.env;
/**
* A CompilerCallbacks implementation for testing
*/
export class TestCompilerCallbacks implements CompilerCallbacks {
/* TestCompilerCallbacks */
messages: CompilerEvent[] = [];
readonly _net: TestCompilerNetAsyncCallbacks;
readonly _fsAsync: DefaultCompilerFileSystemAsyncCallbacks = new DefaultCompilerFileSystemAsyncCallbacks(this);
constructor(basePath?: string) {
if(basePath) {
this._net = new TestCompilerNetAsyncCallbacks(basePath);
}
}
clear() {
this.messages = [];
}
filteredMessages(severity: CompilerErrorSeverity = CompilerErrorSeverity.Info) {
return this.messages.filter(m => CompilerError.severity(m.code) >= severity);
}
printMessages() {
if(this.messages.length) {
process.stdout.write(CompilerError.formatEvent(this.messages));
}
}
hasMessage(code: number): boolean {
return this.messages.find((item) => item.code == code) === undefined ? false : true;
}
fileURLToPath(url: string | URL): string {
return fileURLToPath(url);
}
/** true of at least one error */
hasError(): boolean {
return CompilerError.hasError(this.messages);
}
/* CompilerCallbacks */
loadFile(filename: string): Uint8Array {
try {
return fs.readFileSync(filename);
} catch(e) {
if (e.code === 'ENOENT') {
return null;
} else {
throw e;
}
}
}
fileSize(filename: string): number {
return fs.statSync(filename)?.size;
}
isDirectory(filename: string): boolean {
return fs.statSync(filename)?.isDirectory();
}
get path(): CompilerPathCallbacks {
return {
...path,
isAbsolute: path.win32.isAbsolute
};
}
get fs(): CompilerFileSystemCallbacks {
return fs;
}
get net(): CompilerNetAsyncCallbacks {
return this._net;
}
get fsAsync(): CompilerFileSystemAsyncCallbacks {
return this._fsAsync;
}
resolveFilename(baseFilename: string, filename: string): string {
const basePath =
baseFilename.endsWith('/') || baseFilename.endsWith('\\') ?
baseFilename :
path.dirname(baseFilename);
// Transform separators to platform separators -- we are agnostic
// in our use here but path prefers files may use
// either / or \, although older kps files were always \.
if(path.sep == '/') {
filename = filename.replace(/\\/g, '/');
} else {
filename = filename.replace(/\//g, '\\');
}
if(!path.isAbsolute(filename)) {
filename = path.resolve(basePath, filename);
}
return filename;
}
reportMessage(event: CompilerEvent): void {
// console.log(event.message);
this.messages.push(event);
}
debug(msg: string) {
console.debug(msg);
}
};
class TestCompilerNetAsyncCallbacks implements CompilerNetAsyncCallbacks {
constructor(private basePath: string) {
}
urlToPath(url: string): string {
const p = new URL(url);
return path.join(this.basePath, p.hostname, p.pathname.replaceAll(/[^a-z0-9_.!@#$%() -]/ig, '#'));
}
async fetchBlob(url: string): Promise<Uint8Array> {
const p = this.urlToPath(url);
if(TEST_SAVE_FIXTURES) {
// When TEST_SAVE_FIXTURES env variable is set, we will do the actual
// fetch from the origin so that we can build the fixtures easily
console.log(`Downloading file ${url} --> ${p}`);
let response: Response;
try {
response = await fetch(url);
} catch(e) {
console.error(`failed to download ${url}`);
console.error(e);
throw e; // yes, we want to abort the download
}
fs.mkdirSync(path.dirname(p), {recursive: true});
if(!response.ok) {
// We won't save a file, just delete any existing file
if(fs.existsSync(p)) {
fs.rmSync(p);
}
} else {
const data = new Uint8Array(await response.arrayBuffer());
fs.writeFileSync(p, data);
}
}
if(!fs.existsSync(p)) {
// missing file, this is okay
return null;
}
const data: Uint8Array = fs.readFileSync(p);
return data;
}
async fetchJSON(url: string): Promise<any> {
const data = await this.fetchBlob(url);
return data ? JSON.parse(new TextDecoder().decode(data)) : null;
}
}