@sap-ux/deploy-tooling 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.undeploy = exports.deploy = exports.getCredentials = void 0;
13
+ const axios_extension_1 = require("@sap-ux/axios-extension");
14
+ const btp_utils_1 = require("@sap-ux/btp-utils");
15
+ const store_1 = require("@sap-ux/store");
16
+ const fs_1 = require("fs");
17
+ const config_1 = require("./config");
18
+ const prompt_1 = require("./prompt");
19
+ /**
20
+ * Check the secure storage if it has credentials for the given target.
21
+ *
22
+ * @param target - ABAP target
23
+ */
24
+ function getCredentials(target) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ if (!(0, btp_utils_1.isAppStudio)()) {
27
+ const systemService = yield (0, store_1.getService)({ entityName: 'system' });
28
+ let system = yield systemService.read(new store_1.BackendSystemKey({ url: target.url, client: target.client }));
29
+ if (!system && target.client) {
30
+ // check if there are credentials for the default client
31
+ system = yield systemService.read(new store_1.BackendSystemKey({ url: target.url }));
32
+ }
33
+ return system;
34
+ }
35
+ else {
36
+ return undefined;
37
+ }
38
+ });
39
+ }
40
+ exports.getCredentials = getCredentials;
41
+ /**
42
+ * Enhance axios options and create a service provider instance for an ABAP Cloud system.
43
+ *
44
+ * @param options - predefined axios options
45
+ * @param target - url target configuration
46
+ * @param noPrompt - only if not truthy prompt for anything
47
+ * @param logger - reference to the logger instance
48
+ */
49
+ function createAbapCloudServiceProvider(options, target, noPrompt, logger) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ const providerConfig = Object.assign(Object.assign({}, options), { environment: axios_extension_1.AbapCloudEnvironment.Standalone, service: target.serviceKey });
52
+ if (!target.serviceKey) {
53
+ const storedOpts = yield getCredentials(target);
54
+ if (logger && storedOpts) {
55
+ providerConfig.service = storedOpts.serviceKeys;
56
+ providerConfig.refreshToken = storedOpts.refreshToken;
57
+ logger.info(`Using system [${storedOpts.name}] from System store`);
58
+ }
59
+ if (!storedOpts && !noPrompt) {
60
+ providerConfig.service = yield (0, prompt_1.promptServiceKeys)();
61
+ }
62
+ }
63
+ if (providerConfig.service) {
64
+ return (0, axios_extension_1.createForAbapOnCloud)(providerConfig);
65
+ }
66
+ else {
67
+ throw new Error('Service keys required for deployment to an ABAP Cloud environment.');
68
+ }
69
+ });
70
+ }
71
+ /**
72
+ * Enhance axios options and create a service provider instance for an on-premise ABAP system.
73
+ *
74
+ * @param options - predefined axios options
75
+ * @param target - url target configuration
76
+ */
77
+ function createAbapServiceProvider(options, target) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ options.baseURL = target.url;
80
+ if (target.client) {
81
+ options.params['sap-client'] = target.client;
82
+ }
83
+ if (!options.auth) {
84
+ const storedOpts = yield getCredentials(target);
85
+ if (storedOpts === null || storedOpts === void 0 ? void 0 : storedOpts.password) {
86
+ options.auth = {
87
+ username: storedOpts.username,
88
+ password: storedOpts.password
89
+ };
90
+ }
91
+ }
92
+ return (0, axios_extension_1.createForAbap)(options);
93
+ });
94
+ }
95
+ /**
96
+ * Create an instance of a UI5AbapRepository service connected to the given target configuration.
97
+ *
98
+ * @param config - deployment configuration
99
+ * @param logger - optional reference to the logger instance
100
+ * @returns service instance
101
+ */
102
+ function createDeployService(config, logger) {
103
+ var _a, _b, _c;
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ let provider;
106
+ const options = {};
107
+ if (config.strictSsl === false) {
108
+ options.ignoreCertErrors = true;
109
+ }
110
+ if ((_a = config.credentials) === null || _a === void 0 ? void 0 : _a.password) {
111
+ options.auth = {
112
+ username: (_b = config.credentials) === null || _b === void 0 ? void 0 : _b.username,
113
+ password: (_c = config.credentials) === null || _c === void 0 ? void 0 : _c.password
114
+ };
115
+ }
116
+ options.params = config.target.params || {};
117
+ // Destination only supported on Business Application studio
118
+ if ((0, btp_utils_1.isAppStudio)() && config.target.destination) {
119
+ // Need additional properties to determine the type of destination we are dealing with
120
+ const destinations = yield (0, btp_utils_1.listDestinations)();
121
+ const destination = destinations === null || destinations === void 0 ? void 0 : destinations[config.target.destination];
122
+ if (!destination) {
123
+ throw new Error(`Destination ${config.target.destination} not found on subaccount`);
124
+ }
125
+ provider = (0, axios_extension_1.createForDestination)(options, destination);
126
+ }
127
+ else if ((0, config_1.isUrlTarget)(config.target)) {
128
+ if (config.target.cloud) {
129
+ provider = yield createAbapCloudServiceProvider(options, config.target, config.noRetry, logger);
130
+ }
131
+ else {
132
+ provider = yield createAbapServiceProvider(options, config.target);
133
+ }
134
+ }
135
+ else {
136
+ throw new Error('Unable to handle the configuration in the current environment.');
137
+ }
138
+ return provider.getUi5AbapRepository();
139
+ });
140
+ }
141
+ /**
142
+ * Try executing the deployment command and handle known errors.
143
+ *
144
+ * @param archive - archive file that is to be deployed
145
+ * @param service - instance of the axios-extension deployment service
146
+ * @param config - deployment configuration
147
+ * @param logger - reference to the logger instance
148
+ */
149
+ function tryDeploy(archive, service, config, logger) {
150
+ return __awaiter(this, void 0, void 0, function* () {
151
+ try {
152
+ yield service.deploy({ archive, bsp: config.app, testMode: config.test, safeMode: config.safe });
153
+ }
154
+ catch (e) {
155
+ if (!config.noRetry && (0, axios_extension_1.isAxiosError)(e)) {
156
+ const success = yield handleAxiosError(e.response, archive, service, config, logger);
157
+ if (success) {
158
+ return;
159
+ }
160
+ }
161
+ logger.error('Deployment has failed.');
162
+ logger.debug((0, config_1.getConfigForLogging)(config));
163
+ throw e;
164
+ }
165
+ });
166
+ }
167
+ /**
168
+ * Main function for different deploy retry handling.
169
+ *
170
+ * @param response - response of that trigged and axios error
171
+ * @param archive - archive file that is to be deployed
172
+ * @param service - instance of the axios-extension deployment service
173
+ * @param config - configuration used for the previous request
174
+ * @param logger - reference to the logger instance
175
+ * @returns true if the error was handled otherwise false is return or an error is raised
176
+ */
177
+ function handleAxiosError(response, archive, service, config, logger) {
178
+ var _a;
179
+ return __awaiter(this, void 0, void 0, function* () {
180
+ switch (response === null || response === void 0 ? void 0 : response.status) {
181
+ case 401:
182
+ logger.warn('Deployment failed with authentication error.');
183
+ logger.info('Please maintain correct credentials to avoid seeing this error\n\t(see help: https://www.npmjs.com/package/@sap/ux-ui5-tooling#setting-environment-variables-in-a-env-file)');
184
+ logger.info('Please enter your credentials for this deployment.');
185
+ const credentials = yield (0, prompt_1.promptCredentials)((_a = service.defaults.auth) === null || _a === void 0 ? void 0 : _a.username);
186
+ if (credentials) {
187
+ service.defaults.auth = credentials;
188
+ yield tryDeploy(archive, service, config, logger);
189
+ return true;
190
+ }
191
+ else {
192
+ return false;
193
+ }
194
+ case 412:
195
+ logger.warn('An app in the same repository with different sap app id found.');
196
+ if (config.yes || (yield (0, prompt_1.promptConfirmation)('Do you want to overwrite (Y/n)?'))) {
197
+ yield tryDeploy(archive, service, Object.assign(Object.assign({}, config), { safe: false, noRetry: true }), logger);
198
+ return true;
199
+ }
200
+ else {
201
+ return false;
202
+ }
203
+ default:
204
+ return false;
205
+ }
206
+ });
207
+ }
208
+ /**
209
+ * Deploy the given archive to the given target using the given app description.
210
+ *
211
+ * @param archive - archive file that is to be deployed
212
+ * @param config - deployment configuration
213
+ * @param logger - reference to the logger instance
214
+ */
215
+ function deploy(archive, config, logger) {
216
+ return __awaiter(this, void 0, void 0, function* () {
217
+ if (config.keep) {
218
+ (0, fs_1.writeFileSync)(`archive-${Date.now()}.zip`, archive);
219
+ }
220
+ const service = yield createDeployService(config, logger);
221
+ service.log = logger;
222
+ if (!config.strictSsl) {
223
+ logger.warn('You chose not to validate SSL certificate. Please verify the server certificate is trustful before proceeding. See documentation for recommended configuration (https://help.sap.com/viewer/17d50220bcd848aa854c9c182d65b699/Latest/en-US/4b318bede7eb4021a8be385c46c74045.html).');
224
+ }
225
+ logger.info(`Starting to deploy${config.test === true ? ' in test mode' : ''}.`);
226
+ yield tryDeploy(archive, service, config, logger);
227
+ if (config.test === true) {
228
+ logger.info('Deployment in TestMode completed. A successful TestMode execution does not necessarily mean that your upload will be successful.');
229
+ }
230
+ else {
231
+ logger.info('Successfully deployed.');
232
+ }
233
+ });
234
+ }
235
+ exports.deploy = deploy;
236
+ /**
237
+ * Deploy the given archive to the given target using the given app description.
238
+ *
239
+ * @param config - deployment configuration
240
+ * @param logger - reference to the logger instance
241
+ */
242
+ function undeploy(config, logger) {
243
+ return __awaiter(this, void 0, void 0, function* () {
244
+ const service = yield createDeployService(config, logger);
245
+ service.log = logger;
246
+ if (!config.strictSsl) {
247
+ logger.warn('You chose not to validate SSL certificate. Please verify the server certificate is trustful before proceeding. See documentation for recommended configuration (https://help.sap.com/viewer/17d50220bcd848aa854c9c182d65b699/Latest/en-US/4b318bede7eb4021a8be385c46c74045.html).');
248
+ }
249
+ logger.info(`Starting to undeploy${config.test === true ? ' in test mode' : ''}.`);
250
+ if (yield service.undeploy({ bsp: config.app, testMode: config.test })) {
251
+ logger.info('Successfully undeployed.');
252
+ }
253
+ });
254
+ }
255
+ exports.undeploy = undeploy;
256
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1,5 @@
1
+ export * from './archive';
2
+ export * from './config';
3
+ export * from './deploy';
4
+ export * from './prompt';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./archive"), exports);
18
+ __exportStar(require("./config"), exports);
19
+ __exportStar(require("./deploy"), exports);
20
+ __exportStar(require("./prompt"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ import type { ServiceInfo } from '@sap-ux/btp-utils';
2
+ import prompts from 'prompts';
3
+ /**
4
+ * Prompt for confirmation.
5
+ *
6
+ * @param message - the message to be shown for confirmation.
7
+ * @returns true if confirmed, otherwise false
8
+ */
9
+ export declare function promptConfirmation(message: string): Promise<boolean>;
10
+ /**
11
+ * Prompt for username and password.
12
+ *
13
+ * @param username - optional username that is to be offered as default
14
+ * @returns credentials object with username/password
15
+ */
16
+ export declare function promptCredentials(username?: string): Promise<prompts.Answers<"password" | "username">>;
17
+ /**
18
+ * Prompt for the location of the service keys.
19
+ *
20
+ * @returns credentials object with service keys
21
+ */
22
+ export declare function promptServiceKeys(): Promise<ServiceInfo>;
23
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.promptServiceKeys = exports.promptCredentials = exports.promptConfirmation = void 0;
16
+ const fs_1 = require("fs");
17
+ const prompts_1 = __importDefault(require("prompts"));
18
+ /**
19
+ * Prompt for confirmation.
20
+ *
21
+ * @param message - the message to be shown for confirmation.
22
+ * @returns true if confirmed, otherwise false
23
+ */
24
+ function promptConfirmation(message) {
25
+ return __awaiter(this, void 0, void 0, function* () {
26
+ let abort = false;
27
+ const { confirm } = yield (0, prompts_1.default)({
28
+ type: 'confirm',
29
+ name: 'confirm',
30
+ initial: true,
31
+ message
32
+ }, {
33
+ onCancel() {
34
+ abort = true;
35
+ return false;
36
+ }
37
+ });
38
+ return confirm && !abort;
39
+ });
40
+ }
41
+ exports.promptConfirmation = promptConfirmation;
42
+ /**
43
+ * Prompt for username and password.
44
+ *
45
+ * @param username - optional username that is to be offered as default
46
+ * @returns credentials object with username/password
47
+ */
48
+ function promptCredentials(username) {
49
+ return __awaiter(this, void 0, void 0, function* () {
50
+ const credentials = yield (0, prompts_1.default)([
51
+ {
52
+ type: 'text',
53
+ name: 'username',
54
+ initial: username,
55
+ message: 'Username:'
56
+ },
57
+ {
58
+ type: 'password',
59
+ name: 'password',
60
+ message: 'Password:'
61
+ }
62
+ ]);
63
+ return credentials;
64
+ });
65
+ }
66
+ exports.promptCredentials = promptCredentials;
67
+ /**
68
+ * Prompt for the location of the service keys.
69
+ *
70
+ * @returns credentials object with service keys
71
+ */
72
+ function promptServiceKeys() {
73
+ return __awaiter(this, void 0, void 0, function* () {
74
+ const { path } = yield (0, prompts_1.default)([
75
+ {
76
+ type: 'text',
77
+ name: 'path',
78
+ message: 'Please provide the service keys as file:',
79
+ validate: (input) => (0, fs_1.existsSync)(input)
80
+ }
81
+ ]);
82
+ return JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
83
+ });
84
+ }
85
+ exports.promptServiceKeys = promptServiceKeys;
86
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1,13 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import type { CliOptions } from '../types';
4
+ import type { Logger } from '@sap-ux/logger';
5
+ /**
6
+ * Get a zipped archived based on the given options.
7
+ *
8
+ * @param logger - reference to the logger instance
9
+ * @param options - options provided via CLI
10
+ * @returns Buffer containing the zip file
11
+ */
12
+ export declare function getArchive(logger: Logger, options: CliOptions): Promise<Buffer>;
13
+ //# sourceMappingURL=archive.d.ts.map
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getArchive = void 0;
16
+ const axios_1 = __importDefault(require("axios"));
17
+ const fs_1 = require("fs");
18
+ const yazl_1 = require("yazl");
19
+ const path_1 = require("path");
20
+ const archive_1 = require("../base/archive");
21
+ const https_1 = require("https");
22
+ /**
23
+ * Get/read zip file from the given path.
24
+ *
25
+ * @param logger - reference to the logger instance
26
+ * @param path - path to the zip file
27
+ * @returns Buffer containing the zip file
28
+ */
29
+ function getArchiveFromPath(logger, path) {
30
+ return new Promise((resolve, reject) => {
31
+ logger.info(`Loading archive from ${path}`);
32
+ (0, fs_1.readFile)(path, (err, data) => {
33
+ if (err) {
34
+ reject(`Archive loading has failed. Please ensure ${path} is valid and accessible.`);
35
+ }
36
+ else {
37
+ logger.info('Archive loaded.');
38
+ resolve(data);
39
+ }
40
+ });
41
+ });
42
+ }
43
+ /**
44
+ * Fetch/get zip file from the given url.
45
+ *
46
+ * @param url - url to the zip file
47
+ * @param rejectUnauthorized - strict SSL handling or not
48
+ * @returns Buffer containing the zip file
49
+ */
50
+ function fetchArchiveFromUrl(url, rejectUnauthorized) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ try {
53
+ const response = yield axios_1.default.get(url, {
54
+ httpsAgent: new https_1.Agent({ rejectUnauthorized }),
55
+ responseType: 'arraybuffer'
56
+ });
57
+ return response.data;
58
+ }
59
+ catch (error) {
60
+ throw new Error(`The archive url you provided could not be reached. Please ensure the URL is accessible and does not require authentication. ${error}`);
61
+ }
62
+ });
63
+ }
64
+ /**
65
+ * Create a zipped file containing all files in the given folder.
66
+ *
67
+ * @param logger - reference to the logger instance
68
+ * @param path - path to the folder that is to be zipped
69
+ * @returns Buffer containing the zip file
70
+ */
71
+ function createArchiveFromFolder(logger, path) {
72
+ try {
73
+ logger.info(`Creating archive from ${path}.`);
74
+ const files = (0, archive_1.getFileNames)(path);
75
+ const zip = new yazl_1.ZipFile();
76
+ for (const filePath of files) {
77
+ const relPath = (0, path_1.relative)(path, filePath);
78
+ logger.debug(`Adding ${relPath}`);
79
+ zip.addFile(filePath, relPath);
80
+ }
81
+ logger.info(`Archive created from ${path}.`);
82
+ return (0, archive_1.createBuffer)(zip);
83
+ }
84
+ catch (error) {
85
+ throw new Error(`Archive creation has failed. Please ensure ${path} is valid and accessible.`);
86
+ }
87
+ }
88
+ /**
89
+ * Get a zipped archived based on the given options.
90
+ *
91
+ * @param logger - reference to the logger instance
92
+ * @param options - options provided via CLI
93
+ * @returns Buffer containing the zip file
94
+ */
95
+ function getArchive(logger, options) {
96
+ var _a;
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ if (options.archivePath) {
99
+ return getArchiveFromPath(logger, options.archivePath);
100
+ }
101
+ else if (options.archiveUrl) {
102
+ return fetchArchiveFromUrl(options.archiveUrl, options.strictSsl);
103
+ }
104
+ else {
105
+ return createArchiveFromFolder(logger, (_a = options.archiveFolder) !== null && _a !== void 0 ? _a : process.cwd());
106
+ }
107
+ });
108
+ }
109
+ exports.getArchive = getArchive;
110
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1,23 @@
1
+ import type { AbapDeployConfig, CliOptions } from '../types';
2
+ /**
3
+ * Tries to read the version of the modules package.json but in case of an error, it returns the manually maintained version matching major.minor of the module.
4
+ *
5
+ * @returns the version of the deploy tooling.
6
+ */
7
+ export declare function getVersion(): any;
8
+ /**
9
+ * Read the deployment configuration from a ui5*.yaml file.
10
+ *
11
+ * @param path - path to the ui5*.yaml file
12
+ * @returns the configuration object or throws an error if it cannot be read.
13
+ */
14
+ export declare function getDeploymentConfig(path: string): Promise<AbapDeployConfig>;
15
+ /**
16
+ * Merge the configuration from the ui5*.yaml with CLI options.
17
+ *
18
+ * @param taskConfig - base configuration from the file
19
+ * @param options - CLI options
20
+ * @returns the merged config
21
+ */
22
+ export declare function mergeConfig(taskConfig: AbapDeployConfig, options: CliOptions): Promise<AbapDeployConfig>;
23
+ //# sourceMappingURL=config.d.ts.map