@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
162 lines (145 loc) • 7.64 kB
JavaScript
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