UNPKG

@sap/cds-dk

Version:

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

311 lines (287 loc) 12.4 kB
const fs = require('fs') const path = require('path') const cds = require('../../cds') const { hasJavaNature, BuildError, normalizePath } = require('../util') const BuildTaskProvider = require('../buildTaskProvider') const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION } = require("../constants") const ResourcesTarBuilder = require('./mtx/resourcesTarBuilder') const DEBUG = cds.debug('cli|build') class InternalBuildTaskProvider extends BuildTaskProvider { providesTask(key) { return BUILD_TASKS.includes(key.for) } async lookupTasks(tasks, dependencies) { return this._createTasks(tasks, dependencies) } async applyTaskDefaults(task) { InternalBuildTaskProvider._setDefaultSrcFolder(task) } getPlugin(task) { return require(`./${task.for}`) } async _createTasks(tasks, dependencies) { let db = typeof cds.env.folders.db === "string" ? [normalizePath(cds.env.folders.db)] : cds.env.folders.db let srv = typeof cds.env.folders.srv === "string" ? [normalizePath(cds.env.folders.srv)] : cds.env.folders.srv const dbOptions = { model: [] } const srvOptions = { model: [] } if (Array.isArray(db) && db.length > 0) { db = InternalBuildTaskProvider._getModuleFolder(db) || null if (!db && !dependencies) { // log once DEBUG?.("No database module found") } } if (Array.isArray(srv) && srv.length > 0) { srv = InternalBuildTaskProvider._getModuleFolder(srv) || null if (!srv && !dependencies) { // log once DEBUG?.("No service module found") } } // order of creation is relevant if (dependencies) { const mtxTask = tasks.find(task => task.for === BUILD_TASK_MTX || task.for === BUILD_TASK_MTX_SIDECAR) if (mtxTask) { //restore used tasks as they might have been filtered by 'cds build --for mtx' if (mtxTask._uses) { mtxTask._uses.map(use => use.task).forEach(task => { if (!tasks.includes(task)) { tasks.push(task) } }) } let dbTask = ResourcesTarBuilder.getHanaTenantDbTask(tasks, mtxTask) if (!dbTask) { // db task might be missing if mtx task is enforced by 'cds build --for mtx' if (cds.env.build.tasks) { dbTask = ResourcesTarBuilder.getHanaTenantDbTask(cds.env.build.tasks, mtxTask) } if (!dbTask) { dbTask = this._createDbTask(db, dbOptions) } if (dbTask) { tasks.push(dbTask) } } if (dbTask && !mtxTask._uses?.map(use => use.task).includes(dbTask)) { const use = { task: dbTask } mtxTask._uses ? mtxTask._uses.push(use) : mtxTask._uses = [use] } } } else { !db && DEBUG?.(`project doesn't have a database module '${cds.env.folders.db}'`) !srv && DEBUG?.(`project doesn't have a service module '${cds.env.folders.srv}'`) // create hana build task const dbTask = this._createDbTask(db, dbOptions) if (dbTask) { tasks.push(dbTask) } // create java or node build task const srvTask = this._createSrvTask(srv, srvOptions) if (srvTask) { tasks.push(srvTask) } // create mtx build task const mtxTask = this._createMtxTask(srv) if (mtxTask) { tasks.push(mtxTask) } } } _createDbTask(src, taskOptions) { DEBUG?.("determining database kind") if (!src || InternalBuildTaskProvider._isMtxExtension()) { return null } let task = null if (this._useHana()) { DEBUG?.("found HANA database") // legacy build supports dest property const compileDest = cds.env.data?.dest if (compileDest) { //../db/src/gen // compileDest is relative to src folder in modular build - resolve correctly taskOptions.compileDest = path.relative(path.resolve(cds.root, src), path.resolve(cds.root, compileDest)) } task = { src: src, for: BUILD_TASK_HANA, options: taskOptions } } else { DEBUG?.("found sqlite database - skipping HANA build task") } return task } _useHana() { if (cds.env.requires.db?.kind === "hana" || cds.env.requires.db?.dialect === "hana") { return true } // false if other db has been defined if (cds.env.requires.db?.kind) { return false } // check whether cds config represents a legacy build system config for which requires.db was not configured // Note: compat layer sets requires.db: {} const userEnv = cds.env.for("cds", cds.root, false) return userEnv && (userEnv.data?.model || userEnv.service?.model) } _createMtxTask(src) { DEBUG?.("determining MTX architecture") // preserve order of creation if (InternalBuildTaskProvider._isMtxExtension()) { DEBUG?.("MTX extension app") return { for: BUILD_TASK_MTX_EXTENSION } } if (InternalBuildTaskProvider._isMtxs()) { const sidecarPath = path.join(cds.root, MTX_SIDECAR_FOLDER) let sidecarEnv if (fs.existsSync(sidecarPath)) { sidecarEnv = cds.env.for("cds", sidecarPath) } if ( cds.env.profiles?.includes('with-mtx-sidecar') || // new presets cds.env.requires["cds.xt.ModelProviderService"]?.external // for compatibility with former mtxs presets ) { DEBUG?.("MTX app with sidecar") if (!sidecarEnv) { throw new BuildError(`MTX sidecar directory '${sidecarPath}' not existing. Custom build task configuration required if the folder is named differently.`) } if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) { if (sidecarEnv.profiles?.includes('mtx-sidecar')) { // profile has been set, but cannot be resolved throw new BuildError("MTX configuration cannot be resolved. Run the 'npm install' command to install up-to-date versions of @sap/cds-mtxs and @sap/cds.") } throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`) } return { src: MTX_SIDECAR_FOLDER, for: BUILD_TASK_MTX_SIDECAR } } if (sidecarEnv?.requires["cds.xt.ModelProviderService"]?._in_sidecar || sidecarEnv?.profiles?.includes('mtx-sidecar')) { throw new BuildError(`MTX sidecar requires profile 'with-mtx-sidecar' in project root configuration.`) } if (cds.env.requires["cds.xt.ModelProviderService"]?._in_sidecar || cds.env.profiles?.includes('mtx-sidecar')) { // cds build is executed in sidecar folder throw new BuildError(`Seems that you are executing 'cds build' in the 'mtx/sidecar' folder. Execute 'cds build' in the project root folder instead.`) } // mtxs nodejs app without sidecar if (!hasJavaNature()) { DEBUG?.("MTX Nodejs app without sidecar") return { for: BUILD_TASK_MTX, src } } } } _createSrvTask(src, taskOptions) { DEBUG?.("determining implementation technology") if (!src || InternalBuildTaskProvider._isMtxExtension()) { return null } let task = null if (hasJavaNature()) { task = this._createJavaTask(src, taskOptions) } else { task = this._createNodeTask(src, taskOptions) } return task } _createJavaTask(src, taskOptions) { DEBUG?.("found implementation technology Java") // legacy build supports dest property const compileDest = cds.env.service?.dest if (compileDest) { // compileDest is relative to src folder in modular build - resolve correctly taskOptions.compileDest = path.relative(path.resolve(cds.root, src), path.resolve(cds.root, compileDest)) } return { src: src, for: BUILD_TASK_JAVA, options: taskOptions } } _createNodeTask(src, taskOptions) { DEBUG?.("found implementation technology Nodejs") if (!fs.existsSync(path.join(cds.root, 'package.json'))) { throw new BuildError(`No 'package.json' found in project root folder '${cds.root}'`) } return { src: src, for: BUILD_TASK_NODEJS, options: taskOptions } } static _isMtxExtension() { return !!cds.env.extends } /** * Distinguishes whether the Nodejs project is a Streamlined MTX (cds >=6) or an old MTX project. */ static _isMtxs() { return cds.env.requires.toggles || cds.env.profiles.includes('with-mtx-sidecar') || cds.env.requires['cds.xt.ModelProviderService'] || (typeof cds.env.requires.multitenancy === "object") } static _setDefaultSrcFolder(task) { switch (task.for) { case BUILD_TASK_HANA: task.src ||= normalizePath(cds.env.folders.db) break case BUILD_TASK_JAVA: case BUILD_TASK_JAVA_CF: case BUILD_TASK_NODEJS: case BUILD_TASK_NODE_CF: task.src ||= normalizePath(cds.env.folders.srv) break case BUILD_TASK_FIORI: task.src ||= normalizePath(cds.env.folders.app) break case BUILD_TASK_MTX_SIDECAR: task.src ||= MTX_SIDECAR_FOLDER break case BUILD_TASK_MTX_EXTENSION: if (task.src && task.src !== cds.root) { throw new BuildError("Invalid 'src' property value for build task 'mtx-extension', the only allowed value is '.'") } task.src ||= "." break case BUILD_TASK_MTX: // mtxs with nodejs, but without sidecar task.src ||= normalizePath(cds.env.folders.srv) break default: throw new Error(`Unknown build task '${task.for}'`) } } /** * Determines the module folder from the given list that may represent files or folders w or w/o .cds file extension. * @param {Array} filesOrFolders */ static _getModuleFolder(filesOrFolders) { const resources = [...filesOrFolders] filesOrFolders.forEach(fileOrFolder => { if (path.extname(fileOrFolder) !== FILE_EXT_CDS) { resources.push(fileOrFolder + FILE_EXT_CDS) } }) return resources.reduce((acc, resource) => { if (!acc) { let resourcePath = path.resolve(cds.root, resource) if (fs.existsSync(resourcePath)) { if (fs.lstatSync(resourcePath).isDirectory()) { acc = resource } else { // represents file acc = path.dirname(resource) } } } return acc }, null) } } module.exports = InternalBuildTaskProvider