json-fusion
Version:
Json-Fusion is a Node.js library designed to streamline the process of loading, merging, and reflecting directory hierarchies in JSON files. With Json-Fusion, you can consolidate information scattered across different JSON files, and reflect your filesyst
212 lines (204 loc) • 6.03 kB
JavaScript
;
var fs = require('fs');
var path = require('path');
var globby = require('globby');
var deepmerge = require('deepmerge');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var globby__default = /*#__PURE__*/_interopDefault(globby);
// src/lib/assert.ts
function checkBaseDir(baseDir) {
if (!baseDir) {
throw new Error("baseDir is required");
}
if (baseDir.includes("*")) {
throw new Error(`baseDir must not contain wildcard. (baseDir: ${baseDir})`);
}
}
async function finalize(result, config) {
const finalized = await stringifyMap[config.exportType ?? "json"](result);
if (config.exportType !== "object" && config.export) {
await outputFile(finalized, config);
}
return finalized;
}
async function outputFile(result, config) {
const exportPath = path.resolve(config.cwd ?? process.cwd(), config.export);
await fs.promises.mkdir(exportPath, { recursive: true });
await fs.promises.writeFile(exportPath, JSON.stringify(result, null, 2));
}
var stringifyMap = {
json: (result) => JSON.stringify(result, null, 2),
yaml: async (result) => {
const { stringify } = await import('yaml');
return stringify(result, { indent: 2 });
},
object: (result) => result
};
// src/lib/error.ts
var JsonFusionError = class extends Error {
name = "JsonFusionError";
constructor(message, reasons) {
super(createMessage(message, reasons));
}
};
function createMessage(message, reasons) {
return `${message}
${reasons.map(
({ message: message2, filePath }) => [" -", message2, filePath ? `(filePath: ${filePath})` : ""].join(" ")
).join("\n")}`;
}
// src/lib/loader.ts
var { readFile } = fs.promises;
async function loadContext(baseDir, config) {
const [founds, ignores] = await Promise.all([
globFiles(baseDir, config),
ignoreFiles(baseDir, config)
]);
const files = founds.filter((file) => !ignores.includes(file));
return {
files,
config,
jsons: await loadJsons(files, baseDir, config)
};
}
function globFiles(baseDir, config) {
return globby__default.default(baseDir, {
expandDirectories: {
extensions: (config.extensions ?? ["json"]).flatMap((ext) => extensionAliases[ext])
},
gitignore: false,
cwd: config.cwd
});
}
var extensionAliases = {
yaml: ["yml", "yaml"],
json: ["json"]
};
var extensions = Object.values(extensionAliases).flat();
function getExtension(filePath) {
const ext = filePath.split(".").pop() ?? "";
const found = Object.entries(extensionAliases).find(
([, aliases]) => aliases.includes(ext)
);
return found?.[0] ?? ext;
}
async function ignoreFiles(baseDir, config) {
if ((config.ignore ?? []).length <= 0) {
return [];
}
return globby__default.default(config.ignore, {
gitignore: true,
cwd: config.cwd
});
}
async function loadJsons(files, baseDir, config) {
const loader = new JsonLoader(baseDir, config);
await loader.init();
const result = await Promise.all(files.map((path) => loader.load(path)));
const errors = result.filter((item) => !!item.error);
if (errors.length > 0) {
throw new JsonFusionError("Failed to load jsons", errors.map(toErrorReason));
}
return result;
}
var cwd = process.cwd();
var JsonLoader = class {
constructor(baseDir, config) {
this.baseDir = baseDir;
this.config = config;
}
#parsers = {
json: (raw) => JSON.parse(raw)
};
async init() {
if (this.config.extensions?.includes("yaml")) {
this.#parsers.yaml = await this.#createYamlParser();
}
}
async #createYamlParser() {
const { parse } = await import('yaml');
return (raw) => parse(raw, { prettyErrors: true });
}
#assertSupportedExtension(ext) {
if (!(this.config.extensions ?? ["json"]).includes(ext)) {
throw new Error(`Unsupported file extension: ${ext}`);
}
}
#getPath(filePath) {
return filePath.replace(/^\.\//, "").replace(new RegExp(`^${this.baseDir.replace(/^\.\//, "")}/`), "").replace(new RegExp(`\\.(${extensions.join("|")})$`), "");
}
#parse(raw, ext) {
const parser = this.#parsers[ext];
if (!parser) {
throw new Error(`Unsupported file extension: ${ext}`);
}
return parser(raw);
}
#errorToString(e) {
if (e instanceof Error) {
return `${e.name}: ${e.message}`;
}
return String(e);
}
async load(filePath) {
const importPath = path.resolve(this.config.cwd ?? cwd, filePath);
const ext = getExtension(filePath);
this.#assertSupportedExtension(ext);
const path$1 = this.#getPath(filePath);
const raw = await readFile(importPath, "utf-8");
try {
return {
filePath,
path: path$1,
json: this.#parse(raw, ext)
};
} catch (e) {
return {
filePath,
path: path$1,
error: this.#errorToString(e)
};
}
}
};
function toErrorReason(result) {
return {
message: result.error,
filePath: result.filePath
};
}
function mergeJson(context) {
const jsons = context.jsons.sort((a, b) => a.path.localeCompare(b.path)).map(({ path, json }) => fixHierarchy(path, json, context.config));
return deepmerge.all(jsons);
}
function fixHierarchy(path, json, config) {
const keys = path.split("/").filter((key3) => key3 !== "");
if (!config.noSpreadIndex && keys[keys.length - 1] === "index") {
keys.pop();
}
if (keys.length === 0) {
if (typeof json !== "object") {
throw new Error("root json must be object");
}
return json;
}
return keys.reduceRight((acc, key3) => {
return {
[key3]: acc
};
}, json);
}
// src/index.ts
async function main(baseDir, config) {
checkBaseDir(baseDir);
const context = await loadContext(baseDir, config);
const result = mergeJson(context);
return await finalize(result, config);
}
function jsonFusion(baseDir, config = {}) {
return main(baseDir, config);
}
exports.JsonFusionError = JsonFusionError;
exports.jsonFusion = jsonFusion;
//# sourceMappingURL=out.js.map
//# sourceMappingURL=index.cjs.map