From eb1adcc9e51c922df207070d8db16fc5462f4ff5 Mon Sep 17 00:00:00 2001 From: IainSAP <46536134+IainSAP@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:00:48 +0100 Subject: [PATCH 1/2] Fixes for multiple abap-on-prem new system prompts (#2196) * Fixes for multiple abap-on-prem new system prompts https://github.com/SAP/open-ux-tools/issues/2193 * Adds tests to cover `authRequired` * Adds test to cover * Fixes sonar issue --- .changeset/mighty-meals-divide.md | 5 ++ .../src/prompts/connectionValidator.ts | 79 ++++++++++++++++--- .../sap-system/abap-on-prem/questions.ts | 17 +++- .../sap-system/new-system/questions.ts | 13 +-- .../datasources/sap-system/validators.ts | 6 +- .../odata-service-inquirer.i18n.json | 5 +- .../unit/prompts/connectionValidator.test.ts | 79 ++++++++++++++++--- .../sap-system/abap-on-prem/questions.test.ts | 7 ++ 8 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 .changeset/mighty-meals-divide.md diff --git a/.changeset/mighty-meals-divide.md b/.changeset/mighty-meals-divide.md new file mode 100644 index 0000000000..c175cf88fa --- /dev/null +++ b/.changeset/mighty-meals-divide.md @@ -0,0 +1,5 @@ +--- +'@sap-ux/odata-service-inquirer': patch +--- + +Fixes multiple issues with Abap On Prem system creation diff --git a/packages/odata-service-inquirer/src/prompts/connectionValidator.ts b/packages/odata-service-inquirer/src/prompts/connectionValidator.ts index ffff95a921..cc1e2d8a51 100644 --- a/packages/odata-service-inquirer/src/prompts/connectionValidator.ts +++ b/packages/odata-service-inquirer/src/prompts/connectionValidator.ts @@ -21,10 +21,15 @@ import { errorHandler } from './prompt-helpers'; * Structure to store validity information about url to be validated. */ interface Validity { + // True if the url is in a valid format urlFormat?: boolean; + // True if the url is reachable reachable?: boolean; + // True if the url requires authentication, i.e. returns a 401/403 (note even once authenticated, this will remain true) authRequired?: boolean; + // True if the url is authenticated and accessible authenticated?: boolean; + // True if the url has a cert error that can be skipped canSkipCertError?: boolean; } @@ -41,7 +46,11 @@ const ignorableCertErrors = [ERROR_TYPE.CERT_SELF_SIGNED, ERROR_TYPE.CERT_SELF_S */ export class ConnectionValidator { public readonly validity: Validity = {}; + // The current valid url (not necessarily authenticated but the url is in a valid format) private _validatedUrl: string | undefined; + // The current client code used for requests, the client code has been validated by a successful request + private _validatedClient: string | undefined; + private _odataService: ODataService; private _serviceProvider: ServiceProvider; private _axiosConfig: AxiosRequestConfig; @@ -135,7 +144,7 @@ export class ConnectionValidator { // Full service URL await this.createServiceConnection(axiosConfig, url.pathname); } - + this._validatedClient = url.searchParams.get(SAP_CLIENT_KEY) ?? undefined; return 200; } catch (e) { LoggerHelper.logger.debug(`ConnectionValidator.checkSapService() - error: ${e.message}`); @@ -236,7 +245,7 @@ export class ConnectionValidator { * @param serviceUrl the odata service url to validate * @param options options for the connection validation * @param options.ignoreCertError ignore some certificate errors - * @param options.forceReValidation force re-validation of the url + * @param options.forceReValidation force re-validation of the url even if the same url has been prevously validated * @param options.isSystem if true, the url will be treated as a system url rather than a service url * @param options.odataVersion if specified will restrict catalog requests to only the specified odata version * @returns true if the url is reachable, false if not, or an error message string @@ -283,7 +292,7 @@ export class ConnectionValidator { // More helpful context specific error if (ErrorHandler.getErrorType(error) === ERROR_TYPE.CONNECTION) { this.validity.reachable = false; - return errorHandler.logErrorMsgs(t('errors.serviceUrlNotFound', { url: serviceUrl })); + return errorHandler.logErrorMsgs(t('errors.systemOrserviceUrlNotFound', { url: serviceUrl })); } this.resetValidity(); @@ -301,9 +310,9 @@ export class ConnectionValidator { */ private getValidationResultFromStatusCode(status: string | number): boolean | string | IValidationLink { if (status === 200) { + this.validity.reachable = true; this.validity.authenticated = true; - this.validity.authRequired = false; - } else if (status === 404) { + } else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.NOT_FOUND) { this.validity.reachable = false; return ErrorHandler.getErrorMsgFromType(ERROR_TYPE.NOT_FOUND) ?? false; } else if (ErrorHandler.isCertError(status)) { @@ -313,10 +322,11 @@ export class ConnectionValidator { } else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.AUTH) { this.validity.reachable = true; this.validity.authRequired = true; + this.validity.authenticated = false; } else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.REDIRECT) { this.validity.reachable = true; return t('errors.urlRedirect'); - } else if (status !== 404) { + } else if (ErrorHandler.getErrorType(status) === ERROR_TYPE.CONNECTION) { this.validity.reachable = false; return ErrorHandler.getErrorMsgFromType(ERROR_TYPE.CONNECTION, `http code: ${status}`) ?? false; } @@ -348,6 +358,50 @@ export class ConnectionValidator { return false; } + /** + * Check whether basic auth is required for the given url, or for the previously validated url if none specified. + * This will also set the validity state for the url. This will not validate the URL. + * + * @param urlString - the url to validate, if not provided the previously validated url will be used + * @param client - optional, sap client code, if not provided the previously validated client will be used + * @param ignoreCertError + * @returns true if basic auth is required, false if not + */ + public async isAuthRequired( + urlString = this._validatedUrl, + client = this._validatedClient, + ignoreCertError = false + ): Promise { + if (!urlString) { + return false; + } + + // Dont re-request if already validated + if ( + this._validatedUrl === urlString && + this._validatedClient === client && + this.validity.authRequired !== undefined + ) { + return this.validity.authRequired; + } + // New URL or client so we need to re-request + try { + const url = new URL(urlString); + if (client) { + url.searchParams.append(SAP_CLIENT_KEY, client); + } + this.validity.authRequired = this.validity.reachable = + ErrorHandler.getErrorType( + await this.checkSapService(url, undefined, undefined, { ignoreCertError }) + ) === ERROR_TYPE.AUTH; + + return this.validity.authRequired; + } catch (error) { + errorHandler.logErrorMsgs(error); + return false; // Cannot determine if auth required + } + } + /** * Test the connectivity with the specified service url using the provided credentials. * @@ -387,11 +441,18 @@ export class ConnectionValidator { isSystem, odataVersion }); - + LoggerHelper.logger.debug(`ConnectionValidator.validateUrl() - status: ${status}; url: ${url}`); + // Since an exception was not thrown, this is a valid url + this.validity.urlFormat = true; + this._validatedUrl = url; const valResult = this.getValidationResultFromStatusCode(status); - if (valResult === true && this.validity.authenticated === true) { - return true; + if (valResult === true) { + if (this.validity.authenticated === true) { + return true; + } else if (this.validity.authenticated === false) { + return t('errors.authenticationFailed'); + } } return valResult; } catch (error) { diff --git a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/abap-on-prem/questions.ts b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/abap-on-prem/questions.ts index d0e0098760..b50c07a925 100644 --- a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/abap-on-prem/questions.ts +++ b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/abap-on-prem/questions.ts @@ -198,6 +198,7 @@ export function getAbapOnPremSystemQuestions( requiredOdataVersion?: OdataVersion ): Question[] { const connectValidator = connectionValidator ?? new ConnectionValidator(); + let validClient = true; const questions: Question[] = [ { @@ -229,10 +230,18 @@ export function getAbapOnPremSystemQuestions( guiOptions: { breadcrumb: t('prompts.sapClient.breadcrumb') }, - validate: validateClient + validate: (client) => { + const valRes = validateClient(client); + if (valRes === true) { + validClient = true; + return true; + } + validClient = false; + return valRes; + } } as InputQuestion, { - when: () => (connectValidator.validity.reachable ? connectValidator.validity.authRequired === true : false), + when: () => connectValidator.isAuthRequired(), type: 'input', name: abapOnPremInternalPromptNames.systemUsername, message: t('prompts.systemUsername.message'), @@ -242,7 +251,7 @@ export function getAbapOnPremSystemQuestions( validate: (user: string) => user?.length > 0 } as InputQuestion, { - when: () => (connectValidator.validity.reachable ? connectValidator.validity.authRequired === true : false), + when: () => connectValidator.isAuthRequired(), type: 'password', guiOptions: { mandatory: true @@ -252,7 +261,7 @@ export function getAbapOnPremSystemQuestions( guiType: 'login', mask: '*', validate: async (password, { systemUrl, abapSystemUsername, sapClient }: AbapOnPremAnswers) => { - if (!(systemUrl && abapSystemUsername && password)) { + if (!(systemUrl && abapSystemUsername && password && validClient)) { return false; } const valResult = await connectValidator.validateAuth(systemUrl, abapSystemUsername, password, { diff --git a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/new-system/questions.ts b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/new-system/questions.ts index e174e576f1..1f9d4f3f47 100644 --- a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/new-system/questions.ts +++ b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/new-system/questions.ts @@ -83,6 +83,7 @@ export function getNewSystemQuestions(promptOptions?: OdataServicePromptOptions) */ export function getUserSystemNameQuestion(): InputQuestion { let defaultSystemName: string; + let userModifiedSystemName: boolean = false; const newSystemNamePrompt = { type: 'input', guiOptions: { @@ -93,20 +94,22 @@ export function getUserSystemNameQuestion(): InputQuestion { name: promptNames.userSystemName, message: t('prompts.systemName.message'), default: async (answers: AbapOnPremAnswers & NewSystemAnswers) => { - if (answers.newSystemType === 'abapOnPrem' && answers.systemUrl) { + if (answers.newSystemType === 'abapOnPrem' && answers.systemUrl && !userModifiedSystemName) { defaultSystemName = await suggestSystemName(answers.systemUrl, answers.sapClient); + return defaultSystemName; } - return defaultSystemName; + return answers.userSystemName; }, validate: async (systemName: string, answers: AbapOnPremAnswers & NewSystemAnswers) => { - let validationResult: boolean | string = false; // Dont validate the suggested default system name if (systemName === defaultSystemName) { - validationResult = true; + return true; } - validationResult = await validateSystemName(systemName); + const validationResult = await validateSystemName(systemName); if (validationResult === true) { + // Not the default system name, so the user modified + userModifiedSystemName = true; const backendSystem = new BackendSystem({ name: systemName, url: answers.systemUrl!, diff --git a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/validators.ts b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/validators.ts index 70b36fe39d..7d1bd795c8 100644 --- a/packages/odata-service-inquirer/src/prompts/datasources/sap-system/validators.ts +++ b/packages/odata-service-inquirer/src/prompts/datasources/sap-system/validators.ts @@ -18,8 +18,10 @@ async function isSystemNameInUse(systemName: string): Promise { * * @param systemName a system name to validate * @returns true if the name is valid, otherwise an error message - */ -export async function validateSystemName(systemName: string): Promise { + */ export async function validateSystemName(systemName: string): Promise { + if (!systemName) { + return t('prompts.systemName.emptySystemNameWarning'); + } const systemExists = await isSystemNameInUse(systemName); if (systemExists) { return t('prompts.systemName.systemNameExistsWarning'); diff --git a/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json b/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json index 2de587ddde..482c9fe48f 100644 --- a/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json +++ b/packages/odata-service-inquirer/src/translations/odata-service-inquirer.i18n.json @@ -96,7 +96,8 @@ "message": "System name", "hint": "Entering a system name will save the connection for re-use.", "systemNameExistsWarning": "A system with that name already exists in the secure storage. Please try a different name.", - "reservedSystemNameWarning": "'{{ systemName }}' is a reserved system name. Please try a different name." + "reservedSystemNameWarning": "'{{ systemName }}' is a reserved system name. Please try a different name.", + "emptySystemNameWarning": "System name cannot be empty." }, "systemSelection": { "newSystemChoiceLabel": "New system" @@ -129,7 +130,7 @@ "noSuchHostError": "No such host is known", "odataServiceVersionMismatch": "The template you have chosen supports V{{requiredVersion}} OData services only. The provided version is V{{serviceVersion}}.", "destinationAuthError": "The selected system is returning an authentication error. Please verify the destination configuration", - "serviceUrlNotFound": "Please verify the service url: {{- url}}, target system configuration and network connectivity", + "systemOrserviceUrlNotFound": "Please verify the url: {{- url}}, target system configuration and network connectivity", "urlRedirect": "The service URL is redirecting", "certValidationRequired": "Certificate validation is required to continue.", "exitingGeneration": "Exiting generation. {{exitReason}}", diff --git a/packages/odata-service-inquirer/test/unit/prompts/connectionValidator.test.ts b/packages/odata-service-inquirer/test/unit/prompts/connectionValidator.test.ts index b7cf086a11..719a6ec3b4 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/connectionValidator.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/connectionValidator.test.ts @@ -1,5 +1,5 @@ -import * as axiosExtension from '@sap-ux/axios-extension'; import type { AxiosRequestConfig } from '@sap-ux/axios-extension'; +import * as axiosExtension from '@sap-ux/axios-extension'; import { ODataService, ServiceProvider } from '@sap-ux/axios-extension'; import type { AxiosResponse } from 'axios'; import { AxiosError } from 'axios'; @@ -38,6 +38,7 @@ describe('ConnectionValidator', () => { beforeEach(() => { jest.restoreAllMocks(); + mockIsAppStudio = false; }); test('should handle an invalid url', async () => { @@ -64,7 +65,6 @@ describe('ConnectionValidator', () => { const result = await validator.validateUrl(serviceUrl); expect(result).toBe(true); expect(validator.validity).toEqual({ - authRequired: false, authenticated: true, reachable: true, urlFormat: true @@ -106,7 +106,8 @@ describe('ConnectionValidator', () => { expect(validator.validity).toEqual({ urlFormat: true, reachable: true, - authRequired: true + authRequired: true, + authenticated: false }); const createProviderSpy = jest.spyOn(axiosExtension, 'create'); @@ -125,6 +126,10 @@ describe('ConnectionValidator', () => { ); expect(serviceProviderSpy).toHaveBeenCalledWith('/some/path/to/service/'); + // Username/pword are invalid + jest.spyOn(ODataService.prototype, 'get').mockRejectedValue(newAxiosErrorWithStatus(403)); + expect(await validator.validateAuth(serviceUrl, 'user1', 'password1')).toBe(t('errors.authenticationFailed')); + // Dont authenticate if the url is empty getODataServiceSpy.mockReset(); getODataServiceSpy = jest.spyOn(ODataService.prototype, 'get').mockRejectedValue(newAxiosErrorWithStatus(404)); @@ -226,6 +231,7 @@ describe('ConnectionValidator', () => { expect(validator.validity).toEqual({ urlFormat: true, + authenticated: false, reachable: true, authRequired: true }); @@ -234,7 +240,6 @@ describe('ConnectionValidator', () => { expect(validator.validity).toEqual({ urlFormat: true, reachable: true, - authRequired: false, authenticated: true }); }); @@ -245,7 +250,6 @@ describe('ConnectionValidator', () => { const result = await validator.validateUrl('https://example.com/service'); expect(result).toBe(true); expect(validator.validity).toEqual({ - authRequired: false, authenticated: true, reachable: true, urlFormat: true @@ -263,12 +267,17 @@ describe('ConnectionValidator', () => { const validator = new ConnectionValidator(); const result = await validator.validateUrl('https://example.com/service'); expect(result).toBe(true); - expect(validator.validity).toEqual({ authRequired: true, reachable: true, urlFormat: true }); + expect(validator.validity).toEqual({ + authenticated: false, + authRequired: true, + reachable: true, + urlFormat: true + }); // Change the response to 200 and force re-validation of the same url jest.spyOn(ODataService.prototype, 'get').mockRejectedValueOnce(newAxiosErrorWithStatus(200)); await validator.validateUrl('https://example.com/service', { forceReValidation: true }); expect(validator.validity).toEqual({ - authRequired: false, + authRequired: true, authenticated: true, reachable: true, urlFormat: true @@ -294,10 +303,14 @@ describe('ConnectionValidator', () => { cookies: '', ignoreCertErrors: false, params: { - 'sap-client': '999', - saml2: 'disabled' + 'sap-client': '999' } }); + expect(connectValidator.validity).toEqual({ + authenticated: true, + reachable: true, + urlFormat: true + }); }); test('should validate connectivity with `listServices` when connecting to sap systems', async () => { @@ -336,4 +349,52 @@ describe('ConnectionValidator', () => { expect(listServicesV2Mock).toHaveBeenCalled(); expect(listServicesV4Mock).toHaveBeenCalled(); }); + + test('should determine if authentication is required', async () => { + const connectValidator = new ConnectionValidator(); + expect(await connectValidator.isAuthRequired()).toBe(false); + + // Dont re-request if already validated + let getOdataServiceSpy = jest + .spyOn(ODataService.prototype, 'get') + .mockRejectedValue(newAxiosErrorWithStatus(401)); + await connectValidator.validateUrl('https://example.com/serviceA'); + expect(connectValidator.validity).toEqual({ + authenticated: false, + authRequired: true, + reachable: true, + urlFormat: true + }); + getOdataServiceSpy.mockClear(); + expect(await connectValidator.isAuthRequired()).toBe(true); + expect(getOdataServiceSpy).not.toHaveBeenCalled(); // Should not re-request since url and client have not changed + + // If the url changes, re-request + getOdataServiceSpy.mockClear(); + expect(await connectValidator.isAuthRequired('https://example.com/serviceB')).toBe(true); + expect(getOdataServiceSpy).toHaveBeenCalled(); + + // If the client changes, re-request + getOdataServiceSpy.mockClear(); + getOdataServiceSpy = jest.spyOn(ODataService.prototype, 'get').mockResolvedValue(200); + await connectValidator.validateAuth('https://example.com/serviceA', 'user1', 'password1', { sapClient: '999' }); + expect(connectValidator.validity).toEqual({ + authenticated: true, + authRequired: true, + reachable: true, + urlFormat: true + }); + expect(getOdataServiceSpy).toHaveBeenCalled(); + + getOdataServiceSpy.mockClear(); + // Auth is required even though a 200 since the url initially returned 401 + expect(await connectValidator.isAuthRequired('https://example.com/serviceA', '999')).toBe(true); + // Should not recheck with the same url and client + expect(getOdataServiceSpy).not.toHaveBeenCalled(); + + getOdataServiceSpy.mockClear(); + getOdataServiceSpy = jest.spyOn(ODataService.prototype, 'get').mockRejectedValue(newAxiosErrorWithStatus(401)); + expect(await connectValidator.isAuthRequired('https://example.com/serviceA', '111')).toBe(true); + expect(getOdataServiceSpy).toHaveBeenCalled(); + }); }); diff --git a/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-prem/questions.test.ts b/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-prem/questions.test.ts index f41e36d7f0..256bb59779 100644 --- a/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-prem/questions.test.ts +++ b/packages/odata-service-inquirer/test/unit/prompts/sap-system/abap-on-prem/questions.test.ts @@ -23,6 +23,7 @@ const v2Annotations = ` `; const validateUrlMock = jest.fn().mockResolvedValue(true); const validateAuthMock = jest.fn().mockResolvedValue(true); +const isAuthRequiredMock = jest.fn().mockResolvedValue(false); const serviceProviderMock = {} as Partial; const catalogs = { @@ -37,6 +38,7 @@ const connectionValidatorMock = { validity: {} as ConnectionValidator['validity'], validateUrl: validateUrlMock, validateAuth: validateAuthMock, + isAuthRequired: isAuthRequiredMock, serviceProvider: serviceProviderMock, catalogs }; @@ -198,6 +200,7 @@ describe('questions', () => { authRequired: true, reachable: true }; + connectionValidatorMock.isAuthRequired = jest.fn().mockResolvedValue(true); const newSystemQuestions = getAbapOnPremQuestions(); const userNamePrompt = newSystemQuestions.find((question) => question.name === 'abapSystemUsername'); const passwordPrompt = newSystemQuestions.find((question) => question.name === 'abapSystemPassword'); @@ -211,6 +214,8 @@ describe('questions', () => { authRequired: false, reachable: true }; + connectionValidatorMock.isAuthRequired = jest.fn().mockResolvedValue(false); + expect(await (userNamePrompt?.when as Function)()).toBe(false); expect(await (passwordPrompt?.when as Function)()).toBe(false); @@ -220,6 +225,8 @@ describe('questions', () => { authRequired: true, reachable: true }; + connectionValidatorMock.isAuthRequired = jest.fn().mockResolvedValue(true); + expect(await (userNamePrompt?.when as Function)()).toBe(true); expect(await (passwordPrompt?.when as Function)()).toBe(true); }); From 13d5ee59f6ab2b7c05a74b86d78729fc1d13dad7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 29 Jul 2024 15:10:43 +0000 Subject: [PATCH 2/2] chore: apply latest changesets --- .changeset/mighty-meals-divide.md | 5 ----- packages/cap-config-writer/CHANGELOG.md | 7 +++++++ packages/cap-config-writer/package.json | 2 +- packages/create/CHANGELOG.md | 6 ++++++ packages/create/package.json | 2 +- packages/odata-service-inquirer/CHANGELOG.md | 6 ++++++ packages/odata-service-inquirer/package.json | 2 +- 7 files changed, 22 insertions(+), 8 deletions(-) delete mode 100644 .changeset/mighty-meals-divide.md diff --git a/.changeset/mighty-meals-divide.md b/.changeset/mighty-meals-divide.md deleted file mode 100644 index c175cf88fa..0000000000 --- a/.changeset/mighty-meals-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@sap-ux/odata-service-inquirer': patch ---- - -Fixes multiple issues with Abap On Prem system creation diff --git a/packages/cap-config-writer/CHANGELOG.md b/packages/cap-config-writer/CHANGELOG.md index 1fe725d8d9..d05361dd9e 100644 --- a/packages/cap-config-writer/CHANGELOG.md +++ b/packages/cap-config-writer/CHANGELOG.md @@ -1,5 +1,12 @@ # @sap-ux/cap-config-writer +## 0.7.5 + +### Patch Changes + +- Updated dependencies [eb1adcc] + - @sap-ux/odata-service-inquirer@0.5.5 + ## 0.7.4 ### Patch Changes diff --git a/packages/cap-config-writer/package.json b/packages/cap-config-writer/package.json index c022481012..1b9b5c03fd 100644 --- a/packages/cap-config-writer/package.json +++ b/packages/cap-config-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/cap-config-writer", "description": "Add or update configuration for SAP CAP projects", - "version": "0.7.4", + "version": "0.7.5", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/create/CHANGELOG.md b/packages/create/CHANGELOG.md index 6d61be434a..6356fa6914 100644 --- a/packages/create/CHANGELOG.md +++ b/packages/create/CHANGELOG.md @@ -1,5 +1,11 @@ # @sap-ux/create +## 0.7.28 + +### Patch Changes + +- @sap-ux/cap-config-writer@0.7.5 + ## 0.7.27 ### Patch Changes diff --git a/packages/create/package.json b/packages/create/package.json index 237bd90f05..f7fc04910d 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/create", "description": "SAP Fiori tools module to add or remove features", - "version": "0.7.27", + "version": "0.7.28", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/odata-service-inquirer/CHANGELOG.md b/packages/odata-service-inquirer/CHANGELOG.md index 4096bae64e..1a86c726f9 100644 --- a/packages/odata-service-inquirer/CHANGELOG.md +++ b/packages/odata-service-inquirer/CHANGELOG.md @@ -1,5 +1,11 @@ # @sap-ux/odata-service-inquirer +## 0.5.5 + +### Patch Changes + +- eb1adcc: Fixes multiple issues with Abap On Prem system creation + ## 0.5.4 ### Patch Changes diff --git a/packages/odata-service-inquirer/package.json b/packages/odata-service-inquirer/package.json index f4823037b4..2964fb0860 100644 --- a/packages/odata-service-inquirer/package.json +++ b/packages/odata-service-inquirer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/odata-service-inquirer", "description": "Prompts module that can prompt users for inputs required for odata service writing", - "version": "0.5.4", + "version": "0.5.5", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git",