counterfact
Version:
a library for building a fake REST API for testing
93 lines (92 loc) • 3.76 kB
JavaScript
// 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"));
}
}