@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
128 lines (111 loc) • 4.65 kB
JavaScript
const { utils: { local }} = require ('../cds');
const { join } = require('path');
const { readFileSync, existsSync } = require('fs');
const { login } = require('./auth_manager');
const buildSystem = require('../build')
const { buildResponseError, buildNetworkError, mtxFetch } = require('./util/request');
const { getMessage } = require('./util/logging');
const { schemaRegex } = require('./util/urls');
const JOB_STATUS = {
QUEUED: 'QUEUED',
RUNNING: 'RUNNING',
FINISHED: 'FINISHED',
FAILED: 'FAILED'
};
module.exports = class Push {
static async run(paramValues, pushOptions = {}) {
const params = await login(paramValues);
const projectFolder = params.get('projectFolder');
const extensionName = this.getExtensionName(projectFolder);
if (!('extArchive' in pushOptions)) {
await buildSystem.build();
}
const { src, content } = await this.readSource(params, pushOptions);
const url = params.get('appUrl');
const subdomain = params.get('subdomain');
const target = { url, ...subdomain && { subdomain } };
console.log(`\nPushing extension '${extensionName}' from`, { src }, 'to', target);
const pushResult = await this.pushTgz(params, content, extensionName);
if (params.get('async')) {
await this.pollUntilFinished(pushResult.headers.get('location'), params)
}
console.log ('Activation succeeded.\n')
}
static async readSource(params, pushOptions) {
let src = pushOptions?.extArchive ?? join(params.get('projectFolder'), 'gen/extension.tgz');
let content;
if (existsSync(src)) {
content = readFileSync(src, { encoding: 'base64' });
src = local(src);
} else {
if (!schemaRegex.test(src)) {
throw getMessage(`Non-existent path: ${src}`, { command: 'push' });
}
const response = await mtxFetch('GET', src, {}, params);
content = Buffer.from(await response.arrayBuffer()).toString('base64');
}
return { src, content };
}
static getExtensionName(projectFolder) {
try {
return require(join(projectFolder, 'package.json')).name;
} catch (error) {
throw getMessage(`Extension project at ${projectFolder} is missing package.json file`, { error, command: 'push' });
}
}
static async pushTgz(params, tgz, extensionName) {
const pushUrl = `${params.get('appUrl')}/-/cds/extensibility/push`;
const reqAuth = params.get('reqAuth') || {};
const headers = { 'content-type': 'application/json', ...reqAuth.headers };
const async = params.get('async');
if (async) {
headers['prefer'] = 'respond-async';
}
const response = await mtxFetch('POST', pushUrl, {
headers,
body: JSON.stringify({ extension: tgz, tag: extensionName })
}, params);
return response;
}
static async pollUntilFinished(jobUrl, params) {
const reqAuth = params.get('reqAuth') || {};
const headers = { ...reqAuth.headers };
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
clearInterval(interval);
reject(new Error('cds push timed out after 5 minutes'));
}, 5 * 60 * 1000);
const interval = setInterval(async () => {
try {
const response = await fetch(jobUrl, { headers });
if (!response.ok) {
clearInterval(interval);
clearTimeout(timeout);
reject(await buildResponseError('GET', jobUrl, response));
return;
}
const jobResult = await response.json();
const { status, tasks } = jobResult;
const error = tasks[0]?.error ?? 'cds push failed'
if (status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) {
clearInterval(interval);
clearTimeout(timeout);
if (status === JOB_STATUS.FINISHED) {
resolve();
} else if (status === JOB_STATUS.FAILED) {
reject(new Error(error));
}
}
} catch (e) {
clearInterval(interval);
clearTimeout(timeout);
if (e.status) {
reject(e);
} else {
reject(buildNetworkError('GET', jobUrl, e));
}
}
}, 1000);
});
}
}