UNPKG

@sap/ams-dev

Version:

NodesJS AMS development environment

130 lines (105 loc) 3.88 kB
const cds = require('@sap/cds'); const fs = require("node:fs"); const path = require("node:path"); const env = require('./capAmsDevEnv'); const AmsClient = require('../client/AmsClient'); const { compileDcl } = require('../compileDcl'); const { prepareFolders, debouncePromise } = require('../util'); let watch; // lazy loaded dependency const LOG = cds.log('ams'); const DCL_COMPILATION_DEBOUNCE_PARAMETER = 500; const watchers = {}; // file system watchers const RUNTIME_COMMANDS = ["serve", "test", "watch"]; /** Entry-point for AMS dev plugin. Must only be called once to bootstrap the plugin when the CAP application starts. */ function init() { LOG.debug('AMS Dev Plugin loaded.'); if (RUNTIME_COMMANDS.includes(cds.cli?.command)) { prepareNewBundle(); } if (cds.watched) { cds.once('shutdown', () => { for (const watcher of Object.values(watchers)) { watcher.close(); } }); cds.once('bootstrap', () => { watchDclFolder(); }); } } /* Schedule compilation of DCL to REGO whenever a DCL file is changed. */ async function watchDclFolder() { watch ??= require('node-watch'); const dclFolder = env.dclRoot; await prepareFolders([dclFolder]); watchers.dcl = watch(dclFolder, { recursive: true, filter: /\.dcl$/ }, debouncePromise(dclFileChanged, DCL_COMPILATION_DEBOUNCE_PARAMETER)); } async function dclFileChanged(eventType, filename) { LOG.debug(`DCL file ${filename} was ${eventType}d.`); await prepareNewBundle(eventType, filename); } /** Reaction to DCL file changes. Recompiles DCL to DCN. In Hybrid mode, pushes DCL to AMS Server instead. */ async function prepareNewBundle() { if (cds.env.profiles.includes("hybrid") && env.autoDeployDcl) { // Hybrid testing bound to AMS Server try { await uploadDclToAmsServer(); } catch (e) { LOG.error('DCL could not be uploaded to the AMS server.', e); return; } } else { compileDcn(); } } async function uploadDclToAmsServer() { const iasCredentials = cds.env?.requires?.auth?.credentials; if (iasCredentials == null) { LOG.warn('Cannot upload AMS policies because ias credentials are missing.'); return; } const ams = new AmsClient(iasCredentials); LOG.debug(`Deploying DCL policies to AMS instance ${iasCredentials.authorization_instance_id} on ${iasCredentials.url} \ (disable via requires.auth.ams.autoDeployDcl = false).`); await ams.uploadBaseDCL(env.dclRoot, { deployerAppName: "@sap/ams-dev:CAP_plugin_hybrid_mode_deployer" }); LOG.info('DCL policies succesfully deployed to AMS server (Hybrid testing mode with requires.auth.ams.autoDeployDcl = true detected).'); } /** * Cleans the DCN root directory by removing all files and subdirectories except `data.json`. * @param {string} dcnRoot - The root directory to clean. */ function cleanDcnRoot(dcnRoot) { try { // Read the directory contents const entries = fs.readdirSync(dcnRoot, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dcnRoot, entry.name); if (entry.isDirectory()) { // Remove directory and all its contents recursively fs.rmSync(fullPath, { recursive: true, force: true }); } else if (entry.isFile() && entry.name !== "data.json") { // Remove files that are not `data.json` fs.unlinkSync(fullPath); } } } catch (err) { if (err.code === "ENOENT") { // Directory doesn't exist, nothing to do } else { console.error("Failed to clean the DCN root directory before DCL->DCN compilation:", err); } } } /** Compiles the DCL files of the application to DCN. */ async function compileDcn() { LOG.debug("Compiling DCL to DCN."); await cleanDcnRoot(env.dcnRoot); const args = ["-d", env.dclRoot, "-o", env.dcnRoot]; try { compileDcl(args); } catch (error) { LOG.error("DCL->DCN compilation failed:", error.stdout); LOG.error(error.stdout); } } module.exports = { init };