From c20a20501408bf7114631364c542fb54409ececc Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 7 Feb 2025 10:50:46 +0000 Subject: [PATCH 01/13] fix(cf-deploy-config-writer): support new prompt to support user adding MTA to CAP project during deployment --- .changeset/olive-spiders-burn.md | 7 + .../cf-deploy-config-inquirer/src/index.ts | 3 +- .../src/app/index.ts | 87 +++++-- .../src/app/questions.ts | 35 ++- .../src/app/types.ts | 2 +- .../src/app/utils.ts | 17 ++ .../cf-deploy-config-sub-generator.i18n.json | 3 + .../test/cap-app.test.ts | 226 ++++++++++++++++++ .../test/fixtures/cap/README.md | 25 ++ .../test/fixtures/cap/app/services.cds | 2 + .../fixtures/cap/app/testui5app/README.md | 34 +++ .../cap/app/testui5app/annotations.cds | 1 + .../fixtures/cap/app/testui5app/package.json | 19 ++ .../test/fixtures/cap/app/testui5app/ui5.yaml | 23 ++ .../cap/app/testui5app/webapp/Component.js | 26 ++ .../webapp/controller/App.controller.js | 10 + .../webapp/controller/View1.controller.js | 10 + .../cap/app/testui5app/webapp/css/style.css | 1 + .../testui5app/webapp/i18n/i18n.properties | 11 + .../cap/app/testui5app/webapp/index.html | 35 +++ .../cap/app/testui5app/webapp/manifest.json | 114 +++++++++ .../cap/app/testui5app/webapp/model/models.js | 20 ++ .../testui5app/webapp/test/flpSandbox.html | 84 +++++++ .../app/testui5app/webapp/view/App.view.xml | 7 + .../app/testui5app/webapp/view/View1.view.xml | 6 + .../cap/db/data/my.bookshop-Books.csv | 3 + .../test/fixtures/cap/db/schema.cds | 7 + .../test/fixtures/cap/eslint.config.mjs | 2 + .../test/fixtures/cap/package.json | 20 ++ .../test/fixtures/cap/srv/cat-service.cds | 5 + packages/cf-deploy-config-writer/src/index.ts | 2 +- .../src/types/index.ts | 10 +- 32 files changed, 831 insertions(+), 26 deletions(-) create mode 100644 .changeset/olive-spiders-burn.md create mode 100644 packages/cf-deploy-config-sub-generator/test/cap-app.test.ts create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/README.md create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/services.cds create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/README.md create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/annotations.cds create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/package.json create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/ui5.yaml create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/Component.js create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/App.controller.js create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/View1.controller.js create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/css/style.css create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/i18n/i18n.properties create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/index.html create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/manifest.json create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/model/models.js create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/test/flpSandbox.html create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/App.view.xml create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/View1.view.xml create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/data/my.bookshop-Books.csv create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/schema.cds create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/eslint.config.mjs create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/package.json create mode 100644 packages/cf-deploy-config-sub-generator/test/fixtures/cap/srv/cat-service.cds diff --git a/.changeset/olive-spiders-burn.md b/.changeset/olive-spiders-burn.md new file mode 100644 index 0000000000..f83d55ff71 --- /dev/null +++ b/.changeset/olive-spiders-burn.md @@ -0,0 +1,7 @@ +--- +'@sap-ux/cf-deploy-config-sub-generator': patch +'@sap-ux/cf-deploy-config-inquirer': patch +'@sap-ux/cf-deploy-config-writer': patch +--- + +add support to allow user add MTA configuration when adding HTML5 app to a CAP project diff --git a/packages/cf-deploy-config-inquirer/src/index.ts b/packages/cf-deploy-config-inquirer/src/index.ts index 8c3aee888c..4e3c4a3720 100644 --- a/packages/cf-deploy-config-inquirer/src/index.ts +++ b/packages/cf-deploy-config-inquirer/src/index.ts @@ -88,5 +88,6 @@ export { RouterModuleType, type CfDeployConfigQuestions, type CfDeployConfigAnswers, - type CfAppRouterDeployConfigAnswers + type CfAppRouterDeployConfigAnswers, + type CfAppRouterDeployConfigQuestions }; diff --git a/packages/cf-deploy-config-sub-generator/src/app/index.ts b/packages/cf-deploy-config-sub-generator/src/app/index.ts index ed1950b36b..28f48bbdcc 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/index.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/index.ts @@ -11,7 +11,13 @@ import { } from '@sap-ux/fiori-generator-shared'; import { isInternalFeaturesSettingEnabled } from '@sap-ux/feature-toggle'; import { isFullUrlDestination } from '@sap-ux/btp-utils'; -import { generateAppConfig, ApiHubType, useAbapDirectServiceBinding } from '@sap-ux/cf-deploy-config-writer'; +import { + generateAppConfig, + generateCAPConfig, + ApiHubType, + useAbapDirectServiceBinding, + DefaultMTADestination +} from '@sap-ux/cf-deploy-config-writer'; import { DeploymentGenerator, showOverwriteQuestion, @@ -25,15 +31,22 @@ import { getDestination } from '@sap-ux/deploy-config-generator-shared'; import { t, initI18n, DESTINATION_AUTHTYPE_NOTFOUND, API_BUSINESS_HUB_ENTERPRISE_PREFIX } from '../utils'; -import { loadManifest } from './utils'; -import { getMtaPath, findCapProjectRoot, FileName } from '@sap-ux/project-access'; +import { loadManifest, addMtaContinue } from './utils'; +import { getMtaPath, findCapProjectRoot, FileName, getCapProjectType } from '@sap-ux/project-access'; import { EventName } from '../telemetryEvents'; -import { getCFQuestions } from './questions'; -import type { ApiHubConfig, CFAppConfig } from '@sap-ux/cf-deploy-config-writer'; +import { getCFApprouterQuestionsForCap, getCFQuestions } from './questions'; +import type { ApiHubConfig, CFAppConfig, CAPConfig } from '@sap-ux/cf-deploy-config-writer'; import type { Logger } from '@sap-ux/logger'; -import type { CfDeployConfigOptions } from './types'; -import type { CfDeployConfigAnswers, CfDeployConfigQuestions } from '@sap-ux/cf-deploy-config-inquirer'; +import { CfDeployConfigOptions } from './types'; +import { + type CfAppRouterDeployConfigAnswers, + type CfDeployConfigQuestions, + type CfAppRouterDeployConfigQuestions, + CfDeployConfigAnswers +} from '@sap-ux/cf-deploy-config-inquirer'; import type { YeomanEnvironment } from '@sap-ux/fiori-generator-shared'; +import { withCondition } from '@sap-ux/inquirer-common'; +import type { Answers, Question } from 'inquirer'; /** * Cloud Foundry deployment configuration generator. @@ -49,6 +62,7 @@ export default class extends DeploymentGenerator { private readonly cloudServiceName?: string; private readonly serviceBase?: string; private answers: CfDeployConfigAnswers & Partial = {}; + private appRouterAnswers: CfAppRouterDeployConfigAnswers; private projectRoot: string; private mtaPath?: string; private isCap = false; @@ -75,7 +89,7 @@ export default class extends DeploymentGenerator { this.options = opts; this.destinationName = opts.destinationName ?? ''; - this.addMtaDestination = opts.addMTADestination ?? false; // by default it's false unless passed in i.e. headless flow + this.addMtaDestination = opts.addMTADestination ?? false; // by default, it's false unless passed in i.e. headless flow this.lcapModeOnly = opts.lcapModeOnly ?? false; this.cloudServiceName = opts.cloudServiceName || undefined; this.apiHubConfig = opts.apiHubConfig; @@ -106,6 +120,8 @@ export default class extends DeploymentGenerator { if (!this.launchDeployConfigAsSubGenerator) { await this._init(); + } else { + await this._processProjectConfigs(); } } @@ -134,7 +150,7 @@ export default class extends DeploymentGenerator { private async _processProjectPaths(): Promise { const mtaPathResult = await getMtaPath(this.appPath); this.mtaPath = mtaPathResult?.mtaPath; - const capRoot = await findCapProjectRoot(this.appPath); + const capRoot = await findCapProjectRoot(this.appPath, true, this.fs); if (capRoot) { if (!hasbin.sync(cdsExecutable)) { bail(ErrorHandler.getErrorMsgFromType(ERROR_TYPE.NO_CDS_BIN)); @@ -165,20 +181,36 @@ export default class extends DeploymentGenerator { return; } - if (this.isCap && this.projectRoot && !this.mtaPath) { - // if the user is adding deploy config to a CAP project and there is no mta.yaml in the root, then log error and exit - this.abort = true; - handleErrorMessage(this.appWizard, { errorType: ERROR_TYPE.CAP_DEPLOYMENT_NO_MTA }); - return; + if (!this.launchDeployConfigAsSubGenerator) { + await this._prompting(); } + await this._reconcileAnswersWithOptions(); + } - if (!this.launchDeployConfigAsSubGenerator) { + private async _prompting(): Promise { + const isCAPMissingMTA = this.isCap && this.projectRoot && !this.mtaPath; + if (isCAPMissingMTA) { + // If launched as root generator, add a continue prompt to allow user choose decide if they want to add an MTA config + let questions = (await this._getCFAppRouterQuestions()) as Question[]; + questions = withCondition(questions, (answers: Answers) => answers.addCapMtaContinue === true); + questions.unshift(...addMtaContinue()); + this.appRouterAnswers = (await this.prompt(questions)) as CfAppRouterDeployConfigAnswers; + if ((this.appRouterAnswers as Answers).addCapMtaContinue !== true) { + this.abort = true; + return; + } + this.destinationName = DefaultMTADestination; + this.answers.destinationName = this.destinationName; + this.answers.addManagedAppRouter = false; + } else { await this._handleApiHubConfig(); const questions = await this._getCFQuestions(); this.answers = await this.prompt(questions); } - await this._reconcileAnswersWithOptions(); + await this._handleApiHubConfig(); + const questions = await this._getCFQuestions(); + this.answers = await this.prompt(questions); } /** @@ -196,6 +228,17 @@ export default class extends DeploymentGenerator { } } + /** + * Fetches the Cloud Foundry Approuter configuration questions. + * + * @returns {Promise} - Cloud Foundry Approuter deployment configuration questions + */ + private async _getCFAppRouterQuestions(): Promise { + return await getCFApprouterQuestionsForCap({ + projectRoot: this.destinationRoot() ?? process.cwd() + }); + } + /** * Fetches the Cloud Foundry deployment configuration questions. * @@ -251,8 +294,16 @@ export default class extends DeploymentGenerator { private async _writing(): Promise { try { - const appConfig = this._getAppConfig(); - await generateAppConfig(appConfig, this.fs, DeploymentGenerator.logger as unknown as Logger); + // Step1. (Optional) Generate CAP MTA with specific approuter type managed | standalone + if (this.appRouterAnswers) { + await generateCAPConfig( + this.appRouterAnswers as CAPConfig, + this.fs, + DeploymentGenerator.logger as unknown as Logger + ); + } + // Step2. Append HTML5 app to MTA + await generateAppConfig(this._getAppConfig(), this.fs, DeploymentGenerator.logger as unknown as Logger); } catch (error) { this.abort = true; handleErrorMessage(this.appWizard, { errorMsg: t('cfGen.error.writing', { error }) }); diff --git a/packages/cf-deploy-config-sub-generator/src/app/questions.ts b/packages/cf-deploy-config-sub-generator/src/app/questions.ts index 91439f7ea3..d817f7ee31 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/questions.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/questions.ts @@ -1,7 +1,14 @@ import { isAppStudio } from '@sap-ux/btp-utils'; import { DeploymentGenerator } from '@sap-ux/deploy-config-generator-shared'; import { getMtaPath } from '@sap-ux/project-access'; -import { getPrompts, promptNames } from '@sap-ux/cf-deploy-config-inquirer'; +import { + appRouterPromptNames, + type CfAppRouterDeployConfigPromptOptions, + type CfAppRouterDeployConfigQuestions, + getAppRouterPrompts, + getPrompts, + promptNames +} from '@sap-ux/cf-deploy-config-inquirer'; import { getHostEnvironment, hostEnvironment } from '@sap-ux/fiori-generator-shared'; import { destinationQuestionDefaultOption, getCFChoices } from './utils'; import { t } from '../utils'; @@ -60,3 +67,29 @@ export async function getCFQuestions({ DeploymentGenerator.logger?.debug(t('cfGen.debug.promptOptions', { options: JSON.stringify(options) })); return getPrompts(options); } + +/** + * Retrieve the CF Approuter questions, certain prompts are restricted to support CAP project. + * + * @param options - the options required for retrieving the prompts. + * @param options.projectRoot - the root path of the project. + * @returns the cf approuter config questions. + */ +export async function getCFApprouterQuestionsForCap({ + projectRoot +}: { + projectRoot: string; +}): Promise { + // Disable some prompts, not required for CAP flow + const appRouterPromptOptions: CfAppRouterDeployConfigPromptOptions = { + [appRouterPromptNames.mtaPath]: projectRoot, + [appRouterPromptNames.mtaId]: true, + [appRouterPromptNames.mtaDescription]: false, + [appRouterPromptNames.mtaVersion]: false, + [appRouterPromptNames.routerType]: true, + [appRouterPromptNames.addConnectivityService]: true, + [appRouterPromptNames.addABAPServiceBinding]: false + }; + + return getAppRouterPrompts(appRouterPromptOptions); +} diff --git a/packages/cf-deploy-config-sub-generator/src/app/types.ts b/packages/cf-deploy-config-sub-generator/src/app/types.ts index 3f64b968d2..ff0a05d74d 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/types.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/types.ts @@ -1,5 +1,5 @@ import type { AppWizard } from '@sap-devx/yeoman-ui-types'; -import type { CfDeployConfigAnswers } from '@sap-ux/cf-deploy-config-inquirer'; +import { type CfDeployConfigAnswers } from '@sap-ux/cf-deploy-config-inquirer'; import type { ApiHubConfig } from '@sap-ux/cf-deploy-config-writer'; import type { TelemetryData } from '@sap-ux/fiori-generator-shared'; diff --git a/packages/cf-deploy-config-sub-generator/src/app/utils.ts b/packages/cf-deploy-config-sub-generator/src/app/utils.ts index 46487beb27..94979bf827 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/utils.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/utils.ts @@ -12,6 +12,7 @@ import { import type { Manifest } from '@sap-ux/project-access'; import type { Editor } from 'mem-fs-editor'; import type { CfSystemChoice } from '@sap-ux/cf-deploy-config-inquirer'; +import type { Question } from 'inquirer'; /** * Get the destination choices from API Hub | Local Store | mta.yaml. @@ -168,3 +169,19 @@ export async function loadManifest(fs: Editor, appPath: string): Promise { + return { + ...(jest.requireActual('@sap-ux/btp-utils') as {}), + isAppStudio: () => mockIsAppStudio(), + listDestinations: () => jest.fn() + }; +}); + +const mockFindCapProjectRoot = jest.fn(); +jest.mock('@sap-ux/project-access', () => { + return { + ...(jest.requireActual('@sap-ux/project-access') as {}), + findCapProjectRoot: () => mockFindCapProjectRoot() + }; +}); + +jest.mock('fs', () => { + const fsLib = jest.requireActual('fs'); + const Union = require('unionfs').Union; + const vol = require('memfs').vol; + const _fs = new Union().use(fsLib); + _fs.constants = fsLib.constants; + return _fs.use(vol as unknown as typeof fs); +}); + +jest.mock('hasbin', () => ({ + sync: jest.fn() +})); + +jest.mock('@sap/mta-lib', () => { + return { + Mta: require('./utils/mock-mta').MockMta + }; +}); + +const mockGetHostEnvironment = jest.fn(); +const mockSendTelemetry = jest.fn(); +jest.mock('@sap-ux/fiori-generator-shared', () => ({ + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + ...(jest.requireActual('@sap-ux/fiori-generator-shared') as {}), + sendTelemetry: () => mockSendTelemetry(), + isExtensionInstalled: jest.fn().mockReturnValue(true), + getHostEnvironment: () => mockGetHostEnvironment(), + TelemetryHelper: { + initTelemetrySettings: jest.fn(), + createTelemetryData: jest.fn() + } +})); + +const hasbinSyncMock = hasbin.sync as jest.MockedFunction; + +const mockShowInformation = jest.fn(); +const mockShowError = jest.fn(); +const mockAppWizard = { + showInformation: mockShowInformation, + showError: mockShowError +}; + +describe('Cloud foundry generator tests', () => { + jest.setTimeout(10000); + let cwd: string; + let fsMock: Editor; + const cfGenPath = join(__dirname, '../src/app'); + const OUTPUT_DIR_PREFIX = join('/output'); + const testFixture = new TestFixture(); + + beforeEach(() => { + jest.clearAllMocks(); + memfs.vol.reset(); + const mockChdir = jest.spyOn(process, 'chdir'); + mockChdir.mockImplementation((dir): void => { + cwd = dir; + }); + fsMock = { + dump: jest.fn(), + commit: jest.fn().mockImplementation((callback) => callback()) + } as Partial as Editor; + }); + + beforeAll(async () => { + await initI18n(); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + + it('Validate Approuter prompting aborts if user doesnt want to proceed', async () => { + hasbinSyncMock.mockReturnValue(true); + mockFindCapProjectRoot.mockReturnValueOnce('/capmissingmta'); + const mockGenerateCAPConfig = jest.spyOn(cfDeployWriter, 'generateCAPConfig').mockResolvedValue(fsMock); + const mockGenerateAppConfig = jest.spyOn(cfDeployWriter, 'generateAppConfig').mockResolvedValue(fsMock); + jest.spyOn(fioriGenShared, 'isExtensionInstalled').mockImplementation(() => { + return true; + }); + + memfs.vol.fromNestedJSON( + { + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/webapp/manifest.json`]: testFixture.getContents( + 'cap/app/testui5app/webapp/manifest.json' + ), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/package.json`]: testFixture.getContents( + 'cap/app/testui5app/package.json' + ), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/ui5.yaml`]: + testFixture.getContents('cap/app/testui5app/ui5.yaml'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/services.cds`]: + testFixture.getContents('cap/app/services.cds'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/db/schmea.cds`]: testFixture.getContents('cap/db/schema.cds'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/srv/cat-service.cds`]: + testFixture.getContents('cap/srv/cat-service.cds') + }, + '/' + ); + const appDir = join(OUTPUT_DIR_PREFIX, 'capmissingmta', 'app', 'testui5app'); + + await expect( + yeomanTest + .create( + CFGenerator, + { + resolved: cfGenPath + }, + { cwd: appDir } + ) + .withOptions({ + skipInstall: true, + appWizard: mockAppWizard, + launchStandaloneFromYui: true, + launchDeployConfigAsSubGenerator: false + }) + .withPrompts({ + addCapMtaContinue: false, + routerType: RouterModuleType.Managed, + mtaPath: join(OUTPUT_DIR_PREFIX, 'capmissingmta'), + mtaId: 'capmtaid' + }) + .run() + ).resolves.not.toThrow(); + expect(mockGenerateCAPConfig).not.toHaveBeenCalled(); + expect(mockGenerateAppConfig).not.toHaveBeenCalled(); + expect(mockFindCapProjectRoot).toHaveBeenCalled(); + expect(mockSendTelemetry).toHaveBeenCalled(); + }); + + it('Validate Approuter prompting is shown if HTML5 is being added to a CAP project with missing mta', async () => { + hasbinSyncMock.mockReturnValue(true); + mockFindCapProjectRoot.mockReturnValueOnce('/capmissingmta'); + const mockGenerateCAPConfig = jest.spyOn(cfDeployWriter, 'generateCAPConfig').mockResolvedValue(fsMock); + const mockGenerateAppConfig = jest.spyOn(cfDeployWriter, 'generateAppConfig').mockResolvedValue(fsMock); + jest.spyOn(fioriGenShared, 'isExtensionInstalled').mockImplementation(() => { + return true; + }); + + memfs.vol.fromNestedJSON( + { + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/webapp/manifest.json`]: testFixture.getContents( + 'cap/app/testui5app/webapp/manifest.json' + ), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/package.json`]: testFixture.getContents( + 'cap/app/testui5app/package.json' + ), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/testui5app/ui5.yaml`]: + testFixture.getContents('cap/app/testui5app/ui5.yaml'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/app/services.cds`]: + testFixture.getContents('cap/app/services.cds'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/db/schmea.cds`]: testFixture.getContents('cap/db/schema.cds'), + [`.${OUTPUT_DIR_PREFIX}/capmissingmta/srv/cat-service.cds`]: + testFixture.getContents('cap/srv/cat-service.cds') + }, + '/' + ); + const appDir = join(OUTPUT_DIR_PREFIX, 'capmissingmta', 'app', 'testui5app'); + + await expect( + yeomanTest + .create( + CFGenerator, + { + resolved: cfGenPath + }, + { cwd: appDir } + ) + .withOptions({ + skipInstall: true, + appWizard: mockAppWizard, + launchStandaloneFromYui: true, + launchDeployConfigAsSubGenerator: false + }) + .withPrompts({ + addCapMtaContinue: true, + routerType: RouterModuleType.Managed, + mtaPath: join(OUTPUT_DIR_PREFIX, 'capmissingmta'), + mtaId: 'capmtaid' + }) + .run() + ).resolves.not.toThrow(); + expect(mockGenerateCAPConfig).toHaveBeenCalledWith( + expect.objectContaining({ + addCapMtaContinue: true, + mtaId: 'capmtaid', + mtaPath: '/output/capmissingmta', + routerType: 'managed' + }), + expect.anything(), + expect.anything() + ); + expect(mockGenerateAppConfig).toHaveBeenCalled(); + expect(mockFindCapProjectRoot).toHaveBeenCalled(); + expect(mockSendTelemetry).toHaveBeenCalled(); + }); +}); diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/README.md b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/README.md new file mode 100644 index 0000000000..dbac29eb15 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/README.md @@ -0,0 +1,25 @@ +# Getting Started + +Welcome to your new project. + +It contains these folders and files, following our recommended project layout: + +File or Folder | Purpose +---------|---------- +`app/` | content for UI frontends goes here +`db/` | your domain models and data go here +`srv/` | your service models and code go here +`package.json` | project metadata and configuration +`readme.md` | this getting started guide + + +## Next Steps + +- Open a new terminal and run `cds watch` +- (in VS Code simply choose _**Terminal** > Run Task > cds watch_) +- Start adding content, for example, a [db/schema.cds](db/schema.cds). + + +## Learn More + +Learn more at https://cap.cloud.sap/docs/get-started/. diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/services.cds b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/services.cds new file mode 100644 index 0000000000..fbb0384ad3 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/services.cds @@ -0,0 +1,2 @@ + +using from './project1/annotations'; \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/README.md b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/README.md new file mode 100644 index 0000000000..8f3d6e8319 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/README.md @@ -0,0 +1,34 @@ +## Application Details +| | +| ------------- | +|**Generation Date and Time**
Wed Feb 05 2025 15:24:28 GMT+0000 (Greenwich Mean Time)| +|**App Generator**
@sap/generator-fiori-freestyle| +|**App Generator Version**
1.16.3-pre-20250120124504-86cdda28f.0| +|**Generation Platform**
Visual Studio Code| +|**Template Used**
simple| +|**Service Type**
Local Cap| +|**Service URL**
http://localhost:4004/odata/v4/catalog/| +|**Module Name**
project1| +|**Application Title**
App Title| +|**Namespace**
| +|**UI5 Theme**
sap_horizon| +|**UI5 Version**
1.132.1| +|**Enable Code Assist Libraries**
False| +|**Enable TypeScript**
False| +|**Add Eslint configuration**
False| + +## project1 + +An SAP Fiori application. + +### Starting the generated app + +- This app has been generated using the SAP Fiori tools - App Generator, as part of the SAP Fiori tools suite. In order to launch the generated app, simply start your CAP project and navigate to the following location in your browser: + +http://localhost:4004/project1/webapp/index.html + +#### Pre-requisites: + +1. Active NodeJS LTS (Long Term Support) version and associated supported NPM version. (See https://nodejs.org) + + diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/annotations.cds b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/annotations.cds new file mode 100644 index 0000000000..57192cc1d8 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/annotations.cds @@ -0,0 +1 @@ +using CatalogService as service from '../../srv/cat-service'; \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/package.json b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/package.json new file mode 100644 index 0000000000..ba84f4b94e --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/package.json @@ -0,0 +1,19 @@ +{ + "name": "project1", + "version": "0.0.1", + "description": "An SAP Fiori application.", + "keywords": [ + "ui5", + "openui5", + "sapui5" + ], + "main": "webapp/index.html", + "dependencies": {}, + "devDependencies": { + "@ui5/cli": "^3.0.0", + "@sap/ux-ui5-tooling": "1" + }, + "scripts": { + "deploy-config": "npx -p @sap/ux-ui5-tooling fiori add deploy-config cf" + } +} diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/ui5.yaml b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/ui5.yaml new file mode 100644 index 0000000000..bb0057645a --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/ui5.yaml @@ -0,0 +1,23 @@ +# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: "3.1" +metadata: + name: project1 +type: application +server: + customMiddleware: + - name: fiori-tools-proxy + afterMiddleware: compression + configuration: + ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + ui5: + path: + - /resources + - /test-resources + url: https://sapui5.hana.ondemand.com + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/Component.js b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/Component.js new file mode 100644 index 0000000000..2cc94dd09f --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/Component.js @@ -0,0 +1,26 @@ +sap.ui.define([ + "sap/ui/core/UIComponent", + "project1/model/models" +], (UIComponent, models) => { + "use strict"; + + return UIComponent.extend("project1.Component", { + metadata: { + manifest: "json", + interfaces: [ + "sap.ui.core.IAsyncContentCreation" + ] + }, + + init() { + // call the base component's init function + UIComponent.prototype.init.apply(this, arguments); + + // set the device model + this.setModel(models.createDeviceModel(), "device"); + + // enable routing + this.getRouter().initialize(); + } + }); +}); \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/App.controller.js b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/App.controller.js new file mode 100644 index 0000000000..3b9355dda6 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/App.controller.js @@ -0,0 +1,10 @@ +sap.ui.define([ + "sap/ui/core/mvc/Controller" +], (BaseController) => { + "use strict"; + + return BaseController.extend("project1.controller.App", { + onInit() { + } + }); +}); \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/View1.controller.js b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/View1.controller.js new file mode 100644 index 0000000000..491f3309bb --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/controller/View1.controller.js @@ -0,0 +1,10 @@ +sap.ui.define([ + "sap/ui/core/mvc/Controller" +], (Controller) => { + "use strict"; + + return Controller.extend("project1.controller.View1", { + onInit() { + } + }); +}); \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/css/style.css b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/css/style.css new file mode 100644 index 0000000000..f280a0e771 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/css/style.css @@ -0,0 +1 @@ +/* Enter your custom styles here */ \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/i18n/i18n.properties b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/i18n/i18n.properties new file mode 100644 index 0000000000..f64210fbdd --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/i18n/i18n.properties @@ -0,0 +1,11 @@ +# This is the resource bundle for project1 + +#Texts for manifest.json + +#XTIT: Application name +appTitle=App Title + +#YDES: Application description +appDescription=An SAP Fiori application. +#XTIT: Main view title +title=App Title \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/index.html b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/index.html new file mode 100644 index 0000000000..9df17fc0fe --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/index.html @@ -0,0 +1,35 @@ + + + + + + + App Title + + + + +
+ + \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/manifest.json b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/manifest.json new file mode 100644 index 0000000000..ff30f03a03 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/manifest.json @@ -0,0 +1,114 @@ +{ + "_version": "1.65.0", + "sap.app": { + "id": "project1", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap/generator-fiori:basic", + "version": "1.16.3-pre-20250120124504-86cdda28f.0", + "toolsId": "6cf48606-c416-45d4-976b-8f858c1d4fe4" + }, + "dataSources": { + "mainService": { + "uri": "/odata/v4/catalog/", + "type": "OData", + "settings": { + "annotations": [], + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5", + "icons": { + "icon": "", + "favIcon": "", + "phone": "", + "phone@2": "", + "tablet": "", + "tablet@2": "" + }, + "deviceTypes": { + "desktop": true, + "tablet": true, + "phone": true + } + }, + "sap.ui5": { + "flexEnabled": true, + "dependencies": { + "minUI5Version": "1.132.1", + "libs": { + "sap.m": {}, + "sap.ui.core": {} + } + }, + "contentDensities": { + "compact": true, + "cozy": true + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "project1.i18n.i18n" + } + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + } + }, + "resources": { + "css": [ + { + "uri": "css/style.css" + } + ] + }, + "routing": { + "config": { + "routerClass": "sap.m.routing.Router", + "controlAggregation": "pages", + "controlId": "app", + "transition": "slide", + "type": "View", + "viewType": "XML", + "path": "project1.view" + }, + "routes": [ + { + "name": "RouteView1", + "pattern": ":?query:", + "target": [ + "TargetView1" + ] + } + ], + "targets": { + "TargetView1": { + "id": "View1", + "name": "View1" + } + } + }, + "rootView": { + "viewName": "project1.view.App", + "type": "XML", + "id": "App" + } + } +} diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/model/models.js b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/model/models.js new file mode 100644 index 0000000000..47b027fbc0 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/model/models.js @@ -0,0 +1,20 @@ +sap.ui.define([ + "sap/ui/model/json/JSONModel", + "sap/ui/Device" +], +function (JSONModel, Device) { + "use strict"; + + return { + /** + * Provides runtime information for the device the UI5 app is running on as a JSONModel. + * @returns {sap.ui.model.json.JSONModel} The device model. + */ + createDeviceModel: function () { + var oModel = new JSONModel(Device); + oModel.setDefaultBindingMode("OneWay"); + return oModel; + } + }; + +}); \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/test/flpSandbox.html b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/test/flpSandbox.html new file mode 100644 index 0000000000..6f8d5d2a49 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/test/flpSandbox.html @@ -0,0 +1,84 @@ + + + + + + + + {{appTitle}} + + + + + + + + + + + + + + + + diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/App.view.xml b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/App.view.xml new file mode 100644 index 0000000000..adf4758721 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/App.view.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/View1.view.xml b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/View1.view.xml new file mode 100644 index 0000000000..d2c15055a3 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/app/testui5app/webapp/view/View1.view.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/data/my.bookshop-Books.csv b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/data/my.bookshop-Books.csv new file mode 100644 index 0000000000..0210c0909d --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/data/my.bookshop-Books.csv @@ -0,0 +1,3 @@ +ID,title,stock +1,Wuthering Heights,100 +2,Jane Eyre,500 diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/schema.cds b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/schema.cds new file mode 100644 index 0000000000..653cc58f79 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/db/schema.cds @@ -0,0 +1,7 @@ +namespace my.bookshop; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; +} diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/eslint.config.mjs b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/eslint.config.mjs new file mode 100644 index 0000000000..2fdb4320ca --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/eslint.config.mjs @@ -0,0 +1,2 @@ +import cds from '@sap/cds/eslint.config.mjs' +export default [ ...cds.recommended ] diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/package.json b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/package.json new file mode 100644 index 0000000000..b3c13ce05c --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/package.json @@ -0,0 +1,20 @@ +{ + "name": "captestproject", + "version": "1.0.0", + "description": "A simple CAP project.", + "repository": "", + "license": "UNLICENSED", + "private": true, + "dependencies": { + "@sap/cds": "^8", + "express": "^4" + }, + "devDependencies": { + "@cap-js/sqlite": "^1", + "@cap-js/cds-types": "^0.7.0" + }, + "scripts": { + "start": "cds-serve", + "watch-project1": "cds watch --open project1/webapp/index.html?sap-ui-xx-viewCache=false" + } +} diff --git a/packages/cf-deploy-config-sub-generator/test/fixtures/cap/srv/cat-service.cds b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/srv/cat-service.cds new file mode 100644 index 0000000000..dd8a434031 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/fixtures/cap/srv/cat-service.cds @@ -0,0 +1,5 @@ +using my.bookshop as my from '../db/schema'; + +service CatalogService { + @readonly entity Books as projection on my.Books; +} diff --git a/packages/cf-deploy-config-writer/src/index.ts b/packages/cf-deploy-config-writer/src/index.ts index d52fea8e20..61257e7ad1 100644 --- a/packages/cf-deploy-config-writer/src/index.ts +++ b/packages/cf-deploy-config-writer/src/index.ts @@ -1,4 +1,4 @@ export * from './mta-config'; export * from './cf-writer'; export { DefaultMTADestination } from './constants'; -export { CFBaseConfig, CFAppConfig, RouterModuleType, ApiHubConfig, ApiHubType } from './types'; +export { CFBaseConfig, CFAppConfig, CAPConfig, RouterModuleType, ApiHubConfig, ApiHubType } from './types'; diff --git a/packages/cf-deploy-config-writer/src/types/index.ts b/packages/cf-deploy-config-writer/src/types/index.ts index 0392348f02..22a5fd8f08 100644 --- a/packages/cf-deploy-config-writer/src/types/index.ts +++ b/packages/cf-deploy-config-writer/src/types/index.ts @@ -27,10 +27,12 @@ export type MTADestinationType = Destination & { ServiceKeyName: string; 'sap.cloud.service': string; }; -export enum RouterModuleType { - Standard = 'standard', - Managed = 'managed' -} +export const RouterModuleType = { + Standard: 'standard', + Managed: 'managed' +} as const; + +export type RouterModuleType = (typeof RouterModuleType)[keyof typeof RouterModuleType]; export interface MTABaseConfig { mtaId: string; mtaPath: string; From f697d277272df7c3af704234644083acf7bfe6d0 Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 7 Feb 2025 10:51:48 +0000 Subject: [PATCH 02/13] fix(cf-deploy-config-writer): fix linting --- packages/cf-deploy-config-sub-generator/src/app/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cf-deploy-config-sub-generator/src/app/index.ts b/packages/cf-deploy-config-sub-generator/src/app/index.ts index 28f48bbdcc..f12a151249 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/index.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/index.ts @@ -32,7 +32,7 @@ import { } from '@sap-ux/deploy-config-generator-shared'; import { t, initI18n, DESTINATION_AUTHTYPE_NOTFOUND, API_BUSINESS_HUB_ENTERPRISE_PREFIX } from '../utils'; import { loadManifest, addMtaContinue } from './utils'; -import { getMtaPath, findCapProjectRoot, FileName, getCapProjectType } from '@sap-ux/project-access'; +import { getMtaPath, findCapProjectRoot, FileName } from '@sap-ux/project-access'; import { EventName } from '../telemetryEvents'; import { getCFApprouterQuestionsForCap, getCFQuestions } from './questions'; import type { ApiHubConfig, CFAppConfig, CAPConfig } from '@sap-ux/cf-deploy-config-writer'; From bbf0d8a323513c005c2fc3a394ffb8618a10b41f Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 7 Feb 2025 11:24:14 +0000 Subject: [PATCH 03/13] fix(cf-deploy-config-writer): fix sonar issues --- packages/cf-deploy-config-sub-generator/src/app/questions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cf-deploy-config-sub-generator/src/app/questions.ts b/packages/cf-deploy-config-sub-generator/src/app/questions.ts index d817f7ee31..7bd7efffdd 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/questions.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/questions.ts @@ -5,6 +5,8 @@ import { appRouterPromptNames, type CfAppRouterDeployConfigPromptOptions, type CfAppRouterDeployConfigQuestions, + type CfDeployConfigPromptOptions, + type CfDeployConfigQuestions, getAppRouterPrompts, getPrompts, promptNames @@ -13,7 +15,6 @@ import { getHostEnvironment, hostEnvironment } from '@sap-ux/fiori-generator-sha import { destinationQuestionDefaultOption, getCFChoices } from './utils'; import { t } from '../utils'; import type { ApiHubConfig } from '@sap-ux/cf-deploy-config-writer'; -import type { CfDeployConfigPromptOptions, CfDeployConfigQuestions } from '@sap-ux/cf-deploy-config-inquirer'; /** * Fetches the Cloud Foundry deployment configuration questions. From accee527a5d49fdfffe130e84223ba758a5b9fe8 Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 12:40:27 +0000 Subject: [PATCH 04/13] fix(cf-deploy-config-writer): support new CAP MTA continue option --- .../src/app/index.ts | 27 ++++--- .../src/app/types.ts | 4 + .../src/app/utils.ts | 16 ---- .../cf-deploy-config-sub-generator.i18n.json | 6 +- .../src/cf-writer/app-config.ts | 33 ++------ .../src/cf-writer/cap-config.ts | 5 +- .../src/mta-config/index.ts | 1 + .../cf-deploy-config-writer.i18n.json | 3 +- packages/cf-deploy-config-writer/src/utils.ts | 41 +++++++++- .../unit/__snapshots__/index-cap.test.ts.snap | 78 ++++++++++++++++++- .../test/unit/index-cap.test.ts | 1 + .../src/index.ts | 2 +- .../src/prompts/index.ts | 22 +++++- .../deploy-config-generator-shared.i18n.json | 5 +- 14 files changed, 170 insertions(+), 74 deletions(-) diff --git a/packages/cf-deploy-config-sub-generator/src/app/index.ts b/packages/cf-deploy-config-sub-generator/src/app/index.ts index f12a151249..06266ce6ef 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/index.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/index.ts @@ -28,10 +28,11 @@ import { mtaExecutable, cdsExecutable, generateDestinationName, - getDestination + getDestination, + getConfirmMtaContinuePrompt } from '@sap-ux/deploy-config-generator-shared'; import { t, initI18n, DESTINATION_AUTHTYPE_NOTFOUND, API_BUSINESS_HUB_ENTERPRISE_PREFIX } from '../utils'; -import { loadManifest, addMtaContinue } from './utils'; +import { loadManifest } from './utils'; import { getMtaPath, findCapProjectRoot, FileName } from '@sap-ux/project-access'; import { EventName } from '../telemetryEvents'; import { getCFApprouterQuestionsForCap, getCFQuestions } from './questions'; @@ -131,12 +132,10 @@ export default class extends DeploymentGenerator { this.abort = true; handleErrorMessage(this.appWizard, { errorType: ERROR_TYPE.NO_MTA_BIN }); } - await this._processProjectPaths(); await this._processProjectConfigs(); this.isAbapDirectServiceBinding = await useAbapDirectServiceBinding(this.appPath, false, this.mtaPath); - // restricting local changes is only applicable for CAP flows if (!this.isCap) { this.lcapModeOnly = false; @@ -172,7 +171,6 @@ export default class extends DeploymentGenerator { if (!baseConfigExists) { bail(ErrorHandler.noBaseConfig(baseConfigFile)); } - this.deployConfigExists = this.fs.exists(join(this.appPath, this.options.config ?? FileName.Ui5Yaml)); } @@ -180,7 +178,6 @@ export default class extends DeploymentGenerator { if (this.abort) { return; } - if (!this.launchDeployConfigAsSubGenerator) { await this._prompting(); } @@ -190,16 +187,22 @@ export default class extends DeploymentGenerator { private async _prompting(): Promise { const isCAPMissingMTA = this.isCap && this.projectRoot && !this.mtaPath; if (isCAPMissingMTA) { - // If launched as root generator, add a continue prompt to allow user choose decide if they want to add an MTA config - let questions = (await this._getCFAppRouterQuestions()) as Question[]; + DeploymentGenerator.logger?.debug(t('cfGen.debug.capMissingMTA')); + // If launched as root generator, add a prompt to allow user decide if they want to add an MTA config + let questions = (await getCFApprouterQuestionsForCap({ + projectRoot: this.projectRoot ?? process.cwd() + })) as Question[]; questions = withCondition(questions, (answers: Answers) => answers.addCapMtaContinue === true); - questions.unshift(...addMtaContinue()); + questions.unshift(...getConfirmMtaContinuePrompt()); this.appRouterAnswers = (await this.prompt(questions)) as CfAppRouterDeployConfigAnswers; if ((this.appRouterAnswers as Answers).addCapMtaContinue !== true) { this.abort = true; return; } + // Configure defaults this.destinationName = DefaultMTADestination; + this.options.overwrite = true; // Don't prompt the user to overwrite files we've just written! + this.answers = {}; this.answers.destinationName = this.destinationName; this.answers.addManagedAppRouter = false; } else { @@ -207,10 +210,6 @@ export default class extends DeploymentGenerator { const questions = await this._getCFQuestions(); this.answers = await this.prompt(questions); } - - await this._handleApiHubConfig(); - const questions = await this._getCFQuestions(); - this.answers = await this.prompt(questions); } /** @@ -235,7 +234,7 @@ export default class extends DeploymentGenerator { */ private async _getCFAppRouterQuestions(): Promise { return await getCFApprouterQuestionsForCap({ - projectRoot: this.destinationRoot() ?? process.cwd() + projectRoot: this.projectRoot ?? process.cwd() }); } diff --git a/packages/cf-deploy-config-sub-generator/src/app/types.ts b/packages/cf-deploy-config-sub-generator/src/app/types.ts index ff0a05d74d..71cefea601 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/types.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/types.ts @@ -84,4 +84,8 @@ export interface CfDeployConfigOptions extends CfDeployConfigAnswers { * Telemetry data to be send after deployment configuration has been added */ telemetryData?: TelemetryData; + /** + * Option to invoke the getConfirmMtaContinue prompt + */ + addCapMtaContinue?: boolean; } diff --git a/packages/cf-deploy-config-sub-generator/src/app/utils.ts b/packages/cf-deploy-config-sub-generator/src/app/utils.ts index 94979bf827..411d445411 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/utils.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/utils.ts @@ -169,19 +169,3 @@ export async function loadManifest(fs: Editor, appPath: string): Promise { - const packageExists = fs.exists(join(cfConfig.rootPath, FileName.Package)); - // Append mta scripts only if mta.yaml is at a different level to the HTML5 app - if (cfConfig.isMtaRoot && packageExists) { - await addPackageDevDependency(cfConfig.rootPath, Rimraf, RimrafVersion, fs); - await addPackageDevDependency(cfConfig.rootPath, MbtPackage, MbtPackageVersion, fs); - let deployArgs: string[] = []; - if (fs.exists(join(cfConfig.rootPath, MTAFileExtension))) { - deployArgs = ['-e', MTAFileExtension]; - } - for (const script of [ - { name: 'undeploy', run: undeployMTAScript(cfConfig.mtaId ?? cfConfig.appId) }, - { name: 'build', run: `${MTABuildScript} --mtar archive` }, - { name: 'deploy', run: rootDeployMTAScript(deployArgs) } - ]) { - await updatePackageScript(cfConfig.rootPath, script.name, script.run, fs); - } - } -} /** * Generate UI5 deploy config. * diff --git a/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts b/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts index 0a5b5abe1b..926772ec3f 100644 --- a/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts +++ b/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts @@ -1,6 +1,6 @@ import { create as createStorage } from 'mem-fs'; import { create, type Editor } from 'mem-fs-editor'; -import { addSupportingConfig, addRoutingConfig } from '../utils'; +import { updateRootPackage, addRoutingConfig } from '../utils'; import { createCAPMTA, validateMtaConfig, isMTAFound } from '../mta-config'; import LoggerHelper from '../logger-helper'; import type { Logger } from '@sap-ux/logger'; @@ -29,8 +29,7 @@ export async function generateCAPConfig(config: CAPConfig, fs?: Editor, logger?: const cdsOptionalParams: string[] = [CDSXSUAAService, CDSDestinationService, CDSHTML5RepoService]; createCAPMTA(config.mtaPath, cdsOptionalParams); await addRoutingConfig(config, fs); - addSupportingConfig(config, fs); - LoggerHelper.logger?.debug(`CF CAP Config ${JSON.stringify(config, null, 2)}`); + await updateRootPackage({ mtaId: config.mtaId, projectPath: config.mtaPath }, fs); return fs; } diff --git a/packages/cf-deploy-config-writer/src/mta-config/index.ts b/packages/cf-deploy-config-writer/src/mta-config/index.ts index 49c380da75..ae6c7e827b 100644 --- a/packages/cf-deploy-config-writer/src/mta-config/index.ts +++ b/packages/cf-deploy-config-writer/src/mta-config/index.ts @@ -146,6 +146,7 @@ export function createCAPMTA(cwd: string, options?: string[]): void { if (result?.error) { throw new Error(`Something went wrong installing node modules! ${result.error}`); } + LoggerHelper.logger?.debug(t('debug.capMtaCreated')); } /** diff --git a/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json b/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json index ae2d32e90f..e4e28e9ed3 100644 --- a/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json +++ b/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json @@ -2,7 +2,8 @@ "debug": { "logError": "{{method}} error found {{ error }}", "mtaLoaded": "MTA {{ type }} loaded", - "ui5YamlDoesNotExist": "File ui5.yaml does not exist in the project" + "ui5YamlDoesNotExist": "File ui5.yaml does not exist in the project", + "capMtaCreated": "CAP MTA Configuration created" }, "error": { "unableToLoadMTA": "Unable to load mta.yaml configuration.", diff --git a/packages/cf-deploy-config-writer/src/utils.ts b/packages/cf-deploy-config-writer/src/utils.ts index afa670dc52..c6637bc1e7 100644 --- a/packages/cf-deploy-config-writer/src/utils.ts +++ b/packages/cf-deploy-config-writer/src/utils.ts @@ -7,7 +7,7 @@ import { type Authentication, type Destinations } from '@sap-ux/btp-utils'; -import { addPackageDevDependency, FileName, type Manifest } from '@sap-ux/project-access'; +import { addPackageDevDependency, FileName, type Manifest, updatePackageScript } from '@sap-ux/project-access'; import { MTAVersion, UI5BuilderWebIdePackage, @@ -18,7 +18,15 @@ import { UI5TaskZipperPackageVersion, XSSecurityFile, RouterModule, - XSAppFile + XSAppFile, + rootDeployMTAScript, + undeployMTAScript, + MTAFileExtension, + Rimraf, + RimrafVersion, + MbtPackageVersion, + MbtPackage, + MTABuildScript } from './constants'; import type { Editor } from 'mem-fs-editor'; import { type MTABaseConfig, type CFConfig, type CFBaseConfig, RouterModuleType } from './types'; @@ -271,3 +279,32 @@ export function setMtaDefaults(config: CFBaseConfig): void { config.addConnectivityService ||= false; config.mtaId = toMtaModuleName(config.mtaId); } + +/** + * Update the root package.json with scripts to deploy the MTA. + * + * @param cfConfig writer configuration + * @param fs reference to a mem-fs editor + */ +export async function updateRootPackage( + { mtaId, projectPath }: { mtaId: string; projectPath: string }, + fs: Editor +): Promise { + const packageExists = fs.exists(join(projectPath, FileName.Package)); + // Append mta scripts only if mta.yaml is at a different level to the HTML5 app + if (packageExists) { + await addPackageDevDependency(projectPath, Rimraf, RimrafVersion, fs); + await addPackageDevDependency(projectPath, MbtPackage, MbtPackageVersion, fs); + let deployArgs: string[] = []; + if (fs.exists(join(projectPath, MTAFileExtension))) { + deployArgs = ['-e', MTAFileExtension]; + } + for (const script of [ + { name: 'undeploy', run: undeployMTAScript(mtaId) }, + { name: 'build', run: `${MTABuildScript} --mtar archive` }, + { name: 'deploy', run: rootDeployMTAScript(deployArgs) } + ]) { + await updatePackageScript(projectPath, script.name, script.run, fs); + } + } +} diff --git a/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-cap.test.ts.snap b/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-cap.test.ts.snap index 6c0a24ddfb..3acd1f455d 100644 --- a/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-cap.test.ts.snap +++ b/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-cap.test.ts.snap @@ -133,6 +133,44 @@ resources: " `; +exports[`CF Writer CAP Validate generation of CAP mta configurations managed 2`] = ` +"{ + \\"name\\": \\"captestproject\\", + \\"version\\": \\"1.0.0\\", + \\"description\\": \\"A simple CAP project.\\", + \\"repository\\": \\"\\", + \\"license\\": \\"UNLICENSED\\", + \\"private\\": true, + \\"dependencies\\": { + \\"@sap/cds\\": \\"^8\\", + \\"express\\": \\"^4\\", + \\"@sap/xssec\\": \\"^4\\" + }, + \\"devDependencies\\": { + \\"@cap-js/cds-types\\": \\"^0.8.0\\", + \\"@cap-js/sqlite\\": \\"^1\\", + \\"@sap/cds-dk\\": \\"^8\\", + \\"rimraf\\": \\"^5.0.5\\", + \\"mbt\\": \\"^1.2.29\\" + }, + \\"scripts\\": { + \\"start\\": \\"cds-serve\\", + \\"undeploy\\": \\"cf undeploy captestproject --delete-services --delete-service-keys --delete-service-brokers\\", + \\"build\\": \\"rimraf resources mta_archives && mbt build --mtar archive\\", + \\"deploy\\": \\"cf deploy mta_archives/archive.mtar --retries 1\\" + }, + \\"cds\\": { + \\"requires\\": { + \\"auth\\": \\"xsuaa\\", + \\"connectivity\\": true, + \\"destinations\\": true, + \\"html5-repo\\": true + } + } +} +" +`; + exports[`CF Writer CAP Validate generation of CAP mta configurations standard 1`] = ` "_schema-version: 3.3.0 ID: captestproject @@ -263,6 +301,44 @@ resources: `; exports[`CF Writer CAP Validate generation of CAP mta configurations standard 2`] = ` +"{ + \\"name\\": \\"captestproject\\", + \\"version\\": \\"1.0.0\\", + \\"description\\": \\"A simple CAP project.\\", + \\"repository\\": \\"\\", + \\"license\\": \\"UNLICENSED\\", + \\"private\\": true, + \\"dependencies\\": { + \\"@sap/cds\\": \\"^8\\", + \\"express\\": \\"^4\\", + \\"@sap/xssec\\": \\"^4\\" + }, + \\"devDependencies\\": { + \\"@cap-js/cds-types\\": \\"^0.8.0\\", + \\"@cap-js/sqlite\\": \\"^1\\", + \\"@sap/cds-dk\\": \\"^8\\", + \\"rimraf\\": \\"^5.0.5\\", + \\"mbt\\": \\"^1.2.29\\" + }, + \\"scripts\\": { + \\"start\\": \\"cds-serve\\", + \\"undeploy\\": \\"cf undeploy captestproject --delete-services --delete-service-keys --delete-service-brokers\\", + \\"build\\": \\"rimraf resources mta_archives && mbt build --mtar archive\\", + \\"deploy\\": \\"cf deploy mta_archives/archive.mtar --retries 1\\" + }, + \\"cds\\": { + \\"requires\\": { + \\"auth\\": \\"xsuaa\\", + \\"connectivity\\": true, + \\"destinations\\": true, + \\"html5-repo\\": true + } + } +} +" +`; + +exports[`CF Writer CAP Validate generation of CAP mta configurations standard 3`] = ` "{ \\"name\\": \\"app-router\\", \\"private\\": true, @@ -284,7 +360,7 @@ exports[`CF Writer CAP Validate generation of CAP mta configurations standard 2` " `; -exports[`CF Writer CAP Validate generation of CAP mta configurations standard 3`] = ` +exports[`CF Writer CAP Validate generation of CAP mta configurations standard 4`] = ` "{ \\"authenticationMethod\\": \\"route\\", \\"routes\\": [ diff --git a/packages/cf-deploy-config-writer/test/unit/index-cap.test.ts b/packages/cf-deploy-config-writer/test/unit/index-cap.test.ts index 2b558724bb..e0c9b625a7 100644 --- a/packages/cf-deploy-config-writer/test/unit/index-cap.test.ts +++ b/packages/cf-deploy-config-writer/test/unit/index-cap.test.ts @@ -83,6 +83,7 @@ describe('CF Writer CAP', () => { logger ); expect(localFs.read(join(mtaPath, 'mta.yaml'))).toMatchSnapshot(); + expect(localFs.read(join(mtaPath, 'package.json'))).toMatchSnapshot(); // Ensure it hasn't changed! expect(getCapProjectTypeMock).toHaveBeenCalled(); expect(spawnMock.mock.calls).toHaveLength(2); expect(spawnMock).toHaveBeenCalledWith( diff --git a/packages/deploy-config-generator-shared/src/index.ts b/packages/deploy-config-generator-shared/src/index.ts index 981eda20a4..30c7edfe73 100644 --- a/packages/deploy-config-generator-shared/src/index.ts +++ b/packages/deploy-config-generator-shared/src/index.ts @@ -1,3 +1,3 @@ export { DeploymentGenerator } from './base/generator'; export * from './utils'; -export { getConfirmConfigUpdatePrompt } from './prompts'; +export { getConfirmConfigUpdatePrompt, getConfirmMtaContinuePrompt } from './prompts'; diff --git a/packages/deploy-config-generator-shared/src/prompts/index.ts b/packages/deploy-config-generator-shared/src/prompts/index.ts index 2021f6f358..da7ad6dc8a 100644 --- a/packages/deploy-config-generator-shared/src/prompts/index.ts +++ b/packages/deploy-config-generator-shared/src/prompts/index.ts @@ -5,7 +5,7 @@ import type { Question } from 'inquirer'; * Enumeration of prompt names */ enum promptNames { - confirmConfigUpate = 'confirmConfigUpate' + confirmConfigUpdate = 'confirmConfigUpdate' } /** @@ -18,8 +18,24 @@ export function getConfirmConfigUpdatePrompt(configType?: string): Question[] { return [ { type: 'confirm', - name: promptNames.confirmConfigUpate, - message: t('prompts.confirmConfigUpate.message', { configType }), + name: promptNames.confirmConfigUpdate, + message: t('prompts.confirmConfigUpdate.message', { configType }), + default: false + } + ]; +} + +/** + * Generate a new prompt asking if the user wants to create an approuter configuration within a CAP project. + * + * @returns the CAP MTA continue question. + */ +export function getConfirmMtaContinuePrompt(): Question[] { + return [ + { + type: 'confirm', + name: 'addCapMtaContinue', + message: t('prompts.confirmCAPMtaContinue.message'), default: false } ]; diff --git a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json index 30cd4dde76..48de093b6b 100644 --- a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json +++ b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json @@ -1,7 +1,10 @@ { "prompts": { - "confirmConfigUpate": { + "confirmConfigUpdate": { "message": "{{- configType}} configuration is managed centrally as part of the CI pipeline, local updates to the configuration will not be for productive use. Are you sure you want to continue?" + }, + "confirmCAPMtaContinue": { + "message": "There is no mta.yaml file defined for this project. In order to add deployment configuration for this application, this file must be present. Do you want to create an mta.yaml to continue?" } }, "errors": { From 8c9c8f8be1e4cdc1e3a6a1b0f5c56a3d1468dcc9 Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 13:19:24 +0000 Subject: [PATCH 05/13] fix(cf-deploy-config-writer): add changeset --- .changeset/tasty-cats-search.md | 8 ++++++++ README.md | 2 +- .../src/app/utils.ts | 1 - .../src/cf-writer/app-config.ts | 2 +- .../src/cf-writer/cap-config.ts | 2 +- packages/cf-deploy-config-writer/src/utils.ts | 18 ++++++++++-------- 6 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 .changeset/tasty-cats-search.md diff --git a/.changeset/tasty-cats-search.md b/.changeset/tasty-cats-search.md new file mode 100644 index 0000000000..bf4ff2d788 --- /dev/null +++ b/.changeset/tasty-cats-search.md @@ -0,0 +1,8 @@ +--- +'@sap-ux/cf-deploy-config-sub-generator': patch +'@sap-ux/deploy-config-generator-shared': patch +'@sap-ux/cf-deploy-config-inquirer': patch +'@sap-ux/cf-deploy-config-writer': patch +--- + +Changes to support adding CAP MTA prompt to allow user generate MTA diff --git a/README.md b/README.md index 23a28cc948..c8e4b87ff0 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ When analyzing a problem, it is helpful to be able to debug the modules. How to Each of the packages has an extensive set of unit tests covering as many as possible different scenarios, therefore, as a starting point for debugging, it is a good idea to use the tests. The easiest (but not the only) way to debug a specific test in VSCode is to open a `JavaScript Debug Terminal` and then go to the package that needs to be debugged. Using the debug terminal, execute all tests with `pnpm test` or a specific one, e.g. execute `pnpm test -- test/basic.test.ts` in the `fiori-freestyle-writer` directory (`./packages/fiori-freestyle-writer`). When running either of the commands in the debug terminal, breakpoints set in VSCode will be active. -Additionally for the `*-writer` modules it is sometimes helpful to manually inspect the generated output of the unit tests on the filesystem. This can be achieved by setting the variable `UX_DEBUG` before running the tests e.g. in `fiori-freestyle-writer` run `UX_DEBUG=true pnpm test` and after the tests finish, the generated files can be found at `./test/test-output`. +Additionally, for the `*-writer` modules it is sometimes helpful to manually inspect the generated output of the unit tests on the filesystem. This can be achieved by setting the variable `UX_DEBUG` before running the tests e.g. in `fiori-freestyle-writer` run `UX_DEBUG=true pnpm test` and after the tests finish, the generated files can be found at `./test/test-output`. Additional checks can be performed on the generated projects by also setting `UX_DEBUG_FULL` e.g. `UX_DEBUG=true UX_DEBUG_FULL=true pnpm test`. This includes checks such as `npm install`, `npm run ts-typecheck`, `npm run lint` as appropriate to the project. diff --git a/packages/cf-deploy-config-sub-generator/src/app/utils.ts b/packages/cf-deploy-config-sub-generator/src/app/utils.ts index 411d445411..46487beb27 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/utils.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/utils.ts @@ -12,7 +12,6 @@ import { import type { Manifest } from '@sap-ux/project-access'; import type { Editor } from 'mem-fs-editor'; import type { CfSystemChoice } from '@sap-ux/cf-deploy-config-inquirer'; -import type { Question } from 'inquirer'; /** * Get the destination choices from API Hub | Local Store | mta.yaml. diff --git a/packages/cf-deploy-config-writer/src/cf-writer/app-config.ts b/packages/cf-deploy-config-writer/src/cf-writer/app-config.ts index 32713f739b..0febdc87b7 100644 --- a/packages/cf-deploy-config-writer/src/cf-writer/app-config.ts +++ b/packages/cf-deploy-config-writer/src/cf-writer/app-config.ts @@ -231,7 +231,7 @@ async function generateDeployConfig(cfAppConfig: CFAppConfig, fs: Editor): Promi await updateManifest(config, fs); await updateHTML5AppPackage(config, fs); if (config.isMtaRoot) { - await updateRootPackage({ mtaId: config.mtaId ?? config.appId, projectPath: config.rootPath }, fs); + await updateRootPackage({ mtaId: config.mtaId ?? config.appId, rootPath: config.rootPath }, fs); } } diff --git a/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts b/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts index 926772ec3f..e76f5f4ec1 100644 --- a/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts +++ b/packages/cf-deploy-config-writer/src/cf-writer/cap-config.ts @@ -29,7 +29,7 @@ export async function generateCAPConfig(config: CAPConfig, fs?: Editor, logger?: const cdsOptionalParams: string[] = [CDSXSUAAService, CDSDestinationService, CDSHTML5RepoService]; createCAPMTA(config.mtaPath, cdsOptionalParams); await addRoutingConfig(config, fs); - await updateRootPackage({ mtaId: config.mtaId, projectPath: config.mtaPath }, fs); + await updateRootPackage({ mtaId: config.mtaId, rootPath: config.mtaPath }, fs); return fs; } diff --git a/packages/cf-deploy-config-writer/src/utils.ts b/packages/cf-deploy-config-writer/src/utils.ts index c6637bc1e7..251736ac7a 100644 --- a/packages/cf-deploy-config-writer/src/utils.ts +++ b/packages/cf-deploy-config-writer/src/utils.ts @@ -283,20 +283,22 @@ export function setMtaDefaults(config: CFBaseConfig): void { /** * Update the root package.json with scripts to deploy the MTA. * - * @param cfConfig writer configuration - * @param fs reference to a mem-fs editor + * @param {object} Options + * @param {string} Options.mtaId - MTA ID to be written to package.json + * @param {string} Options.rootPath - MTA project path + * @param fs */ export async function updateRootPackage( - { mtaId, projectPath }: { mtaId: string; projectPath: string }, + { mtaId, rootPath }: { mtaId: string; rootPath: string }, fs: Editor ): Promise { - const packageExists = fs.exists(join(projectPath, FileName.Package)); + const packageExists = fs.exists(join(rootPath, FileName.Package)); // Append mta scripts only if mta.yaml is at a different level to the HTML5 app if (packageExists) { - await addPackageDevDependency(projectPath, Rimraf, RimrafVersion, fs); - await addPackageDevDependency(projectPath, MbtPackage, MbtPackageVersion, fs); + await addPackageDevDependency(rootPath, Rimraf, RimrafVersion, fs); + await addPackageDevDependency(rootPath, MbtPackage, MbtPackageVersion, fs); let deployArgs: string[] = []; - if (fs.exists(join(projectPath, MTAFileExtension))) { + if (fs.exists(join(rootPath, MTAFileExtension))) { deployArgs = ['-e', MTAFileExtension]; } for (const script of [ @@ -304,7 +306,7 @@ export async function updateRootPackage( { name: 'build', run: `${MTABuildScript} --mtar archive` }, { name: 'deploy', run: rootDeployMTAScript(deployArgs) } ]) { - await updatePackageScript(projectPath, script.name, script.run, fs); + await updatePackageScript(rootPath, script.name, script.run, fs); } } } From e22b43f9ef4c885cd2e7542297294ffa7660a73e Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 13:21:38 +0000 Subject: [PATCH 06/13] fix(cf-deploy-config-writer): remove duplicate changeset --- .changeset/olive-spiders-burn.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .changeset/olive-spiders-burn.md diff --git a/.changeset/olive-spiders-burn.md b/.changeset/olive-spiders-burn.md deleted file mode 100644 index f83d55ff71..0000000000 --- a/.changeset/olive-spiders-burn.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@sap-ux/cf-deploy-config-sub-generator': patch -'@sap-ux/cf-deploy-config-inquirer': patch -'@sap-ux/cf-deploy-config-writer': patch ---- - -add support to allow user add MTA configuration when adding HTML5 app to a CAP project From 252c9f6c670ff7ebcf8e1df45f6e01ead6739ee2 Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 16:45:14 +0000 Subject: [PATCH 07/13] fix(cf-deploy-config-writer): fix spelling mistake --- .../deploy-config-generator-shared/test/prompts/index.test.ts | 2 +- packages/flp-config-sub-generator/src/app/index.ts | 2 +- packages/flp-config-sub-generator/test/app.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/deploy-config-generator-shared/test/prompts/index.test.ts b/packages/deploy-config-generator-shared/test/prompts/index.test.ts index d0cd157cdf..c2a7aa0c24 100644 --- a/packages/deploy-config-generator-shared/test/prompts/index.test.ts +++ b/packages/deploy-config-generator-shared/test/prompts/index.test.ts @@ -12,7 +12,7 @@ describe('prompts', () => { expect(result).toEqual([ { type: 'confirm', - name: 'confirmConfigUpate', + name: 'confirmConfigUpdate', message: `${configType} configuration is managed centrally as part of the CI pipeline, local updates to the configuration will not be for productive use. Are you sure you want to continue?`, default: false } diff --git a/packages/flp-config-sub-generator/src/app/index.ts b/packages/flp-config-sub-generator/src/app/index.ts index 4282e996b3..6d67aa435d 100644 --- a/packages/flp-config-sub-generator/src/app/index.ts +++ b/packages/flp-config-sub-generator/src/app/index.ts @@ -153,7 +153,7 @@ export default class extends Generator { const confirmConfigUpdatePrompts = getConfirmConfigUpdatePrompt( this.options.data.additionalPrompts.confirmConfigUpdate.configType ); - questions = withCondition(questions, (answers: Answers) => answers.confirmConfigUpate); + questions = withCondition(questions, (answers: Answers) => answers.confirmConfigUpdate); questions.unshift(...confirmConfigUpdatePrompts); } diff --git a/packages/flp-config-sub-generator/test/app.test.ts b/packages/flp-config-sub-generator/test/app.test.ts index efb4a6afce..4e3c7a6842 100644 --- a/packages/flp-config-sub-generator/test/app.test.ts +++ b/packages/flp-config-sub-generator/test/app.test.ts @@ -213,7 +213,7 @@ describe('flp-config generator', () => { .withOptions({ data: { additionalPrompts: { - confirmConfigUpate: { + confirmConfigUpdate: { show: true } } From 3e51b936b35db2aae696641e50f629f45c72cc4f Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 17:35:05 +0000 Subject: [PATCH 08/13] fix(cf-deploy-config-writer): remove spelling fix --- packages/deploy-config-generator-shared/src/prompts/index.ts | 4 ++-- .../src/translations/deploy-config-generator-shared.i18n.json | 2 +- .../deploy-config-generator-shared/test/prompts/index.test.ts | 2 +- packages/flp-config-sub-generator/src/app/index.ts | 2 +- packages/flp-config-sub-generator/test/app.test.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/deploy-config-generator-shared/src/prompts/index.ts b/packages/deploy-config-generator-shared/src/prompts/index.ts index da7ad6dc8a..bcf2794861 100644 --- a/packages/deploy-config-generator-shared/src/prompts/index.ts +++ b/packages/deploy-config-generator-shared/src/prompts/index.ts @@ -5,7 +5,7 @@ import type { Question } from 'inquirer'; * Enumeration of prompt names */ enum promptNames { - confirmConfigUpdate = 'confirmConfigUpdate' + confirmConfigUdate = 'confirmConfigUdate' } /** @@ -18,7 +18,7 @@ export function getConfirmConfigUpdatePrompt(configType?: string): Question[] { return [ { type: 'confirm', - name: promptNames.confirmConfigUpdate, + name: promptNames.confirmConfigUdate, message: t('prompts.confirmConfigUpdate.message', { configType }), default: false } diff --git a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json index 48de093b6b..4b0b92388d 100644 --- a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json +++ b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json @@ -1,6 +1,6 @@ { "prompts": { - "confirmConfigUpdate": { + "confirmConfigUpate": { "message": "{{- configType}} configuration is managed centrally as part of the CI pipeline, local updates to the configuration will not be for productive use. Are you sure you want to continue?" }, "confirmCAPMtaContinue": { diff --git a/packages/deploy-config-generator-shared/test/prompts/index.test.ts b/packages/deploy-config-generator-shared/test/prompts/index.test.ts index c2a7aa0c24..d0cd157cdf 100644 --- a/packages/deploy-config-generator-shared/test/prompts/index.test.ts +++ b/packages/deploy-config-generator-shared/test/prompts/index.test.ts @@ -12,7 +12,7 @@ describe('prompts', () => { expect(result).toEqual([ { type: 'confirm', - name: 'confirmConfigUpdate', + name: 'confirmConfigUpate', message: `${configType} configuration is managed centrally as part of the CI pipeline, local updates to the configuration will not be for productive use. Are you sure you want to continue?`, default: false } diff --git a/packages/flp-config-sub-generator/src/app/index.ts b/packages/flp-config-sub-generator/src/app/index.ts index 6d67aa435d..4282e996b3 100644 --- a/packages/flp-config-sub-generator/src/app/index.ts +++ b/packages/flp-config-sub-generator/src/app/index.ts @@ -153,7 +153,7 @@ export default class extends Generator { const confirmConfigUpdatePrompts = getConfirmConfigUpdatePrompt( this.options.data.additionalPrompts.confirmConfigUpdate.configType ); - questions = withCondition(questions, (answers: Answers) => answers.confirmConfigUpdate); + questions = withCondition(questions, (answers: Answers) => answers.confirmConfigUpate); questions.unshift(...confirmConfigUpdatePrompts); } diff --git a/packages/flp-config-sub-generator/test/app.test.ts b/packages/flp-config-sub-generator/test/app.test.ts index 4e3c7a6842..efb4a6afce 100644 --- a/packages/flp-config-sub-generator/test/app.test.ts +++ b/packages/flp-config-sub-generator/test/app.test.ts @@ -213,7 +213,7 @@ describe('flp-config generator', () => { .withOptions({ data: { additionalPrompts: { - confirmConfigUpdate: { + confirmConfigUpate: { show: true } } From d35f8e1d005216e00fd13e03af0899df20a9d4ed Mon Sep 17 00:00:00 2001 From: John Long Date: Wed, 12 Feb 2025 19:50:05 +0000 Subject: [PATCH 09/13] fix(flp): fix spelling --- packages/deploy-config-generator-shared/src/prompts/index.ts | 2 +- .../deploy-config-generator-shared/test/prompts/index.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/deploy-config-generator-shared/src/prompts/index.ts b/packages/deploy-config-generator-shared/src/prompts/index.ts index bcf2794861..882bb744b4 100644 --- a/packages/deploy-config-generator-shared/src/prompts/index.ts +++ b/packages/deploy-config-generator-shared/src/prompts/index.ts @@ -19,7 +19,7 @@ export function getConfirmConfigUpdatePrompt(configType?: string): Question[] { { type: 'confirm', name: promptNames.confirmConfigUdate, - message: t('prompts.confirmConfigUpdate.message', { configType }), + message: t('prompts.confirmConfigUpate.message', { configType }), default: false } ]; diff --git a/packages/deploy-config-generator-shared/test/prompts/index.test.ts b/packages/deploy-config-generator-shared/test/prompts/index.test.ts index d0cd157cdf..9e33eba8cf 100644 --- a/packages/deploy-config-generator-shared/test/prompts/index.test.ts +++ b/packages/deploy-config-generator-shared/test/prompts/index.test.ts @@ -12,7 +12,7 @@ describe('prompts', () => { expect(result).toEqual([ { type: 'confirm', - name: 'confirmConfigUpate', + name: 'confirmConfigUdate', message: `${configType} configuration is managed centrally as part of the CI pipeline, local updates to the configuration will not be for productive use. Are you sure you want to continue?`, default: false } From b7152e457f1a4034b29f6c5cec11e44339df2e93 Mon Sep 17 00:00:00 2001 From: John Long Date: Thu, 13 Feb 2025 16:50:55 +0000 Subject: [PATCH 10/13] fix(cf-deploy-config-writer): fix merge conflict --- .../src/prompts/index.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/deploy-config-generator-shared/src/prompts/index.ts b/packages/deploy-config-generator-shared/src/prompts/index.ts index 8d821d3c7c..da7ad6dc8a 100644 --- a/packages/deploy-config-generator-shared/src/prompts/index.ts +++ b/packages/deploy-config-generator-shared/src/prompts/index.ts @@ -24,3 +24,19 @@ export function getConfirmConfigUpdatePrompt(configType?: string): Question[] { } ]; } + +/** + * Generate a new prompt asking if the user wants to create an approuter configuration within a CAP project. + * + * @returns the CAP MTA continue question. + */ +export function getConfirmMtaContinuePrompt(): Question[] { + return [ + { + type: 'confirm', + name: 'addCapMtaContinue', + message: t('prompts.confirmCAPMtaContinue.message'), + default: false + } + ]; +} From 0df5c2d32960eac06e6bfd442ebe7ef95ae30eb4 Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 14 Feb 2025 11:01:40 +0000 Subject: [PATCH 11/13] fix(cf-deploy-config-writer): cleanup code --- .../cf-deploy-config-sub-generator/src/app/index.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/cf-deploy-config-sub-generator/src/app/index.ts b/packages/cf-deploy-config-sub-generator/src/app/index.ts index 06266ce6ef..a6fb4d56e3 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/index.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/index.ts @@ -42,7 +42,6 @@ import { CfDeployConfigOptions } from './types'; import { type CfAppRouterDeployConfigAnswers, type CfDeployConfigQuestions, - type CfAppRouterDeployConfigQuestions, CfDeployConfigAnswers } from '@sap-ux/cf-deploy-config-inquirer'; import type { YeomanEnvironment } from '@sap-ux/fiori-generator-shared'; @@ -227,17 +226,6 @@ export default class extends DeploymentGenerator { } } - /** - * Fetches the Cloud Foundry Approuter configuration questions. - * - * @returns {Promise} - Cloud Foundry Approuter deployment configuration questions - */ - private async _getCFAppRouterQuestions(): Promise { - return await getCFApprouterQuestionsForCap({ - projectRoot: this.projectRoot ?? process.cwd() - }); - } - /** * Fetches the Cloud Foundry deployment configuration questions. * From b8de706174ed7f8c21a3eb1169a00b90ae78f485 Mon Sep 17 00:00:00 2001 From: John Long Date: Fri, 14 Feb 2025 14:37:00 +0000 Subject: [PATCH 12/13] fix(cf-deploy-config-writer): remove unused code and error handling --- .../translations/deploy-config-generator-shared.i18n.json | 1 - .../src/utils/error-handler.ts | 6 ++---- .../test/error-handler.test.ts | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json index 48de093b6b..343f3e3154 100644 --- a/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json +++ b/packages/deploy-config-generator-shared/src/translations/deploy-config-generator-shared.i18n.json @@ -9,7 +9,6 @@ }, "errors": { "abortSignal": "Generator aborted", - "capDeploymentNoMta": "The SAP Fiori application is within a CAP project and deployment should be configured as part of the CAP project. Please ensure you have a mta.yaml file defined for this project.", "fileDoesNotExist": "File does not exist: {{- filePath}}", "folderDoesNotExist": "Folder path does not exist: {{- filePath}}", "noAppName": "Could not determine app name from manifest", diff --git a/packages/deploy-config-generator-shared/src/utils/error-handler.ts b/packages/deploy-config-generator-shared/src/utils/error-handler.ts index cc76d8cfed..8504115f0f 100644 --- a/packages/deploy-config-generator-shared/src/utils/error-handler.ts +++ b/packages/deploy-config-generator-shared/src/utils/error-handler.ts @@ -9,8 +9,7 @@ export enum ERROR_TYPE { NO_MANIFEST = 'NO_MANIFEST', NO_APP_NAME = 'NO_APP_NAME', NO_CDS_BIN = 'NO_CDS_BIN', - NO_MTA_BIN = 'NO_MTA_BIN', - CAP_DEPLOYMENT_NO_MTA = 'CAP_DEPLOYMENT_NO_MTA' + NO_MTA_BIN = 'NO_MTA_BIN' } /** @@ -37,8 +36,7 @@ export class ErrorHandler { [ERROR_TYPE.NO_MANIFEST]: () => t('errors.noManifest'), [ERROR_TYPE.NO_APP_NAME]: () => t('errors.noAppName'), [ERROR_TYPE.NO_CDS_BIN]: () => ErrorHandler.cannotFindBinary(cdsExecutable, cdsPkg), - [ERROR_TYPE.NO_MTA_BIN]: () => ErrorHandler.cannotFindBinary(mtaExecutable, mtaPkg), - [ERROR_TYPE.CAP_DEPLOYMENT_NO_MTA]: () => t('errors.capDeploymentNoMta') + [ERROR_TYPE.NO_MTA_BIN]: () => ErrorHandler.cannotFindBinary(mtaExecutable, mtaPkg) }; public static readonly noBaseConfig = (baseConfig: string): string => t('errors.noBaseConfig', { baseConfig }); diff --git a/packages/deploy-config-generator-shared/test/error-handler.test.ts b/packages/deploy-config-generator-shared/test/error-handler.test.ts index deca567cb7..f683737dbc 100644 --- a/packages/deploy-config-generator-shared/test/error-handler.test.ts +++ b/packages/deploy-config-generator-shared/test/error-handler.test.ts @@ -51,7 +51,6 @@ describe('Error Message Methods', () => { expect(ErrorHandler.getErrorMsgFromType(ERROR_TYPE.NO_MTA_BIN)).toBe( t('errors.noBinary', { bin: mtaExecutable, pkg: mtaPkg }) ); - expect(ErrorHandler.getErrorMsgFromType(ERROR_TYPE.CAP_DEPLOYMENT_NO_MTA)).toBe(t('errors.capDeploymentNoMta')); }); }); From bcb7c7dbd3bc40a7fb53ff4a864b4465b2335fc4 Mon Sep 17 00:00:00 2001 From: John Long Date: Mon, 17 Feb 2025 13:22:58 +0000 Subject: [PATCH 13/13] fix(cf-deploy-config-writer): fix text formatting --- .../src/translations/cf-deploy-config-writer.i18n.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json b/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json index a511317b9b..4223b7c7fc 100644 --- a/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json +++ b/packages/cf-deploy-config-writer/src/translations/cf-deploy-config-writer.i18n.json @@ -9,7 +9,7 @@ "mtaSavedFailed": "MTA saved failed with error {{- error }}" }, "error": { - "unableToLoadMTA": "Unable to load mta.yaml configuration {{ mtaDir }}, error thrown {{ error }}.", + "unableToLoadMTA": "Unable to load mta.yaml configuration {{- mtaDir }}, error thrown {{- error }}.", "updatingMTAExtensionFailed": "Unable to add mta extension configuration to file: {{mtaExtFilePath}}.", "cannotFindBinary": "Cannot find the \"{{bin}}\" executable. Please add it to the path or use \"npm i -g {{- pkg}}\" to install it.", "mtaExtensionFailed": "Unable to create or update the mta extension file for Api Hub Enterprise destination configuration: {{error}}.",