travelm-agency
Version:
Generate type-safe accessors and decoders for your i18n files
186 lines (185 loc) • 8.38 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import cp from "child_process";
import fs from "fs";
import intl_proxy from "intl-proxy";
import path, { dirname } from "path";
import { promisify } from "util";
import { Elm, } from "./elm.min.js";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const readFile = (filePath) => promisify(fs.readFile)(filePath, { encoding: "utf-8" });
const readDir = promisify(fs.readdir);
const writeFile = (filePath, data) => __awaiter(void 0, void 0, void 0, function* () {
yield promisify(fs.mkdir)(path.parse(filePath).dir, { recursive: true });
yield promisify(fs.writeFile)(filePath, data, { encoding: "utf-8" });
});
const getPluginVersion = () => {
const file = path.resolve(__dirname, "..", "package.json");
const nodeJson = JSON.parse(fs.readFileSync(file, { encoding: "utf-8" }));
return nodeJson.version;
};
export function createInstance() {
let ports;
const withElmApp = (consumer_1, ...args_1) => __awaiter(this, [consumer_1, ...args_1], void 0, function* (consumer, devMode = false) {
let error;
if (!ports) {
const version = getPluginVersion();
({ ports } = Elm.Main.init({
flags: { version, intl: intl_proxy, devMode },
}));
const throwOnError = (response) => {
if (response.error) {
error = response.error;
}
};
ports.sendResponse.subscribe(throwOnError);
}
return consumer(ports).then((res) => {
const previousError = error;
if (previousError) {
error = undefined;
throw new Error(previousError);
}
return res;
});
});
const sendTranslations = (filePaths, devMode = false) => withElmApp((ports) => __awaiter(this, void 0, void 0, function* () {
yield Promise.all(filePaths.map((filePath) => __awaiter(this, void 0, void 0, function* () {
const fileName = path.parse(filePath).base;
const fileContent = yield readFile(filePath);
ports.receiveRequest.send({
type: "translation",
fileName,
fileContent,
});
})));
}), devMode);
const finishModule = ({ elmPath, generatorMode = null, addContentHash, devMode = false, i18nArgFirst = false, prefixFileIdentifier = false, customHtmlModule = "Html", customHtmlAttributesModule, }) => withElmApp((ports) => __awaiter(this, void 0, void 0, function* () {
const elmModuleName = yield elmPathToModuleName(elmPath);
return new Promise((resolve, reject) => {
const responseHandler = (res) => __awaiter(this, void 0, void 0, function* () {
ports.sendResponse.unsubscribe(responseHandler);
if (res.error) {
reject(res.error);
}
if (!res.content) {
reject(new Error("Received neither error nor content from Elm."));
}
else {
resolve(res.content);
}
});
ports.sendResponse.subscribe(responseHandler);
ports.receiveRequest.send({
type: "finish",
elmModuleName,
generatorMode,
addContentHash,
i18nArgFirst,
prefixFileIdentifier,
customHtmlAttributesModule: customHtmlAttributesModule !== null && customHtmlAttributesModule !== void 0 ? customHtmlAttributesModule : `${customHtmlModule}.Attributes`,
customHtmlModule: customHtmlModule,
});
});
}), devMode).then((_a) => __awaiter(this, [_a], void 0, function* ({ elmFile, optimizedJson }) {
return ({
elmFile: yield runElmFormat(elmFile),
optimizedJson,
});
}));
return {
withElmApp,
sendTranslations,
finishModule,
};
}
// This function should not be necessary once https://github.com/the-sett/elm-syntax-dsl/issues/42 is fixed.
const runElmFormat = (code) => __awaiter(void 0, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const elmFormatProcess = cp.spawn("npm", ["exec", "elm-format", "--", "--stdin"], { shell: true, stdio: "pipe" });
elmFormatProcess.stderr.pipe(process.stderr);
let formattedCode = "";
elmFormatProcess.stdout.on("data", (chunk) => {
formattedCode += chunk;
});
elmFormatProcess.stdout.on("end", () => {
elmFormatProcess.kill();
resolve(formattedCode);
});
elmFormatProcess.on("error", (err) => {
reject(err);
});
elmFormatProcess.stdin.write(code);
elmFormatProcess.stdin.end();
});
});
export const run = (options) => __awaiter(void 0, void 0, void 0, function* () {
const { elmPath, translationDir, generatorMode, addContentHash, devMode, i18nArgFirst, prefixFileIdentifier, customHtmlModule, customHtmlAttributesModule, } = options;
const translationFilePaths = (yield readDir(translationDir)).map((fileName) => path.resolve(translationDir, fileName));
if (translationFilePaths.length === 0) {
throw new Error(`Given translation directory ${translationDir} does not contain any files`);
}
const instance = createInstance();
yield instance.sendTranslations(translationFilePaths, devMode);
const { elmFile, optimizedJson } = yield instance.finishModule({
elmPath,
generatorMode,
addContentHash,
devMode,
i18nArgFirst,
prefixFileIdentifier,
customHtmlModule,
customHtmlAttributesModule,
});
const elmPromise = writeFile(elmPath, elmFile);
let jsonPromises = [];
if (options.generatorMode === "dynamic") {
jsonPromises = optimizedJson.map(({ filename, content }) => writeFile(path.join(options.jsonPath, filename), content));
}
yield Promise.all([elmPromise, ...jsonPromises]);
});
let elmConfig;
const elmPathToModuleName = (elmPath) => __awaiter(void 0, void 0, void 0, function* () {
const absoluteElmPath = path.resolve(elmPath);
const elmJsonPath = lookForElmJsonRecursively(path.dirname(absoluteElmPath));
const elmJsonDir = path.dirname(elmJsonPath);
if (!elmConfig) {
elmConfig = yield JSON.parse(yield readFile(elmJsonPath));
}
const elmPathRelativeToElmJson = path.relative(elmJsonDir, absoluteElmPath);
const possibleSourceDirs = elmConfig["source-directories"]
.map((srcDir) => srcDir.split(path.posix.sep).join(path.sep))
.filter((srcDir) => elmPathRelativeToElmJson.startsWith(srcDir));
if (possibleSourceDirs.length == 0) {
throw new Error("Could not determine elm module name");
}
if (possibleSourceDirs.length > 1) {
throw new Error("Multiple matching source directories: " + possibleSourceDirs.join(","));
}
return elmPathRelativeToElmJson
.replace(".elm", "")
.replace(possibleSourceDirs[0], "")
.split(path.sep)
.filter((s) => s)
.join(".");
});
const lookForElmJsonRecursively = (directory, level = 0) => {
const attempt = path.join(directory, "elm.json");
if (fs.existsSync(attempt)) {
return attempt;
}
// avoid recursing infinitely because of symlinks or something
if (level > 10) {
throw new Error("Tried to find elm.json recursively but could not find it.");
}
return lookForElmJsonRecursively(path.dirname(directory), level + 1);
};