UNPKG

@sap/cds-dk

Version:

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

199 lines (168 loc) 7.55 kB
const cds = require ('../cds') const { local, tar } = cds.utils; const fs = require('fs'); const { join, dirname } = require('path'); const axios = require('axios'); const { login } = require('./auth_manager'); require('../util/axios').pruneErrors(); const { getMessage } = require('./util/logging'); const { handleHttpError } = require('./util/errors'); const { grepQR } = require('./util/fs'); const { escapeRegex } = require('./util/strings'); function tabSize(jsonString, defaultSize) { return /^ +/m.exec(jsonString)?.[0].length ?? defaultSize; } function getPackageContentsAndTabSize(pkgPath, defaultContents = undefined, defaultTabSize = 0) { try { const contents = fs.readFileSync(pkgPath, { encoding: 'utf-8' }); const tab = tabSize(contents, defaultTabSize); return [JSON.parse(contents), tab]; } catch (error) { if (defaultContents) { return [defaultContents, defaultTabSize]; } throw getMessage('package.json missing or unreadable', { error }); } } function writeObj(obj, objPath, tabSize) { if (!fs.existsSync(dirname(objPath))) { fs.mkdirSync(dirname(objPath), { recursive: true }); } fs.writeFileSync(objPath, JSON.stringify(obj, undefined, tabSize), { encoding: 'utf-8' }); } const INVALID_BASE_MODEL_NAME = /^[._]/; // See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#name module.exports = class Pull { static async run(paramValues) { const params = await login(paramValues); return new Pull(params).run(); } params; projectFolder; env; baseModelName; packagePath; baseModelFolder; #amendPackage = {}; #baseModelWorkspace; #packageContentsAndTabSize; static get DEFAULTS() { return { tabSize: 2, baseModelFolder: '.base', baseModelName: 'base-model' }; } constructor(params) { this.params = params; this.projectFolder = params.get('projectFolder'); this.env = cds.env.for('cds', this.projectFolder); this.baseModelName = this.env.extends; this.packagePath = join(this.projectFolder, 'package.json'); this.baseModelFolder = join(this.projectFolder, this.baseModelWorkspace); } async run() { console.log(`Pulling app base model`, { from: this.params.get('appUrl'), to: local(this.baseModelFolder), ...(this.params.has('subdomain') && { subdomain: this.params.get('subdomain') }) }); this.validateBaseModelName(); fs.rmSync(this.baseModelFolder, { force: true, recursive: true }); fs.mkdirSync(this.baseModelFolder, { recursive: true }); const baseModelTgz = await this.pullTgz(); await tar.xz(baseModelTgz).to(this.baseModelFolder); Pull.transformBasePackage(this.baseModelFolder, this.baseModelName); this.amendExtPackage(); console.log(`Finished. Refer to the base model like so: using from '${this.baseModelName}'`); const baseModelSymlink = join(this.projectFolder, 'node_modules', this.baseModelName); if (!fs.statSync(baseModelSymlink, { throwIfNoEntry: false })?.isSymbolicLink()) { console.log(`Note: the base model has been pulled to an NPM workspace in folder '${local(this.baseModelFolder)}'. Run 'npm install' to install it.`); } } get baseModelWorkspace() { const defaultFolder = Pull.DEFAULTS.baseModelFolder; if (!this.#baseModelWorkspace) { if (this.packageContents.workspaces) { if (this.packageContents.workspaces.includes(defaultFolder)) { this.#baseModelWorkspace = defaultFolder; } else { this.#baseModelWorkspace = this.packageContents.workspaces .map(ws => String(ws)) .find(ws => { try { return require(join(this.projectFolder, ws, 'package.json')).name === this.baseModelName; } catch (e) { /* ignore */ } }) ?? defaultFolder; } } else { this.#amendPackage.workspaces = [defaultFolder]; this.#baseModelWorkspace = defaultFolder; } } return this.#baseModelWorkspace; } validateBaseModelName() { if (!this.baseModelName) { throw getMessage(`Missing configuration 'cds.extends' in package.json`); } if (this.baseModelName.match(INVALID_BASE_MODEL_NAME)) { console.warn(`Invalid base-model name '${this.baseModelName}': must not start with '_' or '.'. ` + `Using '${Pull.DEFAULTS.baseModelName}' instead.`); const invalidNameInUse = grepQR(this.projectFolder, ['.cds', '.csn'], new RegExp(`\\b${escapeRegex(this.baseModelName)}\\b`)); if (invalidNameInUse) { console.error(`Some model files seem to reference the invalid name. Please update them manually.`); } this.baseModelName = Pull.DEFAULTS.baseModelName; this.#amendPackage.extends = this.baseModelName; } } getExtensionName() { try { return require(join(this.projectFolder, 'package.json')).name; } catch (error) { throw getMessage(`Extension project at ${this.projectFolder} is missing package.json file`, { error, command: 'push' }); } } async pullTgz() { const pullUrl = `${(this.params.get('appUrl'))}/-/cds/extensibility/pull`; const options = { ...this.params.get('reqAuth'), responseType: 'arraybuffer' }; return axios.post(pullUrl, { tag: this.getExtensionName() }, options) .then(response => Buffer.from(response.data)) .catch(error => handleHttpError(error, this.params, { url: pullUrl })); } static transformBasePackage(baseModelFolder, baseModelName) { const baseRcPath = join(baseModelFolder, '.cdsrc.json'); const baseRc = getPackageContentsAndTabSize(baseRcPath, {}, this.DEFAULTS.tabSize)[0]; fs.rmSync(baseRcPath, { force: true }); const basePkgPath = join(baseModelFolder, 'package.json'); const basePkg = { ...getPackageContentsAndTabSize(basePkgPath, {}, this.DEFAULTS.tabSize)[0], name: baseModelName, cds: baseRc }; writeObj(basePkg, basePkgPath, this.DEFAULTS.tabSize); } amendExtPackage() { if (Object.keys(this.#amendPackage).length === 0) { return; } console.log(`Amending extension package.json with project configuration`); fs.writeFileSync(this.packagePath, JSON.stringify({ ...this.packageContents, ...this.#amendPackage }, undefined, this.tabSize)); this.#packageContentsAndTabSize = undefined; } get packageContents() { if (!this.#packageContentsAndTabSize) { this.#packageContentsAndTabSize = getPackageContentsAndTabSize(this.packagePath, undefined, Pull.DEFAULTS.tabSize); } return this.#packageContentsAndTabSize[0]; } get tabSize() { if (!this.#packageContentsAndTabSize) { this.#packageContentsAndTabSize = getPackageContentsAndTabSize(this.packagePath, undefined, Pull.DEFAULTS.tabSize); } return this.#packageContentsAndTabSize[1]; } }