UNPKG

counterfact

Version:

a library for building a fake REST API for testing

93 lines (92 loc) 3.76 kB
// Stryker disable all import { once } from "node:events"; import fs from "node:fs/promises"; import nodePath from "node:path"; import { watch as chokidarWatch } from "chokidar"; import createDebug from "debug"; import ts from "typescript"; import { ensureDirectoryExists } from "../util/ensure-directory-exists.js"; import { CHOKIDAR_OPTIONS } from "./constants.js"; import { convertFileExtensionsToCjs } from "./convert-js-extensions-to-cjs.js"; const debug = createDebug("counterfact:server:transpiler"); export class Transpiler extends EventTarget { sourcePath; destinationPath; moduleKind; watcher; constructor(sourcePath, destinationPath, moduleKind) { super(); this.sourcePath = sourcePath; this.destinationPath = destinationPath; this.moduleKind = moduleKind; } get extension() { return this.moduleKind.toLowerCase() === "commonjs" ? ".cjs" : ".js"; } async watch() { debug("transpiler: watch"); this.watcher = chokidarWatch(this.sourcePath, { ...CHOKIDAR_OPTIONS, ignored: `${this.sourcePath}/js`, ignoreInitial: false, }); const transpiles = []; this.watcher.on("all", async (eventName, sourcePathOriginal) => { debug("transpiler event: %s <%s>", eventName, sourcePathOriginal); const JS_EXTENSIONS = ["js", "mjs", "ts", "mts"]; if (!JS_EXTENSIONS.some((extension) => sourcePathOriginal.endsWith(`.${extension}`))) return; const sourcePath = sourcePathOriginal.replaceAll("\\", "/"); const destinationPath = sourcePath .replace(this.sourcePath, this.destinationPath) .replaceAll("\\", "/") .replace(".ts", this.extension); if (["add", "change"].includes(eventName)) { transpiles.push(this.transpileFile(eventName, sourcePath, destinationPath)); } if (eventName === "unlink") { try { await fs.rm(destinationPath); } catch (error) { if (error.code !== "ENOENT") { debug("error removing %s: %o", destinationPath, error); this.dispatchEvent(new Event("error")); throw error; } } this.dispatchEvent(new Event("delete")); } }); await once(this.watcher, "ready"); await Promise.all(transpiles); } async stopWatching() { await this.watcher?.close(); } async transpileFile(eventName, sourcePath, destinationPath) { ensureDirectoryExists(destinationPath); const source = await fs.readFile(sourcePath, "utf8"); const result = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind[this.moduleKind.toLowerCase() === "module" ? "ES2022" : "CommonJS"], target: ts.ScriptTarget.ES2015, }, }).outputText; const fullDestination = nodePath .join(sourcePath .replace(this.sourcePath, this.destinationPath) .replace(".ts", this.extension)) .replaceAll("\\", "/"); const resultWithTransformedFileExtensions = convertFileExtensionsToCjs(result); try { await fs.writeFile(fullDestination, resultWithTransformedFileExtensions); } catch { debug("error transpiling %s", fullDestination); this.dispatchEvent(new Event("error")); throw new Error("could not transpile"); } this.dispatchEvent(new Event("write")); } }