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/examples/fe-fpm-cli/CHANGELOG.md b/examples/fe-fpm-cli/CHANGELOG.md index c812287534..894ef682fa 100644 --- a/examples/fe-fpm-cli/CHANGELOG.md +++ b/examples/fe-fpm-cli/CHANGELOG.md @@ -1,5 +1,17 @@ # @sap-ux/fe-fpm-cli +## 0.0.68 + +### Patch Changes + +- @sap-ux/fe-fpm-writer@0.33.8 + +## 0.0.67 + +### Patch Changes + +- @sap-ux/fe-fpm-writer@0.33.7 + ## 0.0.66 ### Patch Changes diff --git a/examples/fe-fpm-cli/package.json b/examples/fe-fpm-cli/package.json index 6e10e144b2..554d564e04 100644 --- a/examples/fe-fpm-cli/package.json +++ b/examples/fe-fpm-cli/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/fe-fpm-cli", - "version": "0.0.66", + "version": "0.0.68", "description": "A simple CLI to prompt required information to create a building block using the fe-fpm-writer module's prompt and generate functions.", "license": "Apache-2.0", "private": true, diff --git a/examples/simple-generator/CHANGELOG.md b/examples/simple-generator/CHANGELOG.md index 4b5c6e31d9..27c799f8aa 100644 --- a/examples/simple-generator/CHANGELOG.md +++ b/examples/simple-generator/CHANGELOG.md @@ -1,5 +1,38 @@ # @sap-ux/generator-simple-fe +## 1.0.142 + +### Patch Changes + +- @sap-ux/fiori-elements-writer@2.1.21 +- @sap-ux/fiori-freestyle-writer@2.1.3 +- @sap-ux/axios-extension@1.18.6 +- @sap-ux/system-access@0.5.31 + +## 1.0.141 + +### Patch Changes + +- @sap-ux/axios-extension@1.18.6 +- @sap-ux/system-access@0.5.31 +- @sap-ux/fiori-elements-writer@2.1.20 +- @sap-ux/fiori-freestyle-writer@2.1.2 + +## 1.0.140 + +### Patch Changes + +- @sap-ux/fiori-elements-writer@2.1.19 +- @sap-ux/fiori-freestyle-writer@2.1.1 + +## 1.0.139 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-freestyle-writer@2.1.0 + - @sap-ux/fiori-elements-writer@2.1.18 + ## 1.0.138 ### Patch Changes diff --git a/examples/simple-generator/package.json b/examples/simple-generator/package.json index dcc67f3b79..7eff1f9699 100644 --- a/examples/simple-generator/package.json +++ b/examples/simple-generator/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/generator-simple-fe", - "version": "1.0.138", + "version": "1.0.142", "description": "Simple example of a yeoman generator for Fiori elements.", "license": "Apache-2.0", "private": true, diff --git a/packages/abap-deploy-config-inquirer/CHANGELOG.md b/packages/abap-deploy-config-inquirer/CHANGELOG.md index e60ff0c92f..04198587a1 100644 --- a/packages/abap-deploy-config-inquirer/CHANGELOG.md +++ b/packages/abap-deploy-config-inquirer/CHANGELOG.md @@ -1,5 +1,31 @@ # @sap-ux/abap-deploy-config-inquirer +## 1.2.20 + +### Patch Changes + +- @sap-ux/fiori-generator-shared@0.9.2 +- @sap-ux/inquirer-common@0.6.21 +- @sap-ux/axios-extension@1.18.6 +- @sap-ux/system-access@0.5.31 + +## 1.2.19 + +### Patch Changes + +- @sap-ux/axios-extension@1.18.6 +- @sap-ux/fiori-generator-shared@0.9.1 +- @sap-ux/system-access@0.5.31 +- @sap-ux/inquirer-common@0.6.20 + +## 1.2.18 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/inquirer-common@0.6.19 + ## 1.2.17 ### Patch Changes diff --git a/packages/abap-deploy-config-inquirer/package.json b/packages/abap-deploy-config-inquirer/package.json index 0595aa0f52..9eab11efbf 100644 --- a/packages/abap-deploy-config-inquirer/package.json +++ b/packages/abap-deploy-config-inquirer/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/SAP/open-ux-tools.git", "directory": "packages/abap-deploy-config-inquirer" }, - "version": "1.2.17", + "version": "1.2.20", "license": "Apache-2.0", "main": "dist/index.js", "scripts": { diff --git a/packages/abap-deploy-config-sub-generator/CHANGELOG.md b/packages/abap-deploy-config-sub-generator/CHANGELOG.md index d9358a2097..d5675244ff 100644 --- a/packages/abap-deploy-config-sub-generator/CHANGELOG.md +++ b/packages/abap-deploy-config-sub-generator/CHANGELOG.md @@ -1,5 +1,45 @@ # @sap-ux/abap-deploy-config-sub-generator +## 0.0.40 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/telemetry@0.5.64 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/project-access@1.29.10 + - @sap-ux/abap-deploy-config-inquirer@1.2.20 + - @sap-ux/deploy-config-generator-shared@0.0.29 + - @sap-ux/abap-deploy-config-writer@0.0.91 + +## 0.0.39 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/abap-deploy-config-writer@0.0.90 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/telemetry@0.5.63 + - @sap-ux/abap-deploy-config-inquirer@1.2.19 + - @sap-ux/deploy-config-generator-shared@0.0.28 + +## 0.0.38 + +### Patch Changes + +- Updated dependencies [d6118c9] + - @sap-ux/deploy-config-generator-shared@0.0.27 + +## 0.0.37 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/abap-deploy-config-inquirer@1.2.18 + - @sap-ux/deploy-config-generator-shared@0.0.26 + ## 0.0.36 ### Patch Changes diff --git a/packages/abap-deploy-config-sub-generator/package.json b/packages/abap-deploy-config-sub-generator/package.json index c009e98134..4ca3994431 100644 --- a/packages/abap-deploy-config-sub-generator/package.json +++ b/packages/abap-deploy-config-sub-generator/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/SAP/open-ux-tools.git", "directory": "packages/abap-deploy-config-sub-generator" }, - "version": "0.0.36", + "version": "0.0.40", "license": "Apache-2.0", "main": "generators/app/index.js", "scripts": { diff --git a/packages/abap-deploy-config-writer/CHANGELOG.md b/packages/abap-deploy-config-writer/CHANGELOG.md index 734fd68224..62e64eb838 100644 --- a/packages/abap-deploy-config-writer/CHANGELOG.md +++ b/packages/abap-deploy-config-writer/CHANGELOG.md @@ -1,5 +1,20 @@ # @sap-ux/abap-deploy-config-writer +## 0.0.91 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/system-access@0.5.31 + +## 0.0.90 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/system-access@0.5.31 + ## 0.0.89 ### Patch Changes diff --git a/packages/abap-deploy-config-writer/package.json b/packages/abap-deploy-config-writer/package.json index 0a38382ba6..a2b865a630 100644 --- a/packages/abap-deploy-config-writer/package.json +++ b/packages/abap-deploy-config-writer/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/SAP/open-ux-tools.git", "directory": "packages/abap-deploy-config-writer" }, - "version": "0.0.89", + "version": "0.0.91", "license": "Apache-2.0", "main": "dist/index.js", "scripts": { diff --git a/packages/adp-flp-config-sub-generator/CHANGELOG.md b/packages/adp-flp-config-sub-generator/CHANGELOG.md index acfbdb9fb9..c59465a2b4 100644 --- a/packages/adp-flp-config-sub-generator/CHANGELOG.md +++ b/packages/adp-flp-config-sub-generator/CHANGELOG.md @@ -1,5 +1,50 @@ # @sap-ux/adp-flp-config-sub-generator +## 0.0.18 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path +- Updated dependencies [4b8577f] + - @sap-ux/adp-tooling@0.13.2 + - @sap-ux/flp-config-inquirer@0.2.47 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + - @sap-ux/axios-extension@1.18.6 + - @sap-ux/system-access@0.5.31 + +## 0.0.17 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/adp-tooling@0.13.1 + - @sap-ux/axios-extension@1.18.6 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/flp-config-inquirer@0.2.46 + - @sap-ux/system-access@0.5.31 + - @sap-ux/inquirer-common@0.6.20 + +## 0.0.16 + +### Patch Changes + +- Updated dependencies [127bd12] + - @sap-ux/adp-tooling@0.13.0 + - @sap-ux/flp-config-inquirer@0.2.45 + +## 0.0.15 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/flp-config-inquirer@0.2.44 + - @sap-ux/inquirer-common@0.6.19 + - @sap-ux/adp-tooling@0.12.138 + ## 0.0.14 ### Patch Changes diff --git a/packages/adp-flp-config-sub-generator/package.json b/packages/adp-flp-config-sub-generator/package.json index 0e363ec54c..7f0677a80c 100644 --- a/packages/adp-flp-config-sub-generator/package.json +++ b/packages/adp-flp-config-sub-generator/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/adp-flp-config-sub-generator", "description": "Generator for adding FLP configuration to an Adaptation Project", - "version": "0.0.14", + "version": "0.0.18", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/adp-flp-config-sub-generator/src/app/index.ts b/packages/adp-flp-config-sub-generator/src/app/index.ts index e6e155a73e..e0bb64d06e 100644 --- a/packages/adp-flp-config-sub-generator/src/app/index.ts +++ b/packages/adp-flp-config-sub-generator/src/app/index.ts @@ -197,7 +197,7 @@ export default class extends Generator { requestOptions['auth'] = { username: this.credentials.username, password: this.credentials.password }; } const provider = await createAbapServiceProvider(target, requestOptions, false, this.toolsLogger); - const variant = getVariant(this.projectRootPath); + const variant = await getVariant(this.projectRootPath); const manifestService = await ManifestService.initMergedManifest( provider, this.projectRootPath, diff --git a/packages/adp-tooling/CHANGELOG.md b/packages/adp-tooling/CHANGELOG.md index 8ec6626656..a7b181c3e9 100644 --- a/packages/adp-tooling/CHANGELOG.md +++ b/packages/adp-tooling/CHANGELOG.md @@ -1,5 +1,41 @@ # @sap-ux/adp-tooling +## 0.13.2 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path +- Updated dependencies [4b8577f] + - @sap-ux/odata-service-writer@0.26.5 + - @sap-ux/i18n@0.2.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + - @sap-ux/axios-extension@1.18.6 + - @sap-ux/system-access@0.5.31 + +## 0.13.1 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/axios-extension@1.18.6 + - @sap-ux/odata-service-writer@0.26.4 + - @sap-ux/system-access@0.5.31 + - @sap-ux/inquirer-common@0.6.20 + +## 0.13.0 + +### Minor Changes + +- 127bd12: feat: Add Typescript support for Adaptation Project + +## 0.12.138 + +### Patch Changes + +- @sap-ux/inquirer-common@0.6.19 + ## 0.12.137 ### Patch Changes diff --git a/packages/adp-tooling/package.json b/packages/adp-tooling/package.json index 726193ebff..e240673632 100644 --- a/packages/adp-tooling/package.json +++ b/packages/adp-tooling/package.json @@ -9,7 +9,7 @@ "bugs": { "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Aadp-tooling" }, - "version": "0.12.137", + "version": "0.13.2", "license": "Apache-2.0", "author": "@SAP/ux-tools-team", "main": "dist/index.js", @@ -20,8 +20,8 @@ "format": "prettier --write '**/*.{js,json,ts,yaml,yml}' --ignore-path ../../.prettierignore", "lint": "eslint . --ext .ts", "lint:fix": "eslint . --ext .ts --fix", - "test": "jest FIORI_TOOLS_DISABLE_SECURE_STORE=true --ci --forceExit --detectOpenHandles --colors --testPathPattern=test/unit", - "test-u": "jest FIORI_TOOLS_DISABLE_SECURE_STORE=true --ci --forceExit --detectOpenHandles --colors -u", + "test": "cross-env FIORI_TOOLS_DISABLE_SECURE_STORE=true jest --ci --forceExit --detectOpenHandles --colors --testPathPattern=test/unit", + "test-u": "cross-env FIORI_TOOLS_DISABLE_SECURE_STORE=true jest --ci --forceExit --detectOpenHandles --colors -u", "link": "pnpm link --global", "unlink": "pnpm unlink --global" }, @@ -44,6 +44,7 @@ "@sap-ux/system-access": "workspace:*", "@sap-ux/ui5-config": "workspace:*", "@sap-ux/odata-service-writer": "workspace:*", + "@sap-ux/nodejs-utils": "workspace:*", "@sap-ux/i18n": "workspace:*", "adm-zip": "0.5.10", "ejs": "3.1.10", @@ -70,7 +71,8 @@ "express": "4.21.2", "nock": "13.4.0", "rimraf": "5.0.5", - "supertest": "6.3.3" + "supertest": "6.3.3", + "cross-env": "^7.0.3" }, "engines": { "node": ">=18.x" diff --git a/packages/adp-tooling/src/base/abap/manifest-service.ts b/packages/adp-tooling/src/base/abap/manifest-service.ts index 145e8a47db..1cbe5d3d10 100644 --- a/packages/adp-tooling/src/base/abap/manifest-service.ts +++ b/packages/adp-tooling/src/base/abap/manifest-service.ts @@ -141,7 +141,7 @@ export class ManifestService { */ private async fetchMergedManifest(basePath: string, descriptorVariantId: string): Promise { const zip = new ZipFile(); - const files = getWebappFiles(basePath); + const files = await getWebappFiles(basePath); for (const file of files) { zip.addFile(file.relativePath, Buffer.from(file.content, 'utf-8')); } diff --git a/packages/adp-tooling/src/base/helper.ts b/packages/adp-tooling/src/base/helper.ts index 74bfaddffa..04c5f7d60b 100644 --- a/packages/adp-tooling/src/base/helper.ts +++ b/packages/adp-tooling/src/base/helper.ts @@ -1,8 +1,8 @@ import type { Editor } from 'mem-fs-editor'; -import { readdirSync, readFileSync } from 'fs'; -import { join, isAbsolute, relative } from 'path'; - -import { UI5Config } from '@sap-ux/ui5-config'; +import { existsSync, readdirSync, readFileSync } from 'fs'; +import { join, isAbsolute, relative, basename, dirname } from 'path'; +import { getWebappPath, FileName, readUi5Yaml } from '@sap-ux/project-access'; +import type { UI5Config } from '@sap-ux/ui5-config'; import type { DescriptorVariant, AdpPreviewConfig } from '../types'; @@ -11,13 +11,14 @@ import type { DescriptorVariant, AdpPreviewConfig } from '../types'; * * @param {string} basePath - The path to the adaptation project. * @param {Editor} fs - The mem-fs editor instance. - * @returns {DescriptorVariant} The app descriptor variant. + * @returns {Promise} The app descriptor variant. */ -export function getVariant(basePath: string, fs?: Editor): DescriptorVariant { +export async function getVariant(basePath: string, fs?: Editor): Promise { + const webappPath = await getWebappPath(basePath); if (fs) { - return fs.readJSON(join(basePath, 'webapp', 'manifest.appdescr_variant')) as unknown as DescriptorVariant; + return fs.readJSON(join(webappPath, FileName.ManifestAppDescrVar)) as unknown as DescriptorVariant; } - return JSON.parse(readFileSync(join(basePath, 'webapp', 'manifest.appdescr_variant'), 'utf-8')); + return JSON.parse(readFileSync(join(webappPath, FileName.ManifestAppDescrVar), 'utf-8')); } /** @@ -27,8 +28,8 @@ export function getVariant(basePath: string, fs?: Editor): DescriptorVariant { * @param {DescriptorVariant} variant - The descriptor variant object. * @param {Editor} fs - The mem-fs editor instance. */ -export function updateVariant(basePath: string, variant: DescriptorVariant, fs: Editor) { - fs.writeJSON(join(basePath, 'webapp', 'manifest.appdescr_variant'), variant); +export async function updateVariant(basePath: string, variant: DescriptorVariant, fs: Editor): Promise { + fs.writeJSON(join(await getWebappPath(basePath), FileName.ManifestAppDescrVar), variant); } /** @@ -38,12 +39,12 @@ export function updateVariant(basePath: string, variant: DescriptorVariant, fs: * or `appdescr_app_addNewInbound` present in the content of the descriptor variant. * * @param {string} basePath - The base path of the project where the manifest.appdescr_variant is located. - * @returns {boolean} Returns `true` if FLP configuration changes exist, otherwise `false`. + * @returns {Promise} Returns `true` if FLP configuration changes exist, otherwise `false`. * @throws {Error} Throws an error if the variant could not be retrieved. */ -export function flpConfigurationExists(basePath: string): boolean { +export async function flpConfigurationExists(basePath: string): Promise { try { - const variant = getVariant(basePath); + const variant = await getVariant(basePath); return variant.content?.some( ({ changeType }) => changeType === 'appdescr_app_changeInbound' || changeType === 'appdescr_app_addNewInbound' @@ -53,6 +54,18 @@ export function flpConfigurationExists(basePath: string): boolean { } } +/** + * Checks whether TypeScript is supported in the project by verifying the existence of `tsconfig.json`. + * + * @param basePath - The base path of the project. + * @param fs - An optional `mem-fs-editor` instance to check for the file's existence. + * @returns `true` if `tsconfig.json` exists, otherwise `false`. + */ +export function isTypescriptSupported(basePath: string, fs?: Editor): boolean { + const path = join(basePath, 'tsconfig.json'); + return fs ? fs.exists(path) : existsSync(path); +} + /** * Returns the adaptation project configuration, throws an error if not found. * @@ -62,13 +75,19 @@ export function flpConfigurationExists(basePath: string): boolean { */ export async function getAdpConfig(basePath: string, yamlPath: string): Promise { const ui5ConfigPath = isAbsolute(yamlPath) ? yamlPath : join(basePath, yamlPath); - const ui5Conf = await UI5Config.newInstance(readFileSync(ui5ConfigPath, 'utf-8')); - const customMiddlerware = - ui5Conf.findCustomMiddleware<{ adp: AdpPreviewConfig }>('fiori-tools-preview') ?? - ui5Conf.findCustomMiddleware<{ adp: AdpPreviewConfig }>('preview-middleware'); - const adp = customMiddlerware?.configuration?.adp; + let ui5Conf: UI5Config; + let adp: AdpPreviewConfig | undefined; + try { + ui5Conf = await readUi5Yaml(dirname(ui5ConfigPath), basename(ui5ConfigPath)); + const customMiddleware = + ui5Conf.findCustomMiddleware<{ adp: AdpPreviewConfig }>('fiori-tools-preview') ?? + ui5Conf.findCustomMiddleware<{ adp: AdpPreviewConfig }>('preview-middleware'); + adp = customMiddleware?.configuration?.adp; + } catch (error) { + // do nothing here + } if (!adp) { - throw new Error('No system configuration found in ui5.yaml'); + throw new Error(`No system configuration found in ${basename(ui5ConfigPath)}`); } return adp; } @@ -77,10 +96,10 @@ export async function getAdpConfig(basePath: string, yamlPath: string): Promise< * Get all files in the webapp folder. * * @param {string} basePath - The path to the adaptation project. - * @returns {Array<{ relativePath: string; content: string }>} The files in the webapp folder. + * @returns {Promise<{ relativePath: string; content: string }[]>} The files in the webapp folder. */ -export function getWebappFiles(basePath: string): { relativePath: string; content: string }[] { - const dir = join(basePath, 'webapp'); +export async function getWebappFiles(basePath: string): Promise<{ relativePath: string; content: string }[]> { + const dir = await getWebappPath(basePath); const files: { relativePath: string; content: string }[] = []; const getFilesRecursivelySync = (directory: string): void => { diff --git a/packages/adp-tooling/src/base/project-builder.ts b/packages/adp-tooling/src/base/project-builder.ts new file mode 100644 index 0000000000..d61f3dc39d --- /dev/null +++ b/packages/adp-tooling/src/base/project-builder.ts @@ -0,0 +1,21 @@ +import { CommandRunner } from '@sap-ux/nodejs-utils'; + +/** + * Executes a build command in the specified project directory. + * + * This function uses the `CommandRunner` to run the build process via the command `npm run build`. + * + * @param {string} projectPath - The absolute path to the project directory where the build command will be executed. + * @returns {Promise} Resolves when the build process has completed successfully. + * @throws {Error} If the build process fails or if an error occurs during cleanup. + */ +export async function runBuild(projectPath: string): Promise { + const commandRunner = new CommandRunner(); + + try { + await commandRunner.run('npm', ['run', 'build'], { cwd: projectPath }); + } catch (e) { + console.error(`Error during build and clean: ${e.message}`); + throw e; + } +} diff --git a/packages/adp-tooling/src/base/prompt.ts b/packages/adp-tooling/src/base/prompt.ts index 0d4a08ea38..bafafa8f28 100644 --- a/packages/adp-tooling/src/base/prompt.ts +++ b/packages/adp-tooling/src/base/prompt.ts @@ -18,6 +18,7 @@ export type PromptDefaults = { ft?: boolean; package?: string; transport?: string; + ts?: boolean; }; /** @@ -93,6 +94,13 @@ export async function promptGeneratorInput( message: 'Enable Fiori tools?', initial: defaults.ft !== false, validate: (input) => input?.length > 0 + }, + { + type: 'confirm', + name: 'enableTypeScript', + message: 'Enable TypeScript?', + initial: defaults.ts !== false, + validate: (input) => input?.length > 0 } ]); diff --git a/packages/adp-tooling/src/index.ts b/packages/adp-tooling/src/index.ts index 78cf060b31..bad2ab3eae 100644 --- a/packages/adp-tooling/src/index.ts +++ b/packages/adp-tooling/src/index.ts @@ -2,6 +2,7 @@ export * from './types'; export * from './prompts'; export * from './common'; export * from './base/cf'; +export * from './base/project-builder'; export * from './base/abap/manifest-service'; export * from './base/helper'; export * from './preview/adp-preview'; diff --git a/packages/adp-tooling/src/preview/change-handler.ts b/packages/adp-tooling/src/preview/change-handler.ts index b86d940371..c494e0eced 100644 --- a/packages/adp-tooling/src/preview/change-handler.ts +++ b/packages/adp-tooling/src/preview/change-handler.ts @@ -270,7 +270,7 @@ export async function addAnnotationFile( serviceUrl: datasoruces[dataSourceId].uri, fileName: basename(dataSource[annotationDataSourceKey].uri) }, - variant: getVariant(projectRoot), + variant: await getVariant(projectRoot), isCommand: false }, fs @@ -289,7 +289,7 @@ export async function addAnnotationFile( * @returns Promise */ async function getManifestService(basePath: string, logger: Logger): Promise { - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const { target, ignoreCertErrors = false } = await getAdpConfig(basePath, join(basePath, FileName.Ui5Yaml)); const provider = await createAbapServiceProvider(target, { ignoreCertErrors }, true, logger); return await ManifestService.initMergedManifest(provider, basePath, variant, logger as unknown as ToolsLogger); diff --git a/packages/adp-tooling/src/preview/routes-handler.ts b/packages/adp-tooling/src/preview/routes-handler.ts index d7d0af5390..dab5ca7ffc 100644 --- a/packages/adp-tooling/src/preview/routes-handler.ts +++ b/packages/adp-tooling/src/preview/routes-handler.ts @@ -14,7 +14,7 @@ import { DirName, FileName } from '@sap-ux/project-access'; import { type CodeExtChange } from '../types'; import { ManifestService } from '../base/abap/manifest-service'; import type { DataSources } from '../base/abap/manifest-service'; -import { getAdpConfig, getVariant } from '../base/helper'; +import { getAdpConfig, getVariant, isTypescriptSupported } from '../base/helper'; import { createAbapServiceProvider } from '@sap-ux/system-access'; interface WriteControllerBody { @@ -74,7 +74,7 @@ export default class RoutesHandler { * @param data Data that is sent to the client * @param contentType Content type, defaults to json */ - private sendFilesResponse(res: Response, data: object | string, contentType: string = 'application/json') { + private sendFilesResponse(res: Response, data: object | string, contentType: string = 'application/json'): void { res.status(HttpStatusCodes.OK).contentType(contentType).send(data); } @@ -100,12 +100,12 @@ export default class RoutesHandler { * @param res Response * @param next Next Function */ - public handleReadAllFragments = async (_: Request, res: Response, next: NextFunction) => { + public handleReadAllFragments = async (_: Request, res: Response, next: NextFunction): Promise => { try { const files = await this.readAllFilesByGlob('/**/changes/fragments/*.fragment.xml'); - const fileNames = files.map((f) => ({ - fragmentName: f.getName() + const fileNames = files.map((file) => ({ + fragmentName: file.getName() })); this.sendFilesResponse(res, { @@ -125,12 +125,12 @@ export default class RoutesHandler { * @param res Response * @param next Next Function */ - public handleReadAllControllers = async (_: Request, res: Response, next: NextFunction) => { + public handleReadAllControllers = async (_: Request, res: Response, next: NextFunction): Promise => { try { const files = await this.readAllFilesByGlob('/**/changes/coding/*.js'); - const fileNames = files.map((f) => ({ - controllerName: f.getName() + const fileNames = files.map((file) => ({ + controllerName: file.getName() })); this.sendFilesResponse(res, { @@ -150,7 +150,11 @@ export default class RoutesHandler { * @param res Response * @param next Next Function */ - public handleGetControllerExtensionData = async (req: Request, res: Response, next: NextFunction) => { + public handleGetControllerExtensionData = async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { try { const params = req.params as { controllerName: string }; const controllerName = sanitize(params.controllerName); @@ -163,8 +167,11 @@ export default class RoutesHandler { const project = this.util.getProject(); const sourcePath = project.getSourcePath(); + const rootPath = this.util.getProject().getRootPath(); const projectName = project.getName(); + const isTsSupported = isTypescriptSupported(rootPath); + const getPath = (projectPath: string, fileName: string, folder: string = DirName.Coding) => path.join(projectPath, DirName.Changes, folder, fileName).split(path.sep).join(path.posix.sep); @@ -173,7 +180,8 @@ export default class RoutesHandler { const change = JSON.parse(fileStr) as CodeExtChange; if (change.selector.controllerName === controllerName) { - const fileName = change.content.codeRef.replace('coding/', ''); + const baseFileName = change.content.codeRef.replace('coding/', ''); + const fileName = isTsSupported ? baseFileName.replace('.js', '.ts') : baseFileName; controllerPath = getPath(sourcePath, fileName); controllerPathFromRoot = getPath(projectName, fileName); changeFilePath = getPath(projectName, file.getName(), ''); @@ -195,7 +203,8 @@ export default class RoutesHandler { controllerExists, controllerPath: os.platform() === 'win32' ? `/${controllerPath}` : controllerPath, controllerPathFromRoot, - isRunningInBAS + isRunningInBAS, + isTsSupported }); this.logger.debug( controllerExists @@ -214,51 +223,41 @@ export default class RoutesHandler { * @param res Response * @param next Next Function */ - public handleWriteControllerExt = async (req: Request, res: Response, next: NextFunction) => { + public handleWriteControllerExt = async (req: Request, res: Response, next: NextFunction): Promise => { try { const data = req.body as WriteControllerBody; - const controllerExtName = sanitize(data.controllerName); - const projectId = data.projectId; + const name = sanitize(data.controllerName); const sourcePath = this.util.getProject().getSourcePath(); + const rootPath = this.util.getProject().getRootPath(); - if (!controllerExtName) { + if (!name) { res.status(HttpStatusCodes.BAD_REQUEST).send('Controller extension name was not provided!'); this.logger.debug('Bad request. Controller extension name was not provided!'); return; } + const isTsSupported = isTypescriptSupported(rootPath); + const fullPath = path.join(sourcePath, DirName.Changes, DirName.Coding); - const filePath = path.join(fullPath, `${controllerExtName}.js`); + const filePath = path.join(fullPath, `${name}.${isTsSupported ? 'ts' : 'js'}`); if (!fs.existsSync(fullPath)) { fs.mkdirSync(fullPath, { recursive: true }); } if (fs.existsSync(filePath)) { - res.status(HttpStatusCodes.CONFLICT).send( - `Controller extension with name "${controllerExtName}" already exists` - ); - this.logger.debug(`Controller extension with name "${controllerExtName}" already exists`); + res.status(HttpStatusCodes.CONFLICT).send(`Controller extension with name "${name}" already exists`); + this.logger.debug(`Controller extension with name "${name}" already exists`); return; } - const controllerExtPath = `${projectId}.${controllerExtName}`; - - const controllerTemplateFilePath = path.join(__dirname, '../../templates/rta', TemplateFileName.Controller); - - renderFile(controllerTemplateFilePath, { controllerExtPath }, {}, (err, str) => { - if (err) { - throw new Error('Error rendering template: ' + err.message); - } - - fs.writeFileSync(filePath, str, { encoding: 'utf8' }); - }); + await generateControllerFile(rootPath, filePath, name); const message = 'Controller extension created!'; res.status(HttpStatusCodes.CREATED).send(message); - this.logger.debug(`Controller extension with name "${controllerExtName}" was created`); + this.logger.debug(`Controller extension with name "${name}" was created`); } catch (e) { const sanitizedMsg = sanitize(e.message); this.logger.error(sanitizedMsg); @@ -274,7 +273,11 @@ export default class RoutesHandler { * @param res Response * @param next Next Function */ - public handleGetAllAnnotationFilesMappedByDataSource = async (_req: Request, res: Response, next: NextFunction) => { + public handleGetAllAnnotationFilesMappedByDataSource = async ( + _req: Request, + res: Response, + next: NextFunction + ): Promise => { try { const isRunningInBAS = isAppStudio(); @@ -349,7 +352,7 @@ export default class RoutesHandler { private async getManifestService(): Promise { const project = this.util.getProject(); const basePath = project.getRootPath(); - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const { target, ignoreCertErrors = false } = await getAdpConfig( basePath, path.join(basePath, FileName.Ui5Yaml) @@ -358,3 +361,32 @@ export default class RoutesHandler { return await ManifestService.initMergedManifest(provider, basePath, variant, this.logger); } } + +/** + * Generates a controller file for the Adaptation Project based on the project's TypeScript support. + * + * This function creates a controller file in the specified `filePath` by rendering a template. + * It determines whether to use a TypeScript or JavaScript template based on the TypeScript support of the project. + * + * @param {string} rootPath - The root directory of the project. + * @param {string} filePath - The destination path where the generated controller file should be saved. + * @param {string} name - The name of the controller extension (used in TypeScript templates). + * @throws {Error} Throws an error if rendering the template fails. + */ +async function generateControllerFile(rootPath: string, filePath: string, name: string): Promise { + const id = (await getVariant(rootPath))?.id; + const isTsSupported = isTypescriptSupported(rootPath); + const tmplFileName = isTsSupported ? TemplateFileName.TSController : TemplateFileName.Controller; + const tmplPath = path.join(__dirname, '../../templates/rta', tmplFileName); + const extensionPath = `${id}.${name}`; + + const templateData = isTsSupported ? { name, ns: id } : { extensionPath }; + + renderFile(tmplPath, templateData, {}, (err, str) => { + if (err) { + throw new Error(`Error rendering ${isTsSupported ? 'TypeScript' : 'JavaScript'} template: ${err.message}`); + } + + fs.writeFileSync(filePath, str, { encoding: 'utf8' }); + }); +} diff --git a/packages/adp-tooling/src/prompts/add-annotations-to-odata/index.ts b/packages/adp-tooling/src/prompts/add-annotations-to-odata/index.ts index c335fff695..5ad6dd388e 100644 --- a/packages/adp-tooling/src/prompts/add-annotations-to-odata/index.ts +++ b/packages/adp-tooling/src/prompts/add-annotations-to-odata/index.ts @@ -2,10 +2,10 @@ import type { ListQuestion, FileBrowserQuestion, YUIQuestion } from '@sap-ux/inq import type { ManifestNamespace } from '@sap-ux/project-access'; import { AnnotationFileSelectType, type AddAnnotationsAnswers } from '../../types'; import { t } from '../../i18n'; -import { filterDataSourcesByType } from '@sap-ux/project-access'; +import { filterDataSourcesByType, getWebappPath, DirName } from '@sap-ux/project-access'; import { existsSync } from 'fs'; import { validateEmptyString } from '@sap-ux/project-input-validator'; -import { join, isAbsolute, sep } from 'path'; +import { join, isAbsolute, basename } from 'path'; /** * Gets the prompts for adding annotations to OData service. @@ -60,7 +60,7 @@ export function getPrompts( default: '', when: (answers: AddAnnotationsAnswers) => answers.id !== '' && answers.fileSelectOption === AnnotationFileSelectType.ExistingFile, - validate: (value) => { + validate: async (value: string) => { const validationResult = validateEmptyString(value); if (typeof validationResult === 'string') { return validationResult; @@ -71,8 +71,11 @@ export function getPrompts( return t('validators.fileDoesNotExist'); } - const fileName = filePath.split(sep).pop(); - if (existsSync(join(basePath, 'webapp', 'changes', 'annotations', fileName))) { + if ( + existsSync( + join(await getWebappPath(basePath), DirName.Changes, DirName.Annotations, basename(filePath)) + ) + ) { return t('validators.annotationFileAlreadyExists'); } diff --git a/packages/adp-tooling/src/types.ts b/packages/adp-tooling/src/types.ts index 4cc155604b..89607d9993 100644 --- a/packages/adp-tooling/src/types.ts +++ b/packages/adp-tooling/src/types.ts @@ -85,6 +85,10 @@ export interface AdpWriterConfig { * Optional: if set to true then the generated project will be recognized by the SAP Fiori tools */ fioriTools?: boolean; + /** + * Optional: if set to true then the generated project will support typescript + */ + enableTypeScript?: boolean; }; } @@ -278,6 +282,7 @@ export type ParameterRules = { export const enum TemplateFileName { Fragment = 'fragment.xml', Controller = 'controller.ejs', + TSController = 'ts-controller.ejs', Annotation = 'annotation.xml' } diff --git a/packages/adp-tooling/src/writer/inbound-navigation.ts b/packages/adp-tooling/src/writer/inbound-navigation.ts index 620b295a0f..9d282df1c7 100644 --- a/packages/adp-tooling/src/writer/inbound-navigation.ts +++ b/packages/adp-tooling/src/writer/inbound-navigation.ts @@ -25,7 +25,7 @@ export async function generateInboundConfig( fs = create(createStorage()); } - const variant = getVariant(basePath, fs); + const variant = await getVariant(basePath, fs); if (!config?.inboundId) { config.addInboundId = true; @@ -34,7 +34,7 @@ export async function generateInboundConfig( enhanceInboundConfig(config, variant.id, variant.content as Content[]); - updateVariant(basePath, variant, fs); + await updateVariant(basePath, variant, fs); await updateI18n(basePath, variant.id, config, fs); return fs; diff --git a/packages/adp-tooling/src/writer/index.ts b/packages/adp-tooling/src/writer/index.ts index 35f3a4d48d..832b3d7afd 100644 --- a/packages/adp-tooling/src/writer/index.ts +++ b/packages/adp-tooling/src/writer/index.ts @@ -5,7 +5,7 @@ import type { AdpWriterConfig, InternalInboundNavigation } from '../types'; import { enhanceManifestChangeContentWithFlpConfig } from './options'; import { writeTemplateToFolder, writeUI5Yaml, writeUI5DeployYaml } from './project-utils'; -const tmplPath = join(__dirname, '../../templates/project'); +const baseTmplPath = join(__dirname, '../../templates'); /** * Set default values for optional properties. @@ -60,7 +60,7 @@ export async function generate(basePath: string, config: AdpWriterConfig, fs?: E fullConfig.app.content ); } - writeTemplateToFolder(join(tmplPath, '**/*.*'), join(basePath), fullConfig, fs); + writeTemplateToFolder(baseTmplPath, join(basePath), fullConfig, fs); await writeUI5DeployYaml(basePath, fullConfig, fs); await writeUI5Yaml(basePath, fullConfig, fs); @@ -82,6 +82,8 @@ export async function migrate(basePath: string, config: AdpWriterConfig, fs?: Ed const fullConfig = setDefaults(config); + const tmplPath = join(baseTmplPath, 'project'); + // Copy the specified files to target project fs.copyTpl(join(tmplPath, '**/ui5.yaml'), join(basePath), fullConfig, undefined, { globOptions: { dot: true } diff --git a/packages/adp-tooling/src/writer/options.ts b/packages/adp-tooling/src/writer/options.ts index 4eee949d2d..6571991ee7 100644 --- a/packages/adp-tooling/src/writer/options.ts +++ b/packages/adp-tooling/src/writer/options.ts @@ -5,8 +5,8 @@ import type { AbapTarget, FioriToolsProxyConfigBackend } from '@sap-ux/ui5-config'; + import type { - CustomConfig, AdpWriterConfig, InboundContent, Language, @@ -37,14 +37,35 @@ export function enhanceUI5Yaml(ui5Config: UI5Config, config: AdpWriterConfig) { } /** - * Generate the configuration for the custom tasks required for the ui5.yaml. + * Generates the configuration for the custom tasks required for the ui5.yaml. * - * @param ui5Config configuration representing the ui5.yaml - * @param config full project configuration + * Adds a custom task for building TypeScript projects. + * + * @param {UI5Config} ui5Config - The UI5 configuration object representing the ui5.yaml. + * @param {AdpWriterConfig} config - The configuration object containing options for the adaptation project. */ export function enhanceUI5YamlWithCustomTask(ui5Config: UI5Config, config: AdpWriterConfig & { app: CloudApp }) { - const tasks = getAdpCloudCustomTasks(config); - ui5Config.addCustomTasks(tasks); + if (config.options?.enableTypeScript) { + ui5Config.addCustomTasks([ + { + name: 'ui5-tooling-transpile-task', + afterTask: 'replaceVersion', + configuration: { + debug: true, + omitSourceMaps: true, + omitTSFromBuildResult: true, + transformModulesToUI5: { + overridesToOverride: true + } + } + } + ]); + } + + if (config.customConfig?.adp?.environment === 'C') { + const tasks = getAdpCloudCustomTasks(config); + ui5Config.addCustomTasks(tasks); + } } /** @@ -53,13 +74,36 @@ export function enhanceUI5YamlWithCustomTask(ui5Config: UI5Config, config: AdpWr * @param ui5Config configuration representing the ui5.yaml * @param config full project configuration */ -export function enhanceUI5YamlWithCustomConfig(ui5Config: UI5Config, config?: CustomConfig) { - if (config?.adp) { - const { support } = config.adp; +export function enhanceUI5YamlWithCustomConfig(ui5Config: UI5Config, config: AdpWriterConfig) { + const adp = config.customConfig?.adp; + if (adp) { + const { support } = adp; ui5Config.addCustomConfiguration('adp', { support }); } } +/** + * Enhances a UI5 YAML configuration with the transpile middleware for TypeScript support. + * + * @param {UI5Config} ui5Config - The UI5 configuration object representing the ui5.yaml. + * @param {AdpWriterConfig} config - The configuration object containing options for the adaptation project. + * @param {boolean} [config.options.enableTypeScript] - Flag indicating if TypeScript support is enabled. + */ +export function enhanceUI5YamlWithTranspileMiddleware(ui5Config: UI5Config, config: AdpWriterConfig) { + if (config.options?.enableTypeScript) { + ui5Config.updateCustomMiddleware({ + name: 'ui5-tooling-transpile-middleware', + afterMiddleware: 'compression', + configuration: { + debug: true, + transformModulesToUI5: { + overridesToOverride: true + } + } + }); + } +} + /** * Writer configuration with deploy configuration. */ diff --git a/packages/adp-tooling/src/writer/project-utils.ts b/packages/adp-tooling/src/writer/project-utils.ts index b5cb6d1ac0..c4f176e614 100644 --- a/packages/adp-tooling/src/writer/project-utils.ts +++ b/packages/adp-tooling/src/writer/project-utils.ts @@ -7,10 +7,11 @@ import { enhanceUI5Yaml, hasDeployConfig, enhanceUI5YamlWithCustomConfig, - enhanceUI5YamlWithCustomTask + enhanceUI5YamlWithCustomTask, + enhanceUI5YamlWithTranspileMiddleware } from './options'; -import { UI5Config } from '@sap-ux/ui5-config'; +import { UI5Config, getEsmTypesVersion, getTypesPackage } from '@sap-ux/ui5-config'; type PackageJSON = { name: string; version: string }; @@ -35,23 +36,35 @@ export function getPackageJSONInfo(): PackageJSON { /** * Writes a given project template files within a specified folder in the project directory. * - * @param {string} templatePath - The root path of the project template. + * @param {string} baseTmplPath - The root path of the templates folder. * @param {string} projectPath - The root path of the project. * @param {AdpWriterConfig} data - The data to be populated in the template file. * @param {Editor} fs - The `mem-fs-editor` instance used for file operations. * @returns {void} */ export function writeTemplateToFolder( - templatePath: string, + baseTmplPath: string, projectPath: string, data: AdpWriterConfig, fs: Editor ): void { + const tmplPath = join(baseTmplPath, 'project', '**/*.*'); + const tsConfigPath = join(baseTmplPath, 'typescript', 'tsconfig.json'); + const typesVersion = getEsmTypesVersion(data.ui5?.version); + const typesPackage = getTypesPackage(typesVersion); + try { - fs.copyTpl(templatePath, projectPath, data, undefined, { + fs.copyTpl(tmplPath, projectPath, { ...data, typesPackage, typesVersion }, undefined, { globOptions: { dot: true }, processDestinationPath: (filePath: string) => filePath.replace(/gitignore.tmpl/g, '.gitignore') }); + + if (data.options?.enableTypeScript) { + const id = data.app?.id?.split('.').join('/'); + fs.copyTpl(tsConfigPath, join(projectPath, 'tsconfig.json'), { id, typesPackage }, undefined, { + globOptions: { dot: true } + }); + } } catch (e) { throw new Error(`Could not write template files to folder. Reason: ${e.message}`); } @@ -71,11 +84,10 @@ export async function writeUI5Yaml(projectPath: string, data: AdpWriterConfig, f const baseUi5ConfigContent = fs.read(ui5ConfigPath); const ui5Config = await UI5Config.newInstance(baseUi5ConfigContent); ui5Config.setConfiguration({ propertiesFileSourceEncoding: 'UTF-8' }); - enhanceUI5YamlWithCustomConfig(ui5Config, data?.customConfig); + enhanceUI5YamlWithCustomConfig(ui5Config, data); + enhanceUI5YamlWithTranspileMiddleware(ui5Config, data); enhanceUI5Yaml(ui5Config, data); - if (data.customConfig?.adp?.environment === 'C') { - enhanceUI5YamlWithCustomTask(ui5Config, data as AdpWriterConfig & { app: CloudApp }); - } + enhanceUI5YamlWithCustomTask(ui5Config, data as AdpWriterConfig & { app: CloudApp }); fs.write(ui5ConfigPath, ui5Config.toString()); } catch (e) { diff --git a/packages/adp-tooling/templates/project/package.json b/packages/adp-tooling/templates/project/package.json index 57f76789bc..1208a8d77b 100644 --- a/packages/adp-tooling/templates/project/package.json +++ b/packages/adp-tooling/templates/project/package.json @@ -15,7 +15,10 @@ "@sap-ux/ui5-proxy-middleware": "^1.3.0", "@sap-ux/deploy-tooling": "^0.11.7"<%}%>, "@ui5/task-adaptation": "^1.3.0", - "@ui5/cli": "^3.9.2" + "@ui5/cli": "^3.9.2"<%if (locals.options?.enableTypeScript) {%>, + "<%- typesPackage %>": "<%- typesVersion %>", + "typescript": "^5.7.3", + "ui5-tooling-transpile": "^3.6.1"<%}%> }, "scripts": { "build": "ui5 build --exclude-task generateFlexChangesBundle generateComponentPreload minify --clean-dest", diff --git a/packages/adp-tooling/templates/rta/controller.ejs b/packages/adp-tooling/templates/rta/controller.ejs index 30b6214ff7..5e63083996 100644 --- a/packages/adp-tooling/templates/rta/controller.ejs +++ b/packages/adp-tooling/templates/rta/controller.ejs @@ -8,7 +8,7 @@ sap.ui.define( // ,OverrideExecution ) { 'use strict'; - return ControllerExtension.extend("<%= controllerExtPath %>", { + return ControllerExtension.extend("<%= extensionPath %>", { // metadata: { // // extension can declare the public methods // // in general methods that start with "_" are private @@ -47,27 +47,27 @@ sap.ui.define( // /** // * Called when a controller is instantiated and its View controls (if available) are already created. // * Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization. - // * @memberOf {{controllerExtPath}} + // * @memberOf <%= extensionPath %> // */ // onInit: function() { // }, // /** // * Similar to onAfterRendering, but this hook is invoked before the controller's View is re-rendered // * (NOT before the first rendering! onInit() is used for that one!). - // * @memberOf {{controllerExtPath}} + // * @memberOf <%= extensionPath %> // */ // onBeforeRendering: function() { // }, // /** // * Called when the View has been rendered (so its HTML is part of the document). Post-rendering manipulations of the HTML could be done here. // * This hook is the same one that SAPUI5 controls get after being rendered. - // * @memberOf {{controllerExtPath}} + // * @memberOf <%= extensionPath %> // */ // onAfterRendering: function() { // }, // /** // * Called when the Controller is destroyed. Use this one to free resources and finalize activities. - // * @memberOf {{controllerExtPath}} + // * @memberOf <%= extensionPath %> // */ // onExit: function() { // }, diff --git a/packages/adp-tooling/templates/rta/ts-controller.ejs b/packages/adp-tooling/templates/rta/ts-controller.ejs new file mode 100644 index 0000000000..a7e6e15b9d --- /dev/null +++ b/packages/adp-tooling/templates/rta/ts-controller.ejs @@ -0,0 +1,18 @@ +import ControllerExtension from "sap/ui/core/mvc/ControllerExtension"; + +/** + * @namespace <%= ns %> + * @controller + */ +export default class <%= name %> extends ControllerExtension { + overrides = { + /** + * Called when a controller is instantiated and its View controls (if available) are already created. + * Can be used to modify the View before it is displayed, to bind event handlers and do other one-time initialization. + * @memberOf <%= ns %>.<%= name %> + */ + onInit: () => { + const view = this.getView(); + }, + }; +} diff --git a/packages/adp-tooling/templates/typescript/tsconfig.json b/packages/adp-tooling/templates/typescript/tsconfig.json new file mode 100644 index 0000000000..53ea1827f2 --- /dev/null +++ b/packages/adp-tooling/templates/typescript/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2022", + "module": "es2022", + "moduleResolution": "node", + "skipLibCheck": true, + "allowJs": true, + "strict": true, + "strictPropertyInitialization": false, + "rootDir": "./", + "outDir": "./dist", + "baseUrl": "./", + "typeRoots": ["./node_modules/@types", "./node_modules/<%- typesPackage %>"], + "paths": { + "<%= id %>/*": ["./webapp/*"] + } + }, + "include": ["./webapp/**/*"] +} \ No newline at end of file diff --git a/packages/adp-tooling/test/unit/base/abap/manifest-service.test.ts b/packages/adp-tooling/test/unit/base/abap/manifest-service.test.ts index 595486d108..4745a9a013 100644 --- a/packages/adp-tooling/test/unit/base/abap/manifest-service.test.ts +++ b/packages/adp-tooling/test/unit/base/abap/manifest-service.test.ts @@ -112,9 +112,9 @@ describe('ManifestService', () => { describe('initMergedManifest', () => { it('should initialize and fetch the merged manifest', async () => { const variant = { id: 'descriptorVariantId', reference: 'referenceAppId' }; - (getWebappFiles as jest.MockedFunction).mockReturnValue([ - { relativePath: 'path', content: 'content' } - ]); + (getWebappFiles as jest.MockedFunction).mockReturnValue( + Promise.resolve([{ relativePath: 'path', content: 'content' }]) + ); manifestService = await ManifestService.initMergedManifest( provider, 'basePath', diff --git a/packages/adp-tooling/test/unit/base/helper.test.ts b/packages/adp-tooling/test/unit/base/helper.test.ts index 029cef5ac5..de4356c9a9 100644 --- a/packages/adp-tooling/test/unit/base/helper.test.ts +++ b/packages/adp-tooling/test/unit/base/helper.test.ts @@ -1,5 +1,5 @@ import { join } from 'path'; -import { readFileSync } from 'fs'; +import { existsSync, readFileSync } from 'fs'; import type { create, Editor } from 'mem-fs-editor'; import { UI5Config } from '@sap-ux/ui5-config'; @@ -11,10 +11,12 @@ import { getAdpConfig, getWebappFiles, flpConfigurationExists, - updateVariant + updateVariant, + isTypescriptSupported } from '../../../src/base/helper'; const readFileSyncMock = readFileSync as jest.Mock; +const existsSyncMock = existsSync as jest.Mock; jest.mock('fs', () => { return { @@ -40,18 +42,18 @@ describe('helper', () => { jest.clearAllMocks(); }); - test('should return variant', () => { + test('should return variant', async () => { readFileSyncMock.mockImplementation(() => mockVariant); - expect(getVariant(basePath)).toStrictEqual(JSON.parse(mockVariant)); + expect(await getVariant(basePath)).toStrictEqual(JSON.parse(mockVariant)); }); - test('should return variant using fs editor', () => { + test('should return variant using fs editor', async () => { const fs = { readJSON: jest.fn().mockReturnValue(JSON.parse(mockVariant)) } as unknown as Editor; - const result = getVariant(basePath, fs); + const result = await getVariant(basePath, fs); expect(fs.readJSON).toHaveBeenCalledWith(join(basePath, 'webapp', 'manifest.appdescr_variant')); expect(result).toStrictEqual(JSON.parse(mockVariant)); @@ -68,8 +70,8 @@ describe('helper', () => { jest.clearAllMocks(); }); - it('should write the updated variant content to the manifest file', () => { - updateVariant(basePath, mockVariant, fs); + it('should write the updated variant content to the manifest file', async () => { + await updateVariant(basePath, mockVariant, fs); expect(fs.writeJSON).toHaveBeenCalledWith( join(basePath, 'webapp', 'manifest.appdescr_variant'), @@ -85,7 +87,7 @@ describe('helper', () => { jest.clearAllMocks(); }); - it('should return true if valid FLP configuration exists', () => { + it('should return true if valid FLP configuration exists', async () => { readFileSyncMock.mockReturnValue( JSON.stringify({ content: [ @@ -95,13 +97,13 @@ describe('helper', () => { }) ); - const result = flpConfigurationExists(basePath); + const result = await flpConfigurationExists(basePath); expect(result).toBe(true); expect(readFileSyncMock).toHaveBeenCalledWith(appDescrPath, 'utf-8'); }); - it('should return false if no valid FLP configuration exists', () => { + it('should return false if no valid FLP configuration exists', async () => { readFileSyncMock.mockReturnValue( JSON.stringify({ content: [ @@ -111,24 +113,73 @@ describe('helper', () => { }) ); - const result = flpConfigurationExists(basePath); + const result = await flpConfigurationExists(basePath); expect(result).toBe(false); expect(readFileSyncMock).toHaveBeenCalledWith(appDescrPath, 'utf-8'); }); - it('should throw an error if getVariant fails', () => { + it('should throw an error if getVariant fails', async () => { readFileSyncMock.mockImplementation(() => { throw new Error('Failed to retrieve variant'); }); - expect(() => flpConfigurationExists(basePath)).toThrow( + await expect(flpConfigurationExists(basePath)).rejects.toThrow( 'Failed to check if FLP configuration exists: Failed to retrieve variant' ); expect(readFileSyncMock).toHaveBeenCalledWith(appDescrPath, 'utf-8'); }); }); + describe('isTypescriptSupported', () => { + const basePath = '/mock/project/path'; + const tsconfigPath = join(basePath, 'tsconfig.json'); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return true if tsconfig.json exists and fs is not provided', () => { + existsSyncMock.mockReturnValueOnce(true); + + const result = isTypescriptSupported(basePath); + + expect(result).toBe(true); + expect(existsSyncMock).toHaveBeenCalledWith(tsconfigPath); + }); + + it('should return false if tsconfig.json does not exist and fs is not provided', () => { + existsSyncMock.mockReturnValueOnce(false); + + const result = isTypescriptSupported(basePath); + + expect(result).toBe(false); + expect(existsSyncMock).toHaveBeenCalledWith(tsconfigPath); + }); + + it('should return true if tsconfig.json exists and fs is provided', () => { + const mockEditor = { + exists: jest.fn().mockReturnValueOnce(true) + } as unknown as Editor; + + const result = isTypescriptSupported(basePath, mockEditor); + + expect(result).toBe(true); + expect(mockEditor.exists).toHaveBeenCalledWith(tsconfigPath); + }); + + it('should return false if tsconfig.json does not exist and fs is provided', () => { + const mockEditor = { + exists: jest.fn().mockReturnValueOnce(false) + } as unknown as Editor; + + const result = isTypescriptSupported(basePath, mockEditor); + + expect(result).toBe(false); + expect(mockEditor.exists).toHaveBeenCalledWith(tsconfigPath); + }); + }); + describe('getAdpConfig', () => { beforeEach(() => { jest.clearAllMocks(); @@ -163,8 +214,20 @@ describe('helper', () => { jest.clearAllMocks(); }); - test('should return webapp files', () => { - expect(getWebappFiles(basePath)).toEqual([ + test('should return webapp files', async () => { + jest.spyOn(UI5Config, 'newInstance').mockResolvedValue({ + findCustomMiddleware: jest.fn().mockReturnValue({ + configuration: { + adp: mockAdp + } + } as Partial as CustomMiddleware), + getConfiguration: jest.fn().mockReturnValue({ + paths: { + webapp: 'webapp' + } + }) + } as Partial as UI5Config); + expect(await getWebappFiles(basePath)).toEqual([ { relativePath: join('i18n', 'i18n.properties'), content: expect.any(String) diff --git a/packages/adp-tooling/test/unit/base/project-builder.test.ts b/packages/adp-tooling/test/unit/base/project-builder.test.ts new file mode 100644 index 0000000000..571d096e16 --- /dev/null +++ b/packages/adp-tooling/test/unit/base/project-builder.test.ts @@ -0,0 +1,35 @@ +import { CommandRunner } from '@sap-ux/nodejs-utils'; + +import { runBuild } from '../../../src/base/project-builder'; + +const projectPath = '/mock/project/path'; + +describe('runBuildAndClean', () => { + let commandSpy: jest.SpyInstance; + + beforeEach(() => { + commandSpy = jest.spyOn(CommandRunner.prototype, 'run'); + console.error = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should execute the build command', async () => { + commandSpy.mockResolvedValueOnce('Build completed.'); + + await runBuild(projectPath); + + expect(commandSpy).toHaveBeenCalledWith('npm', ['run', 'build'], { cwd: projectPath }); + }); + + it('should throw an error if the build command fails', async () => { + const errorMsg = 'Build failed'; + commandSpy.mockRejectedValueOnce(new Error(errorMsg)); + + await expect(runBuild(projectPath)).rejects.toThrow(errorMsg); + + expect(console.error).toHaveBeenCalledWith(`Error during build and clean: ${errorMsg}`); + }); +}); diff --git a/packages/adp-tooling/test/unit/base/prompt.test.ts b/packages/adp-tooling/test/unit/base/prompt.test.ts index b149a02261..bc71a04b4c 100644 --- a/packages/adp-tooling/test/unit/base/prompt.test.ts +++ b/packages/adp-tooling/test/unit/base/prompt.test.ts @@ -113,7 +113,8 @@ describe('base/prompts', () => { transport: defaults.transport }, options: { - fioriTools: true + fioriTools: true, + enableTypeScript: true } }); }); @@ -156,7 +157,8 @@ describe('base/prompts', () => { transport: defaults.transport }, options: { - fioriTools: false + fioriTools: false, + enableTypeScript: true } }); }); diff --git a/packages/adp-tooling/test/unit/preview/adp-preview.test.ts b/packages/adp-tooling/test/unit/preview/adp-preview.test.ts index 4e0b023478..5a7b6cd9b4 100644 --- a/packages/adp-tooling/test/unit/preview/adp-preview.test.ts +++ b/packages/adp-tooling/test/unit/preview/adp-preview.test.ts @@ -1,21 +1,23 @@ import nock from 'nock'; +import * as fs from 'fs'; import { join } from 'path'; import express from 'express'; +import { renderFile } from 'ejs'; import supertest from 'supertest'; import type { Editor } from 'mem-fs-editor'; -import { type Logger, ToolsLogger } from '@sap-ux/logger'; import type { ReaderCollection } from '@ui5/fs'; import type { SuperTest, Test } from 'supertest'; -import * as fs from 'fs'; -import { AdpPreview } from '../../../src/preview/adp-preview'; -import type { AdpPreviewConfig, CommonChangeProperties } from '../../../src'; +import { type Logger, ToolsLogger } from '@sap-ux/logger'; +import * as systemAccess from '@sap-ux/system-access/dist/base/connect'; +import * as serviceWriter from '@sap-ux/odata-service-writer/dist/data/annotations'; + import * as helper from '../../../src/base/helper'; import * as editors from '../../../src/writer/editors'; +import { AdpPreview } from '../../../src'; import * as manifestService from '../../../src/base/abap/manifest-service'; +import type { AdpPreviewConfig, CommonChangeProperties } from '../../../src'; import { addXmlFragment, tryFixChange } from '../../../src/preview/change-handler'; -import * as systemAccess from '@sap-ux/system-access/dist/base/connect'; -import * as serviceWriter from '@sap-ux/odata-service-writer/dist/data/annotations'; interface GetFragmentsResponse { fragments: { fragmentName: string }[]; @@ -55,6 +57,13 @@ jest.mock('@sap-ux/store', () => { }; }); +jest.mock('ejs', () => ({ + ...jest.requireActual('ejs'), + renderFile: jest.fn() +})); + +const renderFileMock = renderFile as jest.Mock; + const tryFixChangeMock = tryFixChange as jest.Mock; const addXmlFragmentMock = addXmlFragment as jest.Mock; @@ -476,7 +485,7 @@ describe('AdaptationProject', () => { middlewareUtil, logger ); - jest.spyOn(helper, 'getVariant').mockReturnValue({ + jest.spyOn(helper, 'getVariant').mockResolvedValue({ content: [], id: 'adp/project', layer: 'VENDOR', @@ -490,6 +499,8 @@ describe('AdaptationProject', () => { }, ignoreCertErrors: false }); + jest.spyOn(helper, 'isTypescriptSupported').mockReturnValue(false); + jest.spyOn(systemAccess, 'createAbapServiceProvider').mockResolvedValue({} as any); jest.spyOn(manifestService.ManifestService, 'initMergedManifest').mockResolvedValue({ getDataSourceMetadata: jest.fn().mockResolvedValue(` @@ -539,6 +550,7 @@ describe('AdaptationProject', () => { afterEach(() => { mockExistsSync.mockRestore(); + mockWriteFileSync.mockRestore(); }); test('GET /adp/api/fragment', async () => { @@ -617,14 +629,53 @@ describe('AdaptationProject', () => { test('POST /adp/api/controller - creates controller', async () => { mockExistsSync.mockReturnValue(false); + renderFileMock.mockImplementation((templatePath, data, options, callback) => { + callback(undefined, 'test-js-controller'); + }); + const controllerName = 'Share'; + const controllerPath = join('/adp.project', 'webapp', 'changes', 'coding', 'Share.js'); + const response = await server.post('/adp/api/controller').send({ controllerName }).expect(201); + + const message = response.text; + expect(mockWriteFileSync).toHaveBeenNthCalledWith(1, controllerPath, 'test-js-controller', { + encoding: 'utf8' + }); + expect(message).toBe('Controller extension created!'); + }); + + test('POST /adp/api/controller - creates TypeScript controller', async () => { + mockExistsSync.mockReturnValue(false); + jest.spyOn(helper, 'isTypescriptSupported').mockReturnValue(true); + renderFileMock.mockImplementation((templatePath, data, options, callback) => { + callback(undefined, 'test-ts-controller'); + }); + const controllerName = 'Share'; + const controllerPath = join('/adp.project', 'webapp', 'changes', 'coding', 'Share.ts'); const response = await server.post('/adp/api/controller').send({ controllerName }).expect(201); const message = response.text; - expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + expect(mockWriteFileSync).toHaveBeenNthCalledWith(1, controllerPath, 'test-ts-controller', { + encoding: 'utf8' + }); expect(message).toBe('Controller extension created!'); }); + test('POST /adp/api/controller - throws error during rendering a ts template', async () => { + mockExistsSync.mockReturnValue(false); + jest.spyOn(helper, 'isTypescriptSupported').mockReturnValue(true); + renderFileMock.mockImplementation((templatePath, data, options, callback) => { + callback(new Error('Failed to render template'), ''); + }); + + const controllerName = 'Share'; + const response = await server.post('/adp/api/controller').send({ controllerName }).expect(500); + + const message = response.text; + expect(mockWriteFileSync).not.toHaveBeenCalled(); + expect(message).toBe('Error rendering TypeScript template Failed to render template'); + }); + test('POST /adp/api/controller - controller already exists', async () => { mockExistsSync.mockReturnValueOnce(false).mockResolvedValueOnce(true); diff --git a/packages/adp-tooling/test/unit/preview/change-handler.test.ts b/packages/adp-tooling/test/unit/preview/change-handler.test.ts index 2932ccb0cf..17fc56ec4f 100644 --- a/packages/adp-tooling/test/unit/preview/change-handler.test.ts +++ b/packages/adp-tooling/test/unit/preview/change-handler.test.ts @@ -561,7 +561,7 @@ id=\\"btn-30303030\\"" } }) } as any); - jest.spyOn(helper, 'getVariant').mockReturnValue({ + jest.spyOn(helper, 'getVariant').mockResolvedValue({ content: [], id: 'adp/project', layer: 'VENDOR', @@ -589,7 +589,6 @@ id=\\"btn-30303030\\"" error: jest.fn() }; - const fragmentName = 'Share'; const change = { changeType: 'appdescr_app_addAnnotationsToOData', content: { diff --git a/packages/adp-tooling/test/unit/prompts/add-annotations-to-odata/index.test.ts b/packages/adp-tooling/test/unit/prompts/add-annotations-to-odata/index.test.ts index 2e140845d8..94c9addf45 100644 --- a/packages/adp-tooling/test/unit/prompts/add-annotations-to-odata/index.test.ts +++ b/packages/adp-tooling/test/unit/prompts/add-annotations-to-odata/index.test.ts @@ -142,39 +142,39 @@ describe('getPrompts', () => { }); describe('file path validations', () => { - test('should fail with input cannot be empty message', () => { + test('should fail with input cannot be empty message', async () => { jest.spyOn(validators, 'validateEmptyString').mockReturnValueOnce('Input cannot be empty'); - const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate; + const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate as Function; - expect(filePathValidator('')).toBe('Input cannot be empty'); + expect(await filePathValidator('')).toBe('Input cannot be empty'); }); - test('should fail with file doesn not exist message', () => { + test('should fail with file doesn not exist message', async () => { jest.spyOn(validators, 'validateEmptyString').mockReturnValueOnce(true); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false); - const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate; + const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate as Function; - expect(filePathValidator('non-existing-file.xml')).toBe(i18n.t('validators.fileDoesNotExist')); + expect(await filePathValidator('non-existing-file.xml')).toBe(i18n.t('validators.fileDoesNotExist')); }); - test('should fail with file already exists in change directory message', () => { + test('should fail with file already exists in change directory message', async () => { jest.spyOn(validators, 'validateEmptyString').mockReturnValueOnce(true); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true).mockReturnValueOnce(true); - const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate; + const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate as Function; - expect(filePathValidator('existing-file.xml')).toBe(i18n.t('validators.annotationFileAlreadyExists')); + expect(await filePathValidator('existing-file.xml')).toBe(i18n.t('validators.annotationFileAlreadyExists')); }); - test('should pass with relative file path input', () => { + test('should pass with relative file path input', async () => { jest.spyOn(validators, 'validateEmptyString').mockReturnValueOnce(true); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true).mockReturnValueOnce(false); - const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate; + const filePathValidator = (getPrompts(mockBasePath, dataSources)[2] as any).validate as Function; - expect(filePathValidator('existing-file.xml')).toBeTruthy(); + expect(await filePathValidator('existing-file.xml')).toBeTruthy(); }); test('should pass with absolute file path input', () => { diff --git a/packages/adp-tooling/test/unit/writer/__snapshots__/index.test.ts.snap b/packages/adp-tooling/test/unit/writer/__snapshots__/index.test.ts.snap index 4f7e7583ee..3eabf7bf68 100644 --- a/packages/adp-tooling/test/unit/writer/__snapshots__/index.test.ts.snap +++ b/packages/adp-tooling/test/unit/writer/__snapshots__/index.test.ts.snap @@ -117,7 +117,7 @@ builder: } `; -exports[`ADP writer generate S/4HANA cloud config with target destination 1`] = ` +exports[`ADP writer generate S/4HANA cloud config with inboundId 1`] = ` Object { "package.json": Object { "contents": "{ @@ -165,7 +165,7 @@ builder: afterTask: generateCachebusterInfo configuration: target: - destination: UYTCLNT902 + url: http://sap.example app: package: $TMP ", @@ -200,15 +200,14 @@ server: configuration: adp: target: - destination: UYTCLNT902 + url: http://sap.example ignoreCertErrors: false - name: fiori-tools-proxy afterMiddleware: fiori-tools-preview configuration: ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted backend: - - destination: UYTCLNT902 - url: https://REQUIRED_FOR_VSCODE.example + - url: http://sap.example path: /sap ui5: path: @@ -227,15 +226,15 @@ builder: - sap: testId i18n: testKey target: - destination: UYTCLNT902 - url: https://REQUIRED_FOR_VSCODE.example + url: http://sap.example + ignoreCertErrors: false ", "state": "modified", }, } `; -exports[`ADP writer generate S/4HANA cloud config with inboundId 1`] = ` +exports[`ADP writer generate S/4HANA cloud config with target destination 1`] = ` Object { "package.json": Object { "contents": "{ @@ -283,7 +282,7 @@ builder: afterTask: generateCachebusterInfo configuration: target: - url: http://sap.example + destination: DUMMY_DESTINATION app: package: $TMP ", @@ -318,14 +317,15 @@ server: configuration: adp: target: - url: http://sap.example + destination: DUMMY_DESTINATION ignoreCertErrors: false - name: fiori-tools-proxy afterMiddleware: fiori-tools-preview configuration: ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted backend: - - url: http://sap.example + - destination: DUMMY_DESTINATION + url: https://REQUIRED_FOR_VSCODE.example path: /sap ui5: path: @@ -344,8 +344,8 @@ builder: - sap: testId i18n: testKey target: - url: http://sap.example - ignoreCertErrors: false + destination: DUMMY_DESTINATION + url: https://REQUIRED_FOR_VSCODE.example ", "state": "modified", }, @@ -624,6 +624,145 @@ server: } `; +exports[`ADP writer generate enable TypeScript support 1`] = ` +Object { + "package.json": Object { + "contents": "{ + \\"name\\": \\"my-test-app\\", + \\"version\\": \\"0.1.0\\", + \\"private\\": true, + \\"description\\": \\"Adaptation of the.original.app\\", + \\"keywords\\": [ + \\"ui5\\", + \\"sapui5\\", + \\"adaptation-project\\" + ], + \\"dependencies\\": {}, + \\"devDependencies\\": { + \\"@sap/ux-ui5-tooling\\": \\"1\\", + \\"@ui5/task-adaptation\\": \\"^1.3.0\\", + \\"@ui5/cli\\": \\"^3.9.2\\", + \\"@sapui5/types\\": \\"~1.133.0\\", + \\"typescript\\": \\"^5.7.3\\", + \\"ui5-tooling-transpile\\": \\"^3.6.1\\" + }, + \\"scripts\\": { + \\"build\\": \\"ui5 build --exclude-task generateFlexChangesBundle generateComponentPreload minify --clean-dest\\", + \\"start\\": \\"fiori run --open /test/flp.html#app-preview\\", + \\"start-editor\\": \\"fiori run --open /test/adaptation-editor.html\\", + \\"deploy\\": \\"npm run build && fiori deploy --config ui5-deploy.yaml\\", + \\"undeploy\\": \\"npm run build && fiori undeploy --config ui5-deploy.yaml\\", + \\"deploy-test\\": \\"npm run build && fiori deploy --config ui5-deploy.yaml --testMode true\\" + } +} +", + "state": "modified", + }, + "tsconfig.json": Object { + "contents": "{ + \\"compilerOptions\\": { + \\"target\\": \\"es2022\\", + \\"module\\": \\"es2022\\", + \\"moduleResolution\\": \\"node\\", + \\"skipLibCheck\\": true, + \\"allowJs\\": true, + \\"strict\\": true, + \\"strictPropertyInitialization\\": false, + \\"rootDir\\": \\"./\\", + \\"outDir\\": \\"./dist\\", + \\"baseUrl\\": \\"./\\", + \\"typeRoots\\": [\\"./node_modules/@types\\", \\"./node_modules/@sapui5/types\\"], + \\"paths\\": { + \\"my/test/app/*\\": [\\"./webapp/*\\"] + } + }, + \\"include\\": [\\"./webapp/**/*\\"] +}", + "state": "modified", + }, + "ui5-deploy.yaml": Object { + "contents": "# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: \\"3.0\\" +metadata: + name: my-test-app +type: application +builder: + resources: + excludes: + - /test/** + - /localService/** + customTasks: + - name: deploy-to-abap + afterTask: generateCachebusterInfo + configuration: + target: + url: http://sap.example + app: + package: $TMP +", + "state": "modified", + }, + "ui5.yaml": Object { + "contents": "# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: \\"3.0\\" +metadata: + name: my-test-app +type: application +resources: + configuration: + propertiesFileSourceEncoding: UTF-8 +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + configuration: + debug: true + transformModulesToUI5: + overridesToOverride: true + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + adp: + target: + url: http://sap.example + ignoreCertErrors: false + - name: fiori-tools-proxy + afterMiddleware: fiori-tools-preview + configuration: + ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + backend: + - url: http://sap.example + path: /sap + ui5: + path: + - /resources + - /test-resources + url: https://ui5.sap.com + version: '' +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion + configuration: + debug: true + omitSourceMaps: true + omitTSFromBuildResult: true + transformModulesToUI5: + overridesToOverride: true +", + "state": "modified", + }, +} +`; + exports[`ADP writer generate minimal config 1`] = ` Object { ".gitignore": Object { @@ -1446,7 +1585,7 @@ builder: afterTask: generateCachebusterInfo configuration: target: - destination: UYTCLNT902 + destination: DUMMY_DESTINATION app: package: $TMP ", @@ -1481,14 +1620,14 @@ server: configuration: adp: target: - destination: UYTCLNT902 + destination: DUMMY_DESTINATION ignoreCertErrors: false - name: fiori-tools-proxy afterMiddleware: fiori-tools-preview configuration: ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted backend: - - destination: UYTCLNT902 + - destination: DUMMY_DESTINATION url: https://REQUIRED_FOR_VSCODE.example path: /sap ui5: @@ -1508,7 +1647,7 @@ builder: - sap: testId i18n: testKey target: - destination: UYTCLNT902 + destination: DUMMY_DESTINATION url: https://REQUIRED_FOR_VSCODE.example ", "state": "modified", @@ -1704,6 +1843,180 @@ my.test.app_sap.app.crossNavigation.inbounds.sampleId.subTitle=sampleSubTitle } ] } +", + "state": "modified", + }, + "../ts-support/.gitignore": Object { + "contents": "node_modules/ +dist/ +.tmp +.env +*.zip", + "state": "modified", + }, + "../ts-support/package.json": Object { + "contents": "{ + \\"name\\": \\"my-test-app\\", + \\"version\\": \\"0.1.0\\", + \\"private\\": true, + \\"description\\": \\"Adaptation of the.original.app\\", + \\"keywords\\": [ + \\"ui5\\", + \\"sapui5\\", + \\"adaptation-project\\" + ], + \\"dependencies\\": {}, + \\"devDependencies\\": { + \\"@sap/ux-ui5-tooling\\": \\"1\\", + \\"@ui5/task-adaptation\\": \\"^1.3.0\\", + \\"@ui5/cli\\": \\"^3.9.2\\", + \\"@sapui5/types\\": \\"~1.133.0\\", + \\"typescript\\": \\"^5.7.3\\", + \\"ui5-tooling-transpile\\": \\"^3.6.1\\" + }, + \\"scripts\\": { + \\"build\\": \\"ui5 build --exclude-task generateFlexChangesBundle generateComponentPreload minify --clean-dest\\", + \\"start\\": \\"fiori run --open /test/flp.html#app-preview\\", + \\"start-editor\\": \\"fiori run --open /test/adaptation-editor.html\\", + \\"deploy\\": \\"npm run build && fiori deploy --config ui5-deploy.yaml\\", + \\"undeploy\\": \\"npm run build && fiori undeploy --config ui5-deploy.yaml\\", + \\"deploy-test\\": \\"npm run build && fiori deploy --config ui5-deploy.yaml --testMode true\\" + } +} +", + "state": "modified", + }, + "../ts-support/tsconfig.json": Object { + "contents": "{ + \\"compilerOptions\\": { + \\"target\\": \\"es2022\\", + \\"module\\": \\"es2022\\", + \\"moduleResolution\\": \\"node\\", + \\"skipLibCheck\\": true, + \\"allowJs\\": true, + \\"strict\\": true, + \\"strictPropertyInitialization\\": false, + \\"rootDir\\": \\"./\\", + \\"outDir\\": \\"./dist\\", + \\"baseUrl\\": \\"./\\", + \\"typeRoots\\": [\\"./node_modules/@types\\", \\"./node_modules/@sapui5/types\\"], + \\"paths\\": { + \\"my/test/app/*\\": [\\"./webapp/*\\"] + } + }, + \\"include\\": [\\"./webapp/**/*\\"] +}", + "state": "modified", + }, + "../ts-support/ui5-deploy.yaml": Object { + "contents": "# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: \\"3.0\\" +metadata: + name: my-test-app +type: application +builder: + resources: + excludes: + - /test/** + - /localService/** + customTasks: + - name: deploy-to-abap + afterTask: generateCachebusterInfo + configuration: + target: + url: http://sap.example + app: + package: $TMP +", + "state": "modified", + }, + "../ts-support/ui5.yaml": Object { + "contents": "# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: \\"3.0\\" +metadata: + name: my-test-app +type: application +resources: + configuration: + propertiesFileSourceEncoding: UTF-8 +server: + customMiddleware: + - name: ui5-tooling-transpile-middleware + afterMiddleware: compression + configuration: + debug: true + transformModulesToUI5: + overridesToOverride: true + - name: fiori-tools-appreload + afterMiddleware: compression + configuration: + port: 35729 + path: webapp + delay: 300 + - name: fiori-tools-preview + afterMiddleware: fiori-tools-appreload + configuration: + adp: + target: + url: http://sap.example + ignoreCertErrors: false + - name: fiori-tools-proxy + afterMiddleware: fiori-tools-preview + configuration: + ignoreCertError: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted + backend: + - url: http://sap.example + path: /sap + ui5: + path: + - /resources + - /test-resources + url: https://ui5.sap.com + version: '' +builder: + customTasks: + - name: ui5-tooling-transpile-task + afterTask: replaceVersion + configuration: + debug: true + omitSourceMaps: true + omitTSFromBuildResult: true + transformModulesToUI5: + overridesToOverride: true +", + "state": "modified", + }, + "../ts-support/webapp/i18n/i18n.properties": Object { + "contents": "# This is the resource bundle for my.test.app + +#Texts for manifest.json + +#XTIT: Application name +my.test.app_sap.app.title=Adaptation of the.original.app +", + "state": "modified", + }, + "../ts-support/webapp/manifest.appdescr_variant": Object { + "contents": "{ + \\"fileName\\": \\"manifest\\", + \\"layer\\": \\"CUSTOMER_BASE\\", + \\"fileType\\": \\"appdescr_variant\\", + \\"reference\\": \\"the.original.app\\", + \\"id\\": \\"my.test.app\\", + \\"namespace\\": \\"apps/the.original.app/appVariants/my.test.app/\\", + \\"version\\": \\"0.1.0\\", + \\"content\\": [ + { + \\"changeType\\": \\"appdescr_app_setTitle\\", + \\"content\\": {}, + \\"texts\\": { + \\"i18n\\": \\"i18n/i18n.properties\\" + } + } + ] +} ", "state": "modified", }, diff --git a/packages/adp-tooling/test/unit/writer/index.test.ts b/packages/adp-tooling/test/unit/writer/index.test.ts index ddc2e3b556..aee84ee712 100644 --- a/packages/adp-tooling/test/unit/writer/index.test.ts +++ b/packages/adp-tooling/test/unit/writer/index.test.ts @@ -111,6 +111,35 @@ describe('ADP writer', () => { ).toMatchSnapshot(); }); + test('enable TypeScript support', async () => { + const projectDir = join(outputDir, 'ts-support'); + await generate( + projectDir, + { + ...config, + deploy: { + package: '$TMP' + }, + options: { + fioriTools: true, + enableTypeScript: true + }, + ui5: { + version: '1.133.0' + } + }, + fs + ); + expect( + fs.dump( + projectDir, + (file) => + file.dirname === projectDir && + ['package.json', 'ui5.yaml', 'ui5-deploy.yaml', 'tsconfig.json'].includes(file.basename) + ) + ).toMatchSnapshot(); + }); + test('S/4HANA cloud config', async () => { const projectDir = join(outputDir, 's4hana'); Object.assign(config.app, { @@ -130,7 +159,8 @@ describe('ADP writer', () => { package: '$TMP' }, options: { - fioriTools: true + fioriTools: true, + enableTypeScript: false }, ui5: { version: '1.122.1' @@ -171,7 +201,7 @@ describe('ADP writer', () => { reference: 'the.original.app' }, target: { - destination: 'UYTCLNT902' + destination: 'DUMMY_DESTINATION' } }; @@ -193,7 +223,8 @@ describe('ADP writer', () => { package: '$TMP' }, options: { - fioriTools: true + fioriTools: true, + enableTypeScript: false }, ui5: { version: '1.122.1' @@ -237,7 +268,8 @@ describe('ADP writer', () => { package: '$TMP' }, options: { - fioriTools: true + fioriTools: true, + enableTypeScript: false }, ui5: { version: '1.122.1' @@ -292,7 +324,11 @@ describe('ADP writer', () => { } }, options: { - fioriTools: true + fioriTools: true, + enableTypeScript: false + }, + ui5: { + version: '1.133.0' } }; const migrateInputDir = join(__dirname, '../../fixtures/webide-adaptation-project'); diff --git a/packages/adp-tooling/test/unit/writer/project-utils.test.ts b/packages/adp-tooling/test/unit/writer/project-utils.test.ts index b4cc322a11..23972cd5f2 100644 --- a/packages/adp-tooling/test/unit/writer/project-utils.test.ts +++ b/packages/adp-tooling/test/unit/writer/project-utils.test.ts @@ -1,4 +1,4 @@ -import path from 'path'; +import path, { join } from 'path'; import { readFileSync } from 'fs'; import type { Editor } from 'mem-fs-editor'; @@ -28,6 +28,12 @@ describe('Project Utils', () => { }, target: { url: 'http://sap.example' + }, + options: { + enableTypeScript: false + }, + ui5: { + version: '1.133.1' } }; @@ -67,7 +73,7 @@ describe('Project Utils', () => { jest.clearAllMocks(); }); - const templatePath = '../../../templates/project'; + const templatePath = '../../../templates'; const projectPath = 'project'; const writeFilesSpy = jest.fn(); @@ -76,9 +82,26 @@ describe('Project Utils', () => { it('should write template to the specified folder', () => { writeTemplateToFolder(templatePath, projectPath, data, mockFs as unknown as Editor); - expect(writeFilesSpy.mock.calls[0][0]).toEqual(templatePath); + expect(writeFilesSpy.mock.calls[0][0]).toEqual(join(templatePath, 'project', '**', '*.*')); + expect(writeFilesSpy.mock.calls[0][1]).toEqual(projectPath); + expect(writeFilesSpy.mock.calls[0][2]).toEqual({ + ...data, + typesPackage: '@sapui5/types', + typesVersion: '~1.133.0' + }); + }); + + it('should write TS template to the specified folder when project supports typescript', () => { + const newData = { ...data, options: { enableTypeScript: true } }; + writeTemplateToFolder(templatePath, projectPath, newData, mockFs as unknown as Editor); + + expect(writeFilesSpy.mock.calls[0][0]).toEqual(join(templatePath, 'project', '**', '*.*')); expect(writeFilesSpy.mock.calls[0][1]).toEqual(projectPath); - expect(writeFilesSpy.mock.calls[0][2]).toEqual(data); + expect(writeFilesSpy.mock.calls[0][2]).toEqual({ + ...newData, + typesPackage: '@sapui5/types', + typesVersion: '~1.133.0' + }); }); it('should throw error when writing file fails', () => { diff --git a/packages/adp-tooling/tsconfig.json b/packages/adp-tooling/tsconfig.json index a3cd92f5ba..8daf46522e 100644 --- a/packages/adp-tooling/tsconfig.json +++ b/packages/adp-tooling/tsconfig.json @@ -26,6 +26,9 @@ { "path": "../logger" }, + { + "path": "../nodejs-utils" + }, { "path": "../odata-service-writer" }, diff --git a/packages/annotation-generator/CHANGELOG.md b/packages/annotation-generator/CHANGELOG.md index 5e22886ed2..7fd5660a96 100644 --- a/packages/annotation-generator/CHANGELOG.md +++ b/packages/annotation-generator/CHANGELOG.md @@ -1,5 +1,20 @@ # @sap-ux/annotation-generator +## 0.3.11 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/fiori-annotation-api@0.4.11 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/fiori-annotation-api@0.4.10 + ## 0.3.9 ### Patch Changes diff --git a/packages/annotation-generator/package.json b/packages/annotation-generator/package.json index 005f9c8968..182298c4f8 100644 --- a/packages/annotation-generator/package.json +++ b/packages/annotation-generator/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/annotation-generator", - "version": "0.3.9", + "version": "0.3.11", "description": "Library that provides API for generation of annotations by SAP Fiori App Generator", "publisher": "SAPSE", "author": "SAP SE", diff --git a/packages/app-config-writer/CHANGELOG.md b/packages/app-config-writer/CHANGELOG.md index 0f357f6b71..5e8995c265 100644 --- a/packages/app-config-writer/CHANGELOG.md +++ b/packages/app-config-writer/CHANGELOG.md @@ -1,5 +1,20 @@ # @sap-ux/app-config-writer +## 0.5.34 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/axios-extension@1.18.6 + +## 0.5.33 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/axios-extension@1.18.6 + ## 0.5.32 ### Patch Changes diff --git a/packages/app-config-writer/package.json b/packages/app-config-writer/package.json index b8e9bc9ec4..37d13187e3 100644 --- a/packages/app-config-writer/package.json +++ b/packages/app-config-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/app-config-writer", "description": "Add or update configuration for SAP Fiori tools application", - "version": "0.5.32", + "version": "0.5.34", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/cap-config-writer/CHANGELOG.md b/packages/cap-config-writer/CHANGELOG.md index e54cabc279..cb8c27e4a9 100644 --- a/packages/cap-config-writer/CHANGELOG.md +++ b/packages/cap-config-writer/CHANGELOG.md @@ -1,5 +1,27 @@ # @sap-ux/cap-config-writer +## 0.9.13 + +### Patch Changes + +- @sap-ux/fiori-generator-shared@0.9.2 +- @sap-ux/project-access@1.29.10 + +## 0.9.12 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/fiori-generator-shared@0.9.1 + +## 0.9.11 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + ## 0.9.10 ### Patch Changes diff --git a/packages/cap-config-writer/package.json b/packages/cap-config-writer/package.json index e998183756..655dd4f38b 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.9.10", + "version": "0.9.13", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/cards-editor-middleware/CHANGELOG.md b/packages/cards-editor-middleware/CHANGELOG.md index 11cf69fdba..cba6705798 100644 --- a/packages/cards-editor-middleware/CHANGELOG.md +++ b/packages/cards-editor-middleware/CHANGELOG.md @@ -1,5 +1,24 @@ # @sap-ux/cards-editor-middleware +## 0.4.55 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 + +## 0.4.54 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + +## 0.4.53 + +### Patch Changes + +- afbb147: The adaptive card manifest should not be saved + ## 0.4.52 ### Patch Changes diff --git a/packages/cards-editor-middleware/package.json b/packages/cards-editor-middleware/package.json index 3f3c00240a..8ebd000b2c 100644 --- a/packages/cards-editor-middleware/package.json +++ b/packages/cards-editor-middleware/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/cards-editor-middleware", - "version": "0.4.52", + "version": "0.4.55", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/cards-editor-middleware/src/index.ts b/packages/cards-editor-middleware/src/index.ts index 66ad521bf3..c7a459e0af 100644 --- a/packages/cards-editor-middleware/src/index.ts +++ b/packages/cards-editor-middleware/src/index.ts @@ -140,7 +140,6 @@ module.exports = async ({ resources }: MiddlewareParameters): Promise { const server = await getTestServer('lrop-v4'); const response = await server.post(sapCardsGenerator.ApiRoutes.cardsStore).send(payload); expect(response.status).toBe(201); - expect(mockFsPromisesWriteFile).toHaveBeenCalledTimes(3); + expect(mockFsPromisesWriteFile).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/cds-odata-annotation-converter/CHANGELOG.md b/packages/cds-odata-annotation-converter/CHANGELOG.md index 238a498df0..6fd7c9a3fe 100644 --- a/packages/cds-odata-annotation-converter/CHANGELOG.md +++ b/packages/cds-odata-annotation-converter/CHANGELOG.md @@ -1,5 +1,11 @@ # @sap-ux/cds-odata-annotation-converter +## 0.5.0 + +### Minor Changes + +- 4373718: Updated UI texts. + ## 0.4.1 ### Patch Changes diff --git a/packages/cds-odata-annotation-converter/package.json b/packages/cds-odata-annotation-converter/package.json index 80651cfb15..88d63aee41 100644 --- a/packages/cds-odata-annotation-converter/package.json +++ b/packages/cds-odata-annotation-converter/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/cds-odata-annotation-converter", "description": "Converter for OData annotations in CDS format.", - "version": "0.4.1", + "version": "0.5.0", "publisher": "SAPSE", "author": "SAP SE", "license": "Apache-2.0", diff --git a/packages/cds-odata-annotation-converter/src/i18n/i18n.json b/packages/cds-odata-annotation-converter/src/i18n/i18n.json index 154f55ce7f..1871695a4b 100644 --- a/packages/cds-odata-annotation-converter/src/i18n/i18n.json +++ b/packages/cds-odata-annotation-converter/src/i18n/i18n.json @@ -1,8 +1,8 @@ { - "Property_is_not_allowed_here": "Property is not allowed here '{{name}}'", - "The_attribute_value_must_be_a_string_literal": "The attribute value must be a string literal", - "Wrong_element_0_Did_you_mean_1": "Wrong element '{{currentValue}}'. Did you mean '{{proposedValue}}'?", - "Mandatory_property_not_provided_0": "Mandatory property not provided: {{property}}", - "Path_escaping_brackets_should_be_used": "Path escaping brackets ![] should be used", - "Wrong_path_separator_0_Did_you_mean_1": "Wrong path separator '{{currentValue}}'. Did you mean '{{proposedValue}}'?" + "Property_is_not_allowed_here": "The `{{name}}` property is not allowed here.", + "The_attribute_value_must_be_a_string_literal": "The attribute value must be a string literal.", + "Wrong_element_0_Did_you_mean_1": "`{{currentValue}}` is incorrect. Did you mean `{{proposedValue}}`?", + "Mandatory_property_not_provided_0": "The mandatory `{{property}}` property was not provided.", + "Path_escaping_brackets_should_be_used": "Path escaping brackets `![]` must be used.", + "Wrong_path_separator_0_Did_you_mean_1": "`{{currentValue}}` is the wrong path separator. Did you mean `{{proposedValue}}`?" } diff --git a/packages/cds-odata-annotation-converter/test/data/parser/apply/diagnostics.json b/packages/cds-odata-annotation-converter/test/data/parser/apply/diagnostics.json index 44bf0d1d96..3d1ec19e7b 100644 --- a/packages/cds-odata-annotation-converter/test/data/parser/apply/diagnostics.json +++ b/packages/cds-odata-annotation-converter/test/data/parser/apply/diagnostics.json @@ -2,6 +2,6 @@ { "range": "[(37,24)..(37,28)]", "severity": 1, - "message": "The attribute value must be a string literal" + "message": "The attribute value must be a string literal." } ] diff --git a/packages/cds-odata-annotation-converter/test/data/parser/json/diagnostics.json b/packages/cds-odata-annotation-converter/test/data/parser/json/diagnostics.json index 07ab3a2575..e993ecd032 100644 --- a/packages/cds-odata-annotation-converter/test/data/parser/json/diagnostics.json +++ b/packages/cds-odata-annotation-converter/test/data/parser/json/diagnostics.json @@ -1,6 +1,6 @@ [ { - "message": "Wrong element '$edmJS'. Did you mean '$edmJson'?", + "message": "`$edmJS` is incorrect. Did you mean `$edmJson`?", "range": "[(14,8)..(14,16)]", "severity": 1, "data": { diff --git a/packages/cds-odata-annotation-converter/test/data/parser/paths/diagnostics.json b/packages/cds-odata-annotation-converter/test/data/parser/paths/diagnostics.json index b88367fa40..af84ee7b43 100644 --- a/packages/cds-odata-annotation-converter/test/data/parser/paths/diagnostics.json +++ b/packages/cds-odata-annotation-converter/test/data/parser/paths/diagnostics.json @@ -1,7 +1,7 @@ [ { "range": "[(52,39)..(52,40)]", - "message": "Wrong path separator '/'. Did you mean '.'?", + "message": "`/` is the wrong path separator. Did you mean `.`?", "severity": 2, "data": { "caseCheck": { @@ -14,7 +14,7 @@ }, { "rule": "no-odata-path-separator", - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets `![]` must be used.", "range": "[(61,32)..(61,61)]", "severity": 2, "data": { @@ -24,7 +24,7 @@ }, { "rule": "no-odata-path-separator", - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets `![]` must be used.", "range": "[(65,32)..(65,86)]", "severity": 2, "data": { @@ -34,7 +34,7 @@ }, { "rule": "no-odata-path-separator", - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets `![]` must be used.", "range": "[(69,34)..(69,89)]", "severity": 2, "data": { @@ -44,7 +44,7 @@ }, { "rule": "no-odata-path-separator", - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets `![]` must be used.", "range": "[(73,32)..(73,92)]", "severity": 2, "data": { @@ -54,7 +54,7 @@ }, { "rule": "no-odata-path-separator", - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets `![]` must be used.", "range": "[(81,16)..(81,48)]", "severity": 2, "data": { diff --git a/packages/cds-odata-annotation-converter/test/data/parser/property-annotation-value-case-issue/diagnostics.json b/packages/cds-odata-annotation-converter/test/data/parser/property-annotation-value-case-issue/diagnostics.json index f0a34d826b..c8704e0d0e 100644 --- a/packages/cds-odata-annotation-converter/test/data/parser/property-annotation-value-case-issue/diagnostics.json +++ b/packages/cds-odata-annotation-converter/test/data/parser/property-annotation-value-case-issue/diagnostics.json @@ -1,6 +1,6 @@ [ { - "message": "Wrong element '$Value'. Did you mean '$value'?", + "message": "`$Value` is incorrect. Did you mean `$value`?", "range": "[(4,12)..(4,18)]", "severity": 1, "data": { @@ -14,21 +14,21 @@ }, { "range": "[(10,19)..(13,9)]", - "message": "Mandatory property not provided: $value", + "message": "The mandatory `$value` property was not provided.", "severity": 1 }, { - "message": "Property is not allowed here '$alue'", + "message": "The `$alue` property is not allowed here.", "range": "[(12,12)..(12,17)]", "severity": 2 }, { "range": "[(15,15)..(15,25)]", - "message": "Mandatory property not provided: $value", + "message": "The mandatory `$value` property was not provided.", "severity": 1 }, { - "message": "Property is not allowed here 'val'", + "message": "The `val` property is not allowed here.", "range": "[(15,17)..(15,20)]", "severity": 2 } diff --git a/packages/cds-odata-annotation-converter/test/transform/annotation/handler/__snapshots__/path.test.ts.snap b/packages/cds-odata-annotation-converter/test/transform/annotation/handler/__snapshots__/path.test.ts.snap index fadde80d1d..ce78c1c31a 100644 --- a/packages/cds-odata-annotation-converter/test/transform/annotation/handler/__snapshots__/path.test.ts.snap +++ b/packages/cds-odata-annotation-converter/test/transform/annotation/handler/__snapshots__/path.test.ts.snap @@ -16,7 +16,7 @@ Array [ "proposedValue": "@UI.DataPoint#E/Value]", "value": "@UI.DataPoint#E/Value", }, - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets \`![]\` must be used.", "range": Object { "end": Object { "character": 45, @@ -59,7 +59,7 @@ Array [ "proposedValue": "@UI.DataPoint#B/Value]", "value": "@UI.DataPoint#B/Value", }, - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets \`![]\` must be used.", "range": Object { "end": Object { "character": 45, @@ -92,7 +92,7 @@ Array [ "proposedValue": "![@UI.DataPoint#C/Value]", "value": "@UI.DataPoint#C/Value", }, - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets \`![]\` must be used.", "range": Object { "end": Object { "character": 45, @@ -125,7 +125,7 @@ Array [ "proposedValue": "![@UI.DataPoint#D/Value/]", "value": "@UI.DataPoint#D/Value/", }, - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets \`![]\` must be used.", "range": Object { "end": Object { "character": 43, @@ -158,7 +158,7 @@ Array [ "proposedValue": "![@UI.DataPoint/Value]", "value": "@UI.DataPoint/Value", }, - "message": "Path escaping brackets ![] should be used", + "message": "Path escaping brackets \`![]\` must be used.", "range": Object { "end": Object { "character": 40, @@ -204,7 +204,7 @@ Array [ "value": "/", }, }, - "message": "Wrong path separator '/'. Did you mean '.'?", + "message": "\`/\` is the wrong path separator. Did you mean \`.\`?", "range": Object { "end": Object { "character": 21, @@ -238,7 +238,7 @@ Array [ "value": "/", }, }, - "message": "Wrong path separator '/'. Did you mean '.'?", + "message": "\`/\` is the wrong path separator. Did you mean \`.\`?", "range": Object { "end": Object { "character": 13, @@ -273,7 +273,7 @@ Array [ "value": "/", }, }, - "message": "Wrong path separator '/'. Did you mean '.'?", + "message": "\`/\` is the wrong path separator. Did you mean \`.\`?", "range": Object { "end": Object { "character": 13, @@ -295,7 +295,7 @@ Array [ "value": "/", }, }, - "message": "Wrong path separator '/'. Did you mean '.'?", + "message": "\`/\` is the wrong path separator. Did you mean \`.\`?", "range": Object { "end": Object { "character": 21, diff --git a/packages/cf-deploy-config-inquirer/CHANGELOG.md b/packages/cf-deploy-config-inquirer/CHANGELOG.md index fdaa9dbab4..03e2ddfb53 100644 --- a/packages/cf-deploy-config-inquirer/CHANGELOG.md +++ b/packages/cf-deploy-config-inquirer/CHANGELOG.md @@ -1,5 +1,29 @@ # @sap-ux/cf-deploy-config-inquirer +## 0.2.10 + +### Patch Changes + +- @sap-ux/inquirer-common@0.6.21 + +## 0.2.9 + +### Patch Changes + +- @sap-ux/inquirer-common@0.6.20 + +## 0.2.8 + +### Patch Changes + +- d6118c9: Changes to support adding CAP MTA prompt to allow user generate MTA + +## 0.2.7 + +### Patch Changes + +- @sap-ux/inquirer-common@0.6.19 + ## 0.2.6 ### Patch Changes diff --git a/packages/cf-deploy-config-inquirer/package.json b/packages/cf-deploy-config-inquirer/package.json index caa6fa977e..c3f34c1a86 100644 --- a/packages/cf-deploy-config-inquirer/package.json +++ b/packages/cf-deploy-config-inquirer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/cf-deploy-config-inquirer", "description": "Prompts module that can provide prompts for cf deployment config writer", - "version": "0.2.6", + "version": "0.2.10", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", 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/CHANGELOG.md b/packages/cf-deploy-config-sub-generator/CHANGELOG.md index 0975944723..d142190401 100644 --- a/packages/cf-deploy-config-sub-generator/CHANGELOG.md +++ b/packages/cf-deploy-config-sub-generator/CHANGELOG.md @@ -1,5 +1,64 @@ # @sap-ux/cf-deploy-config-sub-generator +## 0.1.15 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/i18n@0.2.2 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + - @sap-ux/deploy-config-generator-shared@0.0.29 + - @sap-ux/cf-deploy-config-inquirer@0.2.10 + - @sap-ux/cf-deploy-config-writer@0.1.15 + +## 0.1.14 + +### Patch Changes + +- d0b656b: change where inti and writing are executing when cf-sub-gen is loaded as a sub-gen +- Updated dependencies [d0b656b] + - @sap-ux/cf-deploy-config-writer@0.1.14 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/cf-deploy-config-writer@0.1.13 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/deploy-config-generator-shared@0.0.28 + - @sap-ux/inquirer-common@0.6.20 + - @sap-ux/cf-deploy-config-inquirer@0.2.9 + +## 0.1.12 + +### Patch Changes + +- a92b715: Init should only be run when standalone + +## 0.1.11 + +### Patch Changes + +- d6118c9: Changes to support adding CAP MTA prompt to allow user generate MTA +- Updated dependencies [d6118c9] + - @sap-ux/deploy-config-generator-shared@0.0.27 + - @sap-ux/cf-deploy-config-inquirer@0.2.8 + - @sap-ux/cf-deploy-config-writer@0.1.12 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/deploy-config-generator-shared@0.0.26 + - @sap-ux/inquirer-common@0.6.19 + - @sap-ux/cf-deploy-config-inquirer@0.2.7 + ## 0.1.9 ### Patch Changes diff --git a/packages/cf-deploy-config-sub-generator/package.json b/packages/cf-deploy-config-sub-generator/package.json index 59c459f911..19caee08cd 100644 --- a/packages/cf-deploy-config-sub-generator/package.json +++ b/packages/cf-deploy-config-sub-generator/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/cf-deploy-config-sub-generator", "description": "Generators for configuring Cloud Foundry deployment configuration", - "version": "0.1.9", + "version": "0.1.15", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", 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..4fd30db0f2 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, @@ -28,12 +34,17 @@ import { t, initI18n, DESTINATION_AUTHTYPE_NOTFOUND, API_BUSINESS_HUB_ENTERPRISE import { loadManifest } from './utils'; import { getMtaPath, findCapProjectRoot, FileName } 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 { getCFQuestions, getCAPMTAQuestions } 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, + CfDeployConfigAnswers +} from '@sap-ux/cf-deploy-config-inquirer'; import type { YeomanEnvironment } from '@sap-ux/fiori-generator-shared'; +import type { Answers } from 'inquirer'; /** * Cloud Foundry deployment configuration generator. @@ -49,6 +60,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 +87,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; @@ -104,6 +116,7 @@ export default class extends DeploymentGenerator { watchTelemetrySettingStore: false }); + // Note: the init phase has to be delayed when loaded as a sub-gen as the yaml configurations are not available. if (!this.launchDeployConfigAsSubGenerator) { await this._init(); } @@ -115,12 +128,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; @@ -134,7 +145,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)); @@ -156,7 +167,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)); } @@ -164,21 +174,34 @@ export default class extends DeploymentGenerator { if (this.abort) { 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) { + DeploymentGenerator.logger?.debug(t('cfGen.debug.capMissingMTA')); + this.appRouterAnswers = (await this.prompt( + await getCAPMTAQuestions({ projectRoot: this.projectRoot ?? process.cwd() }) + )) 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 { await this._handleApiHubConfig(); const questions = await this._getCFQuestions(); this.answers = await this.prompt(questions); } - - await this._reconcileAnswersWithOptions(); } /** @@ -246,13 +269,25 @@ export default class extends DeploymentGenerator { if (!this.launchDeployConfigAsSubGenerator) { await this._writing(); + } else { + // Need to delay `init` as the yaml configurations wont be ready! + await this._init(); + await this._writing(); } } 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 }) }); @@ -280,7 +315,7 @@ export default class extends DeploymentGenerator { } public async install(): Promise { - if (!this.launchDeployConfigAsSubGenerator && this.options.overwrite !== false && !this.abort) { + if (this.options.overwrite !== false && !this.abort) { await this._install(); } } @@ -322,11 +357,6 @@ export default class extends DeploymentGenerator { public async end(): Promise { try { - if ((this.launchDeployConfigAsSubGenerator && !this.abort) || this.options.overwrite === true) { - await this._init(); - await this._writing(); - await this._install(); - } if ( this.options.launchStandaloneFromYui && isExtensionInstalled(this.vscode, YUI_EXTENSION_ID, YUI_MIN_VER_FILES_GENERATED_MSG) 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..47e5c36e03 100644 --- a/packages/cf-deploy-config-sub-generator/src/app/questions.ts +++ b/packages/cf-deploy-config-sub-generator/src/app/questions.ts @@ -1,12 +1,22 @@ import { isAppStudio } from '@sap-ux/btp-utils'; -import { DeploymentGenerator } from '@sap-ux/deploy-config-generator-shared'; +import { DeploymentGenerator, getConfirmMtaContinuePrompt } 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, + type CfDeployConfigPromptOptions, + type CfDeployConfigQuestions, + 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'; import type { ApiHubConfig } from '@sap-ux/cf-deploy-config-writer'; -import type { CfDeployConfigPromptOptions, CfDeployConfigQuestions } from '@sap-ux/cf-deploy-config-inquirer'; +import type { Answers, Question } from 'inquirer'; +import { withCondition } from '@sap-ux/inquirer-common'; /** * Fetches the Cloud Foundry deployment configuration questions. @@ -60,3 +70,46 @@ 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. + */ +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); +} + +/** + * Generate CF Approuter questions for CAP project with an existing HTML5 app and missing MTA configuration. + * + * @param options - the options required for retrieving the prompts. + * @param options.projectRoot - the root path of the project. + * @returns the cf approuter config questions, restricting prompts being shown to the user + */ +export async function getCAPMTAQuestions({ projectRoot }: { projectRoot: string }): Promise { + // 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 + })) as Question[]; + questions = withCondition(questions, (answers: Answers) => answers.addCapMtaContinue === true); + questions.unshift(...getConfirmMtaContinuePrompt()); + return questions; +} 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..71cefea601 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'; @@ -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/translations/cf-deploy-config-sub-generator.i18n.json b/packages/cf-deploy-config-sub-generator/src/translations/cf-deploy-config-sub-generator.i18n.json index 77eb9bdabf..26b51f4b6d 100644 --- a/packages/cf-deploy-config-sub-generator/src/translations/cf-deploy-config-sub-generator.i18n.json +++ b/packages/cf-deploy-config-sub-generator/src/translations/cf-deploy-config-sub-generator.i18n.json @@ -26,7 +26,8 @@ }, "debug": { "promptOptions": "Retrieving CF prompts using: \n {{- options}}", - "initTelemetry": "Initializing telemetry in CF deployment configuration generator" + "initTelemetry": "Initializing telemetry in CF deployment configuration generator", + "capMissingMTA": "CAP project detected with no MTA configuration" } }, "appRouterGen": { diff --git a/packages/cf-deploy-config-sub-generator/test/app.test.ts b/packages/cf-deploy-config-sub-generator/test/app.test.ts index 2c767c8ea2..63b6ebc1f4 100644 --- a/packages/cf-deploy-config-sub-generator/test/app.test.ts +++ b/packages/cf-deploy-config-sub-generator/test/app.test.ts @@ -15,6 +15,7 @@ import * as fioriGenShared from '@sap-ux/fiori-generator-shared'; import * as memfs from 'memfs'; import * as questions from '../src/app/questions'; import * as cfConfigWriter from '@sap-ux/cf-deploy-config-writer'; +import type { Editor } from 'mem-fs-editor'; const mockIsAppStudio = jest.fn(); @@ -86,7 +87,7 @@ describe('Cloud foundry generator tests', () => { const cfGenPath = join(__dirname, '../src/app'); const OUTPUT_DIR_PREFIX = join('/output'); const testFixture = new TestFixture(); - + let fsMock: Editor; beforeEach(() => { jest.clearAllMocks(); memfs.vol.reset(); @@ -98,6 +99,10 @@ describe('Cloud foundry generator tests', () => { beforeAll(async () => { await initI18n(); + fsMock = { + dump: jest.fn(), + commit: jest.fn().mockImplementation((callback) => callback()) + } as Partial as Editor; }); afterAll(() => { @@ -960,6 +965,42 @@ describe('Cloud foundry generator tests', () => { ); }); + it('Ensure init is loaded when loaded as a subgenerator', async () => { + hasbinSyncMock.mockReturnValue(true); + mockGetHostEnvironment.mockReturnValue(hostEnvironment.cli); + memfs.vol.fromNestedJSON({}, '/'); + const appDir = join(OUTPUT_DIR_PREFIX, 'app1'); + + memfs.vol.fromNestedJSON( + { + [`.${OUTPUT_DIR_PREFIX}/app1/webapp/manifest.json`]: + testFixture.getContents('app1/webapp/manifest.json'), + [`.${OUTPUT_DIR_PREFIX}/app1/package.json`]: JSON.stringify({ scripts: {} }), + [`.${OUTPUT_DIR_PREFIX}/app1/ui5.yaml`]: testFixture.getContents('app1/ui5.yaml') + }, + '/' + ); + + await expect( + yeomanTest + .create( + CFGenerator, + { + resolved: cfGenPath + }, + { cwd: appDir } + ) + .withOptions({ + skipInstall: true, + launchDeployConfigAsSubGenerator: true + }) + .withPrompts({}) + .run() + ).resolves.not.toThrow(); + expect(hasbinSyncMock).toHaveBeenCalledWith('mta'); + expect(mockFindCapProjectRoot).toHaveBeenCalled(); + }); + it('Should throw error when base config is not found', async () => { hasbinSyncMock.mockReturnValue(true); mockGetHostEnvironment.mockReturnValue(hostEnvironment.cli); @@ -1113,20 +1154,21 @@ describe('Cloud foundry generator tests', () => { mockSendTelemetry.mockImplementation(() => { throw new Error('Telemetry Error'); }); - + jest.spyOn(cfConfigWriter, 'generateAppConfig').mockResolvedValue(fsMock); + const cfGenSpawnSpy = jest.spyOn(CFGenerator.prototype as any, 'spawnCommand').mockResolvedValue({}); const managedRouterConfig = load(testFixture.getContents('mta-types/managed/mta.yaml')); memfs.vol.fromNestedJSON( { - [`.${OUTPUT_DIR_PREFIX}/app1/webapp/manifest.json`]: + [`.${OUTPUT_DIR_PREFIX}/telemetery/webapp/manifest.json`]: testFixture.getContents('app1/webapp/manifest.json'), - [`.${OUTPUT_DIR_PREFIX}/app1/package.json`]: JSON.stringify({ scripts: {} }), - [`.${OUTPUT_DIR_PREFIX}/app1/mta.yaml`]: dump(managedRouterConfig), - [`.${OUTPUT_DIR_PREFIX}/app1/ui5.yaml`]: testFixture.getContents('app1/ui5.yaml') + [`.${OUTPUT_DIR_PREFIX}/telemetery/package.json`]: JSON.stringify({ scripts: {} }), + [`.${OUTPUT_DIR_PREFIX}/telemetery/mta.yaml`]: dump(managedRouterConfig), + [`.${OUTPUT_DIR_PREFIX}/telemetery/ui5.yaml`]: testFixture.getContents('app1/ui5.yaml') }, '/' ); - const appDir = join(OUTPUT_DIR_PREFIX, 'app1'); + const appDir = join(OUTPUT_DIR_PREFIX, 'telemetery'); await expect( yeomanTest @@ -1138,13 +1180,14 @@ describe('Cloud foundry generator tests', () => { { cwd: appDir } ) .withOptions({ - skipInstall: true, + skipInstall: false, appWizard: mockAppWizard, launchStandaloneFromYui: true, - launchDeployConfigAsSubGenerator: true + launchDeployConfigAsSubGenerator: false }) .withPrompts({}) .run() ).resolves.not.toThrow(); + expect(cfGenSpawnSpy).toHaveBeenCalled(); }); }); diff --git a/packages/cf-deploy-config-sub-generator/test/cap-app.test.ts b/packages/cf-deploy-config-sub-generator/test/cap-app.test.ts new file mode 100644 index 0000000000..4b434c2a43 --- /dev/null +++ b/packages/cf-deploy-config-sub-generator/test/cap-app.test.ts @@ -0,0 +1,226 @@ +import hasbin from 'hasbin'; +import CFGenerator from '../src/app'; +import yeomanTest from 'yeoman-test'; +import { join } from 'path'; +import { TestFixture } from './fixtures'; +import { initI18n, t } from '../src/utils'; +import { RouterModuleType } from '@sap-ux/cf-deploy-config-writer'; +import * as fs from 'fs'; +import * as fioriGenShared from '@sap-ux/fiori-generator-shared'; +import * as memfs from 'memfs'; +import * as cfDeployWriter from '@sap-ux/cf-deploy-config-writer'; +import type { Editor } from 'mem-fs-editor'; + +const mockIsAppStudio = jest.fn(); +jest.mock('@sap-ux/btp-utils', () => { + 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/CHANGELOG.md b/packages/cf-deploy-config-writer/CHANGELOG.md index 0618f47fed..364be3f27f 100644 --- a/packages/cf-deploy-config-writer/CHANGELOG.md +++ b/packages/cf-deploy-config-writer/CHANGELOG.md @@ -1,5 +1,30 @@ # @sap-ux/cf-deploy-config-inquirer +## 0.1.15 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 + +## 0.1.14 + +### Patch Changes + +- d0b656b: change where inti and writing are executing when cf-sub-gen is loaded as a sub-gen + +## 0.1.13 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + +## 0.1.12 + +### Patch Changes + +- d6118c9: Changes to support adding CAP MTA prompt to allow user generate MTA + ## 0.1.11 ### Patch Changes diff --git a/packages/cf-deploy-config-writer/package.json b/packages/cf-deploy-config-writer/package.json index af323e4387..f818629cf8 100644 --- a/packages/cf-deploy-config-writer/package.json +++ b/packages/cf-deploy-config-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/cf-deploy-config-writer", "description": "Add or amend Cloud Foundry and ABAP deployment configuration for SAP projects", - "version": "0.1.11", + "version": "0.1.15", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", 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 2a0f4689e4..e28c1cd0ba 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 @@ -23,7 +23,6 @@ import { ResourceMTADestination, Rimraf, RimrafVersion, - rootDeployMTAScript, UI5DeployBuildScript, undeployMTAScript, WelcomeFile, @@ -35,7 +34,8 @@ import { getDestinationProperties, getTemplatePath, readManifest, - toPosixPath + toPosixPath, + updateRootPackage } from '../utils'; import { addMtaDeployParameters, @@ -230,7 +230,9 @@ async function generateDeployConfig(cfAppConfig: CFAppConfig, fs: Editor): Promi await appendCloudFoundryConfigurations(config, fs); await updateManifest(config, fs); await updateHTML5AppPackage(config, fs); - await updateRootPackage(config, fs); + if (config.isMtaRoot) { + await updateRootPackage({ mtaId: config.mtaId ?? config.appId, rootPath: config.rootPath }, fs); + } } /** @@ -314,26 +316,24 @@ function cleanupStandaloneRoutes({ rootPath, appId }: CFConfig, mtaInstance: Mta * @param mtaInstance MTA configuration instance */ async function saveMta(cfConfig: CFConfig, mtaInstance: MtaConfig): Promise { - let isMtaSaved = false; try { - isMtaSaved = await mtaInstance.save(); + // Cleanup any temp files, not required to handle the exception as the mta.yaml will have been appended already with required changes. + await mtaInstance.save(); + LoggerHelper.logger?.debug(t('debug.mtaSaved')); } catch (error) { LoggerHelper.logger?.debug(t('debug.mtaSavedFailed', { error })); - isMtaSaved = await mtaInstance.save(); } - if (isMtaSaved) { - // Add mtaext if required for API Hub Enterprise connectivity - if (cfConfig.apiHubConfig?.apiHubType === ApiHubType.apiHubEnterprise) { - try { - await mtaInstance.addMtaExtensionConfig(cfConfig.destinationName, cfConfig.serviceHost, { - key: 'ApiKey', - value: cfConfig.apiHubConfig.apiHubKey - }); - } catch (error) { - LoggerHelper.logger?.error(t('error.mtaExtensionFailed', { error })); - } + + // Add mtaext if required for API Hub Enterprise connectivity + if (cfConfig.apiHubConfig?.apiHubType === ApiHubType.apiHubEnterprise) { + try { + await mtaInstance.addMtaExtensionConfig(cfConfig.destinationName, cfConfig.serviceHost, { + key: 'ApiKey', + value: cfConfig.apiHubConfig.apiHubKey + }); + } catch (error) { + LoggerHelper.logger?.error(t('error.mtaExtensionFailed', { error })); } - LoggerHelper.logger?.debug(t('debug.mtaSaved')); } } @@ -408,31 +408,6 @@ async function updateHTML5AppPackage(cfConfig: CFConfig, fs: Editor): 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..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 @@ -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, rootPath: config.mtaPath }, fs); return fs; } 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/mta-config/index.ts b/packages/cf-deploy-config-writer/src/mta-config/index.ts index 7608650462..62acaca75d 100644 --- a/packages/cf-deploy-config-writer/src/mta-config/index.ts +++ b/packages/cf-deploy-config-writer/src/mta-config/index.ts @@ -147,6 +147,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 8cf7bf4b15..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 @@ -3,12 +3,13 @@ "logError": "{{method}} error found {{ error }}", "mtaLoaded": "MTA {{ type }} loaded", "ui5YamlDoesNotExist": "File ui5.yaml does not exist in the project", + "capMtaCreated": "CAP MTA Configuration created", "mtaCreated": "MTA Configuration created {{mtaPath}}.", "mtaSaved": "MTA Configuration has been saved.", "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}}.", 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; diff --git a/packages/cf-deploy-config-writer/src/utils.ts b/packages/cf-deploy-config-writer/src/utils.ts index afa670dc52..251736ac7a 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,34 @@ 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 {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, rootPath }: { mtaId: string; rootPath: string }, + fs: Editor +): Promise { + 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(rootPath, Rimraf, RimrafVersion, fs); + await addPackageDevDependency(rootPath, MbtPackage, MbtPackageVersion, fs); + let deployArgs: string[] = []; + if (fs.exists(join(rootPath, 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(rootPath, script.name, script.run, fs); + } + } +} diff --git a/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-app.test.ts.snap b/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-app.test.ts.snap index fb0eb5ea7d..edacb84fae 100644 --- a/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-app.test.ts.snap +++ b/packages/cf-deploy-config-writer/test/unit/__snapshots__/index-app.test.ts.snap @@ -1139,6 +1139,253 @@ resources: " `; +exports[`CF Writer App Generate deployment configs - ensure mta.save does not exit 1`] = ` +Object { + ".gitignore": Object { + "contents": "node_modules/ +dist/ +.scp/ +.env +Makefile*.mta +mta_archives +mta-* +resources +archive.zip +.*_mta_build_tmp", + "state": "modified", + }, + "package.json": Object { + "contents": "{ + \\"name\\": \\"basicapp\\", + \\"version\\": \\"0.0.1\\", + \\"private\\": true, + \\"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\\", + \\"@sap/ui5-builder-webide-extension\\": \\"^1.1.9\\", + \\"ui5-task-zipper\\": \\"^3.1.3\\", + \\"rimraf\\": \\"^5.0.5\\", + \\"mbt\\": \\"^1.2.29\\" + }, + \\"scripts\\": { + \\"start\\": \\"fiori run --open \\\\\\"test/flpSandbox.html?sap-ui-xx-viewCache=false#basicapp-display\\\\\\"\\", + \\"start-local\\": \\"fiori run --config ./ui5-local.yaml --open \\\\\\"test/flpSandbox.html?sap-ui-xx-viewCache=false#basicapp-display\\\\\\"\\", + \\"build\\": \\"ui5 build --config=ui5.yaml --clean-dest --dest dist\\", + \\"deploy\\": \\"fiori cfDeploy\\", + \\"deploy-config\\": \\"fiori add deploy-config\\", + \\"start-noflp\\": \\"fiori run --open \\\\\\"index.html?sap-ui-xx-viewCache=false\\\\\\"\\", + \\"start-variants-management\\": \\"fiori run --open \\\\\\"preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#preview-app\\\\\\"\\", + \\"unit-tests\\": \\"fiori run --open 'test/unit/unitTests.qunit.html'\\", + \\"int-tests\\": \\"fiori run --open 'test/integration/opaTests.qunit.html'\\", + \\"build:cf\\": \\"ui5 build preload --clean-dest --config ui5-deploy.yaml --include-task=generateCachebusterInfo\\", + \\"build:mta\\": \\"rimraf resources mta_archives && mbt build\\", + \\"undeploy\\": \\"cf undeploy basicapp --delete-services --delete-service-keys --delete-service-brokers\\" + }, + \\"sapuxLayer\\": \\"VENDOR\\" +} +", + "state": "modified", + }, + "ui5-deploy.yaml": Object { + "contents": "# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json + +specVersion: '2.4' +metadata: + name: 'basicapp' +type: application +resources: + configuration: + propertiesFileSourceEncoding: UTF-8 +builder: + resources: + excludes: + - /test/** + - /localService/** + customTasks: + - name: webide-extension-task-updateManifestJson + afterTask: replaceVersion + configuration: + appFolder: webapp + destDir: dist + - name: ui5-task-zipper + afterTask: generateCachebusterInfo + configuration: + archiveName: basicapp + additionalFiles: + - xs-app.json +", + "state": "modified", + }, + "webapp/manifest.json": Object { + "contents": "{ + \\"_version\\": \\"1.59.0\\", + \\"sap.app\\": { + \\"id\\": \\"basicapp\\", + \\"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.14.2-pre-20240708140014-4973a7ece.0\\", + \\"toolsId\\": \\"c1c8a120-2e9a-4d6e-8363-7e2622948044\\" + } + }, + \\"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.125.1\\", + \\"libs\\": { + \\"sap.m\\": {}, + \\"sap.ui.core\\": {}, + \\"sap.f\\": {}, + \\"sap.suite.ui.generic.template\\": {}, + \\"sap.ui.comp\\": {}, + \\"sap.ui.generic.app\\": {}, + \\"sap.ui.table\\": {}, + \\"sap.ushell\\": {} + } + }, + \\"contentDensities\\": { + \\"compact\\": true, + \\"cozy\\": true + }, + \\"models\\": { + \\"i18n\\": { + \\"type\\": \\"sap.ui.model.resource.ResourceModel\\", + \\"settings\\": { + \\"bundleName\\": \\"basicapp.i18n.i18n\\" + } + } + }, + \\"resources\\": { + \\"css\\": [ + { + \\"uri\\": \\"css/style.css\\" + } + ] + }, + \\"routing\\": { + \\"config\\": { + \\"routerClass\\": \\"sap.m.routing.Router\\", + \\"viewType\\": \\"XML\\", + \\"async\\": true, + \\"viewPath\\": \\"basicapp.view\\", + \\"controlAggregation\\": \\"pages\\", + \\"controlId\\": \\"app\\", + \\"clearControlAggregation\\": false + }, + \\"routes\\": [ + { + \\"name\\": \\"RouteView1\\", + \\"pattern\\": \\":?query:\\", + \\"target\\": [ + \\"TargetView1\\" + ] + } + ], + \\"targets\\": { + \\"TargetView1\\": { + \\"viewType\\": \\"XML\\", + \\"transition\\": \\"slide\\", + \\"clearControlAggregation\\": false, + \\"viewId\\": \\"View1\\", + \\"viewName\\": \\"View1\\" + } + } + }, + \\"rootView\\": { + \\"viewName\\": \\"basicapp.view.App\\", + \\"type\\": \\"XML\\", + \\"async\\": true, + \\"id\\": \\"App\\" + } + }, + \\"sap.cloud\\": { + \\"public\\": true, + \\"service\\": \\"basicapp\\" + } +} +", + "state": "modified", + }, + "xs-app.json": Object { + "contents": "{ + \\"welcomeFile\\": \\"/index.html\\", + \\"authenticationMethod\\": \\"route\\", + \\"routes\\": [ + { + \\"source\\": \\"^/sap/.*/(.*)$\\", + \\"target\\": \\"/$1\\", + \\"destination\\": \\"TestDestination\\", + \\"authenticationType\\": \\"none\\", + \\"csrfProtection\\": false + }, + { + \\"source\\": \\"^/resources/(.*)$\\", + \\"target\\": \\"/resources/$1\\", + \\"authenticationType\\": \\"none\\", + \\"destination\\": \\"ui5\\" + }, + { + \\"source\\": \\"^/test-resources/(.*)$\\", + \\"target\\": \\"/test-resources/$1\\", + \\"authenticationType\\": \\"none\\", + \\"destination\\": \\"ui5\\" + }, + { + \\"source\\": \\"^(.*)$\\", + \\"target\\": \\"$1\\", + \\"service\\": \\"html5-apps-repo-rt\\", + \\"authenticationType\\": \\"xsuaa\\" + } + ] +} +", + "state": "modified", + }, + "xs-security.json": Object { + "contents": "{ + \\"xsappname\\": \\"basicapp\\", + \\"tenant-mode\\": \\"dedicated\\", + \\"description\\": \\"Security profile of called application\\", + \\"scopes\\": [], + \\"role-templates\\": [] +} +", + "state": "modified", + }, +} +`; + exports[`CF Writer App Generate deployment configs - generateSupportingConfig read mtaId read from file 1`] = ` "{ \\"name\\": \\"mta-project\\", 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 7070fa0ae6..2463976646 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-app.test.ts b/packages/cf-deploy-config-writer/test/unit/index-app.test.ts index ffcc204a4c..034c8cfc9b 100644 --- a/packages/cf-deploy-config-writer/test/unit/index-app.test.ts +++ b/packages/cf-deploy-config-writer/test/unit/index-app.test.ts @@ -80,13 +80,13 @@ describe('CF Writer App', () => { expect(DefaultMTADestination).toEqual('fiori-default-srv-api'); }); - test('Generate deployment configs - HTML5 App and destination read from ui5.yaml', async () => { + test('Generate deployment configs - ensure mta.save does not exit ', async () => { const mockWriteFileSync = jest.spyOn(Mta.prototype, 'save').mockImplementationOnce(() => { throw new Error(); }); isAppStudioMock.mockResolvedValue(true); listDestinationsMock.mockResolvedValue(destinationsMock); - const appName = 'basicapp01'; + const appName = 'mtaexceptionapp'; const appPath = join(outputDir, appName); fsExtra.mkdirSync(outputDir, { recursive: true }); fsExtra.mkdirSync(appPath); @@ -95,9 +95,22 @@ describe('CF Writer App', () => { expect(isAppStudioMock).toBeCalledTimes(1); expect(listDestinationsMock).toBeCalledTimes(1); expect(localFs.dump(appPath)).toMatchSnapshot(); + expect(mockWriteFileSync).toHaveBeenCalledTimes(1); + }); + + test('Generate deployment configs - HTML5 App and destination read from ui5.yaml', async () => { + isAppStudioMock.mockResolvedValue(true); + listDestinationsMock.mockResolvedValue(destinationsMock); + const appName = 'basicapp01'; + const appPath = join(outputDir, appName); + fsExtra.mkdirSync(outputDir, { recursive: true }); + fsExtra.mkdirSync(appPath); + fsExtra.copySync(join(__dirname, '../sample/basicapp'), appPath); + const localFs = await generateAppConfig({ appPath }, undefined, logger); + expect(isAppStudioMock).toBeCalledTimes(1); + expect(localFs.dump(appPath)).toMatchSnapshot(); // Since mta.yaml is not in memfs, read from disk expect(localFs.read(join(appPath, 'mta.yaml'))).toMatchSnapshot(); - expect(mockWriteFileSync).toHaveBeenCalledTimes(2); }); test('Generate deployment configs - HTML5 App with managed approuter attached with no destination available', async () => { 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/create/CHANGELOG.md b/packages/create/CHANGELOG.md index b7fe60eb24..1faf8503cd 100644 --- a/packages/create/CHANGELOG.md +++ b/packages/create/CHANGELOG.md @@ -1,5 +1,68 @@ # @sap-ux/create +## 0.12.2 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path +- Updated dependencies [4b8577f] + - @sap-ux/mockserver-config-writer@0.8.3 + - @sap-ux/odata-service-writer@0.26.5 + - @sap-ux/adp-tooling@0.13.2 + - @sap-ux/flp-config-inquirer@0.2.47 + - @sap-ux/preview-middleware@0.18.2 + - @sap-ux/project-access@1.29.10 + - @sap-ux/app-config-writer@0.5.34 + - @sap-ux/abap-deploy-config-inquirer@1.2.20 + - @sap-ux/cap-config-writer@0.9.13 + - @sap-ux/abap-deploy-config-writer@0.0.91 + - @sap-ux/cards-editor-config-writer@0.4.10 + - @sap-ux/system-access@0.5.31 + +## 0.12.1 + +### Patch Changes + +- Updated dependencies [1d4ba46] +- Updated dependencies [c8c292c] + - @sap-ux/preview-middleware@0.18.1 + - @sap-ux/project-access@1.29.9 + - @sap-ux/app-config-writer@0.5.33 + - @sap-ux/abap-deploy-config-writer@0.0.90 + - @sap-ux/adp-tooling@0.13.1 + - @sap-ux/cap-config-writer@0.9.12 + - @sap-ux/cards-editor-config-writer@0.4.10 + - @sap-ux/flp-config-inquirer@0.2.46 + - @sap-ux/mockserver-config-writer@0.8.2 + - @sap-ux/odata-service-writer@0.26.4 + - @sap-ux/system-access@0.5.31 + - @sap-ux/abap-deploy-config-inquirer@1.2.19 + +## 0.12.0 + +### Minor Changes + +- 127bd12: feat: Add Typescript support for Adaptation Project + +### Patch Changes + +- Updated dependencies [127bd12] + - @sap-ux/adp-tooling@0.13.0 + - @sap-ux/preview-middleware@0.18.0 + - @sap-ux/flp-config-inquirer@0.2.45 + - @sap-ux/app-config-writer@0.5.32 + +## 0.11.95 + +### Patch Changes + +- @sap-ux/abap-deploy-config-inquirer@1.2.18 +- @sap-ux/cap-config-writer@0.9.11 +- @sap-ux/flp-config-inquirer@0.2.44 +- @sap-ux/adp-tooling@0.12.138 +- @sap-ux/preview-middleware@0.17.48 +- @sap-ux/app-config-writer@0.5.32 + ## 0.11.94 ### Patch Changes diff --git a/packages/create/package.json b/packages/create/package.json index f31b2baeb7..10291cd984 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.11.94", + "version": "0.12.2", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/create/src/cli/add/annotations-to-odata.ts b/packages/create/src/cli/add/annotations-to-odata.ts index 4cd513a98c..e69c394f93 100644 --- a/packages/create/src/cli/add/annotations-to-odata.ts +++ b/packages/create/src/cli/add/annotations-to-odata.ts @@ -44,7 +44,7 @@ async function addAnnotationsToOdata(basePath: string, simulate: boolean, yamlPa basePath = process.cwd(); } await validateAdpProject(basePath); - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const { target, ignoreCertErrors = false } = await getAdpConfig(basePath, yamlPath); const provider = await createAbapServiceProvider( target, diff --git a/packages/create/src/cli/add/component-usages.ts b/packages/create/src/cli/add/component-usages.ts index 46c3bfff62..e24e7f220c 100644 --- a/packages/create/src/cli/add/component-usages.ts +++ b/packages/create/src/cli/add/component-usages.ts @@ -39,7 +39,7 @@ export async function addComponentUsages(basePath: string, simulate: boolean): P } await validateAdpProject(basePath); - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const answers = await promptYUIQuestions(getPromptsForAddComponentUsages(basePath, variant.layer), false); diff --git a/packages/create/src/cli/add/navigation-config.ts b/packages/create/src/cli/add/navigation-config.ts index 813b9c3554..96d60c4107 100644 --- a/packages/create/src/cli/add/navigation-config.ts +++ b/packages/create/src/cli/add/navigation-config.ts @@ -59,7 +59,7 @@ async function addInboundNavigationConfig(basePath: string, simulate: boolean): const appType = await getAppType(basePath); const isAdp = appType === 'Fiori Adaptation'; - if (isAdp && flpConfigurationExists(basePath)) { + if (isAdp && (await flpConfigurationExists(basePath))) { logger.info('FLP Configuration already exists.'); return; } @@ -137,7 +137,7 @@ async function retrieveManifest(basePath: string, fs: Editor): Promise * @throws {Error} If the project is not CloudReady. */ async function retrieveMergedManifest(basePath: string, logger: ToolsLogger): Promise { - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const { target, ignoreCertErrors = false } = await getAdpConfig(basePath, join(basePath, FileName.Ui5Yaml)); const provider = await createAbapServiceProvider(target, { ignoreCertErrors }, true, logger); diff --git a/packages/create/src/cli/add/new-model.ts b/packages/create/src/cli/add/new-model.ts index 9c51f80c2b..2b274fb68b 100644 --- a/packages/create/src/cli/add/new-model.ts +++ b/packages/create/src/cli/add/new-model.ts @@ -35,7 +35,7 @@ async function addNewModel(basePath: string, simulate: boolean): Promise { await validateAdpProject(basePath); - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const answers = await promptYUIQuestions(getPromptsForNewModel(basePath, variant.layer), false); diff --git a/packages/create/src/cli/change/change-data-source.ts b/packages/create/src/cli/change/change-data-source.ts index 1f6f38611e..fe3774f113 100644 --- a/packages/create/src/cli/change/change-data-source.ts +++ b/packages/create/src/cli/change/change-data-source.ts @@ -42,7 +42,7 @@ async function changeDataSource(basePath: string, simulate: boolean, yamlPath: s basePath = process.cwd(); } await validateAdpProject(basePath); - const variant = getVariant(basePath); + const variant = await getVariant(basePath); const { target, ignoreCertErrors = false } = await getAdpConfig(basePath, yamlPath); const provider = await createAbapServiceProvider( target, diff --git a/packages/create/src/cli/change/change-inbound.ts b/packages/create/src/cli/change/change-inbound.ts index 9dffa6bfed..b65d0198a6 100644 --- a/packages/create/src/cli/change/change-inbound.ts +++ b/packages/create/src/cli/change/change-inbound.ts @@ -32,8 +32,8 @@ async function changeInbound(basePath: string, simulate: boolean): Promise } await validateAdpProject(basePath); - validateCloudAdpProject(basePath); - const variant = getVariant(basePath); + await validateCloudAdpProject(basePath); + const variant = await getVariant(basePath); const change = variant.content.find( (change: DescriptorVariantContent) => change.changeType === 'appdescr_app_removeAllInboundsExceptOne' ); diff --git a/packages/create/src/cli/generate/adaptation-project.ts b/packages/create/src/cli/generate/adaptation-project.ts index 8a031e6932..d208b90e54 100644 --- a/packages/create/src/cli/generate/adaptation-project.ts +++ b/packages/create/src/cli/generate/adaptation-project.ts @@ -21,6 +21,7 @@ export function addGenerateAdaptationProjectCommand(cmd: Command): void { .option('--url [url]', 'url pointing to the target system containing the original app') .option('--ignoreCertErrors', 'ignore certificate errors when connecting to the target system') .option('--ft', 'enable the Fiori tools for the generated project') + .option('--ts', 'enable the TypeScript support for the generated project') .option('--package [package]', 'ABAP package to be used for deployments') .option('--transport [transport]', 'ABAP transport to be used for deployments') .action(async (path, options) => { @@ -111,7 +112,8 @@ function createConfigFromDefaults(defaults: PromptDefaults): AdpWriterConfig { transport: defaults.transport ? defaults.transport.toUpperCase() : undefined }, options: { - fioriTools: defaults.ft + fioriTools: defaults.ft, + enableTypeScript: defaults.ts } }; } else { diff --git a/packages/create/src/validation/validation.ts b/packages/create/src/validation/validation.ts index e1dfcab67d..be3b5c77fc 100644 --- a/packages/create/src/validation/validation.ts +++ b/packages/create/src/validation/validation.ts @@ -53,8 +53,8 @@ export async function validateAdpProject(basePath: string): Promise { * * @param basePath - path to the adaptation project */ -export function validateCloudAdpProject(basePath: string): void { - const manifest = getVariant(basePath); +export async function validateCloudAdpProject(basePath: string): Promise { + const manifest = await getVariant(basePath); if ( !manifest?.content?.some( (change: DescriptorVariantContent) => change.changeType === 'appdescr_app_removeAllInboundsExceptOne' diff --git a/packages/create/test/unit/validation/validation.test.ts b/packages/create/test/unit/validation/validation.test.ts index 6e863400c6..d537c9a58b 100644 --- a/packages/create/test/unit/validation/validation.test.ts +++ b/packages/create/test/unit/validation/validation.test.ts @@ -26,10 +26,10 @@ describe('validation', () => { const descriptorVariant = JSON.parse( readFileSync(join(__dirname, '../../fixtures/adaptation-project', 'manifest.appdescr_variant'), 'utf-8') ); - test('throw error for omPremise project', () => { + test('throw error for omPremise project', async () => { jest.spyOn(adp, 'getVariant').mockReturnValue(descriptorVariant); try { - validateCloudAdpProject(''); + await validateCloudAdpProject(''); fail('The function should have thrown an error.'); } catch (error) { expect(error.message).toBe('This command can only be used for Cloud Adaptation Project.'); diff --git a/packages/deploy-config-generator-shared/CHANGELOG.md b/packages/deploy-config-generator-shared/CHANGELOG.md index 5f6bcef386..d55a545ed8 100644 --- a/packages/deploy-config-generator-shared/CHANGELOG.md +++ b/packages/deploy-config-generator-shared/CHANGELOG.md @@ -1,5 +1,30 @@ # @sap-ux/deploy-config-generator-shared +## 0.0.29 + +### Patch Changes + +- @sap-ux/fiori-generator-shared@0.9.2 + +## 0.0.28 + +### Patch Changes + +- @sap-ux/fiori-generator-shared@0.9.1 + +## 0.0.27 + +### Patch Changes + +- d6118c9: Changes to support adding CAP MTA prompt to allow user generate MTA + +## 0.0.26 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + ## 0.0.25 ### Patch Changes diff --git a/packages/deploy-config-generator-shared/package.json b/packages/deploy-config-generator-shared/package.json index aebc43e0b6..d0f096d0c7 100644 --- a/packages/deploy-config-generator-shared/package.json +++ b/packages/deploy-config-generator-shared/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/deploy-config-generator-shared", "description": "Commonly used shared functionality and types to support the deploy config generator.", - "version": "0.0.25", + "version": "0.0.29", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", 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 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 + } + ]; +} 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 4a4e2f4e1b..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 @@ -2,11 +2,13 @@ "prompts": { "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": { "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')); }); }); diff --git a/packages/environment-check/CHANGELOG.md b/packages/environment-check/CHANGELOG.md index c5e337d547..0b0e967567 100644 --- a/packages/environment-check/CHANGELOG.md +++ b/packages/environment-check/CHANGELOG.md @@ -1,5 +1,20 @@ # @sap-ux/environment-check +## 0.17.83 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/axios-extension@1.18.6 + +## 0.17.82 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/axios-extension@1.18.6 + ## 0.17.81 ### Patch Changes diff --git a/packages/environment-check/package.json b/packages/environment-check/package.json index edd3a42c12..8c9681ac8c 100644 --- a/packages/environment-check/package.json +++ b/packages/environment-check/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/environment-check", - "version": "0.17.81", + "version": "0.17.83", "description": "SAP Fiori environment check", "license": "Apache-2.0", "bin": { diff --git a/packages/fe-fpm-writer/CHANGELOG.md b/packages/fe-fpm-writer/CHANGELOG.md index 38565d394a..4cf478d80f 100644 --- a/packages/fe-fpm-writer/CHANGELOG.md +++ b/packages/fe-fpm-writer/CHANGELOG.md @@ -1,5 +1,20 @@ # @sap-ux/fe-fpm-writer +## 0.33.8 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/fiori-annotation-api@0.4.11 + +## 0.33.7 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/fiori-annotation-api@0.4.10 + ## 0.33.6 ### Patch Changes diff --git a/packages/fe-fpm-writer/package.json b/packages/fe-fpm-writer/package.json index 736e275074..7c3f96935e 100644 --- a/packages/fe-fpm-writer/package.json +++ b/packages/fe-fpm-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fe-fpm-writer", "description": "SAP Fiori elements flexible programming model writer", - "version": "0.33.6", + "version": "0.33.8", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/fiori-annotation-api/CHANGELOG.md b/packages/fiori-annotation-api/CHANGELOG.md index d1d9c77740..6a60dae36f 100644 --- a/packages/fiori-annotation-api/CHANGELOG.md +++ b/packages/fiori-annotation-api/CHANGELOG.md @@ -1,5 +1,21 @@ # @sap-ux/fiori-annotation-api +## 0.4.11 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 +- @sap-ux/cds-odata-annotation-converter@0.5.0 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies [4373718] +- Updated dependencies [c8c292c] + - @sap-ux/cds-odata-annotation-converter@0.5.0 + - @sap-ux/project-access@1.29.9 + ## 0.4.9 ### Patch Changes diff --git a/packages/fiori-annotation-api/package.json b/packages/fiori-annotation-api/package.json index d9c078808d..5cf1ee05d8 100644 --- a/packages/fiori-annotation-api/package.json +++ b/packages/fiori-annotation-api/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/fiori-annotation-api", - "version": "0.4.9", + "version": "0.4.11", "description": "Library that provides API for reading and writing annotations in SAP Fiori elements projects.", "publisher": "SAPSE", "author": "SAP SE", diff --git a/packages/fiori-elements-writer/CHANGELOG.md b/packages/fiori-elements-writer/CHANGELOG.md index 05b847148c..c3d1b821e2 100644 --- a/packages/fiori-elements-writer/CHANGELOG.md +++ b/packages/fiori-elements-writer/CHANGELOG.md @@ -1,5 +1,46 @@ # @sap-ux/fiori-elements-writer +## 2.1.21 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/odata-service-writer@0.26.5 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/fe-fpm-writer@0.33.8 + - @sap-ux/cap-config-writer@0.9.13 + - @sap-ux/annotation-generator@0.3.11 + - @sap-ux/ui5-application-writer@1.2.10 + - @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.20 + +### Patch Changes + +- @sap-ux/annotation-generator@0.3.10 +- @sap-ux/cap-config-writer@0.9.12 +- @sap-ux/fe-fpm-writer@0.33.7 +- @sap-ux/fiori-generator-shared@0.9.1 +- @sap-ux/odata-service-writer@0.26.4 +- @sap-ux/ui5-application-writer@1.2.10 +- @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.19 + +### Patch Changes + +- Updated dependencies [f4867e5] + - @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.18 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/ui5-test-writer@0.5.0 + - @sap-ux/cap-config-writer@0.9.11 + ## 2.1.17 ### Patch Changes diff --git a/packages/fiori-elements-writer/package.json b/packages/fiori-elements-writer/package.json index c92c656ca6..b060450826 100644 --- a/packages/fiori-elements-writer/package.json +++ b/packages/fiori-elements-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fiori-elements-writer", "description": "SAP Fiori elements application writer", - "version": "2.1.17", + "version": "2.1.21", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/fiori-freestyle-writer/CHANGELOG.md b/packages/fiori-freestyle-writer/CHANGELOG.md index 7cbb690888..67290cc054 100644 --- a/packages/fiori-freestyle-writer/CHANGELOG.md +++ b/packages/fiori-freestyle-writer/CHANGELOG.md @@ -1,5 +1,46 @@ # @sap-ux/fiori-freestyle-writer +## 2.1.3 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/odata-service-writer@0.26.5 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/cap-config-writer@0.9.13 + - @sap-ux/ui5-application-writer@1.2.10 + - @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.2 + +### Patch Changes + +- @sap-ux/cap-config-writer@0.9.12 +- @sap-ux/fiori-generator-shared@0.9.1 +- @sap-ux/odata-service-writer@0.26.4 +- @sap-ux/ui5-application-writer@1.2.10 +- @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.1 + +### Patch Changes + +- Updated dependencies [f4867e5] + - @sap-ux/ui5-test-writer@0.5.1 + +## 2.1.0 + +### Minor Changes + +- fffc3a7: Add Freestyle OPA templates to ui5-test-writer + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/ui5-test-writer@0.5.0 + - @sap-ux/cap-config-writer@0.9.11 + ## 2.0.20 ### Patch Changes diff --git a/packages/fiori-freestyle-writer/package.json b/packages/fiori-freestyle-writer/package.json index eb62a599f8..8973f333f5 100644 --- a/packages/fiori-freestyle-writer/package.json +++ b/packages/fiori-freestyle-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fiori-freestyle-writer", "description": "SAP Fiori freestyle application writer", - "version": "2.0.20", + "version": "2.1.3", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", @@ -37,6 +37,8 @@ "@sap-ux/ui5-config": "workspace:*", "@sap-ux/fiori-generator-shared": "workspace:*", "@sap-ux/cap-config-writer": "workspace:*", + "@sap-ux/ui5-test-writer": "workspace:*", + "@sap-ux/logger": "workspace:*", "ejs": "3.1.10", "i18next": "20.6.1", "lodash": "4.17.21", diff --git a/packages/fiori-freestyle-writer/src/generateOPATests.ts b/packages/fiori-freestyle-writer/src/generateOPATests.ts new file mode 100644 index 0000000000..9666068cee --- /dev/null +++ b/packages/fiori-freestyle-writer/src/generateOPATests.ts @@ -0,0 +1,53 @@ +import { generateFreestyleOPAFiles } from '@sap-ux/ui5-test-writer'; +import type { Package } from '@sap-ux/ui5-application-writer'; +import type { FreestyleApp, BasicAppSettings } from './types'; +import type { Logger } from '@sap-ux/logger'; +import type { Editor } from 'mem-fs-editor'; + +/** + * Adds test scripts to the package.json object. + * + * @param {Package} packageJson - The package.json object to update. + * @param {boolean} addMock - Whether to include the UI5 mock YAML configuration. + */ +function addTestScripts(packageJson: Package, addMock: boolean): void { + // Note: 'ui5MockYamlScript' is empty when no data source is selected. + const ui5MockYamlScript = addMock ? '--config ./ui5-mock.yaml ' : ''; + packageJson.scripts = { + ...packageJson.scripts, + 'unit-test': `fiori run ${ui5MockYamlScript}--open 'test/unit/unitTests.qunit.html'`, + 'int-test': `fiori run ${ui5MockYamlScript}--open 'test/integration/opaTests.qunit.html'` + }; +} + +/** + * Generates OPA tests for a freestyle application. + * + * @param {string} basePath - The base directory path. + * @param {FreestyleApp} ffApp - The freestyle application configuration. + * @param {boolean} addMock - Whether to include the UI5 mock YAML configuration. + * @param {Package} packageJson - The package.json object to update. + * @param {Editor} [fs] - Optional file system editor instance. + * @param {Logger} [log] - Optional logger instance. + * @returns {Promise} - The modified file system editor. + */ +export async function generateOPATests( + basePath: string, + ffApp: FreestyleApp, + addMock: boolean, + packageJson: Package, + fs?: Editor, + log?: Logger +): Promise { + addTestScripts(packageJson, addMock); + const config = { + appId: ffApp.app.id, + applicationDescription: ffApp.app.description, + applicationTitle: ffApp.app.title, + viewName: (ffApp.template.settings as BasicAppSettings).viewName, + ui5Theme: ffApp.ui5?.ui5Theme, + ui5Version: ffApp.ui5?.version, + enableTypeScript: ffApp.appOptions?.typescript + }; + await generateFreestyleOPAFiles(basePath, config, fs, log); +} diff --git a/packages/fiori-freestyle-writer/src/index.ts b/packages/fiori-freestyle-writer/src/index.ts index 66a02479e0..8cf23d60f0 100644 --- a/packages/fiori-freestyle-writer/src/index.ts +++ b/packages/fiori-freestyle-writer/src/index.ts @@ -13,6 +13,8 @@ import { initI18n } from './i18n'; import { getBootstrapResourceUrls, getPackageScripts } from '@sap-ux/fiori-generator-shared'; import { getTemplateVersionPath, processDestinationPath } from './utils'; import { applyCAPUpdates, type CapProjectSettings } from '@sap-ux/cap-config-writer'; +import type { Logger } from '@sap-ux/logger'; +import { generateOPATests } from './generateOPATests'; /** * Generate a UI5 application based on the specified Fiori Freestyle floorplan template. @@ -20,9 +22,10 @@ import { applyCAPUpdates, type CapProjectSettings } from '@sap-ux/cap-config-wri * @param basePath - the absolute target path where the application will be generated * @param data - configuration to generate the freestyle application * @param fs - an optional reference to a mem-fs editor + * @param log - optional logger * @returns Reference to a mem-fs-editor */ -async function generate(basePath: string, data: FreestyleApp, fs?: Editor): Promise { +async function generate(basePath: string, data: FreestyleApp, fs?: Editor, log?: Logger): Promise { // Load i18n translations asynchronously to ensure proper initialization. // This addresses occasional issues where i18n is not initialized in time, causing tests to fail. await initI18n(); @@ -133,21 +136,28 @@ async function generate(basePath: string, data: FreestyleApp, fs?: Editor) JSON.parse(render(fs.read(join(tmplPath, 'common', 'extend', 'package.json')), ffApp, {})) ); + const addTests = ffApp.appOptions?.addTests; const packageJson: Package = JSON.parse(fs.read(packagePath)); + if (isEdmxProjectType) { + const addMock = !!ffApp.service?.metadata; // Add scripts for non-CAP applications packageJson.scripts = { ...packageJson.scripts, ...getPackageScripts({ localOnly: !!ffApp.service && !ffApp.service?.url, - addMock: !!ffApp.service?.metadata, + addMock, sapClient: ffApp.service?.client, flpAppId: ffApp.app.flpAppId, startFile: data?.app?.startFile, localStartFile: data?.app?.localStartFile, - generateIndex: ffApp.appOptions?.generateIndex + generateIndex: ffApp.appOptions?.generateIndex, + addTest: addTests }) }; + if (addTests) { + await generateOPATests(basePath, ffApp, addMock, packageJson, fs, log); + } } else { // Add deploy-config for CAP applications packageJson.scripts = { diff --git a/packages/fiori-freestyle-writer/src/types.ts b/packages/fiori-freestyle-writer/src/types.ts index df20c999ed..d2f0c212f9 100644 --- a/packages/fiori-freestyle-writer/src/types.ts +++ b/packages/fiori-freestyle-writer/src/types.ts @@ -1,4 +1,4 @@ -import type { Ui5App, App } from '@sap-ux/ui5-application-writer'; +import type { Ui5App, App, AppOptions } from '@sap-ux/ui5-application-writer'; import type { OdataService } from '@sap-ux/odata-service-writer'; import type { CapServiceCdsInfo } from '@sap-ux/cap-config-writer'; @@ -43,6 +43,13 @@ export interface FreestyleApp extends Ui5App { capService?: CapServiceCdsInfo; }; app: FioriApp; + appOptions?: Partial & { + /** + * Generate OPA based tests, for Simple template. + * This will eventually move up to {@link Ui5App.appOptions} + */ + addTests?: boolean; + }; } // We need this for the service version diff --git a/packages/fiori-freestyle-writer/templates/basic/extend/1.120.0/webapp/manifest.json b/packages/fiori-freestyle-writer/templates/basic/extend/1.120.0/webapp/manifest.json index d90b31b1a8..f6ff805d05 100644 --- a/packages/fiori-freestyle-writer/templates/basic/extend/1.120.0/webapp/manifest.json +++ b/packages/fiori-freestyle-writer/templates/basic/extend/1.120.0/webapp/manifest.json @@ -3,7 +3,8 @@ "rootView": { "viewName": "<%- app.id %>.view.App", "type": "XML", - "id": "App" + "id": "App", + "async": true }, "resources": { "css": [ @@ -20,7 +21,9 @@ "transition": "slide", "type": "View", "viewType": "XML", - "path": "<%- app.id %>.view" + "path": "<%- app.id %>.view", + "async": true, + "viewPath": "<%- app.id %>.view" }, "routes": [ { diff --git a/packages/fiori-freestyle-writer/test/__snapshots__/basic.test.ts.snap b/packages/fiori-freestyle-writer/test/__snapshots__/basic.test.ts.snap index 8951fa1612..6c6f6d8c4e 100644 --- a/packages/fiori-freestyle-writer/test/__snapshots__/basic.test.ts.snap +++ b/packages/fiori-freestyle-writer/test/__snapshots__/basic.test.ts.snap @@ -2181,7 +2181,9 @@ archive.zip \\"deploy-config\\": \\"fiori add deploy-config\\", \\"start-noflp\\": \\"fiori run --open \\\\\\"index.html?sap-ui-xx-viewCache=false\\\\\\"\\", \\"start-mock\\": \\"fiori run --config ./ui5-mock.yaml --open \\\\\\"test/flpSandbox.html?sap-ui-xx-viewCache=false#nods1-tile\\\\\\"\\", - \\"start-variants-management\\": \\"fiori run --open \\\\\\"preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#preview-app\\\\\\"\\" + \\"int-test\\": \\"fiori run --config ./ui5-mock.yaml --open 'test/integration/opaTests.qunit.html'\\", + \\"start-variants-management\\": \\"fiori run --open \\\\\\"preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#preview-app\\\\\\"\\", + \\"unit-test\\": \\"fiori run --config ./ui5-mock.yaml --open 'test/unit/unitTests.qunit.html'\\" } } ", @@ -2189,31 +2191,38 @@ archive.zip }, "tsconfig.json": Object { "contents": "{ - \\"compilerOptions\\": { - \\"target\\": \\"es2022\\", - \\"module\\": \\"es2022\\", - \\"skipLibCheck\\": true, - \\"allowJs\\": true, - \\"strict\\": true, - \\"strictPropertyInitialization\\": false, - \\"moduleResolution\\": \\"node\\", - \\"rootDir\\": \\"./webapp\\", - \\"outDir\\": \\"./dist\\", - \\"baseUrl\\": \\"./\\", - \\"paths\\": { - \\"nods1/*\\": [ - \\"./webapp/*\\" - ] - }, - \\"typeRoots\\": [ - \\"./node_modules/@types\\", - \\"./node_modules/@sapui5/ts-types-esm\\" - ] + \\"compilerOptions\\": { + \\"target\\": \\"es2022\\", + \\"module\\": \\"es2022\\", + \\"skipLibCheck\\": true, + \\"allowJs\\": true, + \\"strict\\": true, + \\"strictPropertyInitialization\\": false, + \\"moduleResolution\\": \\"node\\", + \\"rootDir\\": \\"./webapp\\", + \\"outDir\\": \\"./dist\\", + \\"baseUrl\\": \\"./\\", + \\"paths\\": { + \\"nods1/*\\": [ + \\"./webapp/*\\" + ], + \\"unit/*\\": [ + \\"./webapp/test/unit/*\\" + ], + \\"integration/*\\": [ + \\"./webapp/test/integration/*\\" + ] }, - \\"include\\": [ - \\"./webapp/**/*\\" + \\"typeRoots\\": [ + \\"./node_modules/@types\\", + \\"./node_modules/@sapui5/ts-types-esm\\" ] -}", + }, + \\"include\\": [ + \\"./webapp/**/*\\" + ] +} +", "state": "modified", }, "ui5-local.yaml": Object { @@ -2741,6 +2750,229 @@ export function createDeviceModel () { ", "state": "modified", }, + "webapp/test/integration/NavigationJourney.ts": Object { + "contents": "/*global QUnit*/ +import opaTest from \\"sap/ui/test/opaQunit\\"; +import AppPage from \\"./pages/AppPage\\"; +import ViewPage from \\"./pages/View1Page\\"; + +import Opa5 from \\"sap/ui/test/Opa5\\"; + +QUnit.module(\\"Navigation Journey\\"); + +const onTheAppPage = new AppPage(); +const onTheViewPage = new ViewPage(); +Opa5.extendConfig({ + viewNamespace: \\"nods1.view.\\", + autoWait: true +}); + +opaTest(\\"Should see the initial page of the app\\", function () { + // Arrangements + // eslint-disable-next-line @typescript-eslint/no-floating-promises + onTheAppPage.iStartMyUIComponent({ + componentConfig: { + name: \\"nods1\\" + } + }); + + // Assertions + onTheAppPage.iShouldSeeTheApp(); + onTheViewPage.iShouldSeeThePageView(); + + + // Cleanup + // eslint-disable-next-line @typescript-eslint/no-floating-promises + onTheAppPage.iTeardownMyApp(); +});", + "state": "modified", + }, + "webapp/test/integration/opaTests.qunit.html": Object { + "contents": " + + + + Integration tests for Basic Template + + + + + + + +
+
+ + +", + "state": "modified", + }, + "webapp/test/integration/opaTests.qunit.ts": Object { + "contents": "/* global QUnit */ +// https://api.qunitjs.com/config/autostart/ +QUnit.config.autostart = false; + +// import all your OPA journeys here +void Promise.all([ + import(\\"integration/NavigationJourney\\") +]).then(() => { + QUnit.start(); +});", + "state": "modified", + }, + "webapp/test/integration/pages/AppPage.ts": Object { + "contents": "import Opa5 from \\"sap/ui/test/Opa5\\"; + +const sViewName = \\"App\\"; + +export default class AppPage extends Opa5 { + // Actions + + + // Assertions + iShouldSeeTheApp() { + return this.waitFor({ + id: \\"app\\", + viewName: sViewName, + success: function () { + Opa5.assert.ok(true, \\"The \\" + sViewName + \\" view is displayed\\"); + }, + errorMessage: \\"Did not find the \\" + sViewName + \\" view\\" + }); + } + +} + +", + "state": "modified", + }, + "webapp/test/integration/pages/View1Page.ts": Object { + "contents": "import Opa5 from \\"sap/ui/test/Opa5\\"; + +const sViewName = \\"View1\\"; + +export default class View1Page extends Opa5 { + // Actions + + + // Assertions + iShouldSeeThePageView() { + return this.waitFor({ + id: \\"page\\", + viewName: sViewName, + success: function () { + Opa5.assert.ok(true, \\"The \\" + sViewName + \\" view is displayed\\"); + }, + errorMessage: \\"Did not find the \\" + sViewName + \\" view\\" + }); + } + +} + + +", + "state": "modified", + }, + "webapp/test/testsuite.qunit.html": Object { + "contents": " + + + QUnit test suite + + + + +", + "state": "modified", + }, + "webapp/test/testsuite.qunit.ts": Object { + "contents": "/* global window, parent, location */ + +// eslint-disable-next-line fiori-custom/sap-no-global-define,@typescript-eslint/ban-ts-comment +// @ts-nocheck +window.suite = function() { + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf(\\"/\\") + 1); + + oSuite.addTestPage(sContextPath + \\"unit/unitTests.qunit.html\\"); + oSuite.addTestPage(sContextPath + \\"integration/opaTests.qunit.html\\"); + + return oSuite; +}; +", + "state": "modified", + }, + "webapp/test/unit/controller/View1Page.controller.ts": Object { + "contents": "/*global QUnit*/ +import Controller from \\"nods1/controller/View1.controller\\"; + +QUnit.module(\\"View1 Controller\\"); + +QUnit.test(\\"I should test the View1 controller\\", function (assert: Assert) { + const oAppController = new Controller(\\"View1\\"); + oAppController.onInit(); + assert.ok(oAppController); +});", + "state": "modified", + }, + "webapp/test/unit/unitTests.qunit.html": Object { + "contents": " + + + + Unit tests for nods1 + + + + + + + + + +
+
+ + +", + "state": "modified", + }, + "webapp/test/unit/unitTests.qunit.ts": Object { + "contents": "/* global QUnit */ +// https://api.qunitjs.com/config/autostart/ +QUnit.config.autostart = false; + +// import all your QUnit tests here +void Promise.all([ +import(\\"unit/controller/View1Page.controller\\") +]).then(() => { + QUnit.start(); +});", + "state": "modified", + }, "webapp/view/App.view.xml": Object { "contents": " + + + + Integration tests for Basic Template + + + + + + + +
+
+ + +", + "state": "modified", + }, + "webapp/test/integration/opaTests.qunit.ts": Object { + "contents": "/* global QUnit */ +// https://api.qunitjs.com/config/autostart/ +QUnit.config.autostart = false; + +// import all your OPA journeys here +void Promise.all([ + import(\\"integration/NavigationJourney\\") +]).then(() => { + QUnit.start(); +});", + "state": "modified", + }, + "webapp/test/integration/pages/AppPage.ts": Object { + "contents": "import Opa5 from \\"sap/ui/test/Opa5\\"; + +const sViewName = \\"App\\"; + +export default class AppPage extends Opa5 { + // Actions + + + // Assertions + iShouldSeeTheApp() { + return this.waitFor({ + id: \\"app\\", + viewName: sViewName, + success: function () { + Opa5.assert.ok(true, \\"The \\" + sViewName + \\" view is displayed\\"); + }, + errorMessage: \\"Did not find the \\" + sViewName + \\" view\\" + }); + } + +} + +", + "state": "modified", + }, + "webapp/test/integration/pages/View1Page.ts": Object { + "contents": "import Opa5 from \\"sap/ui/test/Opa5\\"; + +const sViewName = \\"View1\\"; + +export default class View1Page extends Opa5 { + // Actions + + + // Assertions + iShouldSeeThePageView() { + return this.waitFor({ + id: \\"page\\", + viewName: sViewName, + success: function () { + Opa5.assert.ok(true, \\"The \\" + sViewName + \\" view is displayed\\"); + }, + errorMessage: \\"Did not find the \\" + sViewName + \\" view\\" + }); + } + +} + + +", + "state": "modified", + }, + "webapp/test/testsuite.qunit.html": Object { + "contents": " + + + QUnit test suite + + + + +", + "state": "modified", + }, + "webapp/test/testsuite.qunit.ts": Object { + "contents": "/* global window, parent, location */ + +// eslint-disable-next-line fiori-custom/sap-no-global-define,@typescript-eslint/ban-ts-comment +// @ts-nocheck +window.suite = function() { + // eslint-disable-next-line + var oSuite = new parent.jsUnitTestSuite(), + sContextPath = location.pathname.substring(0, location.pathname.lastIndexOf(\\"/\\") + 1); + + oSuite.addTestPage(sContextPath + \\"unit/unitTests.qunit.html\\"); + oSuite.addTestPage(sContextPath + \\"integration/opaTests.qunit.html\\"); + + return oSuite; +}; +", + "state": "modified", + }, + "webapp/test/unit/controller/View1Page.controller.ts": Object { + "contents": "/*global QUnit*/ +import Controller from \\"nods1/controller/View1.controller\\"; + +QUnit.module(\\"View1 Controller\\"); + +QUnit.test(\\"I should test the View1 controller\\", function (assert: Assert) { + const oAppController = new Controller(\\"View1\\"); + oAppController.onInit(); + assert.ok(oAppController); +});", + "state": "modified", + }, + "webapp/test/unit/unitTests.qunit.html": Object { + "contents": " + + + + Unit tests for nods1 + + + + + + + + + +
+
+ + +", + "state": "modified", + }, + "webapp/test/unit/unitTests.qunit.ts": Object { + "contents": "/* global QUnit */ +// https://api.qunitjs.com/config/autostart/ +QUnit.config.autostart = false; + +// import all your QUnit tests here +void Promise.all([ +import(\\"unit/controller/View1Page.controller\\") +]).then(() => { + QUnit.start(); +});", + "state": "modified", + }, "webapp/view/App.view.xml": Object { "contents": " { ...commonConfig, appOptions: { loadReuseLibs: false, - typescript: true + typescript: true, + addTests: true } }, settings: {} @@ -127,7 +128,8 @@ describe(`Fiori freestyle template: ${TEST_NAME}`, () => { ...commonConfig, appOptions: { loadReuseLibs: false, - typescript: true + typescript: true, + addTests: true }, ui5: { version: '1.108.1', diff --git a/packages/fiori-freestyle-writer/test/generateOPATests.test.ts b/packages/fiori-freestyle-writer/test/generateOPATests.test.ts new file mode 100644 index 0000000000..d95e81c5bd --- /dev/null +++ b/packages/fiori-freestyle-writer/test/generateOPATests.test.ts @@ -0,0 +1,87 @@ +import { generateOPATests } from '../src/generateOPATests'; +import { generateFreestyleOPAFiles } from '@sap-ux/ui5-test-writer'; +import type { FreestyleApp, BasicAppSettings } from '../src/types'; +import type { Package } from '@sap-ux/ui5-application-writer'; + +jest.mock('@sap-ux/ui5-test-writer', () => ({ + generateFreestyleOPAFiles: jest.fn() +})); + +describe('generateOPATests', () => { + const basePath = '/path/to/base'; + const ffApp = { + app: { + id: 'appId', + description: 'App Description', + title: 'App Title' + }, + template: { + settings: { + viewName: 'ViewName' + } + }, + ui5: { + ui5Theme: 'sap_fiori_3', + version: '1.71.0' + }, + appOptions: { + typescript: true + } + } as FreestyleApp; + + const packageJson = { + scripts: { + 'start-mock': 'fiori run --config ./ui5-mock.yaml --open "test/flpSandbox.html"' + } + } as Package; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should add test scripts to package.json when addMock is true', async () => { + const addMock = true; + await generateOPATests(basePath, ffApp, addMock, packageJson); + expect(generateFreestyleOPAFiles).toHaveBeenCalledWith( + basePath, + { + appId: 'appId', + applicationDescription: 'App Description', + applicationTitle: 'App Title', + viewName: 'ViewName', + ui5Theme: 'sap_fiori_3', + ui5Version: '1.71.0', + enableTypeScript: true + }, + undefined, + undefined + ); + expect(packageJson.scripts?.['unit-test']).toBe( + "fiori run --config ./ui5-mock.yaml --open 'test/unit/unitTests.qunit.html'" + ); + expect(packageJson.scripts?.['int-test']).toBe( + "fiori run --config ./ui5-mock.yaml --open 'test/integration/opaTests.qunit.html'" + ); + }); + + it('should add test scripts to package.json when addMock is false', async () => { + const addMock = false; + await generateOPATests(basePath, ffApp, addMock, packageJson); + expect(generateFreestyleOPAFiles).toHaveBeenCalledWith( + basePath, + { + appId: 'appId', + applicationDescription: 'App Description', + applicationTitle: 'App Title', + viewName: 'ViewName', + ui5Theme: 'sap_fiori_3', + ui5Version: '1.71.0', + enableTypeScript: true + }, + undefined, + undefined + ); + expect(packageJson.scripts?.['unit-test']).toBe("fiori run --open 'test/unit/unitTests.qunit.html'"); + expect(packageJson.scripts?.['int-test']).toBe("fiori run --open 'test/integration/opaTests.qunit.html'"); + }); +}); diff --git a/packages/fiori-freestyle-writer/tsconfig.json b/packages/fiori-freestyle-writer/tsconfig.json index a4a412cba2..c2cf5f7754 100644 --- a/packages/fiori-freestyle-writer/tsconfig.json +++ b/packages/fiori-freestyle-writer/tsconfig.json @@ -19,6 +19,9 @@ { "path": "../fiori-generator-shared" }, + { + "path": "../logger" + }, { "path": "../odata-service-writer" }, @@ -27,6 +30,9 @@ }, { "path": "../ui5-config" + }, + { + "path": "../ui5-test-writer" } ] } diff --git a/packages/fiori-generator-shared/CHANGELOG.md b/packages/fiori-generator-shared/CHANGELOG.md index 1be1dd5097..5b4cf08b87 100644 --- a/packages/fiori-generator-shared/CHANGELOG.md +++ b/packages/fiori-generator-shared/CHANGELOG.md @@ -1,5 +1,27 @@ # @sap-ux/fiori-generator-shared +## 0.9.2 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/telemetry@0.5.64 + - @sap-ux/project-access@1.29.10 + +## 0.9.1 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/telemetry@0.5.63 + +## 0.9.0 + +### Minor Changes + +- fffc3a7: Add Freestyle OPA templates to ui5-test-writer + ## 0.8.1 ### Patch Changes diff --git a/packages/fiori-generator-shared/package.json b/packages/fiori-generator-shared/package.json index 1fd2521ddd..122c4dc638 100644 --- a/packages/fiori-generator-shared/package.json +++ b/packages/fiori-generator-shared/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/fiori-generator-shared", "description": "Commonly used shared functionality and types to support the fiori generator.", - "version": "0.8.1", + "version": "0.9.2", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/fiori-generator-shared/src/npm-package-scripts/types.ts b/packages/fiori-generator-shared/src/npm-package-scripts/types.ts index 6b85684a91..ff913b92de 100644 --- a/packages/fiori-generator-shared/src/npm-package-scripts/types.ts +++ b/packages/fiori-generator-shared/src/npm-package-scripts/types.ts @@ -10,6 +10,8 @@ export interface PackageJsonScripts { 'start-noflp'?: string; /** Optional command to start the application with a mock server configuration. */ 'start-mock'?: string; + /** Optional command to run unit tests. */ + 'unit-test'?: string; /** Optional command to run tests. */ 'int-test'?: string; /** Optional command to add the variants management script. */ diff --git a/packages/flp-config-inquirer/CHANGELOG.md b/packages/flp-config-inquirer/CHANGELOG.md index 1430714c61..d716d06842 100644 --- a/packages/flp-config-inquirer/CHANGELOG.md +++ b/packages/flp-config-inquirer/CHANGELOG.md @@ -1,5 +1,41 @@ # @sap-ux/flp-config-inquirer +## 0.2.47 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/adp-tooling@0.13.2 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + +## 0.2.46 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/adp-tooling@0.13.1 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/inquirer-common@0.6.20 + +## 0.2.45 + +### Patch Changes + +- Updated dependencies [127bd12] + - @sap-ux/adp-tooling@0.13.0 + +## 0.2.44 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/inquirer-common@0.6.19 + - @sap-ux/adp-tooling@0.12.138 + ## 0.2.43 ### Patch Changes diff --git a/packages/flp-config-inquirer/package.json b/packages/flp-config-inquirer/package.json index 6e5c3270f1..b3f7857ca7 100644 --- a/packages/flp-config-inquirer/package.json +++ b/packages/flp-config-inquirer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/flp-config-inquirer", "description": "Prompts module that can prompt users for inputs required for FLP configuration", - "version": "0.2.43", + "version": "0.2.47", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/flp-config-sub-generator/CHANGELOG.md b/packages/flp-config-sub-generator/CHANGELOG.md index da90e2789e..e1fce05c1b 100644 --- a/packages/flp-config-sub-generator/CHANGELOG.md +++ b/packages/flp-config-sub-generator/CHANGELOG.md @@ -1,5 +1,55 @@ # @sap-ux/flp-config-sub-generator +## 0.1.28 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/i18n@0.2.2 + - @sap-ux/flp-config-inquirer@0.2.47 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + - @sap-ux/app-config-writer@0.5.34 + - @sap-ux/deploy-config-generator-shared@0.0.29 + +## 0.1.27 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/app-config-writer@0.5.33 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/flp-config-inquirer@0.2.46 + - @sap-ux/deploy-config-generator-shared@0.0.28 + - @sap-ux/inquirer-common@0.6.20 + +## 0.1.26 + +### Patch Changes + +- @sap-ux/flp-config-inquirer@0.2.45 +- @sap-ux/app-config-writer@0.5.32 + +## 0.1.25 + +### Patch Changes + +- Updated dependencies [d6118c9] + - @sap-ux/deploy-config-generator-shared@0.0.27 + +## 0.1.24 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/deploy-config-generator-shared@0.0.26 + - @sap-ux/flp-config-inquirer@0.2.44 + - @sap-ux/inquirer-common@0.6.19 + - @sap-ux/app-config-writer@0.5.32 + ## 0.1.23 ### Patch Changes diff --git a/packages/flp-config-sub-generator/package.json b/packages/flp-config-sub-generator/package.json index aafb93efad..5bf55351bf 100644 --- a/packages/flp-config-sub-generator/package.json +++ b/packages/flp-config-sub-generator/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/flp-config-sub-generator", "description": "Generator for creating Fiori Launcpad configuration", - "version": "0.1.23", + "version": "0.1.28", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/i18n/CHANGELOG.md b/packages/i18n/CHANGELOG.md index 9e0b111449..28d89f93b7 100644 --- a/packages/i18n/CHANGELOG.md +++ b/packages/i18n/CHANGELOG.md @@ -1,5 +1,11 @@ # @sap-ux/i18n +## 0.2.2 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path + ## 0.2.1 ### Patch Changes diff --git a/packages/i18n/README.md b/packages/i18n/README.md index 82ec97c4ff..22ef634659 100644 --- a/packages/i18n/README.md +++ b/packages/i18n/README.md @@ -42,9 +42,10 @@ For detailed example usage check unit test of [`getCapI18nBundle`](./test/unit/r ```typescript import { getPropertiesI18nBundle } from '@sap-ux/i18n'; import { join } from 'path'; +import { getWebappPath } from '@sap-ux/project-access'; const PROJECT_ROOT = 'absolute/path/to/project'; -const i18nFilePath = join(PROJECT_ROOT, 'webapp', 'i18n', 'i18n.properties'); +const i18nFilePath = join(await getWebappPath(PROJECT_ROOT), 'i18n', 'i18n.properties'); const bundle = await getPropertiesI18nBundle(i18nFilePath); ``` @@ -82,6 +83,7 @@ For detailed example usage check unit test of [`createCapI18nEntries`](./test/un ```typescript import { createPropertiesI18nEntries } from '@sap-ux/i18n'; import { join } from 'path'; +import { getWebappPath } from '@sap-ux/project-access'; const newEntries = [ { @@ -90,7 +92,7 @@ const newEntries = [ } ]; const PROJECT_ROOT = 'absolute/path/to/project'; -const i18nFilePath = join(PROJECT_ROOT, 'webapp', 'i18n', 'i18n.properties'); +const i18nFilePath = join(getWebappPath(PROJECT_ROOT), 'i18n', 'i18n.properties'); const result = await createPropertiesI18nEntries(i18nFilePath, newEntries, PROJECT_ROOT); ``` diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 6be2c3ab5c..53b7d19af1 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/i18n", - "version": "0.2.1", + "version": "0.2.2", "description": "Library for i18n", "repository": { "type": "git", diff --git a/packages/inquirer-common/CHANGELOG.md b/packages/inquirer-common/CHANGELOG.md index 510b0bfdf8..494f570e19 100644 --- a/packages/inquirer-common/CHANGELOG.md +++ b/packages/inquirer-common/CHANGELOG.md @@ -1,5 +1,27 @@ # @sap-ux/inquirer-common +## 0.6.21 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/telemetry@0.5.64 + - @sap-ux/fiori-generator-shared@0.9.2 + +## 0.6.20 + +### Patch Changes + +- @sap-ux/fiori-generator-shared@0.9.1 +- @sap-ux/telemetry@0.5.63 + +## 0.6.19 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + ## 0.6.18 ### Patch Changes diff --git a/packages/inquirer-common/package.json b/packages/inquirer-common/package.json index 9665859795..327cf625fc 100644 --- a/packages/inquirer-common/package.json +++ b/packages/inquirer-common/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/inquirer-common", "description": "Commonly used shared functionality and types to support inquirer modules.", - "version": "0.6.18", + "version": "0.6.21", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/launch-config/CHANGELOG.md b/packages/launch-config/CHANGELOG.md index 7ff2dcfd9f..cc7802e687 100644 --- a/packages/launch-config/CHANGELOG.md +++ b/packages/launch-config/CHANGELOG.md @@ -1,5 +1,18 @@ # @sap-ux/launch-config +## 0.7.28 + +### Patch Changes + +- @sap-ux/project-access@1.29.10 + +## 0.7.27 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + ## 0.7.26 ### Patch Changes diff --git a/packages/launch-config/package.json b/packages/launch-config/package.json index 5f7a6793de..f651308393 100644 --- a/packages/launch-config/package.json +++ b/packages/launch-config/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux/launch-config", - "version": "0.7.26", + "version": "0.7.28", "description": "SAP Fiori tools launch config administration", "repository": { "type": "git", diff --git a/packages/mockserver-config-writer/CHANGELOG.md b/packages/mockserver-config-writer/CHANGELOG.md index 7cdedbf301..9963f64907 100644 --- a/packages/mockserver-config-writer/CHANGELOG.md +++ b/packages/mockserver-config-writer/CHANGELOG.md @@ -1,5 +1,19 @@ # @sap-ux/mockserver-config-writer +## 0.8.3 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path + - @sap-ux/project-access@1.29.10 + +## 0.8.2 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + ## 0.8.1 ### Patch Changes diff --git a/packages/mockserver-config-writer/package.json b/packages/mockserver-config-writer/package.json index a8bfb2add8..f9da788857 100644 --- a/packages/mockserver-config-writer/package.json +++ b/packages/mockserver-config-writer/package.json @@ -1,7 +1,7 @@ { "name": "@sap-ux/mockserver-config-writer", "description": "Add or update configuration for SAP Fiori tools mockserver", - "version": "0.8.1", + "version": "0.8.3", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/mockserver-config-writer/src/mockserver-config/ui5-mock-yaml.ts b/packages/mockserver-config-writer/src/mockserver-config/ui5-mock-yaml.ts index db321095b7..8966d8e6b1 100644 --- a/packages/mockserver-config-writer/src/mockserver-config/ui5-mock-yaml.ts +++ b/packages/mockserver-config-writer/src/mockserver-config/ui5-mock-yaml.ts @@ -1,4 +1,4 @@ -import { join } from 'path'; +import { join, posix, relative, sep } from 'path'; import type { Editor } from 'mem-fs-editor'; import { UI5Config } from '@sap-ux/ui5-config'; import type { CustomMiddleware, DataSourceConfig } from '@sap-ux/ui5-config'; @@ -46,8 +46,11 @@ export async function enhanceYaml( annotationSource.forEach((annotation) => { // Ignore local annotations from YAML file, those are linked through manifest file if (annotation.settings?.localUri !== annotation.uri) { + const localUri = annotation.settings?.localUri; annotationsConfig.push({ - localPath: `./webapp/${annotation.settings?.localUri}`, + localPath: localUri + ? `.${posix.sep}${relative(basePath, join(webappPath, localUri)).replaceAll(sep, posix.sep)}` + : undefined, urlPath: annotation.uri }); } @@ -60,7 +63,9 @@ export async function enhanceYaml( dataSourcesConfig.push({ serviceName: dataSource, servicePath: dataSources[dataSource].uri, - metadataPath: localUri ? `./webapp/${localUri}` : undefined + metadataPath: localUri + ? `.${posix.sep}${relative(basePath, join(webappPath, localUri)).replaceAll(sep, posix.sep)}` + : undefined }); } diff --git a/packages/odata-service-inquirer/CHANGELOG.md b/packages/odata-service-inquirer/CHANGELOG.md index eb338317f0..5e30c6bff2 100644 --- a/packages/odata-service-inquirer/CHANGELOG.md +++ b/packages/odata-service-inquirer/CHANGELOG.md @@ -1,5 +1,35 @@ # @sap-ux/odata-service-inquirer +## 2.2.11 + +### Patch Changes + +- Updated dependencies [4b8577f] + - @sap-ux/telemetry@0.5.64 + - @sap-ux/fiori-generator-shared@0.9.2 + - @sap-ux/inquirer-common@0.6.21 + - @sap-ux/project-access@1.29.10 + - @sap-ux/axios-extension@1.18.6 + +## 2.2.10 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/axios-extension@1.18.6 + - @sap-ux/fiori-generator-shared@0.9.1 + - @sap-ux/telemetry@0.5.63 + - @sap-ux/inquirer-common@0.6.20 + +## 2.2.9 + +### Patch Changes + +- Updated dependencies [fffc3a7] + - @sap-ux/fiori-generator-shared@0.9.0 + - @sap-ux/inquirer-common@0.6.19 + ## 2.2.8 ### Patch Changes diff --git a/packages/odata-service-inquirer/package.json b/packages/odata-service-inquirer/package.json index 5d4fee5b6a..1a5552a06a 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": "2.2.8", + "version": "2.2.11", "repository": { "type": "git", "url": "https://github.com/SAP/open-ux-tools.git", diff --git a/packages/odata-service-writer/CHANGELOG.md b/packages/odata-service-writer/CHANGELOG.md index 04aefe168b..7518503436 100644 --- a/packages/odata-service-writer/CHANGELOG.md +++ b/packages/odata-service-writer/CHANGELOG.md @@ -1,5 +1,22 @@ # @sap-ux/odata-service-writer +## 0.26.5 + +### Patch Changes + +- 4b8577f: fix: usage of static webapp path +- Updated dependencies [4b8577f] + - @sap-ux/mockserver-config-writer@0.8.3 + - @sap-ux/project-access@1.29.10 + +## 0.26.4 + +### Patch Changes + +- Updated dependencies [c8c292c] + - @sap-ux/project-access@1.29.9 + - @sap-ux/mockserver-config-writer@0.8.2 + ## 0.26.3 ### Patch Changes diff --git a/packages/odata-service-writer/package.json b/packages/odata-service-writer/package.json index a476538a5b..f061b9b645 100644 --- a/packages/odata-service-writer/package.json +++ b/packages/odata-service-writer/package.json @@ -9,7 +9,7 @@ "bugs": { "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue+label%3Abug+label%3Aodata-service-writer" }, - "version": "0.26.3", + "version": "0.26.5", "license": "Apache-2.0", "main": "dist/index.js", "scripts": { diff --git a/packages/odata-service-writer/src/data/annotations.ts b/packages/odata-service-writer/src/data/annotations.ts index e50185d755..517c4fee3c 100644 --- a/packages/odata-service-writer/src/data/annotations.ts +++ b/packages/odata-service-writer/src/data/annotations.ts @@ -4,6 +4,7 @@ import { XMLParser } from 'fast-xml-parser'; import { t } from '../i18n'; import type { NamespaceAlias, OdataService, EdmxAnnotationsInfo, EdmxOdataService, CdsAnnotationsInfo } from '../types'; import prettifyXml from 'prettify-xml'; +import { getWebappPath, DirName } from '@sap-ux/project-access'; /** * Updates the cds index or service file with the provided annotations. @@ -15,7 +16,7 @@ import prettifyXml from 'prettify-xml'; * @returns {Promise} A promise that resolves when the cds files have been updated. */ async function updateCdsIndexOrServiceFile(fs: Editor, annotations: CdsAnnotationsInfo): Promise { - const dirPath = join(annotations.projectName, 'annotations'); + const dirPath = join(annotations.projectName, DirName.Annotations); const annotationPath = normalize(dirPath).split(/[\\/]/g).join(posix.sep); const annotationConfig = `\nusing from './${annotationPath}';`; // get index and service file paths @@ -84,7 +85,7 @@ export async function updateCdsFilesWithAnnotations( * @returns {Promise} A promise that resolves when the cds files have been updated. */ async function removeCdsIndexOrServiceFile(fs: Editor, annotations: CdsAnnotationsInfo): Promise { - const dirPath = join(annotations.projectName, 'annotations'); + const dirPath = join(annotations.projectName, DirName.Annotations); const annotationPath = normalize(dirPath).split(/[\\/]/g).join(posix.sep); const annotationConfig = `\nusing from './${annotationPath}';`; // Get index and service file paths @@ -157,14 +158,12 @@ export async function removeAnnotationsFromCDSFiles( * Writes local copies of metadata.xml and local annotations. * * @param {Editor} fs - the memfs editor instance - * @param {string} basePath - the root path of an existing UI5 application * @param {string} webappPath - the webapp path of an existing UI5 application * @param {string} templateRoot - path to the file templates * @param {OdataService} service - the OData service instance with EDMX type */ export async function writeLocalServiceAnnotationXMLFiles( fs: Editor, - basePath: string, webappPath: string, templateRoot: string, service: EdmxOdataService @@ -177,7 +176,7 @@ export async function writeLocalServiceAnnotationXMLFiles( const namespaces = getAnnotationNamespaces(service); fs.copyTpl( join(templateRoot, 'add', 'annotation.xml'), - join(basePath, 'webapp', 'annotations', `${service.localAnnotationsName}.xml`), + join(webappPath, DirName.Annotations, `${service.localAnnotationsName}.xml`), { ...service, namespaces } ); } @@ -194,7 +193,7 @@ export async function writeMetadata(fs: Editor, webappPath: string, service: Edm if (service.metadata) { // mainService should be used in case there is no name defined for service fs.write( - join(webappPath, 'localService', service.name ?? 'mainService', 'metadata.xml'), + join(webappPath, DirName.LocalService, service.name ?? 'mainService', 'metadata.xml'), prettifyXml(service.metadata, { indent: 4 }) ); } @@ -208,20 +207,20 @@ export async function writeMetadata(fs: Editor, webappPath: string, service: Edm * @param {string} serviceName - Name of The OData service. * @param {OdataService} edmxAnnotations - The OData service annotations. */ -export function removeRemoteServiceAnnotationXmlFiles( +export async function removeRemoteServiceAnnotationXmlFiles( fs: Editor, basePath: string, serviceName: string, edmxAnnotations: EdmxAnnotationsInfo | EdmxAnnotationsInfo[] -): void { +): Promise { + const webappPath = await getWebappPath(basePath, fs); // Write annotation xml if annotations are provided and service type is EDMX if (Array.isArray(edmxAnnotations)) { for (const annotationName in edmxAnnotations) { const annotation = edmxAnnotations[annotationName]; const pathToAnnotationFile = join( - basePath, - 'webapp', - 'localService', + webappPath, + DirName.LocalService, serviceName, `${annotation.technicalName}.xml` ); @@ -231,9 +230,8 @@ export function removeRemoteServiceAnnotationXmlFiles( } } else if (edmxAnnotations?.xml) { const pathToAnnotationFile = join( - basePath, - 'webapp', - 'localService', + webappPath, + DirName.LocalService, serviceName, `${edmxAnnotations.technicalName}.xml` ); @@ -251,26 +249,27 @@ export function removeRemoteServiceAnnotationXmlFiles( * @param {string} serviceName - Name of The OData service. * @param {OdataService} edmxAnnotations - The OData service annotations. */ -export function writeRemoteServiceAnnotationXmlFiles( +export async function writeRemoteServiceAnnotationXmlFiles( fs: Editor, basePath: string, serviceName: string, edmxAnnotations: EdmxAnnotationsInfo | EdmxAnnotationsInfo[] -): void { +): Promise { + const webappPath = await getWebappPath(basePath, fs); // Write annotation xml if annotations are provided and service type is EDMX if (Array.isArray(edmxAnnotations)) { for (const annotationName in edmxAnnotations) { const annotation = edmxAnnotations[annotationName]; if (annotation?.xml) { fs.write( - join(basePath, 'webapp', 'localService', serviceName, `${annotation.technicalName}.xml`), + join(webappPath, DirName.LocalService, serviceName, `${annotation.technicalName}.xml`), prettifyXml(annotation.xml, { indent: 4 }) ); } } } else if (edmxAnnotations?.xml) { fs.write( - join(basePath, 'webapp', 'localService', serviceName, `${edmxAnnotations.technicalName}.xml`), + join(webappPath, DirName.LocalService, serviceName, `${edmxAnnotations.technicalName}.xml`), prettifyXml(edmxAnnotations.xml, { indent: 4 }) ); } diff --git a/packages/odata-service-writer/src/delete.ts b/packages/odata-service-writer/src/delete.ts index 08bb0e0d4c..1d067ba156 100644 --- a/packages/odata-service-writer/src/delete.ts +++ b/packages/odata-service-writer/src/delete.ts @@ -73,7 +73,7 @@ export async function deleteServiceData( ui5MockConfig.removeServiceFromMockServerMiddleware(service.path, serviceAnnotationPaths); fs.write(paths.ui5MockYaml, ui5MockConfig.toString()); } - removeRemoteServiceAnnotationXmlFiles( + await removeRemoteServiceAnnotationXmlFiles( fs, basePath, service.name, diff --git a/packages/odata-service-writer/src/update.ts b/packages/odata-service-writer/src/update.ts index afee7de19a..1e12b79a1d 100644 --- a/packages/odata-service-writer/src/update.ts +++ b/packages/odata-service-writer/src/update.ts @@ -116,7 +116,7 @@ export async function addServicesData( extendBackendMiddleware(fs, service, ui5MockConfig, paths.ui5MockYaml); } } - await writeLocalServiceAnnotationXMLFiles(fs, basePath, webappPath, templateRoot, service); + await writeLocalServiceAnnotationXMLFiles(fs, webappPath, templateRoot, service); } // service update should not trigger the package.json update if (paths.packageJson && paths.ui5Yaml) { @@ -125,7 +125,7 @@ export async function addServicesData( if (paths.ui5LocalYaml && ui5LocalConfig) { fs.write(paths.ui5LocalYaml, ui5LocalConfig.toString()); } - writeRemoteServiceAnnotationXmlFiles(fs, basePath, service.name ?? 'mainService', service.annotations); + await writeRemoteServiceAnnotationXmlFiles(fs, basePath, service.name ?? 'mainService', service.annotations); } /** @@ -188,5 +188,5 @@ export async function updateServicesData( await writeMetadata(fs, webappPath, service); } // Write new annotations files - writeRemoteServiceAnnotationXmlFiles(fs, basePath, service.name ?? 'mainService', service.annotations); + await writeRemoteServiceAnnotationXmlFiles(fs, basePath, service.name ?? 'mainService', service.annotations); } diff --git a/packages/odata-service-writer/test/__snapshots__/index.test.ts.snap b/packages/odata-service-writer/test/__snapshots__/index.test.ts.snap index e2f41ce215..8b36a536e4 100644 --- a/packages/odata-service-writer/test/__snapshots__/index.test.ts.snap +++ b/packages/odata-service-writer/test/__snapshots__/index.test.ts.snap @@ -2821,7 +2821,7 @@ Object { mockdataPath: ./webapp/localService/mainService/data generateMockData: true annotations: - - localPath: ./webapp/localService/mainService//SEPM_XYZ/SERVICE.xml + - localPath: ./webapp/localService/mainService/SEPM_XYZ/SERVICE.xml urlPath: /sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(TechnicalName='%2FSEPM_XYZ%2FSERVICE',Version='0001')/$value/ ", "state": "modified", @@ -2851,7 +2851,7 @@ Object { mockdataPath: ./webapp/localService/mainService/data generateMockData: true annotations: - - localPath: ./webapp/localService/mainService//SEPM_XYZ/SERVICE.xml + - localPath: ./webapp/localService/mainService/SEPM_XYZ/SERVICE.xml urlPath: /sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(TechnicalName='%2FSEPM_XYZ%2FSERVICE',Version='0001')/$value/ ", "state": "modified", diff --git a/packages/preview-middleware-client/CHANGELOG.md b/packages/preview-middleware-client/CHANGELOG.md index f14921d3a1..6e7443654e 100644 --- a/packages/preview-middleware-client/CHANGELOG.md +++ b/packages/preview-middleware-client/CHANGELOG.md @@ -1,5 +1,17 @@ # @sap-ux-private/preview-middleware-client +## 0.12.1 + +### Patch Changes + +- 1d4ba46: feat: handling of legacy free ui5 version + +## 0.12.0 + +### Minor Changes + +- 127bd12: feat: Add Typescript support for Adaptation Project + ## 0.11.69 ### Patch Changes diff --git a/packages/preview-middleware-client/package.json b/packages/preview-middleware-client/package.json index b240570eeb..91f847a483 100644 --- a/packages/preview-middleware-client/package.json +++ b/packages/preview-middleware-client/package.json @@ -1,6 +1,6 @@ { "name": "@sap-ux-private/preview-middleware-client", - "version": "0.11.69", + "version": "0.12.1", "description": "Client-side coding hosted by the preview middleware", "repository": { "type": "git", diff --git a/packages/preview-middleware-client/src/adp/api-handler.ts b/packages/preview-middleware-client/src/adp/api-handler.ts index af69bffdba..4d2610017e 100644 --- a/packages/preview-middleware-client/src/adp/api-handler.ts +++ b/packages/preview-middleware-client/src/adp/api-handler.ts @@ -32,6 +32,7 @@ export interface CodeExtResponse { controllerPath: string; controllerPathFromRoot: string; isRunningInBAS: boolean; + isTsSupported: boolean; } export interface AnnotationFileDetails { diff --git a/packages/preview-middleware-client/src/adp/controllers/ControllerExtension.controller.ts b/packages/preview-middleware-client/src/adp/controllers/ControllerExtension.controller.ts index cc108aecce..a7eb9140d4 100644 --- a/packages/preview-middleware-client/src/adp/controllers/ControllerExtension.controller.ts +++ b/packages/preview-middleware-client/src/adp/controllers/ControllerExtension.controller.ts @@ -24,13 +24,7 @@ import type SimpleForm from 'sap/ui/layout/form/SimpleForm'; import type ElementOverlay from 'sap/ui/dt/ElementOverlay'; import type { CodeExtResponse, ControllersResponse } from '../api-handler'; -import { - getExistingController, - getManifestAppdescr, - readControllers, - writeChange, - writeController -} from '../api-handler'; +import { getExistingController, readControllers, writeChange, writeController } from '../api-handler'; import BaseDialog from './BaseDialog.controller'; import { getControllerInfo } from '../utils'; @@ -44,6 +38,7 @@ type ControllerModel = JSONModel & { getProperty(sPath: '/newControllerName'): string; getProperty(sPath: '/viewId'): string; getProperty(sPath: '/controllerPath'): string; + getProperty(sPath: '/controllerExtension'): string; }; /** @@ -158,20 +153,13 @@ export default class ControllerExtension extends BaseDialog { const overlayControl = sap.ui.getCore().byId(selectorId) as unknown as ElementOverlay; const { controllerName, viewId } = getControllerInfo(overlayControl); - const existingController = await this.getExistingController(controllerName); - - if (existingController) { - const { controllerExists, controllerPath, controllerPathFromRoot, isRunningInBAS } = existingController; - - if (controllerExists) { - this.updateModelForExistingController( - controllerExists, - controllerPath, - controllerPathFromRoot, - isRunningInBAS - ); + const data = await this.getExistingController(controllerName); + + if (data) { + if (data?.controllerExists) { + this.updateModelForExistingController(data); } else { - this.updateModelForNewController(viewId); + this.updateModelForNewController(viewId, data.isTsSupported); await this.getControllers(); } @@ -180,17 +168,11 @@ export default class ControllerExtension extends BaseDialog { /** * Updates the model properties for an existing controller. * - * @param {boolean} controllerExists - Whether the controller exists. - * @param {string} controllerPath - The controller path. - * @param {string} controllerPathFromRoot - The controller path from the project root. - * @param {boolean} isRunningInBAS - Whether the environment is BAS or VS Code. + * @param {CodeExtResponse} data - Existing controller data from the server. */ - private updateModelForExistingController( - controllerExists: boolean, - controllerPath: string, - controllerPathFromRoot: string, - isRunningInBAS: boolean - ): void { + private updateModelForExistingController(data: CodeExtResponse): void { + const { controllerExists, controllerPath, controllerPathFromRoot, isRunningInBAS } = data; + this.model.setProperty('/controllerExists', controllerExists); this.model.setProperty('/controllerPath', controllerPath); this.model.setProperty('/controllerPathFromRoot', controllerPathFromRoot); @@ -214,17 +196,19 @@ export default class ControllerExtension extends BaseDialog { /** * Updates the model property for a new controller. * - * @param viewId The view ID + * @param {string} viewId - The view ID. + * @param {boolean} isTsSupported - Whether TypeScript supported for the current project. */ - private updateModelForNewController(viewId: string): void { + private updateModelForNewController(viewId: string, isTsSupported: boolean): void { this.model.setProperty('/viewId', viewId); + this.model.setProperty('/controllerExtension', isTsSupported ? '.ts' : '.js'); } /** * Retrieves existing controller data if found in the project's workspace. * - * @param controllerName Controller name that exists in the view - * @returns Returnsexisting controller data + * @param controllerName Controller name that exists in the view. + * @returns Returns existing controller data. */ private async getExistingController(controllerName: string): Promise { let data: CodeExtResponse | undefined; @@ -257,8 +241,7 @@ export default class ControllerExtension extends BaseDialog { */ private async createNewController(controllerName: string, viewId: string): Promise { try { - const manifest = await getManifestAppdescr(); - await writeController({ controllerName, projectId: manifest.id }); + await writeController({ controllerName }); const controllerRef = { codeRef: `coding/${controllerName}.js`, diff --git a/packages/preview-middleware-client/src/adp/ui/ControllerExtension.fragment.xml b/packages/preview-middleware-client/src/adp/ui/ControllerExtension.fragment.xml index e3a365533a..6dab5b6b06 100644 --- a/packages/preview-middleware-client/src/adp/ui/ControllerExtension.fragment.xml +++ b/packages/preview-middleware-client/src/adp/ui/ControllerExtension.fragment.xml @@ -16,8 +16,8 @@