UNPKG

@webda/core

Version:

Expose API with Lambda

337 lines 12.2 kB
import { deepmerge } from "deepmerge-ts"; import * as fs from "fs"; import * as path from "path"; import { Application, SectionEnum } from "./application.js"; import { FileUtils } from "./utils/serializers.js"; import { join } from "path"; /** * Empty git information */ export const EmptyGitInformation = { branch: "", commit: "", short: "", tag: "", tags: [], version: "" }; /** * An unpacked application load dynamically all webda.module.json * And load also all the package description * * The normal Application is designed to load all this information from * the cachedModule to avoid any unecessary action within a production environment */ export class UnpackedApplication extends Application { constructor(file, logger) { super(file, logger); } /** * Load full configuration * * webda.config.json and complete the cachedModule * * @param file * @returns */ loadConfiguration(file) { if (!fs.existsSync(file)) { this.baseConfiguration = this.completeConfiguration({ version: 3 }); } else { this.baseConfiguration = this.completeConfiguration(FileUtils.load(file)); } } /** * Compute short ids * @returns */ async load() { await super.load(); const keys = Object.keys(this.getModels()); const namespace = this.getNamespace() + "/"; // All models from current namespace should have a short id keys .filter(key => key.startsWith(namespace)) .forEach(key => { this.baseConfiguration.cachedModules.models.shortIds[key.substring(namespace.length)] = key; this.baseConfiguration.cachedModules.models.shortIds[key] = key.substring(namespace.length); }); // If models from other namespaces have no local ones then they have short id too keys .filter(key => !key.startsWith(namespace)) .forEach(key => { const shortId = key.split("/")[1]; if (!this.baseConfiguration.cachedModules.models.shortIds[shortId]) { this.baseConfiguration.cachedModules.models.shortIds[shortId] = key; this.baseConfiguration.cachedModules.models.shortIds[key] = shortId; } }); return this; } /** * Add Moddas, Models and Deployers definitions * It also add the project metadata * * @param configuration * @returns */ completeConfiguration(configuration) { // Only one level of includes is permitted let imports = configuration.imports || []; let effectiveImports = []; for (let importFile of imports) { if (!fs.existsSync(this.getAppPath(importFile))) { this.log("WARN", `Cannot import configuration '${importFile}'`); continue; } effectiveImports.push(importFile); let includeConfiguration = FileUtils.load(this.getAppPath(importFile)); if (includeConfiguration.imports?.length) { this.log("WARN", `Imported configuration '${importFile}' has nested imports that will be skipped`); } configuration = deepmerge(includeConfiguration, configuration); } configuration.imports = effectiveImports; // If cachedModules is defined we do not recompute if (!configuration.cachedModules) { configuration.cachedModules = { project: this.loadProjectInformation(), beans: {}, deployers: {}, models: { list: {}, graph: {}, tree: {}, plurals: {}, shortIds: {}, reflections: {} }, schemas: {}, moddas: {} }; this.mergeModules(configuration); } return configuration; } /** * @returns empty git information */ getGitInformation(_name, _version) { return { branch: "", commit: "", short: "", tag: "", tags: [], version: "" }; } /** * Check package.json */ loadProjectInformation() { const info = { deployment: { name: "" }, package: { name: "", version: "" }, git: EmptyGitInformation, webda: {} }; let packageJson = path.join(this.appPath, "package.json"); if (fs.existsSync(packageJson)) { info.package = FileUtils.load(packageJson); } info.git = this.getGitInformation(info.package?.name, info.package?.version); info.webda = info.package.webda || {}; let parent = path.join(this.appPath, ".."); do { packageJson = path.join(parent, "package.json"); if (fs.existsSync(packageJson)) { let currentInfo = FileUtils.load(packageJson); if (currentInfo.workspaces) { this.log("DEBUG", "Application is running within a workspace"); // Replace any relative path by absolute one for (let i in currentInfo.webda) { if (currentInfo.webda[i].startsWith("./")) { currentInfo.webda[i] = path.join(path.resolve(parent), currentInfo.webda[i].substr(2)); } } info.webda = deepmerge(currentInfo.webda || {}, info.webda); info.webda.workspaces = { packages: currentInfo.workspaces, parent: currentInfo, path: path.resolve(parent) }; break; } } parent = path.resolve(path.join(parent, "..")); } while (parent !== path.resolve(path.join(parent, ".."))); // Check modules return info; } /** * Store the current module * @returns */ getModulesCache() { // @ts-ignore const cacheModules = process.webdaModules || {}; // @ts-ignore process.webdaModules ?? (process.webdaModules = cacheModules); return cacheModules; } /** * Search the node_modules structure for webda.module.json files * * @param path * @returns */ static findModulesFiles(path) { if (!path.endsWith("node_modules") || !fs.existsSync(path)) { return []; } const files = new Set(); const checkFolder = (filepath) => { if (fs.existsSync(join(filepath, "webda.module.json"))) { files.add(join(filepath, "webda.module.json")); } if (fs.existsSync(join(filepath, "node_modules"))) { this.findModulesFiles(join(filepath, "node_modules")).forEach(f => files.add(f)); } }; const recursiveSearch = (dirpath, depth = 0) => { fs.readdirSync(dirpath, { withFileTypes: true }).forEach(file => { if (file.name.startsWith(".")) { return; } const filepath = join(dirpath, file.name); if (file.isDirectory() && file.name.startsWith("@") && depth === 0) { // One recursion recursiveSearch(filepath, depth + 1); } else if (file.isDirectory()) { checkFolder(filepath); } else if (file.isSymbolicLink()) { // We want to follow symbolic links w/o increasing depth let realPath; try { // realpathSync will throw if the symlink is broken realPath = fs.realpathSync(filepath); } catch (err) { return; } checkFolder(realPath); } }); }; recursiveSearch(path, 0); return [...files]; } /** * Load all imported modules and current module * It will compile module * Generate the current module file * Load any imported webda.module.json */ findModules(module) { const appPath = this.getAppPath(); const cacheModules = this.getModulesCache(); // Cache modules to speed up tests if (cacheModules[appPath]) { return cacheModules[appPath]; } // Modules should be cached on deploy let files = []; let currentModule = this.getAppPath("webda.module.json"); if (fs.existsSync(currentModule)) { files.push(currentModule); } UnpackedApplication.findModulesFiles(this.getAppPath("node_modules")).forEach(f => files.push(f)); // Search workspace for webda.module.json if (module.project.webda.workspaces && module.project.webda.workspaces.path !== "") { UnpackedApplication.findModulesFiles(path.join(module.project.webda.workspaces.path, "node_modules")).forEach(f => files.push(f)); } // Ensure we are not adding many times the same modules cacheModules[appPath] = Array.from(new Set(files.map(n => fs.realpathSync(n)))).filter(f => this.filterModule(f)); return cacheModules[appPath]; } /** * Only allow local and core module and sample-app */ filterModule(_filename) { return true; } /** * Load a webda.module.json file * Resolve the linked file to current application * * @param moduleFile to load * @returns */ loadWebdaModule(moduleFile) { let module = FileUtils.load(moduleFile); Object.keys(SectionEnum) .filter(k => Number.isNaN(+k)) .forEach(p => { // Do not keep Beans from other modules if (this.getAppPath("webda.module.json") !== moduleFile && SectionEnum[p] === "beans") { delete module[SectionEnum[p]]; return; } for (let key in module[SectionEnum[p]]) { module[SectionEnum[p]][key] = path.join(path.relative(this.getAppPath(), path.dirname(moduleFile)), module[SectionEnum[p]][key]); } }); for (let key in module.models.list) { module.models.list[key] = path.join(path.relative(this.getAppPath(), path.dirname(moduleFile)), module.models.list[key]); } return module; } /** * Merge all modules into one cached module * * @param module */ mergeModules(configuration) { const module = configuration.cachedModules; const appModule = this.getAppPath("webda.module.json"); let files = this.findModules(module); let value = files .map(f => { let currentModule = this.loadWebdaModule(f); // We only take the Beans from current application if (appModule !== f) { this.log("DEBUG", `Load module without beans '${f}'`); delete currentModule.beans; } else { this.log("DEBUG", `Load module with beans '${f}'`); } return currentModule; }) .reduce((prev, val) => { return deepmerge(prev, val); }, module); Object.keys(SectionEnum) .filter(k => Number.isNaN(+k)) .forEach(p => { module[SectionEnum[p]] = value[SectionEnum[p]]; }); // Copying models module.models.list = value.models.list; module.models.graph = value.models.graph; module.models.tree = value.models.tree; module.models.plurals = value.models.plurals; // Copying schemas module.schemas = value.schemas; } } //# sourceMappingURL=unpackedapplication.js.map