UNPKG

@sap/cds-dk

Version:

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

185 lines (163 loc) 8.8 kB
const cds = require('../../../cds') const { exists, path } = cds.utils const { SEVERITY_ERROR: ERROR, FOLDER_GEN, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants') const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder') const { relativePaths, BuildError, resolveRequiredSapModels, getI18nDefaultFolder } = require('../../util') const DEBUG = cds.debug('cli|build') const DEFAULT_MAIN_FOLDER = "_main" const [major, minor] = cds.version.split('.').map(Number) const COMPAT = major === 9 && minor < 7 // cds10: remove module.exports = class MtxSidecarBuildPlugin extends require('../nodejs') { static get taskDefaults() { return { src: 'mtx/sidecar' } } static hasTask() { if (cds.env.extends) return const mps = cds.env.requires['cds.xt.ModelProviderService'] const isMtxs = cds.env.requires.multitenancy || cds.env.requires.toggles || cds.env.requires['cds.xt.ModelProviderService'] const src = path.join(cds.root, this.taskDefaults?.src ?? 'mtx/sidecar') if (mps?._in_sidecar) { throw new BuildError(`Seems that you are executing 'cds build' in the 'mtx/sidecar' folder. Execute 'cds build' in the project root folder instead.`) } return isMtxs && exists(src) } get requires() { return ['hana'] } 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: * - Node.js 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() { // REVISIT: required to load plugins from mtx/sidecar, not the main app for the sidecar CSN const oldRoot = cds.root cds.root = this.task.src const sidecarEnv = cds.env.for('cds', this.task.src) cds.root = oldRoot const [rMain, rNode] = await Promise.allSettled([ this._buildMainApp(sidecarEnv), this._buildSidecarApp(sidecarEnv) ]) if (rMain.status === 'rejected') throw rMain.reason if (rNode.status === 'rejected') throw rNode.reason } /** * Builds the MTX sidecar Node.js app parts. * @param {import('../../../cds').env} sidecarEnv `cds.env` based on the MTX sidecar directory. */ async _buildSidecarApp(sidecarEnv) { const { dest, src } = this.task const model = COMPAT ? this._compatCompileSidecarSync(sidecarEnv) : this._compileSidecarSync(sidecarEnv) const srv = path.join(dest, sidecarEnv.folders.srv) const promises = [ this.compileToJson(model, path.join(srv, 'csn.json')), this.collectLanguageBundles(model, path.join(srv, getI18nDefaultFolder())), this.copySrvContent(src, dest, dest) ] // copy .npmrc from project root, if it exists and no .npmrc exists in the sidecar if (!exists(path.join(dest, '.npmrc')) && exists('.npmrc')) { promises.push(this.copy(path.join(cds.root, '.npmrc')).to(path.join(dest, '.npmrc'))) } await Promise.all(promises) } /** * Builds the main app parts containing base model CSN with feature CSNs and resources TAR. * @param {import('../../../cds').env} sidecarEnv `cds.env` based on the MTX sidecar directory. */ 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 destMain = path.join(this.task.dest, 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) const promises = [] promises.push(this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)) // promises.push(this.collectAllLanguageBundles(dictionary, sources, destMain, 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 }) promises.push(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 promises.push(new ResourcesTarBuilder(this).createTar(destMain, model)) // copy package.json and .cdsrc.json from project root const packageJson = path.join(cds.root, 'package.json') const cdsrcJson = path.join(cds.root, '.cdsrc.json') if (exists('package.json')) { promises.push(this.copy(packageJson).to(path.join(destMain, 'package.json'))) } if (exists('.cdsrc.json')) { promises.push(this.copy(cdsrcJson).to(path.join(destMain, '.cdsrc.json'))) } return Promise.all(promises) } /** * Synchronous compilation using the MTX sidecar context. * @param {import('../../../cds').env} env `cds.env` based on the MTX sidecar directory. * @returns the compiled MTX sidecar CSN */ _compileSidecarSync(env) { const options = { env, root: this.task.src } const modelPaths = COMPAT ? cds.resolve('*', false) : cds.resolve('*', { ...options, dry: true }) const modelFilePaths = COMPAT ? cds.resolve(modelPaths) : cds.resolve(modelPaths, options) 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, cwd: this.task.src }) messages.forEach(msg => this.messages.push(msg)) return model } // cds10: remove _compatCompileSidecarSync(sidecarEnv) { const env = cds.env const root = cds.root try { cds.root = this.task.src cds.env = sidecarEnv return this._compileSidecarSync(sidecarEnv) } finally { // restore project scope cds.root = root cds.env = env } } }