UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

162 lines (145 loc) 7.64 kB
const path = require('path') const cds = require('../../../cds') const fs = require('fs') const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants') const NodejsBuildPlugin = require('../nodejs') const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder') const { ERROR } = NodejsBuildPlugin const { relativePaths, BuildError, resolveRequiredSapModels, getI18nDefaultFolder } = require('../../util') const DEBUG = cds.debug('cli|build') const DEFAULT_MAIN_FOLDER = "_main" class MtxSidecarBuildPlugin extends NodejsBuildPlugin { get priority() { // must run after 'hana' build task return super.priority - 10 } init() { super.init() if (cds.env.build.target === '.') { this.task.dest = path.join(this.task.dest, FOLDER_GEN) } } /** * Builds the mtx sidecar app consisting of: * - nodejs app model defined by the required sidecar services * - main app model defined by the build task's model options including feature models and resources TAR * * build.target=".": 'dest' -> 'model-provider/gen' * build.target="gen": 'dest' -> 'gen/model-provider' */ async build() { // nodejs app parts have to be built using sidecar env const sidecarEnv = cds.env.for("cds", this.task.src) // build main application await this._buildMainApp(sidecarEnv) // build node application await this._buildNodeApp(sidecarEnv) } /** * Builds the mtx sidecar nodejs app parts. * @param {object} sidecarEnv cds env based on the sidecar dir */ async _buildNodeApp(sidecarEnv) { const destSidecar = this.task.dest const destSidecarSrc = path.join(destSidecar, sidecarEnv.folders.srv) const model = this._compileSidecarSync(sidecarEnv) await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME)) await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder())) await this.copySrvContent(this.task.src, destSidecar, destSidecar) // copy .npmrc from project root, if it exists and no .npmrc exists in the sidecar if (!fs.existsSync(path.join(destSidecar, '.npmrc')) && fs.existsSync(path.join(cds.root, '.npmrc'))) { await this.copy(path.join(cds.root, '.npmrc')).to(path.join(destSidecar, '.npmrc')) } } /** * Builds the main app parts containing base model CSN with feature CSNs and resources TAR. * @param {object} sidecarEnv cds env based on the sidecar dir */ async _buildMainApp(sidecarEnv) { let main = sidecarEnv.requires['cds.xt.ModelProviderService']?.root if (!main) { throw new BuildError("Invalid MTX sidecar configuration. Make sure that the profile 'mtx-sidecar' is configured. Use 'npm install @sap/cds-mtxs' to install an up-to-date version.") } if (!this.hasBuildOption(MTX_SIDECAR_DB_VALIDATION, false) && !cds.env.build.tasks?.length // disable if custom build tasks exist && sidecarEnv.requires.db && cds.env.requires.db // ensure database is configured && sidecarEnv.requires.db.kind !== cds.env.requires.db.kind) { throw new BuildError(`Inconsistent database configuration found - application db '${cds.env.requires.db?.kind}', sidecar db '${sidecarEnv.requires.db?.kind}'`) } const profiles = cds.env.profiles ?? [] if (!profiles.includes("production") && !profiles.includes("prod")) { // need to overwrite 'development' profile setting '../..' with '_main', although it is questionable why cds build is executed with development profile main = DEFAULT_MAIN_FOLDER } const destRoot = this.task.dest const destMain = path.join(destRoot, main) const destMainSrv = path.join(destMain, cds.env.folders.srv) const model = await this.model() if (!model) { return } const { dictionary, sources } = await this.compileAll(model, destMainSrv, destMain) await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain) if (!this.hasBuildOption(CONTENT_EDMX, false)) { //inferred model expected by cds.compile.to.edmx() const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) } // disabled by default const baseModel = cds.compiler.compileSources({ 'base.json': dictionary.base }, { messages: this.messages }) await this.compileToEdmx(baseModel, path.join(destMainSrv, 'odata', cds.env.odata.version), compileOptions) } // create resources TAR // resources are determined based on available database build task, SQLite as fallback await new ResourcesTarBuilder(this).createTar(destMain, model) // copy package.json and .cdsrc.json from project root await this._copyMainConfigFiles(cds.root, destMain) } /** * Synchronous compilation using the sidecar context. * @param {object} sidecarEnv cds env based on the sidecar dir * @returns the compiled mtx sidecar CSN */ _compileSidecarSync(sidecarEnv) { const env = cds.env const root = cds.root try { cds.root = this.task.src cds.env = sidecarEnv const modelPaths = cds.resolve('*', false) const modelFilePaths = cds.resolve(modelPaths) if (!modelFilePaths || modelFilePaths.length === 0) { throw new BuildError("No CDS models found in MTX sidecar. Run the 'npm install' command to install up-to-date versions of the required packages.") } DEBUG?.(`sidecar model: ${relativePaths(cds.root, modelFilePaths).join(", ")}`) // check whether all models belonging to the @sap namespace can be resolved const unresolved = resolveRequiredSapModels(modelPaths) if (unresolved.length > 0) { // log error, but don't fail this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Run the 'npm install' command to install up-to-date versions of the required packages.`, ERROR) } // using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar // using different messages array ensures that cds compilation doesn't fail because of // existing compilation error messages that have been downgraded to warnings // see cap/issues/issues/16401#issuecomment-7205397 const messages = [] const model = cds.load(modelFilePaths, { sync: true, messages }) messages.forEach(msg => this.messages.push(msg)) return model } finally { // restore project scope cds.root = root cds.env = env } } async _copyMainConfigFiles(src, dest) { const packageJson = path.join(src, 'package.json') const cdsrcJson = path.join(src, '.cdsrc.json') const promises = [] if (fs.existsSync(packageJson)) { promises.push(this.copy(packageJson).to(path.join(dest, 'package.json'))) } if (fs.existsSync(cdsrcJson)) { promises.push(this.copy(cdsrcJson).to(path.join(dest, '.cdsrc.json'))) } return promises } } module.exports = MtxSidecarBuildPlugin