UNPKG

@sap/cds-dk

Version:

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

160 lines (146 loc) 6.52 kB
const fs = require('fs') const path = require('path') const cds = require('../../cds') const Plugin = require('../plugin') const { hasOptionValue, getFiles } = require('../util') const { FOLDER_GEN, DEFAULT_BUILTIN_PRIORITY_MAX } = require('../constants') class InternalBuildPlugin extends Plugin { /** * By default custom build plugins are executed before internal build plugins * to ensure built-in plugin content isn't overwritten by mistake. */ get priority() { return DEFAULT_BUILTIN_PRIORITY_MAX / 2 } init() { // REVISIT: no default gen folder for now } async clean() { function _isSubDirectory(parent, child) { return !path.relative(parent, child).startsWith('..') } // build results are deleted by the BuildTaskEngine if the build.target !== '.' // make sure that src is not a subfolder of dest if (cds.env.build.target === '.' && this.task.src !== this.task.dest && !_isSubDirectory(this.task.dest, this.task.src)) { await fs.promises.rm(this.task.dest, { force: true, recursive: true }) } } /** * Called by the framework after {@link #init()}, used to perform more elaborate preparation. * E.g. caching some pre-calculated data that can be used across multiple build tasks. This kind of data * has to be stored in the plugin type specific options section. * @returns {Promise<boolean>} A value 'false' indicates that {@link #prepare()} will not be called for other instances * of this plugin type. * True indicates that {@link #prepare()} will be called for other instances of this of this plugin type. * @deprecated */ async prepare() { // cancel subsequent prepare calls for other plugins of the same type by default return false } /** * Determines whether the given build option value has been set for this build task. * If the value is omitted, the existence of the given property name is checked. * @param {string} qualifiedName * @param {any=} value */ hasBuildOption(qualifiedName, value) { return hasOptionValue(this._getBuildOption(qualifiedName), value) } /** * Returns the value of the given build option defined for this build task. * @param {string} qualifiedName */ getBuildOption(qualifiedName) { return super._getBuildOption(qualifiedName) } /** * Returns whether the build results of this build plugin are created inplace * or in a separate staging folder which is not part of the build tasks src folder. */ isStagingBuild() { return !this.task.src.startsWith(this.task.dest) } async copyNativeContent(srcDir, destDir, customFilter) { function commonStagingBuildFilter(src, destDir) { if (typeof src !== "string" || typeof destDir !== "string") { return false } if (!fs.statSync(src).isDirectory()) { return true //file } if (src === destDir) { return false } const regex = new RegExp(FOLDER_GEN + "\\b") if (src === path.resolve(cds.root, cds.env.build.target)) { return false } return !regex.exec(path.basename(src)) } const files = getFiles(srcDir, (src) => { // do not copy files that: // - from 'cds.env.build.target' // - from 'dest' // - from some generation folder // - do not match filter function , return commonStagingBuildFilter(src, destDir) && (!customFilter || customFilter.call(this, src)) }) return Promise.all( files.map((srcFile) => { let relFile = path.relative(srcDir, srcFile) let destFile = path.join(destDir, relFile) return this.copy(srcFile).to(destFile) }) ) } async compileToJson(model, csnFile) { // This will als add a @source prop containing the relative path to the origin .cds source file // and a parsed _where clause for @restrict.{grant,where} annotations. // The @source annotation is required for correct custom handler resolution if no @impl annotation has been defined as // custom service handler implementations are relative to the origin .cds source files. // For staging builds (task.src !== task.dest) the csn.json file that is served at runtime is copied into a corresponding srv subfolder. // As a consequence the src folder name has to be included in the @source file name while for inplace builds (task.src === task.dest) this is not the case. // This ensures that the paths are relative to the cwd when executing cds run. const jsonOptions = { cwd: cds.root, src: this.hasBuildOption('flatDeployLayout', true) || this.task.src === this.task.dest ? this.task.src : cds.root } const csnStr = cds.compile.to.json(model, jsonOptions) await this.write(csnStr).to(csnFile) return csnStr } /** * Collect and write language bundles into a single i18n.json file. * @param {Object} model * @param {string} bundleDest */ async collectLanguageBundles(model, bundleDest) { // collect effective i18n properties... let bundles = {} const bundleGenerator = cds.localize.bundles4(model) if (bundleGenerator && bundleGenerator[Symbol.iterator]) { for (let [locale, bundle] of bundleGenerator) { // fallback bundle has the name "" if (typeof locale === 'string') { bundles[locale] = bundle } } } // omit bundles in case the fallback bundle is the only existing entry const keys = Object.keys(bundles) if (keys.length === 1 && keys[0] === "" && Object.keys(bundles[keys[0]]).length === 0) { bundles = {} } // copied from ../compile/i18n.js const { file: base = 'i18n' } = cds.env.i18n const file = path.join(bundleDest, base + '.json') // bundleDest might be null if (bundleDest && Object.keys(bundles).length > 0) { await this.write(bundles).to(file) return { file, bundles } } return null } } module.exports = InternalBuildPlugin