From 54f9d76d074c976243ba8084a011376daf3d821a Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:10:44 +0200 Subject: [PATCH 01/11] feat(cloud-foundry-tools): move packages from SAP/cloud-foundry-tools Relocate the cloud-foundry-tools extension (backend, frontend, wrapper) from the standalone SAP/cloud-foundry-tools repo (v8.1.2) into projects/cloud-foundry-tools/. Raw move; monorepo wiring and toolchain fixes follow in subsequent commits. Per-package CHANGELOG.md preserved as CHANGELOG.old.md; fresh empty CHANGELOG.md started for the monorepo. Per-package LICENSE dropped (root REUSE covers legal). Signed-off-by: Jacob Kreyenbuehl --- projects/cloud-foundry-tools/README.md | 20 + .../packages/backend/.mocharc.js | 5 + .../packages/backend/.vscodeignore | 9 + .../packages/backend/CHANGELOG.md | 0 .../packages/backend/CHANGELOG.old.md | 162 ++ .../packages/backend/README.md | 63 + .../packages/backend/media/run-ext.png | Bin 0 -> 10811 bytes .../packages/backend/media/settings.png | Bin 0 -> 21390 bytes .../packages/backend/nyc.config.js | 10 + .../packages/backend/package.json | 366 ++++ .../packages/backend/resources/common/cf.svg | 7 + .../backend/resources/common/login.png | Bin 0 -> 3213 bytes .../packages/backend/resources/dark/add.svg | 19 + .../packages/backend/resources/dark/info.svg | 21 + .../packages/backend/resources/dark/login.svg | 14 + .../backend/resources/dark/reload.svg | 1 + .../backend/resources/dark/set-target.svg | 14 + .../backend/resources/dark/target-a.svg | 14 + .../backend/resources/dark/target.svg | 17 + .../packages/backend/resources/icon.png | Bin 0 -> 7254 bytes .../packages/backend/resources/light/add.svg | 19 + .../packages/backend/resources/light/info.svg | 21 + .../backend/resources/light/login.svg | 14 + .../backend/resources/light/reload.svg | 1 + .../backend/resources/light/set-target.svg | 14 + .../backend/resources/light/target-a.svg | 14 + .../backend/resources/light/target.svg | 17 + .../packages/backend/src/cfView.ts | 194 ++ .../packages/backend/src/cfViewCommands.ts | 617 ++++++ .../packages/backend/src/chisel.ts | 101 + .../packages/backend/src/commands.ts | 737 +++++++ .../packages/backend/src/extension.ts | 239 +++ .../backend/src/logger/logger-wrapper.ts | 49 + .../src/loginTargetView/loginTargetView.ts | 263 +++ .../packages/backend/src/messages.ts | 77 + .../backend/src/run-configuration-example.ts | 87 + .../packages/backend/src/run-configuration.ts | 141 ++ .../packages/backend/src/sql-tools.ts | 49 + .../packages/backend/src/utils.ts | 447 +++++ .../packages/backend/tests/cfView.spec.ts | 300 +++ .../backend/tests/cfViewCommands.spec.ts | 1181 +++++++++++ .../packages/backend/tests/chisel.spec.ts | 154 ++ .../packages/backend/tests/commands-a.spec.ts | 505 +++++ .../packages/backend/tests/commands.spec.ts | 1783 +++++++++++++++++ .../packages/backend/tests/ext/mockUtil.ts | 40 + .../packages/backend/tests/ext/mockVscode.ts | 265 +++ .../packages/backend/tests/extension.spec.ts | 626 ++++++ .../loginTargetView/loginTargetView.spec.ts | 457 +++++ .../packages/backend/tests/messages.spec.ts | 23 + .../backend/tests/resources/.envNoVCAP | 1 + .../packages/backend/tests/resources/.envtest | 6 + .../backend/tests/resources/.envtest_readonly | 5 + .../backend/tests/resources/.testInValidVCAP | 5 + .../backend/tests/resources/.testValidEnv | 5 + .../resources/.testValidEnvVCAPWithQuotes | 5 + .../.testValidEnvVCAPWithQuotesAndSpaces | 5 + .../backend/tests/run-configuration.spec.ts | 319 +++ .../packages/backend/tests/utils.spec.ts | 679 +++++++ .../packages/backend/tsconfig.json | 9 + .../packages/backend/webpack.config.js | 31 + .../packages/frontend/.vscode/launch.json | 20 + .../packages/frontend/.vscode/tasks.json | 23 + .../packages/frontend/CHANGELOG.md | 0 .../packages/frontend/CHANGELOG.old.md | 160 ++ .../packages/frontend/babel.config.js | 4 + .../packages/frontend/codicon.css | 553 +++++ .../packages/frontend/codicon.ttf | Bin 0 -> 70800 bytes .../packages/frontend/index.html | 18 + .../packages/frontend/jest.config.js | 38 + .../packages/frontend/jest.setup.js | 12 + .../packages/frontend/package.json | 62 + .../packages/frontend/src/App.vue | 152 ++ .../frontend/src/components/CFHeader.vue | 26 + .../frontend/src/components/CFSignin.vue | 377 ++++ .../frontend/src/components/CFTarget.vue | 249 +++ .../frontend/src/directives/tooltip.js | 141 ++ .../packages/frontend/src/main.js | 11 + .../packages/frontend/src/styles/tooltip.css | 83 + .../packages/frontend/test/App.spec.js | 184 ++ .../frontend/test/components/CFHeader.spec.js | 57 + .../frontend/test/components/CFSignin.spec.js | 425 ++++ .../frontend/test/components/CFTarget.spec.js | 687 +++++++ .../packages/frontend/vite.config.js | 32 + .../packages/wrapper/.mocharc.js | 5 + .../packages/wrapper/.vscodeignore | 9 + .../packages/wrapper/CHANGELOG.md | 0 .../packages/wrapper/CHANGELOG.old.md | 28 + .../packages/wrapper/README.md | 5 + .../packages/wrapper/nyc.config.js | 9 + .../packages/wrapper/package.json | 70 + .../packages/wrapper/resources/icon.png | Bin 0 -> 7254 bytes .../packages/wrapper/src/extension.ts | 7 + .../packages/wrapper/tests/extension.spec.ts | 29 + .../packages/wrapper/tsconfig.json | 9 + .../packages/wrapper/webpack.config.js | 31 + 95 files changed, 13763 insertions(+) create mode 100644 projects/cloud-foundry-tools/README.md create mode 100644 projects/cloud-foundry-tools/packages/backend/.mocharc.js create mode 100644 projects/cloud-foundry-tools/packages/backend/.vscodeignore create mode 100644 projects/cloud-foundry-tools/packages/backend/CHANGELOG.md create mode 100644 projects/cloud-foundry-tools/packages/backend/CHANGELOG.old.md create mode 100644 projects/cloud-foundry-tools/packages/backend/README.md create mode 100644 projects/cloud-foundry-tools/packages/backend/media/run-ext.png create mode 100644 projects/cloud-foundry-tools/packages/backend/media/settings.png create mode 100644 projects/cloud-foundry-tools/packages/backend/nyc.config.js create mode 100644 projects/cloud-foundry-tools/packages/backend/package.json create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/common/cf.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/common/login.png create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/add.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/info.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/login.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/reload.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/set-target.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/target-a.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/dark/target.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/icon.png create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/add.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/info.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/login.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/reload.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/set-target.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/target-a.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/resources/light/target.svg create mode 100644 projects/cloud-foundry-tools/packages/backend/src/cfView.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/chisel.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/commands.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/extension.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/messages.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/src/utils.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/ext/mockUtil.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.envNoVCAP create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest_readonly create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.testInValidVCAP create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnv create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotes create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotesAndSpaces create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/backend/tsconfig.json create mode 100644 projects/cloud-foundry-tools/packages/backend/webpack.config.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/.vscode/launch.json create mode 100644 projects/cloud-foundry-tools/packages/frontend/.vscode/tasks.json create mode 100644 projects/cloud-foundry-tools/packages/frontend/CHANGELOG.md create mode 100644 projects/cloud-foundry-tools/packages/frontend/CHANGELOG.old.md create mode 100644 projects/cloud-foundry-tools/packages/frontend/babel.config.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/codicon.css create mode 100644 projects/cloud-foundry-tools/packages/frontend/codicon.ttf create mode 100644 projects/cloud-foundry-tools/packages/frontend/index.html create mode 100644 projects/cloud-foundry-tools/packages/frontend/jest.config.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/jest.setup.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/package.json create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/App.vue create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/main.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/src/styles/tooltip.css create mode 100644 projects/cloud-foundry-tools/packages/frontend/test/App.spec.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js create mode 100644 projects/cloud-foundry-tools/packages/frontend/vite.config.js create mode 100644 projects/cloud-foundry-tools/packages/wrapper/.mocharc.js create mode 100644 projects/cloud-foundry-tools/packages/wrapper/.vscodeignore create mode 100644 projects/cloud-foundry-tools/packages/wrapper/CHANGELOG.md create mode 100644 projects/cloud-foundry-tools/packages/wrapper/CHANGELOG.old.md create mode 100644 projects/cloud-foundry-tools/packages/wrapper/README.md create mode 100644 projects/cloud-foundry-tools/packages/wrapper/nyc.config.js create mode 100644 projects/cloud-foundry-tools/packages/wrapper/package.json create mode 100644 projects/cloud-foundry-tools/packages/wrapper/resources/icon.png create mode 100644 projects/cloud-foundry-tools/packages/wrapper/src/extension.ts create mode 100644 projects/cloud-foundry-tools/packages/wrapper/tests/extension.spec.ts create mode 100644 projects/cloud-foundry-tools/packages/wrapper/tsconfig.json create mode 100644 projects/cloud-foundry-tools/packages/wrapper/webpack.config.js diff --git a/projects/cloud-foundry-tools/README.md b/projects/cloud-foundry-tools/README.md new file mode 100644 index 000000000..aecd25e05 --- /dev/null +++ b/projects/cloud-foundry-tools/README.md @@ -0,0 +1,20 @@ +# Cloud Foundry Tools + +This project, migrated from [SAP/cloud-foundry-tools](https://github.com/SAP/cloud-foundry-tools), contains the VS Code extension that provides tools for easy application development with Cloud Foundry (login, create service, bind service, unbind service, and more) in [SAP Business Application Studio][sap bas] and [VS Code][vscode]. + +## Packages + +- [packages/backend](./packages/backend) — `vscode-cf-tools`: The Cloud Foundry Tools VS Code extension (VSIX). +- [packages/frontend](./packages/frontend) — `cloud-foundry-tools-frontend`: Vue 3 frontend bundled into the extension. +- [packages/wrapper](./packages/wrapper) — `vscode-wing-cf-tools`: Deprecated wrapper extension kept for backward compatibility; redirects users to `vscode-cf-tools`. + +## Support + +Open an [issue](https://github.com/SAP/app-studio-toolkit/issues) on GitHub. + +## Contributing + +See [CONTRIBUTING.md](../../CONTRIBUTING.md). + +[sap bas]: https://help.sap.com/viewer/product/SAP%20Business%20Application%20Studio/Cloud/en-US +[vscode]: https://code.visualstudio.com/ diff --git a/projects/cloud-foundry-tools/packages/backend/.mocharc.js b/projects/cloud-foundry-tools/packages/backend/.mocharc.js new file mode 100644 index 000000000..ff3566669 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/.mocharc.js @@ -0,0 +1,5 @@ +module.exports = { + require: ["source-map-support/register"], + spec: "./out/tests/**/*.spec.js", + timeout: 80000, +}; diff --git a/projects/cloud-foundry-tools/packages/backend/.vscodeignore b/projects/cloud-foundry-tools/packages/backend/.vscodeignore new file mode 100644 index 000000000..8e1acee11 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/.vscodeignore @@ -0,0 +1,9 @@ +# ignore all files: +**/** + +# except for: +!resources +!dist +!LICENSE +!README.md +!package.json \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/CHANGELOG.md b/projects/cloud-foundry-tools/packages/backend/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/projects/cloud-foundry-tools/packages/backend/CHANGELOG.old.md b/projects/cloud-foundry-tools/packages/backend/CHANGELOG.old.md new file mode 100644 index 000000000..8ebb62c95 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/CHANGELOG.old.md @@ -0,0 +1,162 @@ +# Historic Changelog + +> This file contains the changelog history from the standalone [SAP/cloud-foundry-tools](https://github.com/SAP/cloud-foundry-tools) repository, prior to migration into this monorepo. New changes are recorded in this package's `CHANGELOG.md`. + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [8.1.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.2) (2026-06-21) + +### Bug Fixes + +- fall back to direct CF lookup when UPS not found in list ([#311](https://github.com/SAP/cloud-foundry-tools/issues/311)) ([4086b95](https://github.com/SAP/cloud-foundry-tools/commit/4086b95aadcd60b740b1ddb47d378fd2d2ba9fcc)) + +## [8.1.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.1) (2026-06-21) + +### Bug Fixes + +- fall back to direct CF lookup when UPS not found in list ([#311](https://github.com/SAP/cloud-foundry-tools/issues/311)) ([4086b95](https://github.com/SAP/cloud-foundry-tools/commit/4086b95aadcd60b740b1ddb47d378fd2d2ba9fcc)) + +# [8.1.0](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.2...v8.1.0) (2026-02-11) + +### Bug Fixes + +- format ([ffab236](https://github.com/SAP/cloud-foundry-tools/commit/ffab236413168d63f783be9e8f484cb27c0289fb)) + +### Features + +- update lodash dependencies ([e2fd273](https://github.com/SAP/cloud-foundry-tools/commit/e2fd273a9090dda7f54eece3fcac6b00ffac0493)) + +## [8.0.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.1...v8.0.2) (2025-12-09) + +**Note:** Version bump only for package vscode-cf-tools + +## [8.0.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.0...v8.0.1) (2025-08-24) + +**Note:** Version bump only for package vscode-cf-tools + +# [8.0.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.5.2...v8.0.0) (2025-08-20) + +### Features + +- package name and publisher are changed ([#304](https://github.com/sap-staging/cloud-foundry-tools/issues/304)) ([33e586d](https://github.com/sap-staging/cloud-foundry-tools/commit/33e586d67270602128d3c4c17179ac632a7c82a5)) + +### BREAKING CHANGES + +- VS Code Marketplace Publishing Support + +## [7.5.2](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.5.1...v7.5.2) (2025-08-06) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.5.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.5.0...v7.5.1) (2025-06-24) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [7.5.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.5...v7.5.0) (2025-02-10) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.4.5](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.4...v7.4.5) (2024-10-14) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.4.4](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.3...v7.4.4) (2024-05-30) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.4.3](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.2...v7.4.3) (2024-05-05) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.4.2](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.1...v7.4.2) (2023-11-26) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.4.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.4.0...v7.4.1) (2023-11-12) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [7.4.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.3.4...v7.4.0) (2023-10-03) + +### Features + +- migrate to vite.js and upgrade deps ([#271](https://github.com/sap-staging/cloud-foundry-tools/issues/271)) ([6e4381c](https://github.com/sap-staging/cloud-foundry-tools/commit/6e4381cd889da81424da5ce0e11bd5b78b15121c)) +- migrating to Vue3 ([#258](https://github.com/sap-staging/cloud-foundry-tools/issues/258)) ([1bbe976](https://github.com/sap-staging/cloud-foundry-tools/commit/1bbe9764f3af8989d15cd5ba48cb706fc97c83d3)) + +## [7.3.4](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.3.3...v7.3.4) (2023-08-09) + +### Bug Fixes + +- update bas-sdk version ([ade94ba](https://github.com/sap-staging/cloud-foundry-tools/commit/ade94bae6f6992047bfb8e98a9594eb045033cae)) + +## [7.3.3](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.3.2...v7.3.3) (2023-07-09) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.3.2](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.3.1...v7.3.2) (2023-07-06) + +### Bug Fixes + +- update bas-sdk to 3.4.2 version ([a4f721d](https://github.com/sap-staging/cloud-foundry-tools/commit/a4f721dac7a1749b7ad60583114109b411afab5f)) + +## [7.3.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.3.0...v7.3.1) (2023-07-04) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [7.3.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.2.2...v7.3.0) (2023-07-04) + +### Features + +- support new functionality of bas-sdk cfendpoint api ([#260](https://github.com/sap-staging/cloud-foundry-tools/issues/260)) ([071fc6c](https://github.com/sap-staging/cloud-foundry-tools/commit/071fc6cd199c5177017daeada712a7ebb70582ca)), closes [#259](https://github.com/sap-staging/cloud-foundry-tools/issues/259) + +## [7.2.2](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.2.1...v7.2.2) (2023-06-29) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [7.2.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.2.0...v7.2.1) (2023-02-15) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [7.2.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.1.1...v7.2.0) (2023-02-15) + +### Features + +- support bind/unbind with quoted VCAP_SERVICES ([#253](https://github.com/sap-staging/cloud-foundry-tools/issues/253)) ([6cabac6](https://github.com/sap-staging/cloud-foundry-tools/commit/6cabac6a5c5b42435d86a90ce2e78c0f942db55a)) + +## [7.1.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.1.0...v7.1.1) (2022-11-16) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [7.1.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.0.4...v7.1.0) (2022-11-16) + +### Bug Fixes + +- make dropdown control searchable ([#248](https://github.com/sap-staging/cloud-foundry-tools/issues/248)) ([126d27b](https://github.com/sap-staging/cloud-foundry-tools/commit/126d27b7c24da77fc91bf3fac7b59e771a9e5c74)) + +## [7.0.4](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.0.1...v7.0.4) (2022-11-03) + +### Bug Fixes + +- [DEVXBUGS-10581] CF sign doesn't detect the last used API endpoint ([#243](https://github.com/sap-staging/cloud-foundry-tools/issues/243)) ([a2b7c93](https://github.com/sap-staging/cloud-foundry-tools/commit/a2b7c93b24db17bd292d6b30dfc3310f67680d47)) +- adding tooltips to target ([e4f53e4](https://github.com/sap-staging/cloud-foundry-tools/commit/e4f53e4009971ef61f1b5305fcba4b784fb3d5dd)) +- bugs ([dc5d257](https://github.com/sap-staging/cloud-foundry-tools/commit/dc5d2574c86d1f9b1bca01ea44983c1e97151988)) +- panel should dispose in any case ([a5d69fd](https://github.com/sap-staging/cloud-foundry-tools/commit/a5d69fdddd0950f2af584b299865116b27f0d603)) +- remove the important from the style ([a426e5c](https://github.com/sap-staging/cloud-foundry-tools/commit/a426e5cb88df128c623c08dc4cedfcc98d9376a6)) + +## [7.0.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.0.0...v7.0.1) (2022-06-29) + +### Bug Fixes + +- fix login from targets view ([#227](https://github.com/sap-staging/cloud-foundry-tools/issues/227)) ([3b9523d](https://github.com/sap-staging/cloud-foundry-tools/commit/3b9523d8d01551b423699acae5d7db429feec84b)) +- leftovers ([#225](https://github.com/sap-staging/cloud-foundry-tools/issues/225)) ([c6cedcb](https://github.com/sap-staging/cloud-foundry-tools/commit/c6cedcb0a67f40f3040d258b442128cb19fc70cf)) + +# [7.0.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v6.5.0...v7.0.0) (2022-06-28) + +### Features + +- support 2fa in the cf tools ui ([#224](https://github.com/sap-staging/cloud-foundry-tools/issues/224)) ([5ed4d20](https://github.com/sap-staging/cloud-foundry-tools/commit/5ed4d20a88b86df180cf98db3dbf615e5e20ddda)) + +## [6.5.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v6.4.2...v6.5.0) (2022-01-30) diff --git a/projects/cloud-foundry-tools/packages/backend/README.md b/projects/cloud-foundry-tools/packages/backend/README.md new file mode 100644 index 000000000..6257a2b43 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/README.md @@ -0,0 +1,63 @@ +# Cloud Foundry Tools + +This VS Code extension provides tools for easy application development with Cloud Foundry. + +## Features + +- Login to Cloud Foundry +- Create, bind, and unbind services +- Adds a new View-Container in VS Code titled **Cloud Foundry** +- Provides commands for Cloud Foundry development and management + +--- + +## Table of Contents + +- [Features](#features) +- [Prerequisite Dependencies](#prerequisite-dependencies) +- [How to Run Locally](#how-to-run-locally) +- [Logger Preferences](#logger-preferences) +- [Known Issues](#known-issues) +- [Contributing](#contributing) + +--- + +## Prerequisite Dependencies + +- [cf-cli v8.7.4](https://github.com/cloudfoundry/cli/releases/tag/v8.7.4) +- [cf-cli targets plugin](https://github.com/guidowb/cf-targets-plugin) +- `cf-service-info` (cf plugin for resource binding functionality): + + ```sh + npm install -g '@sap/cf-tools-local' + ``` + +--- + +## How to Run Locally + +1. Clone the repository +2. Open it in VS Code +3. Run `yarn` and then `yarn ci:artifacts_only` +4. Launch the extension using the 'Run Extension' launch configuration +5. Access available commands via the Command Palette (prefix: `CF`) + +--- + +## Logger Preferences + +You can configure logger preferences in the extension settings: + +![Screenshot showing the settings of the Cloud Foundry Tools extension](https://raw.githubusercontent.com/SAP/cloud-foundry-tools/main/packages/backend/media/settings.png) + +--- + +## Known Issues + +- Improved failure handling and cancellation options for long-running steps are needed. + +--- + +## Contributing + +See [CONTRIBUTING.md](https://github.com/SAP/cloud-foundry-tools/blob/main/CONTRIBUTING.md) for contribution guidelines. diff --git a/projects/cloud-foundry-tools/packages/backend/media/run-ext.png b/projects/cloud-foundry-tools/packages/backend/media/run-ext.png new file mode 100644 index 0000000000000000000000000000000000000000..3cbfc3a3e35cdd33fa509cb1c89d0eb328a99ae9 GIT binary patch literal 10811 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 F{{h}2{wx3h literal 0 HcmV?d00001 diff --git a/projects/cloud-foundry-tools/packages/backend/media/settings.png b/projects/cloud-foundry-tools/packages/backend/media/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..92456750f09e478dd6ddb78cfbb5cf343f5efaae GIT binary patch literal 21390 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 IWZ3n80aM7;YybcN literal 0 HcmV?d00001 diff --git a/projects/cloud-foundry-tools/packages/backend/nyc.config.js b/projects/cloud-foundry-tools/packages/backend/nyc.config.js new file mode 100644 index 000000000..6000fd3ad --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/nyc.config.js @@ -0,0 +1,10 @@ +const baseNycConfig = require("../../nyc.config"); + +module.exports = Object.assign(baseNycConfig, { + branches: 90, + lines: 95, + functions: 91, + statements: 95, + include: ["**/src/**"], + exclude: ["**/src/run-configuration-*", "**/src/sql-tools.ts", "**/src/logger/logger-wrapper.ts", "**/usage/*.ts"], +}); diff --git a/projects/cloud-foundry-tools/packages/backend/package.json b/projects/cloud-foundry-tools/packages/backend/package.json new file mode 100644 index 000000000..f20f97476 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/package.json @@ -0,0 +1,366 @@ +{ + "name": "vscode-cf-tools", + "displayName": "Cloud Foundry Tools", + "version": "8.1.2", + "private": true, + "description": "Vscode extension of Cloud Foundry tools", + "categories": [ + "Other" + ], + "bugs": { + "url": "https://github.com/SAP/cloud-foundry-tools.git/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/SAP/cloud-foundry-tools.git" + }, + "license": "Apache 2.0", + "publisher": "SAPOSS", + "main": "./dist/extension.js", + "scripts": { + "build": "npm-run-all clean compile webpack-dev frontend:all", + "bundle": "webpack --mode production", + "ci": "npm-run-all clean compile copy:test-resources coverage bundle frontend:all package", + "ci:artifacts_only": "npm-run-all clean compile webpack-dev frontend:all package", + "clean": "npm-run-all clean:dist clean:out clean:vsix", + "clean:dist": "rimraf ./dist", + "clean:frontend": "cd dist/ && shx rm -rf ./media", + "clean:out": "rimraf ./out", + "clean:vsix": "rimraf ./*.vsix", + "compile": "tsc -p ./", + "copy:test-resources": "shx cp -r ./tests/resources ./out/tests/resources", + "coverage": "nyc mocha --colors", + "frontend:all": "cd ../frontend && npm-run-all clean build && cd ../backend && npm run frontend:copy", + "frontend:copy": "npm-run-all clean:frontend && shx cp -r ../frontend/dist/. ./dist/media/", + "package": "vsce package --yarn", + "test": "mocha --colors", + "watch": "tsc -watch -p ./", + "webpack-dev": "webpack --mode development", + "webpack-dev:watch": "webpack --mode development --watch" + }, + "contributes": { + "commands": [ + { + "command": "cf.login", + "category": "CF", + "title": "Login to Cloud Foundry", + "icon": { + "light": "resources/light/login.svg", + "dark": "resources/dark/login.svg" + } + }, + { + "command": "cf.login.weak", + "category": "CF", + "title": "Login to Cloud Foundry", + "icon": { + "light": "resources/light/login.svg", + "dark": "resources/dark/login.svg" + } + }, + { + "command": "cf.set.orgspace", + "category": "CF", + "title": "Set Org and Space" + }, + { + "command": "cf.select.space", + "category": "CF", + "title": "Select a space from your allowed spaces" + }, + { + "command": "cf.targets.create", + "category": "CF", + "title": "Create a Cloud Foundry target", + "icon": { + "light": "resources/light/add.svg", + "dark": "resources/dark/add.svg" + } + }, + { + "command": "cf.targets.reload", + "category": "CF", + "title": "Reload Targets Tree", + "icon": { + "light": "resources/light/reload.svg", + "dark": "resources/dark/reload.svg" + } + }, + { + "command": "cf.target.set", + "category": "CF", + "title": "Set Cloud Foundry Target", + "icon": { + "light": "resources/light/set-target.svg", + "dark": "resources/dark/set-target.svg" + } + }, + { + "command": "cf.target.delete", + "category": "CF", + "title": "Delete Cloud Foundry Target" + }, + { + "command": "cf.services.bind.local", + "category": "CF", + "title": "Bind a service to a locally run application" + }, + { + "command": "cf.services.unbind", + "title": "Unbind a service from a locally run application - (Hidden from command palette)" + }, + { + "command": "cf.services.bind", + "title": "Bind a service to a locally run application - (Hidden from command palette)" + }, + { + "command": "cf.services.binding.state", + "title": "Get bind state to a service - (Hidden from command palette)" + }, + { + "command": "cf.services.create", + "category": "CF", + "title": "Create a service instance", + "icon": { + "light": "resources/light/add.svg", + "dark": "resources/dark/add.svg" + } + }, + { + "command": "cf.ups.create", + "category": "CF", + "title": "Create a user provided service instance" + }, + { + "command": "cf.deploy-service.api", + "category": "CF", + "title": "Make a rest API call to the Deploy Service" + }, + { + "command": "cf.services.get-space-services", + "category": "CF", + "title": "Fetch available services (and ups) from particular space" + }, + { + "command": "cf.services.get-ups-services", + "category": "CF", + "title": "Fetch available User Provided services from particular space" + }, + { + "command": "cf.services.get-services", + "category": "CF", + "title": "Fetch available services from particular space" + } + ], + "configuration": { + "type": "object", + "title": "CloudFoundryTools", + "properties": { + "CloudFoundryTools.logging.loggingLevel": { + "type": "string", + "enum": [ + "off", + "fatal", + "error", + "warn", + "info", + "debug", + "trace" + ], + "default": "error", + "description": "The verbosity of logging. The Order is None < fatal < error < warn < info < debug < trace.", + "scope": "resource" + }, + "CloudFoundryTools.logging.sourceLocationTracking": { + "type": "boolean", + "default": false, + "description": "Should Source Code Location Info be added to log entries, DANGER - May be very slow, only use in debugging scenarios", + "scope": "resource" + }, + "CloudFoundryTools.showTargetInformation": { + "type": "boolean", + "default": false, + "description": "Display the current Cloud Foundry target information in the status bar", + "scope": "resource" + } + } + }, + "menus": { + "commandPalette": [ + { + "when": "false", + "command": "cf.login.weak" + }, + { + "when": "false", + "command": "cf.target.delete" + }, + { + "when": "false", + "command": "cf.targets.create" + }, + { + "when": "false", + "command": "cf.deploy-service.api" + }, + { + "when": "false", + "command": "cf.target.set" + }, + { + "when": "false", + "command": "cf.services.unbind" + }, + { + "when": "false", + "command": "cf.services.bind" + }, + { + "when": "false", + "command": "cf.services.binding.state" + }, + { + "when": "false", + "command": "cf.services.get-space-services" + }, + { + "when": "false", + "command": "cf.services.get-ups-services" + }, + { + "when": "false", + "command": "cf.services.get-services" + } + ], + "view/title": [ + { + "command": "cf.targets.create", + "when": "view == cfView", + "group": "navigation@1" + }, + { + "command": "cf.targets.reload", + "when": "view == cfView", + "group": "navigation@2" + } + ], + "view/item/context": [ + { + "command": "cf.services.create", + "when": "view == cfView && viewItem =~ /^services-active$/", + "group": "inline" + }, + { + "command": "cf.login", + "when": "view == cfView && viewItem == cf-login-required", + "group": "inline" + }, + { + "command": "cf.services.bind.local", + "when": "view == cfView && viewItem == cf-service" + }, + { + "command": "cf.target.set", + "when": "view == cfView && viewItem =~ /^cf-target$|^cf-target-not-current$/", + "group": "inline" + }, + { + "command": "cf.target.set", + "when": "view == cfView && viewItem =~ /^cf-target$/", + "group": "appearance@1" + }, + { + "command": "cf.target.delete", + "when": "view == cfView && viewItem =~ /^cf-target(-active)?$/", + "group": "appearance@2" + } + ] + }, + "problemMatchers": [ + { + "name": "chisel-client", + "pattern": [ + { + "regexp": "!!!DO NOT MATCH ME!!!", + "file": 1, + "location": 2, + "message": 3 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "^.+client: Connecting.+", + "endsPattern": "^.+client: Connected.+" + } + } + ], + "views": { + "cf-view": [ + { + "id": "cfView", + "name": "Targets" + } + ] + }, + "viewsContainers": { + "activitybar": [ + { + "id": "cf-view", + "title": "Cloud Foundry", + "icon": "resources/common/cf.svg" + } + ] + } + }, + "activationEvents": [ + "onStartupFinished" + ], + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "dependencies": { + "@sap-devx/webview-rpc": "0.4.1", + "@sap/bas-sdk": "^3.13.2", + "@sap/cf-tools": "^3.2.2", + "@sap/swa-for-sapbas-vsx": "^2.0.7", + "@sap/wing-run-config-types": "^4.2.0", + "@vscode-logging/types": "2.0.0", + "@vscode-logging/wrapper": "2.0.0", + "cheerio": "^1.0.0", + "comment-json": "^4.2.2", + "lodash": "4.17.21", + "properties-reader": "2.3.0" + }, + "devDependencies": { + "@types/cheerio": "^0.22.35", + "@types/comment-json": "2.4.2", + "@types/node": "^14.14.6", + "@types/properties-reader": "^2.1.3", + "@types/proxyquire": "^1.3.28", + "@types/sinon": "^17.0.3", + "@types/vscode": "^1.43.0", + "@vscode-logging/types": "^2.0.0", + "clean-webpack-plugin": "^4.0.0", + "mocha": "^9.2.2", + "proxyquire": "2.1.3", + "sinon": "^19.0.2", + "standard-version": "^9.5.0", + "temp-dir": "^2.0.0", + "ts-loader": "^9.2.8", + "ts-node": "^10.7.0", + "vsce": "1.100.1", + "webpack": "^5.94.0", + "webpack-cli": "^4.9.2" + }, + "engines": { + "vscode": "^1.50.0" + }, + "icon": "resources/icon.png" +} diff --git a/projects/cloud-foundry-tools/packages/backend/resources/common/cf.svg b/projects/cloud-foundry-tools/packages/backend/resources/common/cf.svg new file mode 100644 index 000000000..570153e86 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/common/cf.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/resources/common/login.png b/projects/cloud-foundry-tools/packages/backend/resources/common/login.png new file mode 100644 index 0000000000000000000000000000000000000000..c1b0678bb7be58015a8c05d464b0c3346b800746 GIT binary patch literal 3213 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 literal 0 HcmV?d00001 diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/add.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/add.svg new file mode 100644 index 000000000..53941283b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/add.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/info.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/info.svg new file mode 100644 index 000000000..54a1b8d7b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/info.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/login.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/login.svg new file mode 100644 index 000000000..36d335428 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/login.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/reload.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/reload.svg new file mode 100644 index 000000000..d79fdaa4e --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/reload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/set-target.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/set-target.svg new file mode 100644 index 000000000..7265ba2d2 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/set-target.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/target-a.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/target-a.svg new file mode 100644 index 000000000..fc8a755ca --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/target-a.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/dark/target.svg b/projects/cloud-foundry-tools/packages/backend/resources/dark/target.svg new file mode 100644 index 000000000..a8ca9329a --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/dark/target.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/icon.png b/projects/cloud-foundry-tools/packages/backend/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8ee6692f7f7d2a1dd85a310439d940027e6a59 GIT binary patch literal 7254 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 + + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/info.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/info.svg new file mode 100644 index 000000000..54a1b8d7b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/info.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/login.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/login.svg new file mode 100644 index 000000000..d9e9408d3 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/login.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/reload.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/reload.svg new file mode 100644 index 000000000..e03457481 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/reload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/set-target.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/set-target.svg new file mode 100644 index 000000000..9e0c1cd4c --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/set-target.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/target-a.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/target-a.svg new file mode 100644 index 000000000..666928674 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/target-a.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/resources/light/target.svg b/projects/cloud-foundry-tools/packages/backend/resources/light/target.svg new file mode 100644 index 000000000..a8ca9329a --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/resources/light/target.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/projects/cloud-foundry-tools/packages/backend/src/cfView.ts b/projects/cloud-foundry-tools/packages/backend/src/cfView.ts new file mode 100644 index 000000000..aec732855 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/cfView.ts @@ -0,0 +1,194 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import * as _ from "lodash"; +import { cfGetApps, cfGetServiceInstancesList, cfGetTargets, CFTarget } from "@sap/cf-tools"; +let cfView: CFView; + +export type CFTreeChildNode = CFMessageNode | CFService | CFApplication | CFFolder | CFTargetTI; +export class CFTargetTI extends vscode.TreeItem { + public contextValue = `cf-target${ + _.includes(this.target.label, "(no targets)") ? "-notargets" : this.target.isCurrent ? "-active" : "" + }`; + + constructor(public readonly target: CFTarget) { + super( + target.label, + target.isCurrent ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed + ); + this.tooltip = target.label; + this.iconPath = { + light: path.join(__dirname, "..", "resources", "light", `target${this.target.isCurrent ? "-a" : ""}.svg`), + dark: path.join(__dirname, "..", "resources", "dark", `target${this.target.isCurrent ? "-a" : ""}.svg`), + }; + } + + // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#properties-overriding-accessors-and-vice-versa-is-an-error + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + get description(): string { + return `${_.includes(this.contextValue, "-active") ? "(Active Target)" : ""}${this.target.isDirty ? "*" : ""}`; + } +} + +export class CFMessageNode extends vscode.TreeItem { + public iconPath = { + light: path.join(__dirname, "..", "resources", "light", "info.svg"), + dark: path.join(__dirname, "..", "resources", "dark", "info.svg"), + }; + constructor(label: string, public readonly parent: CFFolder) { + super(label, vscode.TreeItemCollapsibleState.None); + } +} + +export class CFLoginNode extends CFMessageNode { + public contextValue = "cf-login-required"; + constructor(parent: CFFolder) { + super(`Login required`, parent); + this.tooltip = `Log in to see details`; + } +} + +export class CFTargetNotCurrent extends CFMessageNode { + public contextValue = "cf-target-not-current"; + + constructor(parent: CFFolder, targetName?: string) { + super(`Target needs to be active`, parent); + this.tooltip = `Set the '${targetName}' as the active target to see details.`; + } +} + +export class CFService extends vscode.TreeItem { + public iconPath = vscode.ThemeIcon.File; + public contextValue = "cf-service"; + + constructor(public readonly label: string, public readonly type: string, public readonly parent: CFFolder) { + super(label, vscode.TreeItemCollapsibleState.None); + } +} + +export class CFApplication extends vscode.TreeItem { + public iconPath = vscode.ThemeIcon.File; + public contextValue = "cf-application"; + + constructor(public readonly label: string, public readonly state: string, public readonly parent: CFFolder) { + super(label, vscode.TreeItemCollapsibleState.None); + } +} + +export class CFFolder extends vscode.TreeItem { + public iconPath = vscode.ThemeIcon.Folder; + + constructor(public readonly label: string, public readonly parent: CFTargetTI) { + super(label, vscode.TreeItemCollapsibleState.Collapsed); + this.tooltip = `${label}`; + } +} + +export class CFServicesFolder extends CFFolder { + constructor(public readonly label: string, parent: CFTargetTI) { + super(label, parent); + this.contextValue = `services${parent.target.isCurrent ? "-active" : ""}`; + } +} + +export class CFAppsFolder extends CFFolder { + constructor(public readonly label: string, parent: CFTargetTI) { + super(label, parent); + this.contextValue = `apps${parent.target.isCurrent ? "-active" : ""}`; + } +} + +export class CFView implements vscode.TreeDataProvider { + public static get(): CFView { + return cfView; + } + + public readonly privateOnDidChangeTreeData: vscode.EventEmitter< + vscode.TreeItem | undefined + > = new vscode.EventEmitter(); + public readonly onDidChangeTreeData: vscode.Event = this.privateOnDidChangeTreeData + .event; + public targets?: CFTargetTI[]; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(context: vscode.ExtensionContext, configFilePath: string) { + // this.subscribeOnFileEventsAndRefresh(context, configFilePath); + this.refresh(); + cfView = this; + } + + public refresh(): void { + this.privateOnDidChangeTreeData.fire(undefined); + } + + public getParent(element: vscode.TreeItem): vscode.ProviderResult { + return _.get(element, "parent") as vscode.TreeItem; + } + + public getTreeItem(element: vscode.TreeItem): Promise { + if (/^cf-(service|application)$/.test(element.contextValue || "")) { + const item = _.cloneDeep(element); + item.label = + element.contextValue === "cf-application" + ? `${element.label?.toString()} (${(element as CFApplication).state})` + : `${element.label?.toString()} (${(element as CFService).type})`; + return Promise.resolve(item); + } + return Promise.resolve(element); + } + + public async getChildren(parent: vscode.TreeItem): Promise { + if (parent) { + if (parent instanceof CFTargetTI) { + return [new CFServicesFolder("Services", parent), new CFAppsFolder("Applications", parent)]; + } else if (/^(services|apps)/.test(parent.contextValue || "")) { + if (/-active$/.test(parent.contextValue || "")) { + try { + return parent instanceof CFAppsFolder + ? /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + _.map(await cfGetApps(), (app) => new CFApplication(app.name, _.get(app, "state"), parent)) + : _.map( + await cfGetServiceInstancesList(), + (service) => new CFService(service.label, service.serviceName, parent as CFFolder) + ); + } catch (e) { + return [new CFLoginNode(parent as CFFolder)]; + } + } else { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + return [new CFTargetNotCurrent(parent as CFFolder, _.get(parent, "parent.target.label"))]; + } + } + return []; + } + // return list of targets + this.targets = _.map(await cfGetTargets(), (target) => new CFTargetTI(target)); + return this.targets; + } + + public getCurrentTarget(): CFTarget | undefined { + return _.get(_.find(this.targets, "target.isCurrent"), "target"); + } + + public getTargets(): CFTargetTI[] | undefined { + return this.targets; + } + + // keep temporary as a reference for future usage + // private subscribeOnFileEventsAndRefresh(context: vscode.ExtensionContext, configFilePath: string) { + // if (_.size(configFilePath) > 0) { + // const fsw = vscode.workspace.createFileSystemWatcher(configFilePath); + // context.subscriptions.push(fsw.onDidChange(() => this.refresh())); + // context.subscriptions.push(fsw.onDidCreate(() => this.refresh())); + // context.subscriptions.push(fsw.onDidDelete(() => this.refresh())); + // } + // } +} + +export function getTargetRoot(node: CFTreeChildNode | undefined): CFTargetTI { + let item = node; + while (_.get(item, "parent")) { + item = _.get(item, "parent") as CFTreeChildNode; // walk up until target folder found + } + return item as CFTargetTI; +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts b/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts new file mode 100644 index 000000000..13a04ec84 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts @@ -0,0 +1,617 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { CFView, CFService, CFTargetTI, CFTargetNotCurrent, getTargetRoot } from "./cfView"; +import { messages } from "./messages"; +import * as https from "https"; +import { + updateGitIgnoreList, + isWindows, + toText, + UpsServiceQueryOprions, + ServiceQueryOptions, + resolveFilterValue, +} from "./utils"; +import { + DEFAULT_TARGET, + ServiceInstanceInfo, + IServiceQuery, + eFilters, + Cli, + cfGetConfigFileField, + cfBindLocalServices, + ServiceTypeInfo, + cfBindLocalUps, + cfGetInstanceMetadata, + cfGetAuthToken, + padQuery, + eServiceTypes, + CliResult, +} from "@sap/cf-tools"; +import * as _ from "lodash"; +import { + getAvailableServices, + updateServicesOnCFPageSize, + isServiceTypeInfoInArray, + updateInstanceNameAndTags, + getInstanceName, + fetchServicePlanList, + CMD_CREATE_SERVICE, + verifyLoginRetry, + getUserProvidedServiceInstances, + getServiceInstances, + CMD_BIND_TO_DEFAULT_SERVICE, +} from "./commands"; +import { stringify } from "comment-json"; +import { checkAndCreateChiselTask, deleteChiselParamsFromFile } from "./chisel"; +import { getModuleLogger } from "./logger/logger-wrapper"; + +const YES = "Yes"; +const NO = "No"; +const CANCEL = "Cancel"; +const LOGGER_MODULE = "cfViewCommands"; + +interface BindDetails { + instances: ServiceInstanceInfo[]; + tags?: string[]; + keyNames?: string[]; + serviceKeyParams?: unknown[]; +} + +interface BindLocalData { + instanceName: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + chiselTask?: any; +} + +export interface CmdOptions { + silent?: boolean; + "skip-reload"?: boolean; + "quote-vcap"?: boolean; +} + +export function cmdReloadTargets(): Promise { + return Promise.resolve(CFView.get().refresh()); +} + +async function execHttp(options: https.RequestOptions): Promise { + return new Promise((resolve, reject) => { + https + .get(options, (resp) => { + let data = ""; + // A chunk of data has been recieved. + resp.on("data", (chunk) => { + data += chunk; + }); + // The whole response has been received. Print out the result. + resp.on("end", () => { + resolve(data); + }); + }) + .on("error", (err) => { + getModuleLogger(LOGGER_MODULE).error("execHttp: get failed", { error: toText(err) }, { host: options.host }); + reject(err.message); + }); + }); +} + +async function askUserForPath(): Promise { + return vscode.window.showOpenDialog({ + openLabel: "Select folder for .env file", + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined, + }); +} + +async function getToBindInstances( + serviceInfos: ServiceTypeInfo[], + availableServices: ServiceInstanceInfo[], + instanceNames: string[], + tags: string[] +) { + for (const serviceTypeInfo of serviceInfos) { + await updateInstanceNameAndTags(availableServices, serviceTypeInfo, instanceNames, tags); + } +} + +type TEnvPath = { + path: vscode.Uri; + ignore?: boolean; +}; + +type BindArgs = { + instances: ServiceInstanceInfo[]; + envPath: TEnvPath; + tags?: string[]; + serviceKeyNames?: string[]; + serviceKeyParams?: unknown[]; + options?: CmdOptions; +}; + +async function doBind(opts: BindArgs) { + async function withProgress(cb: () => Thenable, services: string[]) { + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }, + cb + ); + const serviceNames = _.join(services, ","); + if (!opts.options?.silent) { + void vscode.window.showInformationMessage(messages.service_bound_successful(serviceNames)); + } + getModuleLogger(LOGGER_MODULE).info("The service %s has been bound.", serviceNames); + } + + const ups = _.filter(opts.instances, ["serviceName", eServiceTypes.user_provided]); + const services = _.difference(opts.instances, ups); + if (_.size(services)) { + const labels = _.map(services, "label"); + const cb = () => + cfBindLocalServices( + opts.envPath.path.fsPath, + labels, + opts.tags, + opts.serviceKeyNames, + opts.serviceKeyParams, + opts.options?.["quote-vcap"] + ); + await withProgress(cb, labels); + } + if (_.size(ups)) { + const labels = _.map(ups, "label"); + const cb = () => cfBindLocalUps(opts.envPath.path.fsPath, labels, opts.tags, opts.options?.["quote-vcap"]); + await withProgress(cb, labels); + } + if (!opts.envPath.ignore) { + void updateGitIgnoreList(opts.envPath.path.fsPath); + } +} + +async function getServiceInstanceInfo(instanceName: string): Promise { + const data = await cfGetInstanceMetadata(instanceName); + return { + label: _.get(data, "serviceName"), + serviceName: _.get(data, "service"), + plan_guid: _.get(data, "plan_guid"), + plan: _.get(data, "plan"), + }; +} + +export async function cfDeployServiceAPI(urlPath: string): Promise { + const cfApi: string = await cfGetConfigFileField("Target"); + if (_.size(cfApi) > 0) { + // https://api.cf. + const deployServiceUrl = cfApi.replace("api.cf", "deploy-service.cfapps"); + + const accessToken = (await cfGetAuthToken()).replace("\n", "").replace("\n", ""); // lgtm [js/incomplete-sanitization] + + const urlObj = new URL(deployServiceUrl); + + const options = { + protocol: urlObj.protocol, + host: urlObj.host, + port: urlObj.port ? urlObj.port : urlObj.protocol === "https:" ? 443 : 80, + method: "get", + path: urlPath ? urlPath : urlObj.pathname, + headers: { Authorization: accessToken }, + }; + + return execHttp(options); + } + + getModuleLogger(LOGGER_MODULE).error("No CF API endpoint", { path: urlPath }); + throw new Error(messages.no_cf_api_endpoint); +} + +export async function cmdDeployServiceAPI(servicePath: string, message: string): Promise { + if (await verifyLoginRetry()) { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: message ? message : messages.load_data_from_deploy, + cancellable: false, + }, + () => cfDeployServiceAPI(servicePath) + ); + } +} + +export async function execSetTarget(item: CFTargetTI, options?: CmdOptions): Promise { + const response: CliResult = await Cli.execute(["set-target", "-f", item.target.label]); + if (response.exitCode !== 0) { + if (!options?.silent) { + void vscode.window.showErrorMessage(response.stdout); + } + getModuleLogger(LOGGER_MODULE).error(`execSetTarget:: run 'set-target -f' with lable ${item.target.label} failed`, { + output: response.stdout, + }); + } else { + if (!options?.["skip-reload"]) { + await cmdReloadTargets(); + } + } +} + +export async function execSaveTarget(item?: CFTargetTI, options?: CmdOptions): Promise { + if (item?.contextValue !== "cf-target-notargets") { + const response: CliResult = await Cli.execute( + _.concat(["save-target"], item?.target.label ? ["-f", item.target.label] : []) + ); + if (response.exitCode !== 0) { + if (!options?.silent) { + void vscode.window.showErrorMessage(response.stdout); + } + getModuleLogger(LOGGER_MODULE).error( + `execSaveTarget:: run 'save-target -f' with lable ${item?.target.label} failed`, + { + output: response.stdout, + } + ); + } + } +} + +export async function cmdSetCurrentTarget( + newTarget: CFTargetTI | CFTargetNotCurrent | undefined +): Promise { + const item = getTargetRoot(newTarget); + if (false === item?.target?.isCurrent) { + let answer = YES; + try { + const currTarget = CFView.get().getCurrentTarget(); + if (currTarget?.isDirty) { + answer = await vscode.window + .showWarningMessage(messages.target_dirty_save(currTarget.label), YES, NO, CANCEL) + .then((selection) => { + if (selection === YES) { + return execSaveTarget().then(() => selection); + } else if (selection) { + return NO; + } + // if ESC was pressend and selection is undefined + return CANCEL; + }); + } + + if (answer === CANCEL) { + return; + } + + return execSetTarget(item); + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(LOGGER_MODULE).error( + `cmdSetCurrentTargetCommand with new target ${stringify(newTarget)} exception thrown`, + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + { error: toText(e) } + ); + } + } +} + +export async function cmdDeleteTarget(item: CFTargetTI, options?: CmdOptions): Promise { + const targetLabel = item.target.label; + if (targetLabel === DEFAULT_TARGET) { + return; + } + const cliResult = await Cli.execute(["delete-target", targetLabel]); + if (cliResult.exitCode === 0) { + if (!options?.["skip-reload"]) { + await cmdReloadTargets(); + } + if (!options?.silent) { + void vscode.window.showInformationMessage(messages.target_deleted(targetLabel)); + } + getModuleLogger(LOGGER_MODULE).debug(`cmdDeleteTarget:: command "delete-target" of ${targetLabel} succeeded.`); + } else { + void vscode.window.showErrorMessage(cliResult.stdout); + getModuleLogger(LOGGER_MODULE).error(`cmdSetCurrentTargetCommand:: run 'delete-target of ${targetLabel} failed`, { + output: cliResult.stdout, + }); + } +} + +export async function cmdGetSpaceServices( + query?: IServiceQuery, + progressTitle?: string +): Promise { + _.each(query?.filters, (filter) => { + filter.value = encodeURIComponent(filter.value); + }); + return getAvailableServices({ query, ups: { isShow: true } }, progressTitle); +} + +async function composeQueryToObtainInstances(serviceInfos: ServiceTypeInfo[]): Promise { + let query; + if (_.get(serviceInfos, ["0", "plan"])) { + query = padQuery((query as unknown) as IServiceQuery, [ + { key: eFilters.service_plan_names, value: resolveFilterValue(serviceInfos[0].plan) }, + ]); + } + if (/*!_.size(plans) &&*/ _.get(serviceInfos, "[0].name")) { + if (eServiceTypes.user_provided === serviceInfos[0].name) { + query = padQuery((query as unknown) as IServiceQuery, [ + { key: eFilters.service_plan_names, value: "nothing-to-show" }, + ]); // tricky - to show the only user-provided instances + } else { + const guids = _.map( + await fetchServicePlanList({ + filters: [{ key: eFilters.service_offering_names, value: resolveFilterValue(serviceInfos[0].name) }], + }), + "guid" + ); + query = padQuery((query as unknown) as IServiceQuery, [ + { key: eFilters.service_plan_guids, value: _.join(guids) }, + ]); + } + } + return query; +} + +async function collectBindDetails( + service: CFService | ServiceTypeInfo[], + requstedInstance?: string +): Promise { + let details; + + if (_.get(service, "contextValue") === "cf-service" && _.get(service, "label")) { + details = { + instances: [{ label: (service as CFService).label, serviceName: (service as CFService).type || "unknown" }], + }; + } else { + // service is ServiceTypeInfo + const query = await composeQueryToObtainInstances(service as ServiceTypeInfo[]); + let availableServices = await getAvailableServices({ query, ups: _.get(service, ["0", "ups"]) }); + if (_.isEmpty(availableServices)) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + getModuleLogger(LOGGER_MODULE).debug(`No services found for plan ${_.get(service, "[0].plan")}`); + if (!_.find(service as ServiceTypeInfo[], "allowCreate")) { + void vscode.window.showInformationMessage(messages.no_services_instances_found); + return details; + } + } + + updateServicesOnCFPageSize(availableServices); + + if (isServiceTypeInfoInArray(service)) { + const instanceNames: string[] = []; + const tags: string[] = []; + const serviceTypeInfos = service as ServiceTypeInfo[]; + if (!requstedInstance) { + for (const serviceTypeInfo of serviceTypeInfos) { + if (serviceTypeInfo.allowCreate) { + // add 'create service' menu item + availableServices = _.concat([{ label: CMD_CREATE_SERVICE, serviceName: "" }], availableServices); + if (_.size(availableServices) && !_.isEmpty(serviceTypeInfo.allowCreate.name)) { + // add 'Bind to the default service instance' menu item + availableServices = _.concat( + [ + { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + label: `${CMD_BIND_TO_DEFAULT_SERVICE}${serviceTypeInfo.allowCreate.name}`, + plan: serviceTypeInfo.allowCreate.plan, + serviceName: serviceTypeInfo.allowCreate.serviceName ?? "", + }, + ], + availableServices + ); + } + } + await updateInstanceNameAndTags(availableServices, serviceTypeInfo, instanceNames, tags); + } + } else { + let foundInstance = _.find(availableServices, ["label", requstedInstance]); + if (!foundInstance) { + foundInstance = await getServiceInstanceInfo(requstedInstance).catch(() => undefined); + if (!foundInstance) { + throw new Error(messages.no_services_instance_byname_found(requstedInstance)); + } + availableServices.push(foundInstance); + } + instanceNames.push(foundInstance.label); + tags.push(serviceTypeInfos[0].tag); + } + + if (_.size(instanceNames) > 0) { + const serviceKeyNames: string[] = _.compact( + _.flatMap(serviceTypeInfos, (serviceTypeInfo) => serviceTypeInfo.serviceKeyName) + ); + const serviceKeyParams: unknown[] = _.compact(_.map(serviceTypeInfos, "serviceKeyParam")); + const instances = []; + for (const name of instanceNames) { + let serviceInstance = _.find(availableServices, ["label", name]); + // seems like it just created service and it is needed to fetch it's info + if (!serviceInstance) { + serviceInstance = await getServiceInstanceInfo(name); + } + instances.push(serviceInstance); + } + details = { + instances: _.compact(instances), + tags: _.compact(tags), + keyNames: serviceKeyNames, + serviceKeyParams, + }; + } + } else { + const instanceName = await getInstanceName(availableServices); + if (instanceName) { + details = { instances: [{ label: instanceName, serviceName: "unknown" }] }; + } + } + } + return details; +} + +class EnvPathHelper { + static getPath(env: vscode.Uri | TEnvPath): vscode.Uri { + return _.get(env, "fsPath") + ? (env as vscode.Uri) + : _.get(env, "path") + ? (env.path as vscode.Uri) + : vscode.Uri.file(""); + } + static getIgnore(env: vscode.Uri | TEnvPath): boolean { + return (_.get(env, "ignore") as boolean) || false; + } + static isPathEmpty(value: vscode.Uri): boolean { + return value.scheme === "file" && value.fsPath === (isWindows ? "\\" : "/"); + } +} + +export async function cmdBindLocal( + service: CFService | ServiceTypeInfo[], + envPath: vscode.Uri | TEnvPath, + instanceName?: string, + opts?: CmdOptions +): Promise { + // Handle .env path + let filePath = EnvPathHelper.getPath(envPath); + if (EnvPathHelper.isPathEmpty(filePath)) { + const uriArray = await askUserForPath(); + if (_.size(uriArray) === 0) { + // aborted + return; + } + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + filePath = vscode.Uri.file(path.join(_.get(uriArray, "[0].fsPath"), ".env")); + } + try { + const bindDetails = await collectBindDetails(service, instanceName); + if (bindDetails) { + await doBind({ + instances: bindDetails.instances, + envPath: { path: filePath, ignore: EnvPathHelper.getIgnore(envPath) }, + tags: bindDetails.tags, + serviceKeyNames: bindDetails.keyNames, + serviceKeyParams: bindDetails.serviceKeyParams, + options: opts, + }); + const instanceName = _.get(_.head(bindDetails.instances), "label"); + if (instanceName) { + const chiselTask = await checkAndCreateChiselTask(filePath.fsPath, instanceName); + if (chiselTask) { + await deleteChiselParamsFromFile(filePath.fsPath); + } + return chiselTask ? { instanceName, chiselTask } : { instanceName }; + } + } + } catch (e) { + if (e) { + // login to cf canceled by user + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(LOGGER_MODULE).error( + `cmdBindLocal exception thrown`, + { error: toText(new Error(e?.message as string)) }, + { service: service }, + { envPath: filePath }, + { instanceName: instanceName } + ); + } + } + return; +} + +export async function bindLocalService( + serviceInfos: ServiceTypeInfo[], + envPath: vscode.Uri | TEnvPath, + opts?: CmdOptions +): Promise { + try { + const filePath = EnvPathHelper.getPath(envPath); + if (EnvPathHelper.isPathEmpty(filePath)) { + return []; + } + const query = await composeQueryToObtainInstances(serviceInfos); + const availableServices: ServiceInstanceInfo[] = await getAvailableServices({ query }); + if (_.isEmpty(availableServices)) { + if (!_.isEmpty(_.get(serviceInfos, ["0", "plan"]))) { + getModuleLogger(LOGGER_MODULE).debug(`No service found for the plan ${serviceInfos[0].plan}`); + } + if (!_.find(serviceInfos, "allowCreate")) { + void vscode.window.showInformationMessage(messages.no_services_instances_found); + } + return []; + } + + updateServicesOnCFPageSize(availableServices); + + if (isServiceTypeInfoInArray(serviceInfos)) { + const instanceNames: string[] = []; + const tags: string[] = []; + await getToBindInstances(serviceInfos, availableServices, instanceNames, tags); + const instances = _.reduce( + instanceNames, + (result, name) => { + result.push(_.find(availableServices, ["label", name])); + return result; + }, + <(ServiceInstanceInfo | undefined)[]>[] + ); + if (_.size(instanceNames) > 0) { + await doBind({ + instances: _.compact(instances), + envPath: { path: filePath, ignore: EnvPathHelper.getIgnore(envPath) }, + tags: _.compact(tags), + options: opts, + }); + return instanceNames; + } + } else { + const instanceName = await getInstanceName(availableServices); + if (instanceName) { + const service = _.find(availableServices, ["label", instanceName]); + await doBind({ + instances: service ? [service] : [], + envPath: { path: filePath, ignore: EnvPathHelper.getIgnore(envPath) }, + options: opts, + }); + return [instanceName]; + } + } + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(LOGGER_MODULE).error( + `bindLocalService exception thrown`, + { error: toText(new Error(e?.message as string)) }, + { serviceInfos: serviceInfos }, + { envPath: EnvPathHelper.getPath(envPath) } + ); + } +} + +export async function cmdGetUpsServiceInstances( + options?: UpsServiceQueryOprions, + progressTitle?: string +): Promise { + return getUserProvidedServiceInstances(options, progressTitle); +} + +export async function cmdGetServiceInstances( + serviceQueryOptions?: ServiceQueryOptions, + progressTitle?: string +): Promise { + let query: IServiceQuery | undefined; + if (serviceQueryOptions) { + // prepare a query to filter the service list by service plan (e.g.: hdi-shared) + // filter the list by service type name (e.g.: hana, hanatrial) + const serviceInfo = { + name: serviceQueryOptions.name || "", + plan: serviceQueryOptions.plan || "", + tag: serviceQueryOptions.tag || "", + prompt: "", + }; + query = await composeQueryToObtainInstances([serviceInfo]); + } + return getServiceInstances(query, progressTitle); +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/chisel.ts b/projects/cloud-foundry-tools/packages/backend/src/chisel.ts new file mode 100644 index 000000000..306658b45 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/chisel.ts @@ -0,0 +1,101 @@ +import * as _ from "lodash"; +import { getModuleLogger } from "./logger/logger-wrapper"; +import { toText, writeProperties } from "./utils"; +import * as PropertiesReader from "properties-reader"; + +const LOGGER_MODULE = "chisel"; + +//Chisel fields array +enum ChiselKeys { + CHISEL_URL = "CHISEL_URL", + CHISEL_USER = "CHISEL_USER", + CHISEL_PASSWORD = "CHISEL_PASSWORD", + TUNNEL_PARAM = "TUNNEL_PARAM", +} + +function initReader(filePath: string): PropertiesReader.Reader | undefined { + try { + return PropertiesReader(filePath); + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(LOGGER_MODULE).error( + "checkAndCreateChiselTask: propertiesReader : environment file is broken or not exists", + { filePath: filePath }, + { exception: toText(new Error(e?.message as string)) } + ); + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function checkAndCreateChiselTask(filePath: string, name: string): any | undefined { + const envProperties = initReader(filePath); + if (envProperties) { + const chiselUrl = envProperties.get(ChiselKeys.CHISEL_URL); + if (_.isEmpty(chiselUrl)) { + getModuleLogger(LOGGER_MODULE).debug("checkAndCreateChiselTask: empty chisel_url", { filePath: filePath }); + return undefined; + } + getModuleLogger(LOGGER_MODULE).debug( + "checkAndCreateChiselTask: chisel task <%s> composed", + `openChiselTunnerFor-${name}`, + { filePath: filePath } + ); + return { + label: `openChiselTunnerFor-${name}`, + type: "shell", + command: "chisel", + isBackground: true, + problemMatcher: "$chisel-client", + args: [ + "client", + "--auth", + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + `${envProperties.get(ChiselKeys.CHISEL_USER)}:${envProperties.get(ChiselKeys.CHISEL_PASSWORD)}`, + chiselUrl, + envProperties.get(ChiselKeys.TUNNEL_PARAM), + ], + }; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function dropChiselProperties(propObj: any): Record { + const properties: Record = {}; + _.each(_.keys(propObj), (key) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!(Object as any).values(ChiselKeys).includes(key)) { + const value = propObj[key]; + if (!_.isEmpty(value)) { + properties[key] = value; + } + } + }); + return properties; +} + +export async function deleteChiselParamsFromFile(filePath: string): Promise { + const envProperties = initReader(filePath); + if (!envProperties) { + return false; + } + const chiselUrl = envProperties.get(ChiselKeys.CHISEL_URL); + if (_.isEmpty(chiselUrl)) { + return false; + } + try { + await writeProperties(filePath, dropChiselProperties(envProperties.getAllProperties())); + getModuleLogger(LOGGER_MODULE).debug( + `deleteChiselParamsFromFile: override the paramters to ${filePath} file without chisel parameters` + ); + return true; + } catch (err) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger( + LOGGER_MODULE + ).error( + `deleteChiselParamsFromFile: failed to override the paramters to ${filePath} file without chisel parameters`, + { exception: toText(new Error(err?.message as string)) } + ); + return false; + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/commands.ts b/projects/cloud-foundry-tools/packages/backend/src/commands.ts new file mode 100644 index 000000000..14653cea4 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/commands.ts @@ -0,0 +1,737 @@ +import * as vscode from "vscode"; +import { + ServiceInfo, + PlanInfo, + ServiceInstanceInfo, + CF_PAGE_SIZE, + IServiceQuery, + Cli, + CliResult, + cfGetServices, + cfCreateService, + cfGetServicePlansList, + ServiceTypeInfo, + cfGetTarget, + ITarget, + cfCreateUpsInstance, + cfGetServiceInstances, + eFilters, + eServiceTypes, + cfGetConfigFileJson, + cfSetOrgSpace, +} from "@sap/cf-tools"; +import { messages } from "./messages"; +import { cmdReloadTargets } from "./cfViewCommands"; +import * as _ from "lodash"; +import { + validateParams, + generateParams4Service, + getAllServiceInstances, + DisplayServices, + isRegexExpression, + toText, + UpsServiceQueryOprions, + getUpsServiceInstances, + resolveFilterValue, + notifyWhenServicesInfoResultIncomplete, + examCFTarget, +} from "./utils"; +import { stringify, parse } from "comment-json"; +import { getModuleLogger } from "./logger/logger-wrapper"; +import { CFLoginNode, getTargetRoot } from "./cfView"; +import { openLoginView } from "./loginTargetView/loginTargetView"; + +const OK = "OK"; +const Cancel = "Cancel"; +const MORE_RESULTS = "More results..."; +export const CMD_CREATE_SERVICE = "+ Create a new service instance"; +export const CMD_BIND_TO_DEFAULT_SERVICE = "Bind to the default service instance: "; +const LOGGER_MODULE = "commands"; + +export function isCFResource(obj: unknown): boolean { + return _.has(obj, "relationships") && _.has(obj, "links") && _.has(obj, "guid"); +} + +export function isServiceTypeInfoInArray(obj: unknown): boolean { + return _.isArray(obj) && _.has(obj, "[0].name"); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function invokeLongFunctionWithProgress(longFunction: any, progressMessage: string): Thenable { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Window, + title: progressMessage, + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + }, + () => longFunction() + ); +} + +async function setCfTarget(message: string) { + let commandId: string; + let target; + const args = []; + + try { + target = await cfGetTarget(); + } catch (e) { + // not logged in ... + } + if (!target || !target.user) { + commandId = "cf.login"; + args.push(true); + } else if (!target.org) { + commandId = "cf.set.orgspace"; + } else if (!target.space) { + commandId = "cf.select.space"; + } else { + getModuleLogger(LOGGER_MODULE).error("setCfTarget: cfGetTarget failed", { target: target }, { output: message }); + return Promise.reject(new Error(message)); + } + + const result = await vscode.commands.executeCommand(commandId, ...args); + if (undefined === result) { + return Promise.reject(); // canceled + } + return result; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function runBaseWithProgressAndLoginRetry( + isCancelable: boolean, + titleMessage: string, + longFunction: () => Promise, + onError: (error: Error) => Promise, + args?: any, + errArgs?: any +): Promise { + while (true) { + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: titleMessage, + cancellable: isCancelable, + // eslint-disable-next-line prefer-spread + }, + (progress, token) => longFunction.apply(null, <[]>(_.isArray(args) ? _.concat(args, token) : [args, token])) + ); + } catch (error) { + // eslint-disable-next-line prefer-spread + await onError.apply(null, <[Error]>_.concat([error], errArgs || [])); + } + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function onErrorSetCfTarget(error: Error): Promise { + const errorText = toText(error); + if ( + errorText.includes(messages.cf_setting_not_set) || + errorText.includes("login") || + errorText.includes("re-authenticate") + ) { + await setCfTarget(errorText); + } else { + return Promise.reject(error); + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function runWithProgressAndLoginRetry( + isCancelable: boolean, + titleMessage: string, + longFunction: () => Promise, + args?: any +): Promise { + return runBaseWithProgressAndLoginRetry(isCancelable, titleMessage, longFunction, onErrorSetCfTarget, args); +} + +export async function verifyLoginRetry(options?: { weak?: boolean }): Promise { + let result: unknown; + try { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + result = await runWithProgressAndLoginRetry( + false, + messages.verify_cf_connectivity, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + examCFTarget as () => Promise, + [messages.cf_setting_not_set, ["user", "org", "space"], options?.weak] + ); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error("verifyLoginRetry failed", { exception: toText(e) }, { options: options }); + } + return result; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function onErrorCfLogin(error: Error, endPoint?: string): Promise { + const errorText = toText(error); + if (errorText.includes("login") || errorText.includes("re-authenticate")) { + const result = await cmdLogin(false, false, endPoint); + if (_.isUndefined(result)) { + return Promise.reject(""); // canceled + } + } else { + return Promise.reject(error); + } +} + +async function verifyLoginRetryPartial(opts?: { endPoint?: string }): Promise { + let result: unknown; + try { + result = await runBaseWithProgressAndLoginRetry( + false, + messages.verify_cf_connectivity, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + examCFTarget as () => Promise, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onErrorCfLogin as () => Promise, + [messages.login, ["user"], false], + opts?.endPoint ? [opts.endPoint] : [] + ); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error("verifyLoginRetryPartial failed", { exception: toText(e) }); + } + return result; +} + +export async function cmdCFSetOrgSpace(): Promise { + try { + return await cmdLogin(false, true); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + void vscode.window.showErrorMessage(toText(e)); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error("cmdCFSetOrgSpace failed", { exception: toText(e) }); + return ""; + } +} + +function pickCfTargetWithProgress(): Thenable { + return vscode.window + .withProgress( + { + location: vscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + }, + () => cfGetTarget() + ) + .then( + (t) => t, + () => undefined + ); +} + +export async function cmdLogin( + weak = false, + target = true, + extEndPoint?: string, + opts?: { isSplit: boolean; isLoginOnly?: boolean } +): Promise { + try { + let endpoint: string | undefined = extEndPoint; + let space: string | undefined = ""; + let org: string | undefined = ""; + + opts = opts ?? { isSplit: true, isLoginOnly: true }; + + let result = weak ? (_.get(await pickCfTargetWithProgress(), "user") ? OK : undefined) : undefined; + if (!result) { + const node = weak as unknown; + if (node instanceof CFLoginNode) { + // attempt to login from targets tree + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + const configJson = await cfGetConfigFileJson(_.get(getTargetRoot(node), ["target", "label"])); + if (configJson) { + endpoint = _.get(configJson, "Target"); + space = _.get(configJson, ["SpaceFields", "Name"]); + org = _.get(configJson, ["OrganizationFields", "Name"]); + } + } + + opts.isLoginOnly = true; + result = await openLoginView(opts, endpoint, org, space); + } + + // User is signed in (result = OK) and trying to log in from CF targets view + if (target && OK === result) { + const currentCFTarget = await cfGetTarget(); + endpoint = !!endpoint ? endpoint : currentCFTarget["api endpoint"]; + org = !!org ? org : currentCFTarget?.org; + space = !!space ? space : currentCFTarget?.space; + + // Need only to set active target + if (endpoint && org && space) { + await invokeLongFunctionWithProgress(cfSetOrgSpace.bind(undefined, org, space), messages.set_org_space); + void vscode.window.showInformationMessage(messages.success_set_org_space); + getModuleLogger(LOGGER_MODULE).debug( + "cmdCFSetOrgSpace: Organization <%s> and space <%s> have been set", + org, + space + ); + result = OK; + } else { + // Needs to complete the target settings (org & space) + result = await openLoginView(opts, endpoint, org, space); + } + } + + return result; + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const errText = toText(e); + void vscode.window.showErrorMessage(errText); + getModuleLogger(LOGGER_MODULE).error("cmdLogin failed", { weak: weak }, { exception: errText }); + return ""; + } +} + +export async function cmdSelectSpace(): Promise { + try { + return await cmdLogin(false, true); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const errText = toText(e); + void vscode.window.showErrorMessage(errText); + getModuleLogger(LOGGER_MODULE).error("cmdSelectSpace failed", { exception: errText }); + return ""; + } +} + +async function cmdCreateTarget(): Promise { + const targetName = await vscode.window.showInputBox({ prompt: messages.name_for_target, ignoreFocusOut: true }); + if (targetName) { + const cliResult: CliResult = await Cli.execute(["save-target", "-f", targetName]); + if (cliResult.exitCode !== 0) { + getModuleLogger(LOGGER_MODULE).error("cmdCreateTarget : command 'save-target -f <%s>' failed", targetName, { + output: cliResult.stdout, + }); + return Promise.reject(new Error(cliResult.stdout)); + } + + await cmdReloadTargets(); + void vscode.window.showInformationMessage(messages.target_created(targetName)); + getModuleLogger(LOGGER_MODULE).debug("cmdCreateTarget: the <%s> target has been created", targetName); + return targetName; + } +} + +function ask4ArbitraryParams(serviceInfo: ServiceInfo, plan: PlanInfo): Thenable { + const data = generateParams4Service(serviceInfo.label, plan.label); + const value = stringify(data); + const xsappname: string = _.get(data, "xsappname"); + const pos = value.indexOf(xsappname); + return vscode.window.showInputBox({ + ignoreFocusOut: true, + prompt: messages.create_service_enter_params, + value, + valueSelection: pos > 0 ? [pos, pos + _.size(xsappname)] : undefined, + validateInput: validateParams(_.get(serviceInfo, "label"), _.get(plan, "label")), + }); +} + +async function onGetServicePlansFromCF( + opt: { planName?: string; serviceGuid: string }, + isCancelable = false, + title?: string +): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return runWithProgressAndLoginRetry( + isCancelable, + title || messages.loading_service_plan_list, + cfGetServicePlansList as () => Promise, + _.merge( + { filters: [{ key: eFilters.service_offering_guids, value: opt.serviceGuid }] }, + opt.planName ? { filters: [{ key: eFilters.names, value: resolveFilterValue(opt.planName) }] } : {} + ) + ); +} + +export async function onCreateService( + planInfo: PlanInfo, + instanceName: string, + params: unknown, + tags: string[] | undefined, + progress: vscode.Progress<{ message?: string; increment?: number }>, + cancelToken: vscode.CancellationToken +): Promise { + const response = await cfCreateService(planInfo.guid, instanceName, params || {}, tags || [], { + progress: progress, + cancelToken: cancelToken, + }); + void vscode.window.showInformationMessage(messages.service_created(response.name)); + getModuleLogger(LOGGER_MODULE).debug("onCreateService: the service has been created", { response: response }); + return response.name; +} + +async function createUpsInstance(name: string, info?: ServiceTypeInfo): Promise { + let result; + const quietMode = info?.tag && info.allowCreate?.name; + const credentials = quietMode + ? "{}" + : await vscode.window.showInputBox({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }); + if (credentials) { + const tags = quietMode + ? info?.tag + : await vscode.window.showInputBox( + _.merge({ prompt: messages.enter_tags, ignoreFocusOut: true }, info?.tag ? { value: info.tag } : {}) + ); + if (undefined !== tags) { + const sysLogUrl = quietMode + ? "" + : await vscode.window.showInputBox({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }); + if (undefined !== sysLogUrl) { + const routeUrl = quietMode + ? "" + : await vscode.window.showInputBox({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }); + if (undefined !== routeUrl) { + // canceled + const response = await cfCreateUpsInstance({ + instanceName: name, + syslog_drain_url: sysLogUrl, + route_service_url: routeUrl, + credentials: parse(credentials), + tags: _.compact(_.split(tags, /[,\s]/)), + }); + void vscode.window.showInformationMessage( + isCFResource(response) ? messages.service_created(response.name) : _.toString(response) + ); + getModuleLogger(LOGGER_MODULE).debug("createUpsInstance: the service has been created", { + response: response, + }); + result = response?.name; + } + } + } + } + return result; +} + +async function createServiceInstance(name: string, info?: ServiceTypeInfo): Promise { + const serviceName = info?.allowCreate?.serviceName || info?.name; + const servicePlan = info?.allowCreate?.plan || info?.plan; + const serviceTag = info?.allowCreate?.tag || info?.tag; + + const servicesInfo: ServiceInfo[] = await runWithProgressAndLoginRetry( + true, + messages.loading_services, + cfGetServices, + { filters: [{ key: eFilters.names, value: resolveFilterValue(serviceName) }] } + ); + if (_.size(servicesInfo)) { + const serviceInfo = + _.size(servicesInfo) === 1 && serviceName + ? servicesInfo[0] + : await vscode.window.showQuickPick(servicesInfo, { + placeHolder: messages.select_services, + ignoreFocusOut: true, + }); + + if (serviceInfo) { + const plansInfo: PlanInfo[] = await onGetServicePlansFromCF({ + serviceGuid: serviceInfo.guid, + planName: servicePlan, + }); + if (_.size(plansInfo)) { + const planInfo = + _.size(plansInfo) === 1 && servicePlan + ? plansInfo[0] + : await vscode.window.showQuickPick(plansInfo, { + placeHolder: messages.select_service_plan, + ignoreFocusOut: true, + }); + + if (planInfo) { + const params = info?.allowCreate?.getParams + ? stringify(await info.allowCreate.getParams()) + : await ask4ArbitraryParams(serviceInfo, planInfo); + if (params) { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: messages.creating_service(name, serviceInfo.label, planInfo.label), + cancellable: true, + }, + (progress, cancelToken) => + onCreateService( + planInfo, + name, + parse(params), + serviceTag ? [serviceTag] : undefined, + progress, + cancelToken + ) + ); + } + } + } else { + const message = servicePlan + ? messages.no_service_plan_info_found(servicePlan, serviceInfo.label) + : messages.no_service_plans_found(serviceInfo.label); + throw new Error(message); + } + } + } else { + throw new Error( + serviceName && !isRegexExpression(serviceName) + ? messages.no_services_instances_found_for_type(serviceName) + : messages.no_services_instances_found + ); + } +} + +async function createService(isUps: boolean, info?: ServiceTypeInfo): Promise { + // first ask for service-name + const options = _.merge( + { prompt: info?.allowCreate?.namePrompt ? `${info.allowCreate.namePrompt}` : messages.enter_service_name }, + info?.allowCreate?.name ? { value: `${info.allowCreate.name}` } : {}, + { ignoreFocusOut: true } + ); + // Show instance name input box + const instanceName = await vscode.window.showInputBox(options); + if (instanceName) { + try { + return await (isUps ? createUpsInstance(instanceName, info) : createServiceInstance(instanceName, info)); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const errText = toText(error); + void vscode.window.showErrorMessage(errText); + getModuleLogger(LOGGER_MODULE).error( + "createService: create instance <%s> failed", + instanceName, + { ups: isUps }, + { info: info }, + { exception: errText } + ); + } + } +} + +export async function cmdCreateService(info?: ServiceTypeInfo): Promise { + if (await verifyLoginRetry()) { + return createService(false, info); + } +} + +export async function cmdCreateUps(info?: ServiceTypeInfo): Promise { + if (await verifyLoginRetry()) { + return createService(true, info); + } +} + +export async function fetchServicePlanList(query?: IServiceQuery): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return runWithProgressAndLoginRetry(false, messages.loading_service_plan_list, cfGetServicePlansList, query); +} + +export function getAvailableServices(opts?: DisplayServices, progressTitle?: string): Promise { + return notifyWhenServicesInfoResultIncomplete( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + runWithProgressAndLoginRetry(true, progressTitle || messages.loading_services, getAllServiceInstances, opts) + ); +} + +export async function getServiceInstances( + query?: IServiceQuery | undefined, + progressTitle?: string +): Promise { + return notifyWhenServicesInfoResultIncomplete( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + runWithProgressAndLoginRetry(true, progressTitle || messages.loading_services, cfGetServiceInstances, query) + ); +} + +export async function getUserProvidedServiceInstances( + options?: UpsServiceQueryOprions, + progressTitle?: string +): Promise { + return notifyWhenServicesInfoResultIncomplete( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + runWithProgressAndLoginRetry(true, progressTitle || messages.loading_ups_services, getUpsServiceInstances, options) + ); +} + +async function askUserForServiceInstanceName( + availableServices: ServiceInstanceInfo[], + serviceType?: ServiceTypeInfo +): Promise { + const serviceName = _.get(serviceType, "name"); + const availableServicesToShow = availableServices; + + if (!_.size(availableServicesToShow)) { + void vscode.window.showErrorMessage( + serviceName && !isRegexExpression(serviceName) + ? messages.no_services_instances_found_for_type(serviceName) + : messages.no_services_instances_found + ); + getModuleLogger(LOGGER_MODULE).debug( + "askUserForServiceInstanceName: no service instances found for <%s> type", + serviceName + ); + return; + } + + const pickItems = _.map(availableServicesToShow, (s) => { + return { label: s.label, description: s.serviceName + (s.plan ? ` (${s.plan})` : "") }; + }); + + return vscode.window + .showQuickPick(pickItems, { + placeHolder: serviceType ? serviceType.prompt : messages.select_service, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .then((pickedItem) => { + return _.get(pickedItem, "label"); + }); +} + +async function checkForMoreServices( + instanceName: string | undefined, + availableServices: ServiceInstanceInfo[], + serviceType?: ServiceTypeInfo +): Promise { + if (instanceName === MORE_RESULTS && _.size(availableServices) > 0) { + const lastElem = availableServices.pop(); + if (lastElem && !lastElem.serviceName.startsWith("more:")) { + // unexpected + availableServices.push(lastElem); + return false; + } + // it is more: + const nextPage = Number.parseInt(_.get(lastElem, 'serviceName.split(":")[1]') as string, 10); + + const nextAvailableServices = await getAvailableServices({ + query: { page: nextPage }, + ups: { tag: serviceType?.ups?.tag }, + }); + if (_.size(nextAvailableServices)) { + _.forEach(nextAvailableServices, (nas) => availableServices.push(nas)); + if (_.size(nextAvailableServices) === CF_PAGE_SIZE) { + const siInfo: ServiceInstanceInfo = { + label: MORE_RESULTS, + serviceName: `more:${nextPage + 1}`, + alwaysShow: true, + }; + availableServices.push(siInfo); + } + return true; + } + } + return false; +} + +export async function getInstanceName( + availableServices: ServiceInstanceInfo[], + serviceType?: ServiceTypeInfo +): Promise { + let instanceName = await askUserForServiceInstanceName(availableServices, serviceType); + while (await checkForMoreServices(instanceName, availableServices, serviceType)) { + instanceName = await askUserForServiceInstanceName(availableServices, serviceType); + } + return instanceName; +} + +export async function updateInstanceNameAndTags( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo | undefined, + instanceNames: string[], + tags: string[] +): Promise { + let instanceName = await getInstanceName(availableServices, serviceTypeInfo); + if (instanceName === CMD_CREATE_SERVICE) { + instanceName = await (serviceTypeInfo?.name === eServiceTypes.user_provided + ? cmdCreateUps(serviceTypeInfo) + : cmdCreateService(serviceTypeInfo)); + } else if (instanceName === `${CMD_BIND_TO_DEFAULT_SERVICE}${serviceTypeInfo?.allowCreate?.name}`) { + const defaultInstance = _.find(availableServices, { + label: serviceTypeInfo?.allowCreate?.name, + plan: serviceTypeInfo?.allowCreate?.plan, + serviceName: serviceTypeInfo?.allowCreate?.serviceName, + }); + if (defaultInstance) { + instanceName = defaultInstance.label; + } else { + instanceName = await (serviceTypeInfo?.name === eServiceTypes.user_provided + ? createUpsInstance(serviceTypeInfo?.allowCreate?.name || "", serviceTypeInfo) + : createServiceInstance(serviceTypeInfo?.allowCreate?.name || "", serviceTypeInfo)); + } + } + if (_.size(instanceName) > 0) { + instanceNames.push(instanceName!); + serviceTypeInfo?.tag && tags.push(serviceTypeInfo.tag); + } + return instanceName; +} + +export function updateServicesOnCFPageSize(availableServices: ServiceInstanceInfo[]): void { + if (_.size(availableServices) === CF_PAGE_SIZE) { + availableServices.push({ label: MORE_RESULTS, serviceName: "more:2", alwaysShow: true }); + } +} + +export async function cmdSelectAndSaveTarget(): Promise { + try { + let label; + if (await verifyLoginRetryPartial()) { + const target = await cfGetTarget(true); + const affairs = [ + { + id: "save", + label: messages.set_targets_save(target.org || "undefined", target.space || "undefined"), + detail: messages.set_targets_save_details, + }, + { id: "pick-save", label: messages.set_targets_pick_save, detail: messages.set_targets_pick_save_details }, + ]; + let result; + const action = await vscode.window.showQuickPick(affairs, { + placeHolder: messages.set_targets_choose_the_operation, + canPickMany: false, + matchOnDetail: true, + }); + if (action?.id === "pick-save") { + result = await cmdLogin(false, false); + } else if (action?.id === "save") { + if (!target.org || !target.space) { + if ( + OK === + (await vscode.window.showWarningMessage( + messages.target_setup_not_completed(target.org, target.space), + OK, + Cancel + )) + ) { + result = OK; + } + } else { + result = OK; + } + } + if (OK === result) { + label = await cmdCreateTarget(); + } + } + return label; + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const errText = toText(e); + void vscode.window.showErrorMessage(errText); + getModuleLogger(LOGGER_MODULE).error(`cmdSelectAndSaveTarget exception thrown`, { error: errText }); + } +} + +// for testing purpose only +export const internal = { + onErrorCfLogin, +}; diff --git a/projects/cloud-foundry-tools/packages/backend/src/extension.ts b/projects/cloud-foundry-tools/packages/backend/src/extension.ts new file mode 100644 index 000000000..6149b3905 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/extension.ts @@ -0,0 +1,239 @@ +import { + ExtensionContext, + window, + StatusBarItem, + EventEmitter, + workspace, + commands, + StatusBarAlignment, + extensions, + ConfigurationChangeEvent, +} from "vscode"; +import * as _ from "lodash"; +import { CFTargetNotCurrent, CFTargetTI, CFView } from "./cfView"; +import { messages } from "./messages"; +import { + cmdLogin, + cmdCreateService, + cmdCFSetOrgSpace, + cmdSelectSpace, + cmdCreateUps, + cmdSelectAndSaveTarget, +} from "./commands"; +import { + cmdDeployServiceAPI, + cmdSetCurrentTarget, + cmdDeleteTarget, + cmdBindLocal, + cmdReloadTargets, + cmdGetSpaceServices, + cmdGetUpsServiceInstances, + cmdGetServiceInstances, + execSetTarget, + execSaveTarget, +} from "./cfViewCommands"; + +import { cfGetConfigFilePath, cfGetConfigFileField, ITarget, cfGetTarget, OK } from "@sap/cf-tools"; +import * as fs from "fs"; +import { DependencyHandler } from "./run-configuration"; +import { IRunConfigRegistry } from "@sap/wing-run-config-types"; +import { toText } from "./utils"; +import { getModuleLogger, initLogger } from "./logger/logger-wrapper"; + +const LOGGER_MODULE = "extension"; +let cfStatusBarItem: StatusBarItem; +const runConfigExtName = "sap.vscode-wing-run-config"; +let treeDataProvider: CFView; +let extPath: string; + +const targetChangedEventEmitter = new EventEmitter(); +const onDidChangeTarget = targetChangedEventEmitter.event; + +async function updateStatusBar(): Promise { + let isUpdated; + const beforeText = _.get(cfStatusBarItem, "text"); + const results = await Promise.all([cfGetConfigFileField("OrganizationFields"), cfGetConfigFileField("SpaceFields")]); + const orgField: string = _.get(results, "[0].Name"); + const spaceField: string = _.get(results, "[1].Name"); + const isNoTarget = _.isEmpty(orgField) && _.isEmpty(spaceField); + const updatedText = `${isNoTarget ? messages.not_targeted : messages.targeting(orgField, spaceField)}`; + + if (beforeText !== updatedText) { + _.set(cfStatusBarItem, "text", updatedText); + if (beforeText) { + // eliminate firing event on initialization + const target = async () => { + try { + return await cfGetTarget(); + } catch (e) { + return; + } + }; + targetChangedEventEmitter.fire(isNoTarget ? undefined : await target()); + isUpdated = true; + } + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return isUpdated; +} + +export function onCFConfigFileChange(): void { + void updateStatusBar().then((isUpdated) => { + if (isUpdated) { + getModuleLogger(LOGGER_MODULE).debug( + "onCFConfigFileChange: the Cloud Foundry 'config' file has changed. The status bar will be updated" + ); + treeDataProvider.refresh(); + } + }); +} + +function displayTargetWhenAllowed(): void { + if (workspace.getConfiguration().get("CloudFoundryTools.showTargetInformation")) { + cfStatusBarItem.show(); + } else { + cfStatusBarItem.hide(); + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function callbackOnDidChangeConfiguration(e: ConfigurationChangeEvent, context: ExtensionContext): void { + if (e.affectsConfiguration("CloudFoundryTools.showTargetInformation")) { + displayTargetWhenAllowed(); + } +} + +async function init(context: ExtensionContext, cfConfigFilePath: string): Promise { + cfStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 100); + context.subscriptions.push(cfStatusBarItem); + + // !!!! does not work on theia 1.5.0 + // cfStatusBarItem.command = {command: loginCmdId, arguments: [true], title: ""}; + // start workarround --> remove following workarround in future theia releases + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + context.subscriptions.push(commands.registerCommand("cf.login.weak", cmdLogin.bind(null, true))); + cfStatusBarItem.command = "cf.login"; + // end workarround + + void updateStatusBar(); + + if (!_.isEmpty(cfConfigFilePath)) { + fs.watchFile(cfConfigFilePath, onCFConfigFileChange); + } + + displayTargetWhenAllowed(); + + let platformExtension = extensions.getExtension(runConfigExtName); + if (platformExtension) { + if (!platformExtension.isActive) { + try { + await platformExtension.activate(); + } catch (e) { + platformExtension = undefined; + getModuleLogger(LOGGER_MODULE).error("activate <%s> extension fails", runConfigExtName, { + exception: toText(new Error(e?.message as string)), + }); + } + } + if (platformExtension) { + const genericDependencyHandler = new DependencyHandler("cf-tools-rsource-dependency"); + platformExtension.exports.registerDependency(genericDependencyHandler); + // eslint-disable-next-line @typescript-eslint/unbound-method + context.subscriptions.push(commands.registerCommand("cf.services.bind", genericDependencyHandler.bind)); + // eslint-disable-next-line @typescript-eslint/unbound-method + context.subscriptions.push(commands.registerCommand("cf.services.unbind", genericDependencyHandler.unbind)); + // eslint-disable-next-line @typescript-eslint/unbound-method + context.subscriptions.push( + commands.registerCommand("cf.services.binding.state", genericDependencyHandler.getBindState) + ); + } + } else { + getModuleLogger(LOGGER_MODULE).error("activate: the <%s> extension has not been set", runConfigExtName); + } +} + +export async function activate(context: ExtensionContext): Promise { + await initLogger(context); + extPath = context.extensionPath; + + const cfConfigFilePath: string = cfGetConfigFilePath(); + treeDataProvider = new CFView(context, cfConfigFilePath); + const view = window.createTreeView("cfView", { treeDataProvider, showCollapseAll: true }); + + context.subscriptions.push( + commands.registerCommand("cf.login", (weak: boolean, target: boolean, extEndPoint: string | undefined) => { + return cmdLogin(weak, target, extEndPoint, { isSplit: !!weak }).then((result) => { + if (OK === result) { + const active = _.find(treeDataProvider.getTargets(), "target.isCurrent"); + if (active) { + // after re-login the target data become invalid -> perform re-create target + void cmdDeleteTarget(active, { "skip-reload": true, silent: true }).then(() => { + void execSaveTarget(active, { "skip-reload": true, silent: true }).then(() => { + void execSetTarget(active, { silent: true }); + }); + }); + } + } + return result; + }); + }) + ); + + context.subscriptions.push(workspace.onDidChangeConfiguration((e) => callbackOnDidChangeConfiguration(e, context))); + + function revealTargetItem(label?: string) { + setTimeout(() => { + const treeItem = _.find(treeDataProvider.getTargets(), label ? ["label", label] : "target.isCurrent"); + if (treeItem) { + void view.reveal(treeItem, { select: true, focus: true, expand: true }); + } + }, 400); + } + + context.subscriptions.push( + commands.registerCommand("cf.target.set", async (item: CFTargetTI | CFTargetNotCurrent) => { + await cmdSetCurrentTarget(item); + revealTargetItem(); + }) + ); + context.subscriptions.push(commands.registerCommand("cf.services.create", cmdCreateService)); + context.subscriptions.push(commands.registerCommand("cf.ups.create", cmdCreateUps)); + context.subscriptions.push(commands.registerCommand("cf.services.bind.local", cmdBindLocal)); + context.subscriptions.push(commands.registerCommand("cf.set.orgspace", cmdCFSetOrgSpace)); + context.subscriptions.push( + commands.registerCommand("cf.targets.create", async () => { + const label = await cmdSelectAndSaveTarget(); + if (label) { + revealTargetItem(label); + } + }) + ); + context.subscriptions.push(commands.registerCommand("cf.target.delete", cmdDeleteTarget)); + context.subscriptions.push(commands.registerCommand("cf.targets.reload", cmdReloadTargets)); + context.subscriptions.push(commands.registerCommand("cf.select.space", cmdSelectSpace)); + context.subscriptions.push(commands.registerCommand("cf.deploy-service.api", cmdDeployServiceAPI)); + context.subscriptions.push(commands.registerCommand("cf.services.get-space-services", cmdGetSpaceServices)); + context.subscriptions.push(commands.registerCommand("cf.services.get-ups-services", cmdGetUpsServiceInstances)); + context.subscriptions.push(commands.registerCommand("cf.services.get-services", cmdGetServiceInstances)); + + // postpone the rest of initialization after the extension is activated + setTimeout(() => { + void init(context, cfConfigFilePath); + }, 10); + + return { + // allows to external extensions to listen onDidChangeTarget event + onDidChangeTarget, + }; +} + +export function getPath() { + return extPath; +} + +export function deactivate(): void { + const cfConfigFilePath = cfGetConfigFilePath(); + if (cfConfigFilePath) { + fs.unwatchFile(cfConfigFilePath); + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts b/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts new file mode 100644 index 000000000..ad45a11ad --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts @@ -0,0 +1,49 @@ +import { ExtensionContext, window } from "vscode"; +import { readFile as readFileCallback } from "fs"; +import { resolve } from "path"; +import { configureLogger, NOOP_LOGGER } from "@vscode-logging/wrapper"; +import { IChildLogger, IVSCodeExtLogger } from "@vscode-logging/types"; +import { promisify } from "util"; +/** + * Note that the values of these configuration properties must match those defined in the package.json + */ +const LOGGING_LEVEL_CONFIG_PROP = "CloudFoundryTools.logging.loggingLevel"; +const SOURCE_TRACKING_CONFIG_PROP = "CloudFoundryTools.logging.sourceLocationTracking"; + +const readFile = promisify(readFileCallback); +// On file load we initialize our logger to `NOOP_LOGGER` +// this is done because the "real" logger cannot be initialized during file load. +// only once the `activate` function has been called in extension.ts +// as the `ExtensionContext` argument to `activate` contains the required `logPath` +let loggerImpel: IVSCodeExtLogger = NOOP_LOGGER; + +export function getLogger(): IChildLogger { + return loggerImpel; +} + +function setLogger(newLogger: IVSCodeExtLogger): void { + loggerImpel = newLogger; +} + +export function getModuleLogger(name: string): IChildLogger { + return getLogger().getChildLogger({ label: name }); +} + +export async function initLogger(context: ExtensionContext): Promise { + const meta = JSON.parse(await readFile(resolve(context.extensionPath, "package.json"), "utf8")) as { + displayName: string; + }; + + const extLogger = configureLogger({ + extName: meta.displayName, + logPath: context.logUri.fsPath, + logOutputChannel: window.createOutputChannel(meta.displayName), + // set to `true` if you also want your VSCode extension to log to the console. + logConsole: false, + loggingLevelProp: LOGGING_LEVEL_CONFIG_PROP, + sourceLocationProp: SOURCE_TRACKING_CONFIG_PROP, + subscriptions: context.subscriptions, + }); + + setLogger(extLogger); +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts b/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts new file mode 100644 index 000000000..c138ea863 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts @@ -0,0 +1,263 @@ +import * as vscode from "vscode"; +import * as extension from "../extension"; +import { join, sep } from "path"; +import * as fs from "fs"; +import { RpcExtension } from "@sap-devx/webview-rpc/out.ext/rpc-extension"; +import { getModuleLogger } from "../logger/logger-wrapper"; +import { + cfGetAvailableOrgs, + cfGetAvailableSpaces, + cfGetConfigFileField, + cfGetTarget, + cfLogin, + cfLogout, + cfSetOrgSpace, + CredentialsLoginOptions, + ITarget, + OK, + SSOLoginOptions, + Organization, + Space, +} from "@sap/cf-tools"; +import { messages } from "../messages"; +import { cfendpoint } from "@sap/bas-sdk"; +import { toText } from "../utils"; +import * as cheerio from "cheerio"; + +let loginTarget: LoginTarget | undefined; +let initTarget: { endpoint: string | undefined; org?: string | undefined; space?: string | undefined }; +let panel: vscode.WebviewPanel | undefined; +let isLoginOnly: boolean | undefined; +let cmdLoginResult: string | undefined; + +const LOGGER_MODULE = "loginTargetView"; + +export function openLoginView( + opts: { isSplit: boolean; isLoginOnly?: boolean }, + endpoint?: string, + org?: string, + space?: string +): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + initTarget = { + endpoint: endpoint, + org: org, + space: space, + }; + isLoginOnly = opts.isLoginOnly; + // Every time the view is opened need to recalculate the login result + cmdLoginResult = undefined; + + return new Promise((resolve, reject) => { + try { + const split = opts.isSplit ? vscode.ViewColumn.Beside : vscode.ViewColumn.One; + panel = panel + ? panel + : vscode.window.createWebviewPanel("cfLogin", "Cloud Foundry Sign In", split, { + enableScripts: true, + localResourceRoots: [vscode.Uri.file(join(extension.getPath(), "dist", "media"))], + }); + panel.reveal(); + panel.onDidDispose(() => { + resolve(cmdLoginResult); + panel = undefined; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loginTarget = undefined; + }); + + const extensionPath = extension.getPath(); + const mediaPath = join(extensionPath, "dist", "media"); + const htmlFileName = "index.html"; + + let indexHtml = fs.readFileSync(join(mediaPath, htmlFileName), "utf8"); + if (indexHtml) { + // Local path to main script run in the webview + const scriptPathOnDisk = vscode.Uri.file(join(mediaPath, sep)); + const scriptUri = panel.webview.asWebviewUri(scriptPathOnDisk); + const baseUrl = scriptUri.toString(); + const $ = cheerio.load(indexHtml); + + function replaceAttributePaths(elements: any, attributeName: string) { + elements.each((index: number, element: cheerio.Element) => { + const currentAttr = $(element).attr(attributeName); + if (currentAttr) { + $(element).attr(attributeName, `${baseUrl}/${currentAttr}`); + } + }); + } + + replaceAttributePaths($("[src]"), "src"); + replaceAttributePaths($("[href]"), "href"); + + indexHtml = $.html(); + } + + panel.webview.html = indexHtml; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + loginTarget = new LoginTarget(panel); + } catch (e) { + reject(e.toString()); + } + }); +} + +class LoginTarget { + private rpc: RpcExtension; + private currentTarget: ITarget | undefined; + + constructor(private readonly panel: vscode.WebviewPanel) { + this.rpc = new RpcExtension(panel.webview); + this.rpc.registerMethod({ func: this.init, thisArg: this }); + this.rpc.registerMethod({ func: this.loginClick, thisArg: this }); + this.rpc.registerMethod({ func: this.logoutClick, thisArg: this }); + this.rpc.registerMethod({ func: this.getSelectedTarget, thisArg: this }); + this.rpc.registerMethod({ func: this.getOrgs, thisArg: this }); + this.rpc.registerMethod({ func: this.getSpaces, thisArg: this }); + this.rpc.registerMethod({ func: this.applyTarget, thisArg: this }); + this.rpc.registerMethod({ func: this.openPasscodeLink, thisArg: this }); + } + + private async getTarget(): Promise { + // TODO: need to pass the endpoint to check if logged-in to correct endpoint. + try { + return (await this.invokeLongFunctionWithProgressForm(cfGetTarget, false)) as ITarget; + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const errText = toText(e); + getModuleLogger(LOGGER_MODULE).error("getTarget failed", { exception: errText }); + return undefined; + } + } + + private getCFDefaultLandscape(): Promise { + return cfGetConfigFileField("Target").then((cfgEndpoint: string) => { + return cfgEndpoint || cfendpoint.getCFEndpoint(); + }); + } + + private calculatePasscodeUrl(endpoint: string): string { + return endpoint.replace(/api./g, "login.") + "/passcode"; + } + + async init(): Promise { + this.currentTarget = await this.getTarget(); + if (!initTarget) { + initTarget = { endpoint: undefined, org: undefined, space: undefined }; + } + + initTarget.endpoint = initTarget?.endpoint + ? initTarget?.endpoint + : this.currentTarget && this.currentTarget["api endpoint"] + ? this.currentTarget["api endpoint"] + : await this.getCFDefaultLandscape(); + + return { + defaultEndpoint: initTarget.endpoint, + isLoggedIn: this.currentTarget !== undefined, + passcodeUrl: this.calculatePasscodeUrl(initTarget.endpoint), + currentOrg: this.currentTarget?.org, + currentSpace: this.currentTarget?.space, + }; + } + + async loginClick(payload: SSOLoginOptions | CredentialsLoginOptions) { + try { + cmdLoginResult = await this.invokeLongFunctionWithProgressForm(cfLogin, payload); + if (OK !== cmdLoginResult) { + void vscode.window.showErrorMessage(messages.login_failed); + return false; + } + void vscode.window.showInformationMessage(messages.login_success); + getModuleLogger(LOGGER_MODULE).debug("executeLogin: login succeeded"); + + if (isLoginOnly) { + setTimeout(() => { + panel?.dispose(); + }); + } + return true; + } catch (error) { + getModuleLogger(LOGGER_MODULE).error("executelogin: login error", error); + return false; + } + } + + async logoutClick() { + try { + await this.invokeLongFunctionWithProgressForm(cfLogout); + this.currentTarget = undefined; + void vscode.window.showInformationMessage(messages.logout_success); + getModuleLogger(LOGGER_MODULE).debug("executeLogout: logout succeeded"); + return true; + } catch (error) { + getModuleLogger(LOGGER_MODULE).error("executeLogout: logout error", error); + return false; + } + } + + getSelectedTarget() { + return { + endpoint: this.currentTarget?.["api endpoint"], + org: this.currentTarget?.org, + space: this.currentTarget?.space, + }; + } + + getOrgs(): Promise { + return cfGetAvailableOrgs() + .then((orgs: Organization[]) => { + getModuleLogger(LOGGER_MODULE).debug("executeGetAvaliableOrgs: get avaliable orgs succeeded"); + return orgs; + }) + .catch((error) => { + getModuleLogger(LOGGER_MODULE).error("executeGetAvaliableOrgs: get avaliable orgs failed", error); + return []; + }); + } + + getSpaces(org: string): Promise { + return cfGetAvailableSpaces(org) + .then((spaces: Space[]) => { + getModuleLogger(LOGGER_MODULE).debug("executeGetAvaliableSpaces: get avaliable spaces succeeded"); + return spaces; + }) + .catch((error) => { + getModuleLogger(LOGGER_MODULE).error("executeGetAvaliableSpaces: get avaliable spaces failed", error); + return []; + }); + } + + async applyTarget(org: string, space: string) { + try { + await cfSetOrgSpace(org, space); + cmdLoginResult = OK; + void vscode.window.showInformationMessage(messages.success_set_org_space); + getModuleLogger(LOGGER_MODULE).debug("executeSetOrgSpace: set org & spaces succeeded"); + panel?.dispose(); + return cmdLoginResult; + } catch (error) { + cmdLoginResult = undefined; + getModuleLogger(LOGGER_MODULE).error("executeSetOrgSpace: set org & spaces failed", error); + return "Error"; + } + } + + openPasscodeLink(endpoint: string) { + void vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(this.calculatePasscodeUrl(endpoint))); + } + + async invokeLongFunctionWithProgressForm(longFunction: Function, ...args: any): Promise { + try { + await this.rpc.invoke("setBusyIndicator", [true]); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + return await longFunction(...args); + } finally { + await this.rpc.invoke("setBusyIndicator", [false]); + } + } +} + +// for testing purpose only +export const internal = { + LoginTarget, +}; diff --git a/projects/cloud-foundry-tools/packages/backend/src/messages.ts b/projects/cloud-foundry-tools/packages/backend/src/messages.ts new file mode 100644 index 000000000..90db86884 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/messages.ts @@ -0,0 +1,77 @@ +import * as _ from "lodash"; + +export const messages = { + enter_cf_endpoint: "Enter Cloud Foundry endpoint", + enter_user_email: "Enter e-mail address", + label_enter_password: `Enter ${_.reverse(["d", "r", "o", "w", "s", "s", "a", "p"]).join("")}`, + login: "You need to login to Cloud Foundry to continue.", + loggin_in: "Logging in...", + logout: "Logging out...", + login_success: "You have been signed in to Cloud Foundry.", + logout_success: "You have been signed out.", + getting_orgs: "Getting Organizations...", + getting_spaces: "Getting Spaces...", + select_org: "Select Organization", + select_space: "Select Space", + set_org_space: "Setting Organization and Space...", + success_set_org_space: "The Organization and Space have been set.", + no_available_orgs: "There are no available organizations.", + no_available_spaces: "There are no available spaces.", + name_for_target: "Enter a name for the target", + not_targeted: "Login to CF", + space_not_set: "Space not set", + cf_setting_not_set: + "Could not find the Cloud Foundry settings. Make sure you have assigned an org and space in Cloud Foundry.", + enter_service_name: "Enter a name for the service instance", + loading_services: "Loading Cloud Foundry services...", + loading_ups_services: "Loading Cloud Foundry user provided services...", + loading_service_plan_list: "Loading Cloud Foundry service plans...", + binding_service_to_file: "Binding the service instance to a local '.env' file...", + select_service: "Select a Cloud Foundry service instance", + select_services: "Select a Cloud Foundry service", + select_service_plan: "Select a Cloud Foundry service plan", + no_services_instances_found: `No service instances found`, + create_service_enter_params: `Enter arbitrary parameters to be passed along to the service broker`, + enter_sys_log_url: `Enter a system logs drain URL`, + enter_route_service_url: `Enter a route service URL`, + enter_credentials: `Enter credentials`, + enter_tags: `Enter a tag for the service instance`, + load_data_from_deploy: `Loading data from Deploy Service...`, + no_cf_api_endpoint: `No CF API endpoint`, + verify_cf_connectivity: `Verifying Cloud Foundry connectivity details...`, + set_targets_save_details: `Save the current selected target`, + set_targets_pick_save: `Create new ...`, + set_targets_pick_save_details: `Create and save a new target`, + set_targets_choose_the_operation: `Save your current target or specify a new one`, + no_services_instance_byname_found: (name: string): string => `Could not find the ${name} service instance`, + login_failed: `Authentication failed.`, + authentication_failed: (result: string): string => `Authentication failed: ${result}`, + targeting: (orgField: string, spaceField: string): string => `Targeting CF ${orgField}/${spaceField}`, + service_created: (instanceName: string): string => `The ${instanceName} service has been created.`, + target_created: (targetName: string): string => `The '${targetName}' target has been created.`, + creating_service: (instanceName: string, servieInfoLabel: string, planInfoLabel: string): string => + `Creating the ${instanceName}(${servieInfoLabel} - ${planInfoLabel}) service...`, + target_deleted: (targetLabel: string): string => `The '${targetLabel}' target has been deleted.`, + activation_extension: (extname: string): string => `Activating the "${extname}" extension`, + no_services_found: (plan: string): string => `No services were found for the '${plan}' service_plan`, + no_services_instances_found_for_type: (name: string): string => `No service instances found for the ${name} type`, + extension_active: (extname: string): string => `The "${extname}" extension is now active.`, + error_service_params_mandatory: (name: string): string => `You must provide a value for the ${name}`, + error_service_params_max_length: (name: string, length: number): string => + `The ${name} may be composed of up to ${length} characters`, + error_service_params_value_not_allowed: (name: string): string => `Invalid value for the ${name}`, + service_bound_successful: (service: string): string => `The ${service} service has been bound.`, + service_unbound_successful: (service: string): string => `The ${service} service has been unbound.`, + no_service_plan_info_found: (plan: string, service: string): string => + `Could not find the ${plan} service plan info for the ${service} service`, + no_service_plans_found: (service: string): string => `Could not find a service plan for the ${service} service`, + target_dirty_save: (name: string): string => + `The ${name} target has been modified. Would you like to save the changes?`, + service_instances_list_incomplete: (list: string[]): string => + `Could not get the details of ${ + _.size(list) > 3 ? "some service instances" : "the following service instances: '" + list.join() + "'" + }. Try to verify the ${_.size(list) > 3 ? "" : "specified "}service instances, then log in again to Cloud Foundry.`, + target_setup_not_completed: (org: string | undefined, space: string | undefined): string => + `The CF target definition is incomplete - org='${org}', space='${space}'. Do you want to continue?`, + set_targets_save: (org: string, space: string): string => `Save current target (${org}/${space})`, +}; diff --git a/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts b/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts new file mode 100644 index 000000000..4c0d4e6ef --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts @@ -0,0 +1,87 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import { + IRunnable, + IRunConfigContext, + IDependencyContext, + ConfigurationTarget, + IRunConfigHandler, + IConfigurationData, + ConfigObject, +} from "@sap/wing-run-config-types"; + +class Runnable implements IRunnable { + public displayName = "CF Tools Example"; + public displayType = "CF Tools Type"; + public path = "path"; + constructor(public id: string) {} +} + +class DependencyContext implements IDependencyContext { + public displayName = "cf-tools-rsource-dependency"; + public bindable = true; + public displayType = "cf-tools-rsource-dependency"; + public type = "hana"; + public dependencyHandlerId = "cf-tools-rsource-dependency"; +} + +export class RunConfigContext implements IRunConfigContext { + public iconBuffer: Buffer; + public displayName = "CF Tools Example"; + public groupName = this.appId; + public envPath: vscode.Uri = vscode.Uri.file( + path.join("C:", "Users", "your-i-number", "devx-wing", "test-run-config", "env", ".env") + ); + public path: vscode.Uri; + public dependencies = [new DependencyContext()]; + + constructor(private readonly id: string, private readonly appId: string) { + this.groupName = appId; + this.path = vscode.Uri.file(""); + this.iconBuffer = Buffer.alloc(1); + } +} + +export class RunConfigHandler implements IRunConfigHandler { + constructor(private readonly id: string) {} + + public getRunnables(): Promise { + return Promise.resolve([ + new Runnable(this.id), + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + new Runnable(this.id + 1), + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + new Runnable(this.id + 2), + ]); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public getRunConfigContext(runnableId: string, confData: IConfigurationData): Promise { + return Promise.resolve(new RunConfigContext(this.id, "Cf App")); + } + + public createConfiguration( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + runnableId: string + ): Promise { + return Promise.resolve({ + config: new ConfigObject( + { + type: "node", + request: "launch", + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + name: "cf run " + Date.now(), + env: { + Saggi: 1, + "run.config": "sage", + }, + }, + ConfigurationTarget.launch + ), + }); + } + + public getId(): string { + return "cf_run_id_" + this.id; + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts b/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts new file mode 100644 index 000000000..58bdd5585 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts @@ -0,0 +1,141 @@ +import * as types from "@sap/wing-run-config-types"; +import * as cfViewCommands from "./cfViewCommands"; +import { getEnvResources, removeResourceFromEnv, toText } from "./utils"; +import { checkAndCreateChiselTask } from "./chisel"; +import * as _ from "lodash"; +import * as vscode from "vscode"; +import * as cfLocal from "@sap/cf-tools"; +import { messages } from "./messages"; +import { getModuleLogger } from "./logger/logger-wrapper"; + +export class DependencyHandler implements types.IDependencyHandler { + private readonly id: string; + private static readonly MODULE_NAME = "DependencyHandler"; // workarround in minimized mode + + constructor(id: string) { + this.id = id; + } + + public async getBindState(bindContext: types.IBindContext): Promise { + let bindState = types.BindState.notbound; + + try { + if ( + _.get( + (await getEnvResources(_.get(bindContext, ["envPath", "fsPath"]))).vcapObject, + _.get(bindContext, ["depContext", "type"]) + ) + ) { + bindState = types.BindState.cloud; + } + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(DependencyHandler.MODULE_NAME).error("getBindState: processing failed", { exception: toText(e) }); + } + return bindState; + } + + public async bind( + bindContext: types.IBindContext, + options?: cfViewCommands.CmdOptions + ): Promise { + const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); + const serviceType: cfLocal.ServiceTypeInfo[] = [ + { + name: _.get(bindContext.depContext, ["type"], ""), + plan: _.get(bindContext.depContext, ["data", "plan"], ""), + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + tag: resourceTag ? resourceTag + _.get(bindContext, "depContext.data.resourceName") : "", + prompt: "", + }, + ]; + try { + const instanceNames = await cfViewCommands.bindLocalService(serviceType, bindContext.envPath, options); + let chiselTask; + if (_.size(instanceNames)) { + // Get metadata of service instance by service name + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const instanceType: string = (await cfLocal.cfGetInstanceMetadata(_.get(instanceNames, "[0]"))).service; + + // Create chisel task if neccessary + if (_.get(bindContext, "depContext.data.isCreateChiselTask") || /^hana(trial)?$/.test(instanceType)) { + // Create it in dependent task + const chiselTaskNameSuffix = instanceNames?.join("&") || ""; + chiselTask = await checkAndCreateChiselTask(bindContext.envPath?.fsPath, chiselTaskNameSuffix); + if (chiselTask) { + if (!options?.silent) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + void vscode.window.showInformationMessage( + `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselTask.label}'` + ); + } + getModuleLogger(DependencyHandler.MODULE_NAME).info( + "bind: <%s> task for opening the VPN tunnel to the Cloud Foundry space has been created", + chiselTask.label + ); + + if (_.isNil(bindContext.configData.dependentTasks)) { + // eslint-disable-next-line require-atomic-updates + bindContext.configData.dependentTasks = []; + } + bindContext.configData.dependentTasks.push(chiselTask); + } + } + return _.merge( + { + configData: bindContext.configData, + resource: { + name: _.get(instanceNames, "[0]"), + type: instanceType || "", + }, + }, + chiselTask ? { resource: { data: { chiselTask } } } : {} + ); + } + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(DependencyHandler.MODULE_NAME).error("bind: processing failed", { exception: toText(e) }); + } + } + + public async unbind( + bindContext: types.IBindContext, + options?: cfViewCommands.CmdOptions + ): Promise { + const configObject = new types.ConfigObject({}, types.ConfigurationTarget.launch); + const configurationData: types.IConfigurationData = { config: configObject, dependentTasks: [] }; + try { + const removedResourceDetails = await removeResourceFromEnv(bindContext); + if (!options?.silent) { + void vscode.window.showInformationMessage( + messages.service_unbound_successful(_.get(removedResourceDetails, "resourceName")) + ); + } + getModuleLogger(DependencyHandler.MODULE_NAME).info( + "unbind: the <%s> service has been unbound", + _.get(removedResourceDetails, "resourceName") + ); + return Promise.resolve({ + configData: configurationData, + resource: { + name: _.get(removedResourceDetails, "resourceName") || "", + type: _.get(removedResourceDetails, "resourceData.label") || "", + data: _.get(removedResourceDetails, "resourceData") || {}, + }, + }); + } catch (e) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + void vscode.window.showErrorMessage(toText(e)); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + getModuleLogger(DependencyHandler.MODULE_NAME).error("unbind: processing failed", { exception: toText(e) }); + } + } + + public getId(): string { + return this.id; + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts b/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts new file mode 100644 index 000000000..a4e5e13f9 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts @@ -0,0 +1,49 @@ +import * as vscode from "vscode"; +import { dataContentAsObject } from "@sap/cf-tools"; +import * as _ from "lodash"; +import { parse } from "comment-json"; + +const CONNECTIONS = "connections"; + +function saveSettings(filePath: string, configuration: any) { + const sqltoolsSettings = vscode.workspace.getConfiguration("sqltools", vscode.Uri.file(filePath)); + const connections: any[] = sqltoolsSettings.get(CONNECTIONS) || []; + + const existingConnIndex = connections.findIndex((conn: any) => conn.name === configuration.name); + if (existingConnIndex >= 0) { + connections[existingConnIndex] = configuration; + } else { + connections.push(configuration); + } + void sqltoolsSettings.update(CONNECTIONS, connections); +} + +export async function checkAndCreateSQLToolsSettings(filePath: string): Promise { + const envFileContent = await dataContentAsObject(filePath); + const vcapServicesStr = _.get(envFileContent, "VCAP_SERVICES"); + if (vcapServicesStr) { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + const vcapServices = parse(vcapServicesStr); + const hanaServices = _.get(vcapServices, "hana"); + if (hanaServices) { + const serviceNames = _.map(hanaServices, (srv) => { + const conn = { + name: _.get(srv, "name"), + dialect: "SAPHana", + database: _.get(srv, "credentials.schema"), + server: _.get(srv, "credentials.host"), + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + port: Number.parseInt(_.get(srv, "credentials.port"), 10), + username: _.get(srv, "credentials.user"), + password: _.get(srv, "credentials.password"), + connectionTimeout: 30, + }; + saveSettings(filePath, conn); + return srv.name as string; + }); + return serviceNames.join(","); + } + } + + return ""; +} diff --git a/projects/cloud-foundry-tools/packages/backend/src/utils.ts b/projects/cloud-foundry-tools/packages/backend/src/utils.ts new file mode 100644 index 000000000..e9db8d6cb --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/src/utils.ts @@ -0,0 +1,447 @@ +import * as vscode from "vscode"; +import * as path from "path"; +import * as fs from "fs"; +import { messages } from "./messages"; +import * as types from "@sap/wing-run-config-types"; +import * as _ from "lodash"; +import * as PropertiesReader from "properties-reader"; +import { parse } from "comment-json"; +import { EOL, platform } from "os"; +import { + IServiceQuery, + ServiceInstanceInfo, + cfGetUpsInstances, + eFilters, + ServiceTypeInfo, + cfGetManagedServiceInstances, + cfGetTarget, +} from "@sap/cf-tools"; +import { getModuleLogger } from "./logger/logger-wrapper"; +import { existsSync } from "fs"; + +export const isWindows = platform().includes("win"); +type TypeValidationResult = string | undefined | null; + +const LOGGER_MODULE = "utils"; + +export interface DisplayServices { + query?: IServiceQuery; + ups?: ServiceTypeInfo["ups"]; +} + +export interface BaseServiceQueryOptions { + tag?: string; // service tags + credentials?: { + tag?: string; + }; +} + +export type UpsServiceQueryOprions = BaseServiceQueryOptions; + +export interface ServiceQueryOptions extends BaseServiceQueryOptions { + name?: string; //service type name + plan?: string; //Service plan +} + +function spotRedirectUri() { + // expected host pattern is 'DOMAIN.PLATFORM.REG...APPNAME.cloud.sap' + const urlInput: string = + _.get(process, "env.WS_BASE_URL") || + "https://wingtestsubacc-workspaces-ws-gwzd6.staging-01.dev10.int.webide.cloud.sap"; + const host = new URL(urlInput).hostname; + // "https://*.cry10.int.applicationstudio.cloud.sap/**" + return `https://*.${_.join(_.drop(_.split(host, ".")), ".")}/**`; +} + +export const ENV_VCAP_RESOURCES = "VCAP_SERVICES"; + +export function toText(e: Error): string { + return _.get(e, "message") || _.get(e, "name", _.toString(e)); +} + +export function getEnvResources(envFilePath: string): Promise<{ vcapObject: any; isQuotedVcap: boolean }> { + try { + let isQuotedVcap = false; + if (existsSync(envFilePath)) { + const envProperties = PropertiesReader(envFilePath); + let vcapProperty = envProperties.getRaw(ENV_VCAP_RESOURCES); + if (vcapProperty) { + // In case VCAP_SERVICES is wrapped with single quotes - unwrap it + if (_.startsWith(vcapProperty, "'") && _.endsWith(vcapProperty, "'")) { + vcapProperty = vcapProperty.substring(1, vcapProperty.length - 1); + isQuotedVcap = true; + } + return Promise.resolve({ vcapObject: JSON.parse(vcapProperty), isQuotedVcap }); + } else { + getModuleLogger(LOGGER_MODULE).debug( + "getEnvResources: the '.env' file is missing a key <%s>", + ENV_VCAP_RESOURCES, + { filePath: envFilePath } + ); + return Promise.resolve({ vcapObject: null, isQuotedVcap }); + } + } else { + getModuleLogger(LOGGER_MODULE).debug("getEnvResources: the '.env' file does not exist", { + filePath: envFilePath, + }); + return Promise.resolve({ vcapObject: null, isQuotedVcap }); + } + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error( + "getEnvResources: could not get the '.env' file resources", + { exception: toText(new Error(error?.message as string)) }, + { filePath: envFilePath } + ); + throw error; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function findServiceByResourceNameTag(vcapServices: any, yamlResourceName: string, resourceTag: string): any { + for (const key in vcapServices) { + for (const service of vcapServices[key]) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + if ( + service.tags && + service.tags.find((t: string) => t.startsWith(resourceTag) && t.substr(resourceTag.length) === yamlResourceName) + ) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return [key, service]; + } + } + } + return undefined; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function removeResourceFromEnv( + bindContext: types.IBindContext +): Promise<{ resourceName: string; envPath: string; resourceData: any }> { + let instanceData; + const envFilePath: string = bindContext.envPath.fsPath; + const envResources = await getEnvResources(envFilePath); + const vcapServicesObj = envResources.vcapObject; + const bindResourceType = + bindContext.depContext.type === types.DependencyType.runtimeservice + ? bindContext.depContext.displayType + : bindContext.depContext.type; + let instanceName = ""; + // If this is a tagged resource - remove by tag + const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); + const resourceName: string = _.get(bindContext, "depContext.data.resourceName"); + if (!_.isEmpty(resourceTag)) { + const keyAndService = findServiceByResourceNameTag(vcapServicesObj, resourceName, resourceTag); + if (!_.isEmpty(keyAndService)) { + const resourceTypeKey = keyAndService[0]; + instanceData = keyAndService[1]; + instanceName = instanceData.instance_name; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + _.remove(vcapServicesObj[resourceTypeKey], (hanaService: any) => { + return hanaService.instance_name === instanceName; + }); + // If after removal no service instances left - remove this resource type completely to prevent something like: VCAP_SERVICES={"hana":[]} + if (_.isEmpty(vcapServicesObj[resourceTypeKey])) { + delete vcapServicesObj[resourceTypeKey]; + } + } else { + getModuleLogger( + LOGGER_MODULE + ).debug( + "removeResourceFromEnv: the <%s> tagged <%s> resource was not found in the '.env' file", + resourceTag, + resourceName, + { filePath: envFilePath } + ); + } + } else { + // Remove by resource dependency type only + const serviceToDelete = _.get(vcapServicesObj, bindResourceType); + if (!_.isEmpty(serviceToDelete)) { + instanceData = serviceToDelete[0]; + instanceName = instanceData.instance_name; + delete vcapServicesObj[bindResourceType]; + } + } + + // Update VCAP_SERVICES in the .env file + const envProperties = PropertiesReader(envFilePath); + + // If VCAP_SERVICES was already wrapped with single quotes - maintain them when writing back + const quote = envResources.isQuotedVcap ? "'" : ""; + envProperties.set(ENV_VCAP_RESOURCES, quote + JSON.stringify(vcapServicesObj) + quote); + + await envProperties.save(envFilePath); + return { resourceName: instanceName, envPath: envFilePath, resourceData: instanceData }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function generateParams4Service(serviceLabel: string, plan: string): any { + if ("xsuaa" === serviceLabel && "application" === plan) { + return { + xsappname: `xsuaa_${_.now()}`, + "tenant-mode": "dedicated", + description: "Security profile of called application", + scopes: [ + { + name: "uaa.user", + description: "UAA", + }, + ], + "role-templates": [ + { + name: "Token_Exchange", + description: "UAA", + "scope-references": ["uaa.user"], + }, + ], + "oauth2-configuration": { + "redirect-uris": [`${spotRedirectUri()}`], + }, + }; + } + return {}; +} + +function validateXsuaaTenantMode(value: string) { + if (!_.isUndefined(value) && !["shared", "dedicated", "external"].includes(_.trim(value))) { + throw new Error(messages.error_service_params_value_not_allowed("tenant-mode")); + } +} + +function validateXsuaaOauth2Configuration(value: unknown) { + for (const uri of _.get(value, "redirect-uris", [])) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + if (!_.startsWith(uri, "http") && !_.startsWith(uri, "localhost")) { + throw new Error(messages.error_service_params_value_not_allowed("redirect-uris")); + } + } +} + +function validateXsuaaName(value: string) { + value = _.trim(value); + if (!value) { + throw new Error(messages.error_service_params_mandatory("xsappname")); + } + if (_.size(value) >= 100) { + throw new Error(messages.error_service_params_max_length("xsappname", 100)); + } + if (/[^a-zA-Z0-9_\-"]/gm.test(value)) { + throw new Error(messages.error_service_params_value_not_allowed("xsappname")); + } +} + +function validateParamsXsuaa(value: string): TypeValidationResult { + let result; + try { + const data = parse(_.trim(value)); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + validateXsuaaName(_.get(data, "xsappname")); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + validateXsuaaTenantMode(_.get(data, "tenant-mode")); + validateXsuaaOauth2Configuration(_.get(data, "oauth2-configuration")); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + result = toText(e); + } + return result; +} + +function validateParamsJson(value: string): TypeValidationResult { + let result; + try { + parse(_.trim(value)); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + result = toText(e); + } + return result; +} + +export function validateParams(serviceLabel: string, plan?: string): (value: string) => TypeValidationResult { + return "xsuaa" === serviceLabel && "application" === plan ? validateParamsXsuaa : validateParamsJson; +} + +export function isRegexExpression(statement: string): boolean { + return /^\/\S+\/$/g.test(statement); +} + +export function composeFilterPattern(value: string): string { + return value ? (isRegexExpression(value) ? _.trim(value, "/") : `^${value}$`) : value; +} + +async function doGetUpsServiceInstances(query?: IServiceQuery, filterCredTag?: string): Promise { + const upsServices = await cfGetUpsInstances(query); + const pattern = _.size(upsServices) && filterCredTag ? composeFilterPattern(filterCredTag) : undefined; + const ups2Show = pattern + ? upsServices.filter((service) => { + // cretentials.tags can be 'string', 'string array' or 'array of objects' only + const tags = _.isString(service.credentials?.tags) ? [service.credentials.tags] : service.credentials?.tags; + return _.find(tags, (tag: string) => new RegExp(pattern).test(tag)); + }) + : upsServices; + return ups2Show; +} + +export async function getUpsServiceInstances(options?: UpsServiceQueryOprions): Promise { + return doGetUpsServiceInstances(undefined, options?.credentials?.tag); +} + +export async function getAllServiceInstances(opts?: DisplayServices): Promise { + let ups2Show: ServiceInstanceInfo[] = []; + if (opts?.ups?.isShow || opts?.ups?.tag) { + const copyQuery = _.cloneDeep(opts.query); + _.remove(copyQuery?.filters || [], (item) => { + return !_.includes( + [ + eFilters.names, + eFilters.space_guids, + eFilters.organization_guids, + eFilters.oder_by, + eFilters.page, + eFilters.per_page, + eFilters.updated_ats, + eFilters.created_ats, + ], + item.key + ); + }); + ups2Show = await doGetUpsServiceInstances(copyQuery, opts.ups.tag); + } + return _.concat(await cfGetManagedServiceInstances(opts?.query), ups2Show); +} + +const UTF8 = "utf8"; +const GITIGNORE = ".gitignore"; + +export async function updateGitIgnoreList(envPath: string): Promise { + const project = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(envPath)); + if (project) { + const isNotEmptyPattern = (value: string): boolean => { + return _.size(value) > 0 && !/^\s*#/.test(value); + }; + const isPatternFound = async (patterns: string[]): Promise => { + for (const pattern of patterns) { + if ( + isNotEmptyPattern(pattern) && + _.includes( + _.map(await vscode.workspace.findFiles(new vscode.RelativePattern(project, pattern)), "fsPath"), + envPath + ) + ) { + return true; + } + } + return false; + }; + const ignoreFiles = await vscode.workspace.findFiles(new vscode.RelativePattern(project, GITIGNORE)); + if (!_.size(ignoreFiles)) { + try { + // .gitignore file not exists -> create one + const filePath = path.normalize(path.join(project.uri.fsPath, GITIGNORE)); + await fs.promises.open(filePath, "w"); + ignoreFiles.push(vscode.Uri.file(filePath)); + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error("updateGitIgnoreList: creation .gitignore file failed", { + exception: toText(new Error(e?.message as string)), + }); + } + } + for (const file of ignoreFiles) { + try { + const patterns = _.split(await fs.promises.readFile(file.fsPath, { encoding: UTF8 }), EOL); + if (!(await isPatternFound(patterns))) { + await fs.promises.writeFile( + file.fsPath, + _.join( + _.concat(patterns, [ + `# auto generated wildcard`, + _.replace(path.relative(project.uri.fsPath, envPath), /\\/g, "/"), + ]), + EOL + ), + { encoding: UTF8 } + ); + } + } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + getModuleLogger(LOGGER_MODULE).error( + "updateGitIgnoreList: update .gitignore file failed", + { exception: toText(new Error(e?.message as string)) }, + { filePath: file } + ); + } + } + } +} + +export async function writeProperties(filePath: string, properties: Record): Promise { + let text = ""; + Object.keys(properties).forEach((key) => { + const value = properties[key]; + if (value) { + text += `${key}=${value.trim()}\n`; + } + }); + await fs.promises.writeFile(filePath, text); +} + +export function resolveFilterValue(value: string | undefined): string { + // allowed patterns: 'hana'|' "hana ", " xsuaa"'|'["xsuaa", "hana"]' + // eslint-disable-next-line @typescript-eslint/unbound-method + return _.join(_.map(_.isString(value) ? _.split(value, ",") : value, _.trim)); +} + +export function notifyWhenServicesInfoResultIncomplete( + list: Promise +): Promise { + const isUnknown = (value: string | undefined) => "unknown" === value; + return list.then((infos) => { + const results = _.reduce( + infos, + (res, info) => { + if (isUnknown(info.serviceName) || isUnknown(info.plan)) { + res.push(info.label); + } + return res; + }, + [] + ); + if (_.size(results)) { + void vscode.window.showWarningMessage(messages.service_instances_list_incomplete(results)); + } + return infos; + }); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function examCFTarget(errorMessage: string, keys: string[], weak: boolean): Promise { + const target = await cfGetTarget(); + if (weak) { + return target; + } + return _.reduce( + keys, + (cond, key) => { + cond = cond && _.get(target, key); + return cond; + }, + true + ) + ? target + : Promise.reject(new Error(errorMessage)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function invokeLongFunctionWithProgress(longFunction: any, progressMessage: string): Thenable { + return vscode.window.withProgress( + { + location: vscode.ProgressLocation.Window, + title: progressMessage, + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + }, + () => longFunction() + ); +} diff --git a/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts new file mode 100644 index 000000000..83d360644 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts @@ -0,0 +1,300 @@ +import { expect } from "chai"; +import * as _ from "lodash"; +import * as path from "path"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; + +mockVscode(nsVsMock.testVscode, "src/cfView.ts"); + +import * as cfView from "../src/cfView"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; +import type { TreeItem } from "vscode"; + +describe("cfView tests", () => { + let sandbox: SinonSandbox; + let cfLocalMock: SinonMock; + let cfViewMock: SinonMock; + + before(() => { + sandbox = createSandbox(); + }); + + after(() => { + sandbox.restore(); + }); + + beforeEach(() => { + cfViewMock = sandbox.mock(cfView.CFView); + cfLocalMock = sandbox.mock(cfLocal); + }); + + afterEach(() => { + cfLocalMock.verify(); + cfViewMock.verify(); + }); + + describe("CFTargetTI", () => { + const activePattern = "(Active Target)"; + const contextPattern = "cf-target"; + it("ok:: get description - active, not dirty", () => { + const item = new cfView.CFTargetTI({ label: "test", isCurrent: true, isDirty: false }); + expect(item.description).to.be.equal(activePattern); + expect(item.contextValue).to.be.equal(`${contextPattern}-active`); + const parts = _.split(_.get(item, "iconPath.dark") as string, path.sep); + expect(_.includes(parts, "target-a.svg")).to.be.true; + }); + + it("ok:: get description - not active, is dirty", () => { + const item = new cfView.CFTargetTI({ label: "test", isCurrent: false, isDirty: true }); + expect(item.description).to.be.equal("*"); + expect(item.contextValue).to.be.equal(`${contextPattern}`); + const parts = _.split(_.get(item, "iconPath.light") as string, path.sep); + expect(_.includes(parts, "target.svg")).to.be.true; + }); + + it("ok:: get description - (no targets), is active, is dirty", () => { + const item = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + expect(item.description).to.be.equal(`*`); + expect(item.contextValue).to.be.equal(`${contextPattern}-notargets`); + expect(item.tooltip).to.be.equal(item.label); + const parts = _.split(_.get(item, "iconPath.dark") as string, path.sep); + expect(_.includes(parts, "target-a.svg")).to.be.true; + }); + }); + + describe("Tree Nodes", () => { + const root = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + const parent = new cfView.CFFolder("folder", root); + + it("ok:: CFLoginNode properties verifying", () => { + const item = new cfView.CFLoginNode(parent); + expect(item.contextValue).to.be.equal(`cf-login-required`); + expect(item.label).to.be.equal(`Login required`); + expect(item.tooltip).to.be.equal(`Log in to see details`); + const parts = _.split(_.get(item, "iconPath.dark") as string, path.sep); + expect(_.includes(parts, "info.svg")).to.be.true; + expect(item.parent).to.be.equal(parent); + }); + + it("ok:: CFTargetNotCurrent properties verifying", () => { + const target = "my-target"; + const item = new cfView.CFTargetNotCurrent(parent, target); + expect(item.contextValue).to.be.equal(`cf-target-not-current`); + expect(item.label).to.be.equal(`Target needs to be active`); + expect(item.tooltip).to.be.equal(`Set the '${target}' as the active target to see details.`); + const parts = _.split(_.get(item, "iconPath.light") as string, path.sep); + expect(_.includes(parts, "info.svg")).to.be.true; + expect(item.parent).to.be.equal(parent); + }); + + it("ok:: CFService properties verifying", () => { + const name = "test-service"; + const type = "xsuaa"; + const item = new cfView.CFService(name, type, parent); + expect(item.contextValue).to.be.equal(`cf-service`); + expect(item.label).to.be.equal(name); + expect(item.type).to.be.equal(type); + expect(item.parent).to.be.equal(parent); + }); + + it("ok:: CFApplication properties verifying", () => { + const name = "test-app"; + const state = "STOPPED"; + const item = new cfView.CFApplication(name, state, parent); + expect(item.contextValue).to.be.equal(`cf-application`); + expect(item.label).to.be.equal(name); + expect(item.state).to.be.equal(state); + expect(item.parent).to.be.equal(parent); + }); + + it("ok:: CFServicesFolder properties verifying", () => { + const name = "services"; + const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const item = new cfView.CFServicesFolder(name, parent); + expect(item.contextValue).to.be.equal(`services`); + expect(item.label).to.be.equal(name); + expect(item.tooltip).to.be.equal(item.label); + }); + + it("ok:: CFAppsFolder properties verifying", () => { + const name = "apps"; + const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const item = new cfView.CFAppsFolder(name, parent); + expect(item.contextValue).to.be.equal(`apps`); + expect(item.label).to.be.equal(name); + expect(item.tooltip).to.be.equal(item.label); + }); + }); + + describe("CFView ", () => { + const root = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + const folder = new cfView.CFFolder("folder", root); + + let instance: cfView.CFView; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const testContext: any = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; + beforeEach(() => { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + instance = new cfView.CFView(testContext, ""); + }); + + it("ok:: verify getParent of CFAppsFolder", () => { + const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const item = new cfView.CFAppsFolder("name", parent); + expect(instance.getParent(item)).to.be.equal(parent); + }); + + it("ok:: verify getParent of CFServicesFolder", () => { + const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const item = new cfView.CFServicesFolder("name", parent); + expect(instance.getParent(item)).to.be.equal(parent); + }); + + it("ok:: verify getParent of CFApplication", () => { + expect(instance.getParent(new cfView.CFApplication("name", "state", folder))).to.be.equal(folder); + }); + + it("ok:: verify getTreeItem of CFAppsFolder", async () => { + const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const item = new cfView.CFAppsFolder("name", parent); + expect(await instance.getTreeItem(item)).to.be.equal(item); + }); + + it("ok:: verify getTreeItem of CFApplication", async () => { + const name = "test-app"; + const state = "STOPPED"; + const item = new cfView.CFApplication(name, state, folder); + const viewItem = await instance.getTreeItem(item); + expect(viewItem.label).to.be.equal(`${name} (${state})`); + expect(instance.getParent(item)).to.be.equal(folder); + }); + + it("ok:: verify getTreeItem of CFService", async () => { + const name = "test-app"; + const type = "type"; + const item = new cfView.CFService(name, type, folder); + const viewItem = await instance.getTreeItem(item); + expect(viewItem.label).to.be.equal(`${name} (${type})`); + expect(instance.getParent(item)).to.be.equal(folder); + }); + + it("ok:: verify CFFolder contextValue is not defined", async () => { + const name = "test-app"; + const item = new cfView.CFFolder(name, root); + const viewItem = await instance.getTreeItem(item); + expect(viewItem.label).to.be.equal(`${name}`); + expect(viewItem.tooltip).to.be.equal(`${name}`); + expect(instance.getParent(item)).to.be.equal(root); + }); + + it("ok:: verify getChildren - root", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + cfLocalMock.expects("cfGetTargets").resolves(targets); + expect(await instance.getChildren((undefined as unknown) as TreeItem)).to.deep.equal( + _.map(targets, (target) => new cfView.CFTargetTI(target)) + ); + }); + + it("ok:: verify getChildren - target, not active", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + const expected = [ + new cfView.CFServicesFolder("Services", instance.targets[0]), + new cfView.CFAppsFolder("Applications", instance.targets[0]), + ]; + expect(await instance.getChildren(instance.targets[0])).to.deep.equal(expected); + }); + + it("ok:: verify getChildren - target/services, not active", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + const parent = new cfView.CFServicesFolder("Services", instance.targets[0]); + const expected = [new cfView.CFTargetNotCurrent(parent, _.get(parent, "parent.target.label") as string)]; + expect(await instance.getChildren(parent)).to.deep.equal(expected); + }); + + it("ok:: verify getChildren - target/services, is active", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + const parent = new cfView.CFServicesFolder("Services", instance.targets[1]); + const services = [ + { label: "s-1", serviceName: "name-1" }, + { label: "s-2", serviceName: "name-2" }, + ]; + cfLocalMock.expects("cfGetServiceInstancesList").resolves(services); + const expected = _.map(services, (service) => new cfView.CFService(service.label, service.serviceName, parent)); + expect(await instance.getChildren(parent)).to.deep.equal(expected); + }); + + it("ok:: verify getChildren - target/applications, is active", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + const parent = new cfView.CFAppsFolder("Apps", instance.targets[1]); + const apps = [ + { name: "a-1", state: "STARTED" }, + { name: "a-2", state: "STOPPED" }, + ]; + cfLocalMock.expects("cfGetApps").resolves(apps); + const expected = _.map(apps, (app) => new cfView.CFApplication(app.name, app.state, parent)); + expect(await instance.getChildren(parent)).to.deep.equal(expected); + }); + + it("ok:: verify getChildren - target/applications, log in required", async () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + const parent = new cfView.CFAppsFolder("Apps", instance.targets[1]); + cfLocalMock.expects("cfGetApps").rejects(new Error("error")); + expect(await instance.getChildren(parent)).to.deep.equal([new cfView.CFLoginNode(parent)]); + }); + + it("ok:: verify getChildren - target/applications, incorrect hierachy", async () => { + expect(await instance.getChildren(new nsVsMock.testVscode.TreeItem("Apps"))).to.deep.equal([]); + }); + + it("ok:: verify getCurrentTarget, active is defined", () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + expect(instance.getCurrentTarget()).to.be.equal(instance.targets[1].target); + }); + + it("ok:: verify getCurrentTarget, active is not defined", () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: false, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + expect(instance.getCurrentTarget()).to.be.equal(undefined); + }); + + it("ok:: verify getTargets", () => { + const targets = [ + { label: "t-1", isCurrent: false, isDirty: false }, + { label: "t-2", isCurrent: true, isDirty: false }, + ]; + instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + expect(instance.getTargets()).to.deep.equal(instance.targets); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts new file mode 100644 index 000000000..69c874e4e --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts @@ -0,0 +1,1181 @@ +import { expect, assert } from "chai"; +import * as _ from "lodash"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; + +nsVsMock.setTestRoots(["/", "/some_path"]); + +mockVscode(nsVsMock.testVscode, "src/cfViewCommands.ts"); +mockVscode(nsVsMock.testVscode, "src/logger/logger-wrapper.ts"); + +import * as cfViewCommands from "../src/cfViewCommands"; +import * as commands from "../src/commands"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import * as cfLocalUtils from "@sap/cf-tools/out/src/utils"; +import { + ServiceTypeInfo, + ServiceInstanceInfo, + Cli, + DEFAULT_TARGET, + CFTarget, + IServiceQuery, + eFilters, + PlanInfo, + CF_PAGE_SIZE, + eServiceTypes, +} from "@sap/cf-tools"; +import * as cfView from "../src/cfView"; +import * as https from "https"; +import * as fspath from "path"; +import { messages } from "../src/messages"; +import { fail } from "assert"; +import { DisplayServices, ServiceQueryOptions, UpsServiceQueryOprions } from "../src/utils"; +import * as chisel from "../src/chisel"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; + +describe("cfViewCommands tests", () => { + let sandbox: SinonSandbox; + let vscodeWindowMock: SinonMock; + let vscodeWorkspaceMock: SinonMock; + let cfViewCommandsMock: SinonMock; + let commandsMock: SinonMock; + let cliMock: SinonMock; + let httpsMock: SinonMock; + let cfLocalMock: SinonMock; + let cfLocalUtilsMock: SinonMock; + let cfViewMock: SinonMock; + let chiselMock: SinonMock; + const cliResult = { exitCode: 0, stdout: "", stderr: "" }; + + before(() => { + sandbox = createSandbox(); + }); + + beforeEach(() => { + cfViewMock = sandbox.mock(cfView.CFView); + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + vscodeWorkspaceMock = sandbox.mock(nsVsMock.testVscode.workspace); + cfViewCommandsMock = sandbox.mock(cfViewCommands); + cfLocalMock = sandbox.mock(cfLocal); + cfLocalUtilsMock = sandbox.mock(cfLocalUtils); + cliMock = sandbox.mock(Cli); + commandsMock = sandbox.mock(commands); + httpsMock = sandbox.mock(https); + chiselMock = sandbox.mock(chisel); + }); + + afterEach(() => { + sandbox.restore(); + cfLocalMock.verify(); + cfLocalUtilsMock.verify(); + vscodeWindowMock.verify(); + vscodeWorkspaceMock.verify(); + cfViewCommandsMock.verify(); + cliMock.verify(); + cfViewMock.verify(); + httpsMock.verify(); + commandsMock.verify(); + chiselMock.verify(); + }); + + it("cmdReloadTargets", async () => { + cfViewMock.expects("get").returns({ + refresh: () => { + return; + }, + }); + await cfViewCommands.cmdReloadTargets(); + }); + + describe("cfDeployServiceAPI", () => { + const testToken = " \ntestToken\n"; + const errorText = "my error"; + const result = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + on: (type: string, callback: any) => callback(new Error(errorText)), + }; + const urlPath = "url/path"; + + it("exception:: https get on - error", async () => { + const ap = "https://api.cf.sap.hana.ondemand.com"; + const target = new URL(_.replace(ap, "api.cf", "deploy-service.cfapps")); + const options = { + protocol: target.protocol, + host: target.host, + port: 443, + method: "get", + path: target.pathname, + headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + }; + cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + httpsMock.expects("get").withArgs(options).returns(result); + cfLocalMock.expects("cfGetAuthToken").resolves(testToken); + try { + await cfViewCommands.cfDeployServiceAPI(""); + fail("should not reach here"); + } catch (e) { + expect(e).to.equal(errorText); + } + }); + + it("exception:: https get on - error, url parse data", async () => { + const ap = "https://api.cf.sap.hana.ondemand.com:8090"; + const target = new URL(_.replace(ap, "api.cf", "deploy-service.cfapps")); + const options = { + protocol: target.protocol, + host: target.host, + port: target.port, + method: "get", + path: urlPath, + headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + }; + cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + httpsMock.expects("get").withArgs(options).returns(result); + cfLocalMock.expects("cfGetAuthToken").resolves(testToken); + try { + await cfViewCommands.cfDeployServiceAPI(urlPath); + fail("should not reach here"); + } catch (e) { + expect(e).to.equal(errorText); + } + }); + + it("exception:: https get on - error, url parse data, default port", async () => { + const ap = "http://api.cf.sap.hana.ondemand.com"; + const target = new URL(_.replace(ap, "api.cf", "deploy-service.cfapps")); + const options = { + protocol: target.protocol, + host: target.host, + port: 80, + method: "get", + path: urlPath, + headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + }; + cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + httpsMock.expects("get").withArgs(options).returns(result); + cfLocalMock.expects("cfGetAuthToken").resolves(testToken); + try { + await cfViewCommands.cfDeployServiceAPI(urlPath); + fail("should not reach here"); + } catch (e) { + expect(e).to.equal(errorText); + } + }); + + it("exception:: no target", async () => { + cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves([]); + try { + await cfViewCommands.cfDeployServiceAPI(""); + fail("should not reach here"); + } catch (e) { + expect(e.message).to.equal(messages.no_cf_api_endpoint); + } + }); + }); + + describe("cmdDeployServiceAPI", () => { + it("ok:: empty args", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.load_data_from_deploy, + cancellable: false, + }) + .resolves(); + await cfViewCommands.cmdDeployServiceAPI("", ""); + }); + + it("ok:: empty args, connectivity canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.load_data_from_deploy, + cancellable: false, + }) + .never(); + await cfViewCommands.cmdDeployServiceAPI("", ""); + }); + + it("ok:: with specified message", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + const message = "myMessage"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, title: message, cancellable: false }) + .resolves(); + await cfViewCommands.cmdDeployServiceAPI("", message); + }); + }); + + describe("cmdSetCurrentTarget scope", () => { + const currentTarget: CFTarget = { label: "current Target", isDirty: false, isCurrent: true }; + const localView = { + getCurrentTarget: () => currentTarget, + refresh: () => "", + }; + + it("ok:: target is current already, nothing done", async () => { + cliMock.expects("execute").never(); + cfViewMock.expects("get").never(); + await cfViewCommands.cmdSetCurrentTarget( + new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: true }) + ); + }); + + it("ok:: target is set, reload called", async () => { + const target = { label: "my Target", isDirty: false, isCurrent: false }; + cfViewMock.expects("get").twice().returns(localView); + cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); + }); + + it("ok:: target is current, is dirty, not save - No", async () => { + const target = { label: "my Target", isDirty: false, isCurrent: false }; + currentTarget.isCurrent = true; + currentTarget.isDirty = true; + cfViewMock.expects("get").twice().returns(localView); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .resolves("No"); + cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); + }); + + it("ok:: target is current, is dirty, not save - Cancel", async () => { + const target = { label: "my Target", isDirty: false, isCurrent: false }; + currentTarget.isCurrent = true; + currentTarget.isDirty = true; + cfViewMock.expects("get").returns(localView); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .resolves(undefined); + cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).never(); + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); + }); + + it("ok:: target is current, is dirty, save - Yes", async () => { + const target = { label: "my Target", isDirty: false, isCurrent: false }; + currentTarget.isCurrent = true; + currentTarget.isDirty = true; + cfViewMock.expects("get").twice().returns(localView); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .resolves("Yes"); + cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + cliMock.expects("execute").withExactArgs(["save-target"]).resolves(cliResult); + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); + }); + + it("exception:: target is current, exception thrown", async () => { + currentTarget.isCurrent = true; + currentTarget.isDirty = true; + cfViewMock.expects("get").returns(localView); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .resolves("Yes"); + const error = new Error("my error"); + cliMock.expects("execute").withExactArgs(["save-target"]).throws(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + await cfViewCommands.cmdSetCurrentTarget( + new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: false }) + ); + }); + + it("fail:: target is current, not dirty, execute failed", async () => { + const target = { label: "my Target", isDirty: false, isCurrent: false }; + currentTarget.isCurrent = true; + currentTarget.isDirty = false; + cfViewMock.expects("get").returns(localView); + cliResult.exitCode = 1; + cliResult.stdout = "any error"; + cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(cliResult.stdout).resolves(); + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); + }); + + it("ok:: 'CFTargetNotCurrent' item received", async () => { + cliMock.expects("execute").never(); + await cfViewCommands.cmdSetCurrentTarget( + new cfView.CFTargetNotCurrent( + new cfView.CFAppsFolder( + "apps", + new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: true }) + ) + ) + ); + }); + + it("ok:: targets tree broken", async () => { + await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetNotCurrent({ label: "label" } as cfView.CFFolder)); + }); + + it("ok:: null received, targets tree broken", async () => { + await cfViewCommands.cmdSetCurrentTarget(undefined); + }); + }); + + describe("execSetTarget scope", () => { + const item = new cfView.CFTargetTI({ label: "my Target", isCurrent: false, isDirty: true }); + + it("ok:: silent mode required", async () => { + const cliResult = { exitCode: -1, stdout: "", stderr: "" }; + cliMock.expects("execute").withExactArgs(["set-target", "-f", item.target.label]).resolves(cliResult); + vscodeWindowMock.expects("showErrorMessage").never(); + await cfViewCommands.execSetTarget(item, { silent: true }); + }); + + it("ok:: 'skip-reload' mode required", async () => { + const cliResult = { exitCode: 0, stdout: "", stderr: "" }; + cliMock.expects("execute").withExactArgs(["set-target", "-f", item.target.label]).resolves(cliResult); + cfViewMock.expects("get").never(); + await cfViewCommands.execSetTarget(item, { silent: true, "skip-reload": true }); + }); + }); + + describe("execSaveTarget scope", () => { + const item = new cfView.CFTargetTI({ label: "my Target", isCurrent: false, isDirty: true }); + + it("ok:: error occured", async () => { + const cliResult = { exitCode: -1, stdout: "", stderr: "" }; + cliMock.expects("execute").withExactArgs(["save-target", "-f", item.target.label]).resolves(cliResult); + vscodeWindowMock.expects("showErrorMessage").resolves(); + await cfViewCommands.execSaveTarget(item); + }); + + it("ok:: silent mode required", async () => { + const cliResult = { exitCode: -1, stdout: "", stderr: "" }; + cliMock.expects("execute").withExactArgs(["save-target", "-f", item.target.label]).resolves(cliResult); + vscodeWindowMock.expects("showErrorMessage").never(); + await cfViewCommands.execSaveTarget(item, { silent: true }); + }); + + it("ok:: without args", async () => { + const cliResult = { exitCode: 0, stdout: "", stderr: "" }; + cliMock.expects("execute").withExactArgs(["save-target"]).resolves(cliResult); + await cfViewCommands.execSaveTarget(); + }); + + it("ok:: 'no-targets' node received", async () => { + cliMock.expects("execute").never(); + await cfViewCommands.execSaveTarget( + new cfView.CFTargetTI({ label: "my (no targets)", isCurrent: false, isDirty: true }) + ); + }); + }); + + describe("cmdDeleteTarget scope", () => { + const localView = { + refresh: () => "", + }; + const item = new cfView.CFTargetTI({ label: "other", isCurrent: true, isDirty: false }); + + it("ok:: default-target", async () => { + cliMock.expects("execute").withArgs(["delete-target"]).never(); + await cfViewCommands.cmdDeleteTarget( + new cfView.CFTargetTI({ label: DEFAULT_TARGET, isCurrent: true, isDirty: false }) + ); + }); + + it("ok:: other deleted", async () => { + cliResult.exitCode = 0; + cliResult.stdout = ""; + cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cfViewMock.expects("get").returns(localView); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.target_deleted(item.target.label)) + .resolves(); + await cfViewCommands.cmdDeleteTarget(item); + }); + + it("ok:: other deleted, silent and 'skip-reload' mode", async () => { + cliResult.exitCode = 0; + cliResult.stdout = ""; + cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cfViewMock.expects("get").never(); + vscodeWindowMock.expects("showInformationMessage").never(); + await cfViewCommands.cmdDeleteTarget(item, { silent: true, "skip-reload": true }); + }); + + it("ok:: other failure", async () => { + cliResult.stdout = "some error during delete"; + cliResult.exitCode = -1; + cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cfViewMock.expects("get").never(); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(cliResult.stdout).resolves(); + await cfViewCommands.cmdDeleteTarget(item); + }); + }); + + describe("cmdGetSpaceServices scope", () => { + it("ok:: verify args", async () => { + const title = "progress title"; + const query: IServiceQuery = { + filters: [ + { key: eFilters.names, value: "@^%$_^%+-_jlkj" }, + { key: eFilters.service_broker_names, value: "+-QWERE_+%?>jlkj" }, + ], + }; + const modified = _.cloneDeep(query); + _.each(modified.filters, (filter) => { + filter.value = encodeURIComponent(filter.value); + }); + const arg = { query: modified, ups: { isShow: true } }; + commandsMock.expects("getAvailableServices").withExactArgs(arg, title).resolves(); + await cfViewCommands.cmdGetSpaceServices(query, title); + }); + + it("ok:: undefined query", async () => { + const title = "progress title"; + const arg: unknown = { query: undefined, ups: { isShow: true } }; + commandsMock.expects("getAvailableServices").withExactArgs(arg, title).resolves(); + await cfViewCommands.cmdGetSpaceServices(undefined, title); + }); + }); + + describe("cmdGetServiceInstances scope", () => { + const title = "progress title - retrieve hana services"; + + it("ok:: retrieve all services", async () => { + const expServices: ServiceInstanceInfo[] = [ + { label: "s1", serviceName: "hana" }, + { label: "s2", serviceName: "hana" }, + { label: "s2", serviceName: "hanatrial" }, + ]; + commandsMock.expects("getServiceInstances").withExactArgs(undefined, title).resolves(expServices); + expect(await cfViewCommands.cmdGetServiceInstances(undefined, title)).to.deep.equal(expServices); + }); + + it("ok:: retrieve 'hana 'services types with 'hdi-shared' plan", async () => { + const rspServices: ServiceInstanceInfo[] = [ + { label: "s1", serviceName: "hana", plan: "hdi-shared" }, + { label: "s2", serviceName: "hana", plan: "hdi-shared" }, + ]; + const serviceQueryOptions: ServiceQueryOptions = { name: "hana", plan: "hdi-shared" }; + const plans: PlanInfo[] = [ + { label: "hdi-shared", guid: "ABCD", description: "" }, + { label: "lite", guid: "EFGH", description: "" }, + ]; + const serviceInstanceQuery = { + filters: [{ key: eFilters.service_plan_names, value: serviceQueryOptions.plan }], + per_page: CF_PAGE_SIZE, + }; + commandsMock + .expects("fetchServicePlanList") + .withExactArgs({ filters: [{ key: eFilters.service_offering_names, value: serviceQueryOptions.name }] }) + .resolves(plans); + serviceInstanceQuery.filters = _.concat(serviceInstanceQuery.filters, [ + { key: eFilters.service_plan_guids, value: _.join(_.map(plans, "guid")) }, + ]); + commandsMock.expects("getServiceInstances").withExactArgs(serviceInstanceQuery, title).resolves(rspServices); + await cfViewCommands.cmdGetServiceInstances(serviceQueryOptions, title); + }); + + it("ok:: retrieve all services when service types and plan are missing", async () => { + const expServices: ServiceInstanceInfo[] = [ + { label: "s1", serviceName: "hana" }, + { label: "s2", serviceName: "hana" }, + { label: "s2", serviceName: "hanatrial" }, + ]; + const serviceQueryOptions: ServiceQueryOptions = {}; + commandsMock.expects("getServiceInstances").withExactArgs(undefined, title).resolves(expServices); + await cfViewCommands.cmdGetServiceInstances(serviceQueryOptions, title); + }); + }); + + describe("cmdGetUpsServiceInstances scope", () => { + const title = "progress title - retrieve all services"; + + it("ok:: verify calling 'getUserProvidedServiceInstances' happens with specified args", async () => { + const options: UpsServiceQueryOprions = { tag: "tags", credentials: { tag: "hana" } }; + commandsMock.expects("getUserProvidedServiceInstances").withExactArgs(options, title).resolves(); + await cfViewCommands.cmdGetUpsServiceInstances(options, title); + }); + }); + + describe("cmdBindLocal scope", () => { + const path = nsVsMock.testVscode.Uri.file("some/path"); + const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + let origin: unknown; + const plans: PlanInfo[] = [ + { label: "hdi-shared", guid: "ABCD", description: "" }, + { label: "hana", guid: "EFGH", description: "" }, + ]; + const services: ServiceInstanceInfo[] = [ + { serviceName: "type1", label: "service1" }, + { serviceName: "type2", label: "service2" }, + ]; + const opts: DisplayServices = { + query: { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }, + ups: _.get(service, ["0", "ups"]), + }; + + before(() => { + origin = commands.updateInstanceNameAndTags; + }); + + beforeEach(() => { + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + instanceNames.push(services[0].label); + return Promise.resolve(instanceNames[0]); + } + ); + }); + + afterEach(() => { + _.set(commands, "updateInstanceNameAndTags", origin); + }); + + it("ok:: select path canceled", async () => { + const emptyPath = nsVsMock.testVscode.Uri.file(""); + vscodeWindowMock + .expects("showOpenDialog") + .withExactArgs({ + openLabel: "Select folder for .env file", + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: nsVsMock.testVscode.workspace.workspaceFolders[0].uri, + }) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, emptyPath)).to.be.undefined; + }); + + it("ok:: path selected, service is CFService type, quote-vcap is false", async () => { + const emptyPath = nsVsMock.testVscode.Uri.file(""); + sandbox.stub(nsVsMock.testVscode.workspace, "workspaceFolders").value(undefined); + vscodeWindowMock + .expects("showOpenDialog") + .withExactArgs({ + openLabel: "Select folder for .env file", + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: nsVsMock.testVscode.workspace.workspaceFolders, + }) + .resolves([{ fsPath: `other${fspath.sep}path` }]); + const otherService = { contextValue: "cf-service", label: "my-service" }; + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(otherService.label)) + .resolves(); + const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + expect( + await cfViewCommands.cmdBindLocal( + (otherService as unknown) as cfView.CFService, + { + path: emptyPath, + ignore: true, + }, + undefined, + { "quote-vcap": false } + ) + ).deep.equal({ instanceName: otherService.label }); + + cfLocalMock + .expects("cfBindLocalServices") + .withExactArgs( + `other${fspath.sep}path${fspath.sep}.env`, + [otherService.label], + undefined, + undefined, + undefined, + false + ) + .resolves(); + expect(stubWithProgress.calledOnce).to.be.true; + expect(stubWithProgress.args[0].length).to.be.equal(2); + expect(stubWithProgress.args[0][0]).to.be.deep.equal({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + }); + + it("ok:: path selected, service is CFService type", async () => { + const emptyPath = nsVsMock.testVscode.Uri.file(""); + sandbox.stub(nsVsMock.testVscode.workspace, "workspaceFolders").value(undefined); + vscodeWindowMock + .expects("showOpenDialog") + .withExactArgs({ + openLabel: "Select folder for .env file", + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: nsVsMock.testVscode.workspace.workspaceFolders, + }) + .resolves([{ fsPath: `other${fspath.sep}path` }]); + const otherService = { contextValue: "cf-service", label: "my-service" }; + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(otherService.label)) + .resolves(); + const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + expect( + await cfViewCommands.cmdBindLocal((otherService as unknown) as cfView.CFService, { + path: emptyPath, + ignore: true, + }) + ).deep.equal({ instanceName: otherService.label }); + + cfLocalMock + .expects("cfBindLocalServices") + .withExactArgs( + `other${fspath.sep}path${fspath.sep}.env`, + [otherService.label], + undefined, + undefined, + undefined, + undefined + ) + .resolves(); + expect(stubWithProgress.calledOnce).to.be.true; + expect(stubWithProgress.args[0].length).to.be.equal(2); + expect(stubWithProgress.args[0][0]).to.be.deep.equal({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, no services found", async () => { + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.no_services_instances_found).resolves(); + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, no services found, creation allowed", async () => { + const cloneService = _.merge(_.cloneDeep(service[0]), { allowCreate: { name: "create" } }); + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + commandsMock + .expects("updateInstanceNameAndTags") + .withExactArgs( + [ + { + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + cloneService.allowCreate.name, + plan: cloneService.allowCreate.plan, + serviceName: cloneService.allowCreate.serviceName ?? "", + }, + { + label: commands.CMD_CREATE_SERVICE, + serviceName: "", + }, + ], + cloneService, + [], + [] + ) + .resolves(undefined); + expect(await cfViewCommands.cmdBindLocal([cloneService], { path, ignore: true })).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, canceled", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set(commands, "updateInstanceNameAndTags", () => { + return Promise.resolve(); + }); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, type selected", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + instanceName: services[0].label, + }); + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, required specific service", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("updateInstanceNameAndTags").never(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, services[0].label)).deep.equal({ + instanceName: services[0].label, + }); + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, required service not found", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("updateInstanceNameAndTags").never(); + cfLocalMock.expects("cfGetInstanceMetadata").withExactArgs("not-existed").rejects(new Error("not found")); + vscodeWindowMock + .expects("showErrorMessage") + .withArgs(messages.no_services_instance_byname_found("not-existed")) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, "not-existed")).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, service not in list but found via direct lookup (CF eventual consistency)", async () => { + // Simulate CF list API returning stale results: services[0] missing, only services[1] present + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([services[1]]); + commandsMock.expects("updateInstanceNameAndTags").never(); + cfLocalMock + .expects("cfGetInstanceMetadata") + .withExactArgs(services[0].label) + .resolves({ serviceName: services[0].label, service: service[0].plan, plan_guid: "GUID" }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, services[0].label)).deep.equal({ + instanceName: services[0].label, + }); + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no services, services found, ups selected, quote-vcap is true", async () => { + service[0].plan = ""; + service[0].ups = { isShow: true }; + opts.query = undefined; + opts.ups = service[0].ups; + const services: ServiceInstanceInfo[] = [ + { serviceName: eServiceTypes.user_provided, label: "ups1" }, + { serviceName: eServiceTypes.user_provided, label: "ups2" }, + ]; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + instanceNames.push(services[0].label); + return Promise.resolve(instanceNames[0]); + } + ); + const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, undefined, { "quote-vcap": true }) + ).deep.equal({ + instanceName: services[0].label, + }); + + cfLocalMock.expects("cfBindLocalUps").withExactArgs("some/path", [services[0].label], [], true).resolves(); + expect(stubWithProgress.calledOnce).to.be.true; + expect(stubWithProgress.args[0].length).to.be.equal(2); + expect(stubWithProgress.args[0][0]).to.be.deep.equal({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no services, services found, ups selected", async () => { + service[0].plan = ""; + service[0].ups = { isShow: true }; + opts.query = undefined; + opts.ups = service[0].ups; + const services: ServiceInstanceInfo[] = [ + { serviceName: eServiceTypes.user_provided, label: "ups1" }, + { serviceName: eServiceTypes.user_provided, label: "ups2" }, + ]; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + instanceNames.push(services[0].label); + return Promise.resolve(instanceNames[0]); + } + ); + const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + instanceName: services[0].label, + }); + + cfLocalMock.expects("cfBindLocalUps").withExactArgs("some/path", [services[0].label], [], undefined).resolves(); + expect(stubWithProgress.calledOnce).to.be.true; + expect(stubWithProgress.args[0].length).to.be.equal(2); + expect(stubWithProgress.args[0][0]).to.be.deep.equal({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + }); + + it("ok:: path selected, service is not ServiceTypeInfo type, no ups, services found, type selected", async () => { + service[0].plan = plans[0].label; + delete (service[0] as any).name; + opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[0].label); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + instanceName: services[0].label, + }); + }); + + it("ok:: path selected, service is not ServiceTypeInfo type, no ups, services found, type selection canceled", async () => { + service[0].plan = plans[0].label; + delete (service[0] as any).name; + opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("getInstanceName").withExactArgs(services).resolves(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .never(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, selected new", async () => { + service[0].plan = plans[0].label; + service[0].name = ""; + opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + const newService = "my-service"; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + instanceNames.push(newService); + return Promise.resolve(instanceNames[0]); + } + ); + cfLocalMock + .expects("cfGetInstanceMetadata") + .withExactArgs(newService) + .resolves({ serviceName: newService, service: service[0].plan, plan_guid: "GUID" }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(newService)) + .resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + instanceName: newService, + }); + }); + + it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, create allowed", async () => { + service[0].plan = plans[0].label; + service[0].name = ""; + service[0].allowCreate = {}; + opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + let expectedServicesList; + let servicePlans; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[]) => { + expectedServicesList = availableServices; + servicePlans = _.get(serviceTypeInfo, "plans"); + return Promise.resolve(); + } + ); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + expect(_.has(_.find(expectedServicesList, ["label", commands.CMD_CREATE_SERVICE]), "serviceName")).to.be.true; + expect(servicePlans).to.be.undefined; + }); + + it("ok:: return chisel task", async () => { + opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[0].label)) + .resolves(); + const chiselTask = { + label: `openChiselTunnerFor-${services[0].label}`, + type: "shell", + command: "chisel", + isBackground: true, + problemMatcher: "$chisel-client", + args: ["client", "--auth"], + }; + chiselMock.expects("checkAndCreateChiselTask").withExactArgs(path.fsPath, services[0].label).resolves(chiselTask); + chiselMock.expects("deleteChiselParamsFromFile").withExactArgs(path.fsPath).resolves(); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + instanceName: services[0].label, + chiselTask: chiselTask, + }); + }); + }); + + describe("bindLocalService scope", () => { + let origin: unknown; + const services: ServiceInstanceInfo[] = [ + { serviceName: "hdi-shared", label: "label1" }, + { serviceName: "hdi-shared", label: "label2" }, + ]; + const plans: PlanInfo[] = [ + { label: "hdi-shared", guid: "ABCD", description: "" }, + { label: "hana", guid: "EFGH", description: "" }, + ]; + before(() => { + origin = commands.updateInstanceNameAndTags; + }); + + beforeEach(() => { + _.set( + commands, + "updateInstanceNameAndTags", + (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + instanceNames.push(services[1].label); + return Promise.resolve(instanceNames[0]); + } + ); + }); + + afterEach(() => { + _.set(commands, "updateInstanceNameAndTags", origin); + }); + + const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + const opts: DisplayServices = { + query: { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }, + }; + const envPath = nsVsMock.testVscode.Uri.file("root/folder"); + + it("ok:: path is empty", async () => { + const path = nsVsMock.testVscode.Uri.file(""); + expect(await cfViewCommands.bindLocalService(service, { path, ignore: true })).to.be.empty; + }); + + it("ok:: path defined, plan required, no service selected", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + _.set(commands, "updateInstanceNameAndTags", () => { + return Promise.resolve([]); + }); + expect(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true })).to.be.undefined; + }); + + it("ok:: path defined, plan required, service selected", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[1].label)) + .resolves(); + assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ + services[1].label, + ]); + }); + + it("ok:: path defined, plan required, service selected, silent mode required", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[1].label)) + .never(); + assert.deepEqual( + await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }, { silent: true }), + [services[1].label] + ); + }); + + it("ok:: serviceInfo is not array, service selected", async () => { + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(undefined); + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[1].label); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_bound_successful(services[1].label)) + .resolves(); + delete (service[0] as any).name; + assert.deepEqual(await cfViewCommands.bindLocalService(service, envPath), [services[1].label]); + }); + + it("ok:: serviceInfo is not array, select service canceled", async () => { + const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + delete (service[0] as any).name; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("getInstanceName").withExactArgs(services).resolves(); + expect(await cfViewCommands.bindLocalService(service, envPath)).to.be.undefined; + }); + + it("ok:: serviceInfo is array, no services found", async () => { + service[0].name = ""; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + vscodeWindowMock.expects("withProgress").never(); + expect(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true })).to.be.empty; + }); + + it("ok:: serviceInfo is array, no services found, plan is not provided", async () => { + const copyService: ServiceTypeInfo[] = [{ name: "", plan: "", prompt: "", tag: "" }]; + const opts: DisplayServices = { query: undefined }; + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + expect(await cfViewCommands.bindLocalService(copyService, { path: envPath, ignore: true })).to.be.empty; + }); + + it("show error:: serviceInfo is not array, service selected, doBind rejected", async () => { + commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[1].label); + const error = new Error("error"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.binding_service_to_file, + cancellable: false, + }) + .rejects(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + delete (service[0] as any).name; + await cfViewCommands.bindLocalService(service, envPath); + }); + + it("ok:: serviceInfo is array, service name required, no services found", async () => { + service[0].plan = ""; + service[0].name = " hana , xsuaa "; + commandsMock + .expects("fetchServicePlanList") + .withExactArgs(_.cloneDeep({ filters: [{ key: eFilters.service_offering_names, value: "hana,xsuaa" }] })) + .resolves(plans); + const query: IServiceQuery = { + filters: [{ key: eFilters.service_plan_guids, value: _.join(_.map(plans, "guid")) }], + per_page: CF_PAGE_SIZE, + }; + commandsMock.expects("getAvailableServices").withExactArgs({ query }).resolves(services); + assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ + services[1].label, + ]); + }); + + it("ok:: serviceInfo is array, service name - ups only requested, no services found", async () => { + service[0].plan = ""; + service[0].name = eServiceTypes.user_provided; + const upsServices: ServiceInstanceInfo[] = [ + { serviceName: eServiceTypes.user_provided, label: "label1" }, + { serviceName: eServiceTypes.user_provided, label: "label2" }, + ]; + const query: IServiceQuery = { + filters: [{ key: eFilters.service_plan_names, value: "nothing-to-show" }], + per_page: CF_PAGE_SIZE, + }; + commandsMock.expects("getAvailableServices").withExactArgs({ query }).resolves(upsServices); + assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ + services[1].label, + ]); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts new file mode 100644 index 000000000..1b00ca61e --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts @@ -0,0 +1,154 @@ +import { expect } from "chai"; +import { join } from "path"; +import * as tmpTestDir from "temp-dir"; +import * as rimraf from "rimraf"; +import * as PropertiesReader from "properties-reader"; +import * as fs from "fs"; +import * as sinon from "sinon"; + +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; +mockVscode(nsVsMock.testVscode, "src/chisel.ts"); + +import * as utils from "../src/utils"; +import { deleteChiselParamsFromFile, checkAndCreateChiselTask } from "../src/chisel"; + +describe("chisel unit tests", () => { + const serviceName = "chisel-service-name"; + let envFilePath: string; + + /* important: + VCAP_SERVICES contains special charecters and also \n that may cause to an issue in the a string + when we read the text from the file the propertyReader add to the string "\" before each "\n" (e.g.: \\n) + therefore we must define the VCAP service with \\n + */ + const VCAP_SERVICE_WITH_ALL_CHARS = + '{"hana":[{"name":"test-service","instance_name":"test-service","label":"hana","tags":["hana","database","relational","mta-resource-name:hdi_hdb","endpoint:https://api.test.com","org:org","space:space"],"plan":"hdi-shared","credentials":{"certificate":"-----BEGIN CERTIFICATE-----\\nXXXXz\\nCAUw7C29=\\n-----END CERTIFICATE-----\\n","driver":"test.Driver","hdi_password":"hdi_password_value","hdi_user":"hdi_user","host":"host_value","password":"password_value","port":"XXXX","schema":"schema_key","service_key_name":"xxxx","url":"jdbc:test://test.ondemand.com:36603?encrypt=true\u0026validateCertificate=true\u0026currentschema=SCHEMA_42","user":"user_value"}}]}'; + const envWithChisel = { + TUNNEL_PARAM: "2020:127.0.0.1:2020", + CHISEL_URL: "https://chise-server.cfapps.test.hana.ondemand.com", + CHISEL_USER: "chisel-user", + CHISEL_PASSWORD: "chisel-password", + VCAP_SERVICES: VCAP_SERVICE_WITH_ALL_CHARS, + }; + + beforeEach(() => { + envFilePath = join(tmpTestDir, ".envtest"); + }); + + afterEach(() => { + rimraf.sync(envFilePath, { glob: false }); + sinon.restore(); + }); + + describe("checkAndCreateChiselTask", () => { + const expectedChiselTask = { + label: `openChiselTunnerFor-${serviceName}`, + type: "shell", + command: "chisel", + isBackground: true, + problemMatcher: "$chisel-client", + args: [ + "client", + "--auth", + "chisel-user:chisel-password", + "https://chise-server.cfapps.test.hana.ondemand.com", + "2020:127.0.0.1:2020", + ], + }; + it("ok:: verify created chisel task structure", async () => { + await utils.writeProperties(envFilePath, envWithChisel); + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).deep.equal(expectedChiselTask); + }); + + it("ok:: create 'undefined' when chisel params not appear in the environment file", async () => { + await utils.writeProperties(envFilePath, {}); + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + }); + + it("ok:: create 'undefined' chisel URL is empty", async () => { + await utils.writeProperties(envFilePath, { CHISEL_URL: "" }); + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + }); + + it("ok:: create 'undefined' when .env file does not exists", async () => { + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + }); + + it("ok:: create chisel task and ignore from invalid format text in the .env file", async () => { + const text = + "invalid format text that ignore by the reader\n" + + "TUNNEL_PARAM= 2020:127.0.0.1:2020\n" + + "CHISEL_URL= https://chise-server.cfapps.test.hana.ondemand.com\n" + + "CHISEL_USER= chisel-user\n" + + "CHISEL_PASSWORD= chisel-password\n" + + "VCAP_SERVICES= VCAP_SERVICES_VALUE"; + + await fs.promises.writeFile(envFilePath, text); + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).deep.equal(expectedChiselTask); + }); + }); + + describe("deleteChiselParamsFromFile", () => { + const expectedProperties = { + VCAP_SERVICES: VCAP_SERVICE_WITH_ALL_CHARS, + }; + it("ok:: return true when delete chisel params from the .env file", async () => { + await utils.writeProperties(envFilePath, envWithChisel); + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.true; + const actualProperties = PropertiesReader(envFilePath); + expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + }); + + it("ok:: return true and ignored from empty properties when override the .env file", async () => { + const text = + "TUNNEL_PARAM=2020:127.0.0.1:2020\n" + + "CHISEL_URL= https://chise-server.cfapps.test.hana.ondemand.com\n" + + 'CHISEL_USER= "chisel-user"\n' + + "CHISEL_PASSWORD= chisel-password\n" + + "VCAP_SERVICES=" + + VCAP_SERVICE_WITH_ALL_CHARS + + "\n" + + "EMPTY_VALUE="; + await fs.promises.writeFile(envFilePath, text); + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.true; + const actualProperties = PropertiesReader(envFilePath); + expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + }); + + it("ok:: return false when chisel params does not exists in .env file", async () => { + await utils.writeProperties(envFilePath, expectedProperties); + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.false; + const actualProperties = PropertiesReader(envFilePath); + expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + }); + + it("ok:: return false when .env file does not exists ", async () => { + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.false; + }); + + it("ok:: return false when write to .env file is failed", async () => { + const envWithChisel = { + TUNNEL_PARAM: "2020:127.0.0.1:2020", + CHISEL_URL: "https://chise-server.cfapps.test.hana.ondemand.com", + CHISEL_USER: "chisel-user", + CHISEL_PASSWORD: "chisel-password", + VCAP_SERVICES: "test", + }; + await utils.writeProperties(envFilePath, envWithChisel); + const expObj = { + VCAP_SERVICES: "test", + }; + sinon + .stub(utils, "writeProperties") + .withArgs(envFilePath, expObj) + .throwsException("mock failed to write to .env file"); + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.false; + }); + + it("ok:: return false when properties does not exists in the .env file", async () => { + //await writeProperties(envFilePath, {}); + expect(await deleteChiselParamsFromFile(envFilePath)).to.be.false; + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts new file mode 100644 index 000000000..eb3a26d85 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts @@ -0,0 +1,505 @@ +import { expect } from "chai"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; +mockVscode(nsVsMock.testVscode, "src/commands.ts"); +import * as commands from "../src/commands"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import { Cli, CliResult, ITarget } from "@sap/cf-tools"; +import { messages } from "../src/messages"; +import * as cfView from "../src/cfView"; +import { toText } from "../src/utils"; +import * as proxyquire from "proxyquire"; + +describe("commands unit tests", () => { + let sandbox: SinonSandbox; + let vscodeWindowMock: SinonMock; + let cliMock: SinonMock; + let cfLocalMock: SinonMock; + let cfViewMock: SinonMock; + let openLoginViewMock: SinonMock; + let internal: any; + + const orgs: any[] = [ + { label: "devx", guid: "1" }, + { label: "devx2", guid: "2" }, + { label: "HRTT", guid: "3" }, + { label: "SAP_CoCo_Messaging", guid: "4" }, + ]; + const spaces: any[] = [{ label: "ArchTeam" }, { label: "platform2" }]; + const openLoginViewProxy = { + openLoginView: () => Promise.reject(new Error("not implemented")), + }; + + before(() => { + internal = proxyquire("../src/commands", { + vscode: { + "@noCallThru": true, + }, + "@noCallThru": true, + "./loginTargetView/loginTargetView": openLoginViewProxy, + }).internal; + sandbox = createSandbox(); + }); + + after(() => { + sandbox.restore(); + }); + + beforeEach(() => { + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + cfViewMock = sandbox.mock(cfView.CFView); + cfLocalMock = sandbox.mock(cfLocal); + openLoginViewMock = sandbox.mock(openLoginViewProxy); + cliMock = sandbox.mock(Cli); + }); + + afterEach(() => { + cfLocalMock.verify(); + vscodeWindowMock.verify(); + cliMock.verify(); + cfViewMock.verify(); + openLoginViewMock.verify(); + sandbox.restore(); + }); + + describe("verifyLoginRetry", () => { + it("fail:: canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .rejects(undefined); + expect(await commands.verifyLoginRetry()).to.be.undefined; + }); + }); + + describe("onErrorCfLogin", () => { + it("should handle login error by calling cmdLogin and retrying empty string", async () => { + const errorMessage = "login"; + openLoginViewMock + .expects("openLoginView") + .withExactArgs({ isSplit: true, isLoginOnly: true }, "https://example.com", "", "") + .resolves(undefined); + let expErr: any = null; + const error = new Error(errorMessage); + + await internal + .onErrorCfLogin(error, "https://example.com") + .catch((errorMessage: Error) => (expErr = errorMessage)); + expect(expErr).to.be.equal(""); + }); + + it("should handle undefined error gracefully", async () => { + const err = new Error("test"); + let expErr: any = null; + await internal.onErrorCfLogin(err).catch((errorMessage: Error) => (expErr = errorMessage)); + + // Assert that console.error was not called + expect(expErr.message).to.exist; + expect(expErr.message).to.be.equal(err.message); + }); + }); + + describe("cmdSelectAndSaveTarget", () => { + const target: ITarget = { + "api endpoint": "endPoint", + org: "test-org", + space: "test-space", + user: "user", + "api version": "3.100.0", + }; + const cliResult: CliResult = { stdout: "some text", stderr: "", exitCode: 0 }; + + it("ok:: show selection dialog - canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + cfLocalMock.expects("cfGetTarget").resolves(target); + const org = target.org || ""; + const space = target.space || ""; + const affairs = [ + { + id: "save", + label: messages.set_targets_save(org, space), + detail: messages.set_targets_save_details, + }, + { id: "pick-save", label: messages.set_targets_pick_save, detail: messages.set_targets_pick_save_details }, + ]; + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(affairs, { + placeHolder: messages.set_targets_choose_the_operation, + canPickMany: false, + matchOnDetail: true, + }) + .resolves(); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("fail:: login wrong", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves(); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("fail:: unsupported operation selected", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + cfLocalMock.expects("cfGetTarget").resolves(target); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "wrong" }); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("ok:: save current target operation selected", async () => { + const label = "test-Target"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + //...cmdCreateTarget + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(label); + const saveTargetParams = ["save-target", "-f", label]; + cliMock.expects("execute").withExactArgs(saveTargetParams).resolves(cliResult); + cfViewMock.expects("get").returns({ + refresh: () => { + return; + }, + }); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.target_created(label)); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.equals(label); + }); + + it("ok:: save current target operation selected, create target command canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + //...cmdCreateTarget canceled + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("ok:: save current target operation selected, target setup is incompleted, canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0" }; + cfLocalMock.expects("cfGetTarget").resolves(t); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .resolves("Cancel"); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("ok:: save current target operation selected, target setup is incompleted (partial), canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0", org: "test-org" }; + cfLocalMock.expects("cfGetTarget").resolves(t); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .resolves("Cancel"); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("ok:: save current target operation selected, target setup is incompleted (partial), continued", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0", space: "test-space" }; + cfLocalMock.expects("cfGetTarget").resolves(t); + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .resolves("OK"); + //...cmdCreateTarget canceled + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it("ok:: save current target operation selected, create target command throws exception", async () => { + const label = "test-Target"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + //...cmdCreateTarget + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(label); + const saveTargetParams = ["save-target", "-f", label]; + const cli: CliResult = { exitCode: 1, stdout: "create target command error", stderr: "" }; + cliMock.expects("execute").withExactArgs(saveTargetParams).resolves(cli); + // + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(toText(new Error(cli.stdout))) + .resolves(); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it.skip("ok:: new target operation selected, other endPoint typed", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "pick-save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + const selectedEndPoint = "other-end-point"; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .resolves(selectedEndPoint); + cliMock.expects("execute").withExactArgs(["api", selectedEndPoint], undefined, undefined).resolves(cliResult); + // ...cmdCFSetOrgSpace + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .resolves(orgs); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(orgs, { + placeHolder: messages.select_org, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(orgs[1]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_spaces }) + .resolves(spaces); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(spaces, { + placeHolder: messages.select_space, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(spaces[0]); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.set_org_space }) + .resolves(); + //...cmdCreateTarget canceled + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it.skip("ok:: new target operation selected, other endPoint typed, cmdCFSetOrgSpace fails", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "pick-save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + const selectedEndPoint = "other-end-point"; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .resolves(selectedEndPoint); + cliMock.expects("execute").withExactArgs(["api", selectedEndPoint], undefined, undefined).resolves(cliResult); + // ...cmdCFSetOrgSpace fails + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .resolves(orgs); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(orgs, { + placeHolder: messages.select_org, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it.skip("ok:: new target operation selected, endPoint selection canceled", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "pick-save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .resolves(); + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + + it.skip("ok:: new target operation selected, same endPoint typed", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({}); + vscodeWindowMock.expects("showQuickPick").resolves({ id: "pick-save" }); + cfLocalMock.expects("cfGetTarget").resolves(target); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .resolves(target["api endpoint"]); + // ...cmdCFSetOrgSpace + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .resolves(orgs); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(orgs, { + placeHolder: messages.select_org, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(orgs[1]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_spaces }) + .resolves(spaces); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(spaces, { + placeHolder: messages.select_space, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(spaces[0]); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.set_org_space }) + .resolves(); + //...cmdCreateTarget canceled + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .resolves(); + // + expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts new file mode 100644 index 000000000..82a38cf10 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts @@ -0,0 +1,1783 @@ +import { expect } from "chai"; +import * as _ from "lodash"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; + +mockVscode(nsVsMock.testVscode, "src/commands.ts"); +import * as commands from "../src/commands"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import { ServiceTypeInfo, ServiceInstanceInfo, Cli, CF_PAGE_SIZE, OK } from "@sap/cf-tools"; +import { messages } from "../src/messages"; +import * as cfLocalUtils from "@sap/cf-tools/out/src/utils"; +import * as loginTargetView from "../src/loginTargetView/loginTargetView"; +import * as cfView from "../src/cfView"; +import { fail } from "assert"; +import { validateParams } from "../src/utils"; +import { stringify } from "comment-json"; +import type { CancellationToken, Progress } from "vscode"; + +describe("commands unit tests", () => { + let sandbox: SinonSandbox; + let vscodeWindowMock: SinonMock; + let vscodeCommandsMock: SinonMock; + let commandsMock: SinonMock; + let cliMock: SinonMock; + let cfLocalMock: SinonMock; + let cfViewMock: SinonMock; + let cfLocalUtilsMock: SinonMock; + let cfLoginTargetMock: SinonMock; + const testCFDefaultLandscape = `https://my.api.cf.sap.hana.ondemand.com`; + let originalCFDefaultLandscape: string; + + before(() => { + sandbox = createSandbox(); + }); + + after(() => { + sandbox.restore(); + }); + + beforeEach(() => { + sandbox.restore(); + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + vscodeCommandsMock = sandbox.mock(nsVsMock.testVscode.commands); + commandsMock = sandbox.mock(commands); + cfViewMock = sandbox.mock(cfView.CFView); + cfLocalMock = sandbox.mock(cfLocal); + cliMock = sandbox.mock(Cli); + cfLocalUtilsMock = sandbox.mock(cfLocalUtils); + cfLoginTargetMock = sandbox.mock(loginTargetView); + originalCFDefaultLandscape = _.get(process, "env.CF_API_ENDPOINT"); + _.set(process, "env.CF_API_ENDPOINT", testCFDefaultLandscape); + }); + + afterEach(() => { + cfLocalMock.verify(); + vscodeWindowMock.verify(); + vscodeCommandsMock.verify(); + commandsMock.verify(); + cliMock.verify(); + cfLocalUtilsMock.verify(); + cfLoginTargetMock.verify(); + cfViewMock.verify(); + _.set(process, "env.CF_API_ENDPOINT", originalCFDefaultLandscape); + }); + + describe("cmdLogin", () => { + const target = new cfView.CFTargetTI({ label: "target", isCurrent: true, isDirty: false }); + const parent = new cfView.CFFolder("parent", target); + const node = new cfView.CFLoginNode(parent); + + it("ok:: triggered from targets tree", async () => { + const json = { + Target: "my-endPoint", + SpaceFields: { + Name: "space", + }, + OrganizationFields: { + Name: "org", + }, + }; + cfLocalUtilsMock.expects("cfGetConfigFileJson").resolves(json); + const ap = "https://api.sap.test.ondemand.com"; + cfLoginTargetMock.expects("openLoginView").resolves(ap); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + const result = await commands.cmdLogin(node as any); + expect(result).to.exist; + expect(result).to.be.equal(ap); + }); + + it("ok:: triggered from targets tree, config file not recognized", async () => { + const json = { + Target: "my-endPoint", + SpaceFields: { + Name: "space", + }, + OrganizationFields: { + Name: "org", + }, + }; + cfLocalUtilsMock.expects("cfGetConfigFileJson").resolves(json); + cfLoginTargetMock.expects("openLoginView").resolves("OK"); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023" }); + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + const result = await commands.cmdLogin(node as any); + expect(result).to.exist; + expect(result).to.be.equal("OK"); + }); + + it.skip("'ok:: return an empty string, we caught the error", async () => { + cfLocalUtilsMock.expects("cfGetConfigFileJson").resolves(); + cfLoginTargetMock.expects("openLoginView").resolves("OK"); + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + const result = await commands.cmdLogin(node as any); + expect(result).to.exist; + expect(result).to.be.equal(""); + }); + + it("'ok:: select space return ok", async () => { + cfLoginTargetMock.expects("openLoginView").resolves("OK"); + cfLoginTargetMock + .expects("openLoginView") + .withArgs({ isLoginOnly: true, isSplit: true }, undefined, undefined) + .resolves("OK"); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023" }); + + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + const result = await commands.cmdSelectSpace(); + expect(result).to.exist; + expect(result).to.be.equal("OK"); + }); + }); + + describe("updateServicesOnCFPageSize", () => { + it("ok:: size not equal to cf page size", () => { + const availableServices: ServiceInstanceInfo[] = []; + commands.updateServicesOnCFPageSize(availableServices); + expect(availableServices).to.be.empty; + }); + + it("ok:: size equal to cf page size", () => { + const availableServices: ServiceInstanceInfo[] = new Array(CF_PAGE_SIZE); + commands.updateServicesOnCFPageSize(availableServices); + expect(availableServices).to.have.lengthOf(CF_PAGE_SIZE + 1); + }); + }); + + describe("cmdCreateService", () => { + beforeEach(() => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + }); + + it("fail:: cf connectivity canceled", async () => { + vscodeWindowMock.restore(); + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves(); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: instanceName is not specified", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: withProgress is called, servicesInfo is empty", async () => { + vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([]); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: withProgress is called, servicesInfo is not empty, planInfo is empty", async () => { + const info = { service_plans_url: "test_service_plans_url", label: "testLabel", ignoreFocusOut: true }; + vscodeWindowMock.expects("showQuickPick").resolves(info); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "serviceName" }]); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_service_plans_found(info.label)) + .resolves(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([]); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: withProgress is called, servicesInfo is not empty, select service canceled", async () => { + vscodeWindowMock.expects("showQuickPick").resolves(undefined); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "serviceName" }]); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: withProgress is called, servicesInfo is not empty, planInfo several", async () => { + vscodeWindowMock.expects("showQuickPick").never(); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "myService" }]); + const plans = [{ label: "plan1" }, { label: "plan2" }]; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([plans[0]]); + vscodeWindowMock + .expects("showInputBox") + .withArgs({ + ignoreFocusOut: true, + prompt: messages.create_service_enter_params, + value: "{}", + valueSelection: undefined, + validateInput: validateParams("testServiceInfoLabel"), + }) + .resolves("{}"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.creating_service("instanceName", "myService", "plan1"), + cancellable: true, + }) + .resolves(); + const service = { name: "myService", plan: "plan1", tag: "", prompt: "" }; + expect(await commands.cmdCreateService(service)).to.be.undefined; + }); + + it("fail:: withProgress filtered is called, servicesInfo is not empty, planInfo is empty", async () => { + vscodeWindowMock.expects("showQuickPick").never(); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "myService" }]); + // const plans = [{ label: 'plan1' }, { label: 'plan2' }]; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves(); + const service = { name: "myService", plan: "wrong", tag: "", prompt: "" }; + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_service_plan_info_found(service.plan, "myService")) + .resolves(); + expect(await commands.cmdCreateService(service)).to.be.undefined; + }); + + it("ok:: withProgress is called, servicesInfo is not empty, planInfo is not empty", async () => { + vscodeWindowMock + .expects("showQuickPick") + .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .resolves({ label: "testPlanInfoLabel", guid: "plan-guid" }); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("showInputBox") + .withArgs({ + ignoreFocusOut: true, + prompt: messages.create_service_enter_params, + value: "{}", + valueSelection: undefined, + validateInput: validateParams("testServiceInfoLabel"), + }) + .resolves("{}"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([{}]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.creating_service("instanceName", "testServiceInfoLabel", "testPlanInfoLabel"), + cancellable: true, + }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "serviceName" }]); + expect(await commands.cmdCreateService()).to.be.equal("instanceName"); + }); + + it("fail:: withProgress is called, servicesInfo is not empty, planInfo is not empty, params canceled", async () => { + vscodeWindowMock + .expects("showQuickPick") + .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .resolves({ label: "testPlanInfoLabel" }); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("showInputBox") + .withArgs({ + ignoreFocusOut: true, + prompt: messages.create_service_enter_params, + value: "{}", + valueSelection: undefined, + validateInput: validateParams("testServiceInfoLabel"), + }) + .resolves(undefined); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([{}]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "serviceName" }]); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: withProgress is called, servicesInfo is not empty, select plan canceled", async () => { + vscodeWindowMock + .expects("showQuickPick") + .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .resolves(undefined); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([{}]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "serviceName" }]); + expect(await commands.cmdCreateService()).to.be.undefined; + }); + + it("fail:: with service metadata info", async () => { + const info: ServiceTypeInfo = { + name: "name", + plan: "", + prompt: "", + tag: "", + allowCreate: { + name: "customizedName", + namePrompt: "namePrompt", + }, + }; + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_services_instances_found_for_type(info.name)); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + value: info.allowCreate?.name, + prompt: info.allowCreate?.namePrompt || messages.enter_service_name, + ignoreFocusOut: true, + }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([]); + await commands.cmdCreateService(info); + }); + + it("fail:: with service metadata info - quiet params", async () => { + vscodeWindowMock.expects("showQuickPick").never(); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: "myService" }]); + const plans = [{ label: "plan1" }, { label: "plan2" }]; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([plans[0]]); + vscodeWindowMock + .expects("showInputBox") + .withArgs({ + ignoreFocusOut: true, + placeHolder: messages.create_service_enter_params, + valueSelection: undefined, + validateInput: validateParams("testServiceInfoLabel"), + }) + .never(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.creating_service("instanceName", "myService", "plan1"), + cancellable: true, + }) + .resolves(); + const service = { + name: "myService", + plan: "plan1", + tag: "", + prompt: "", + allowCreate: { + getParams: () => Promise.resolve({ some: { data: "value" } }), + }, + }; + await commands.cmdCreateService(service); + }); + + it("fail:: withProgress is called but thrown exception", async () => { + const error = new Error("withProgress error"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceName"); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + const result = await commands.cmdCreateService(); + expect(result).to.be.undefined; + }); + + async function testAllowCreateParams( + expectedInstanceName: string, + expectedServiceName: string, + expectedPlanName: string, + info: ServiceTypeInfo, + plans: { label: string }[] + ) { + vscodeWindowMock.expects("showQuickPick").never(); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(expectedInstanceName); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: expectedServiceName }]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves(plans); + vscodeWindowMock + .expects("showInputBox") + .withArgs({ + ignoreFocusOut: true, + placeHolder: messages.create_service_enter_params, + valueSelection: undefined, + validateInput: validateParams("testServiceInfoLabel"), + }) + .never(); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.creating_service(expectedInstanceName, expectedServiceName, expectedPlanName), + cancellable: true, + }) + .resolves(expectedServiceName); + const serviceName = await commands.cmdCreateService(info); + expect(serviceName).to.equal(expectedServiceName); + } + + it("ok:: use the info params because serviceName, plan and tag are not defined in allowCreate", async () => { + const expectedServiceName = "nameA"; + const expectedPlanName = "planA"; + const expectedInstanceName = "instanceName"; + const info: ServiceTypeInfo = { + name: expectedServiceName, + plan: expectedPlanName, + prompt: "promptA", + tag: "tagA", + allowCreate: { + getParams: () => Promise.resolve({ some: { data: "value" } }), + }, + }; + const plans = [{ label: expectedPlanName }]; + + await testAllowCreateParams(expectedInstanceName, expectedServiceName, expectedPlanName, info, plans); + }); + + it("ok:: use allowCreate serviceName, plan and tag because they are defined in allowCreate ", async () => { + const expectedServiceName = "nameB"; + const expectedPlanName = "planB"; + const expectedInstanceName = "instanceName"; + const info: ServiceTypeInfo = { + name: "nameA", + plan: "planA", + prompt: "promptA", + tag: "tagA", + allowCreate: { + serviceName: expectedServiceName, + plan: expectedPlanName, + tag: "tagB", + getParams: () => Promise.resolve({ some: { data: "value" } }), + }, + }; + const plans = [{ label: expectedPlanName }]; + + await testAllowCreateParams(expectedInstanceName, expectedServiceName, expectedPlanName, info, plans); + }); + }); + + describe("onCreateService - continue", () => { + it("createService throws an error", async () => { + const error = new Error("testError"); + cfLocalMock.expects("cfCreateService").throws(error); + try { + await commands.onCreateService( + { guid: "", label: "", description: "" }, + "testInstance", + undefined, + undefined, + (undefined as unknown) as Progress<{ message?: string; increment?: number }>, + (undefined as unknown) as CancellationToken + ); + fail("test should not reach here"); + } catch (e) { + expect(_.get(e, "message")).to.be.equal(error.message); + } + }); + + it("ok:: createService suceeded and returns cf resource", async () => { + const params = { params: { data: "value" } }; + const tags = ["tag1", "other"]; + cfLocalMock + .expects("cfCreateService") + .withArgs("guid", "testInstance", params, tags) + .resolves({ + guid: "instance-guid", + name: "testInstance", + links: {}, + relationships: { + service_plan: { + data: { + guid: "guid", + }, + }, + }, + metadata: {}, + }); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_created("testInstance")) + .resolves(); + await commands.onCreateService( + { guid: "guid", label: "label", description: "" }, + "testInstance", + params, + tags, + (undefined as unknown) as Progress<{ message?: string; increment?: number }>, + (undefined as unknown) as CancellationToken + ); + }); + }); + + describe("isCFResource", () => { + it("fail:: obj is undefined", () => { + expect(commands.isCFResource(undefined)).to.be.false; + }); + + it("fail:: obj has no links property", () => { + expect( + commands.isCFResource({ + guid: "instance-guid", + name: "testInstance", + relationships: { + service_plan: { + data: { + guid: "guid", + }, + }, + }, + metadata: {}, + }) + ).to.be.false; + }); + + it("fail:: obj has no guid attribute", () => { + expect( + commands.isCFResource({ + name: "testInstance", + links: {}, + relationships: { + service_plan: { + data: { + guid: "guid", + }, + }, + }, + metadata: {}, + }) + ).to.be.false; + }); + + it("fail:: obj has no relationships attribute", () => { + expect( + commands.isCFResource({ + guid: "instance-guid", + links: {}, + }) + ).to.be.false; + }); + + it("ok:: obj is a cf resource", () => { + expect( + commands.isCFResource({ + guid: "instance-guid", + links: {}, + relationships: { + service_plan: { + data: { + guid: "guid", + }, + }, + }, + metadata: {}, + }) + ).to.be.true; + }); + }); + + describe("isServiceTypeInfoInArray", () => { + it("fail:: obj is undefined", () => { + expect(commands.isServiceTypeInfoInArray(undefined)).to.be.false; + }); + + it("fail:: obj is not an array", () => { + expect(commands.isServiceTypeInfoInArray({ metadata: {} })).to.be.false; + }); + + it("fail:: obj has no name property", () => { + expect(commands.isServiceTypeInfoInArray([{ metadata: {} }])).to.be.false; + }); + + it("ok:: obj has name property", () => { + expect(commands.isServiceTypeInfoInArray([{ name: "test" }])).to.be.true; + }); + }); + + describe("getAvailableServices", () => { + let commandId = "cf.set.orgspace"; + + it("fail:: loading services undefined", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves(); + expect(await commands.getAvailableServices()).to.be.undefined; + }); + + it("ok:: success", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{}]); + const result = await commands.getAvailableServices(); + expect(result).to.have.lengthOf(1); + }); + + it("fail:: on any error", async () => { + const errorMessage = "test error"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + try { + await commands.getAvailableServices(); + fail("should fail"); + } catch (e) { + expect(e.message).to.be.equal(errorMessage); + } + }); + + it("ok:: logout, on any login ok", async () => { + const errorMessage = messages.cf_setting_not_set; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(OK); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{}]); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023" }); + expect(await commands.getAvailableServices()).to.have.lengthOf(1); + }); + + it("fail:: on any login error", async () => { + const errorMessage = messages.cf_setting_not_set; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023" }); + vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(undefined); // canceled by user + try { + await commands.getAvailableServices(); + fail("should fail"); + } catch (e) { + expect(e).to.be.equal(undefined); + } + }); + it.skip("ok:: on any login ok, org no space", async () => { + commandId = "cf.select.space"; + const errorMessage = messages.cf_setting_not_set; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(OK); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{}]); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023", org: "org" }); + const result = await commands.getAvailableServices(); + expect(result).to.have.lengthOf(1); + }); + + it("fail:: not logged in, cfGetTarget unknown fail", async () => { + commandId = "cf.select.space"; + const errorMessage = messages.cf_setting_not_set; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023", org: "org", space: "space" }); + try { + await commands.getAvailableServices(); + fail("should fail"); + } catch (e) { + expect(e.message).to.be.equal(errorMessage); + } + }); + }); + + describe("getServiceInstances", () => { + it("ok:: on success", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{}]); + const result = await commands.getServiceInstances(); + expect(result).to.have.lengthOf(1); + }); + + it("fail:: on any error", async () => { + const errorMessage = "test error"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + try { + await commands.getServiceInstances(); + fail("test should fail here"); + } catch (e) { + expect(e.message).to.be.equal(errorMessage); + } + }); + }); + + describe("getUserProvidedServiceInstances", () => { + it("ok:: on success", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_ups_services, + cancellable: true, + }) + .resolves([{}]); + const result = await commands.getUserProvidedServiceInstances(); + expect(result).to.have.lengthOf(1); + }); + + it("fail:: on any error", async () => { + const errorMessage = "test error"; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_ups_services, + cancellable: true, + }) + .rejects(new Error(errorMessage)); + try { + await commands.getUserProvidedServiceInstances(); + fail("test should fail here"); + } catch (e) { + expect(e.message).to.be.equal(errorMessage); + } + }); + }); + + describe("updateInstanceNameAndTags", () => { + it("ok:: availableServices is empty", async () => { + vscodeWindowMock.expects("showQuickPick").never(); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found).resolves(); + const result = await commands.updateInstanceNameAndTags([], undefined, [], []); + expect(result).to.be.undefined; + }); + + it("ok:: availableServices is empty for service type", async () => { + vscodeWindowMock.expects("showQuickPick").never(); + const name = "absent_service"; + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_services_instances_found_for_type(name)) + .resolves(); + const result = await commands.updateInstanceNameAndTags([], { name, plan: "", tag: "", prompt: "" }, [], []); + expect(result).to.be.undefined; + }); + + it("ok:: availableServices is not empty", async () => { + const plans = [{ guid: "plan_guid_1", label: "uuaa", description: "plan_description_1" }]; + const availableServices = [ + { serviceName: "testServiceName1", label: "testLabel1", plan_guid: "plan_guid_1", plan: plans[0].label }, + { serviceName: "testServiceName2", label: "More results..." }, + ]; + const pickItems = [ + { description: `testServiceName1 (${plans[0].label})`, label: "testLabel1" }, + { description: "testServiceName2", label: "More results..." }, + ]; + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(pickItems, { + placeHolder: "testPrompt", + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(pickItems[0]); + const result = await commands.updateInstanceNameAndTags( + availableServices, + { tag: "testTag", name: "testServiceName1", plan: "testPlan", prompt: "testPrompt" }, + [], + [] + ); + expect(result).to.be.equal("testLabel1"); + }); + + it("ok:: availableServices have more services", async () => { + const availableServices = [{ serviceName: "testServiceName1", label: "More results..." }]; + const pickItems = [{ description: "testServiceName1", label: "More results..." }]; + + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs([pickItems[0]], { + placeHolder: "testPrompt", + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(pickItems[0]); + const result = await commands.updateInstanceNameAndTags( + availableServices, + { tag: "testTag", name: "testName", plan: "testPlan", prompt: "testPrompt" }, + [], + [] + ); + expect(result).to.be.equal("More results..."); + }); + + it("ok:: Bind to default service, when default service instance existed.", async () => { + const expectedInstanceName = "defaultService"; + const info: ServiceTypeInfo = { + name: "nameA", + plan: "planA", + prompt: "promptA", + tag: "tagA", + allowCreate: { + serviceName: "name", + plan: "planName", + tag: "tagB", + name: expectedInstanceName, + getParams: () => Promise.resolve({ some: { data: "value" } }), + }, + }; + + const infoName = info?.allowCreate?.name || ""; + const infoServiceName = info?.allowCreate?.serviceName || ""; + const infoPlan = info?.allowCreate?.plan || ""; + + const availableServices = [ + { + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + serviceName: infoServiceName, + plan: infoPlan, + }, + { + label: commands.CMD_CREATE_SERVICE, + serviceName: "", + }, + { + serviceName: infoServiceName, + label: expectedInstanceName, + plan: infoPlan, + }, + ]; + const pickItems = [ + { + description: `${infoServiceName} (${infoPlan})`, + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + }, + { + description: "", + label: commands.CMD_CREATE_SERVICE, + }, + { + description: `${infoServiceName} (${infoPlan})`, + label: expectedInstanceName, + }, + ]; + + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(pickItems, { + placeHolder: "promptA", + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(pickItems[0]); + const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + expect(result).to.be.equal(infoName); + }); + + it("ok:: Bind to default service,when default service instance doesn't existed (createServiceInstance).", async () => { + const expectedServiceName = "nameB"; + const expectedPlanName = "planB"; + const expectedInstanceName = "instanceName"; + const info: ServiceTypeInfo = { + name: "nameA", + plan: "planA", + prompt: "promptA", + tag: "tagA", + allowCreate: { + serviceName: expectedServiceName, + plan: expectedPlanName, + tag: "tagB", + name: expectedInstanceName, + getParams: () => Promise.resolve({ some: { data: "value" } }), + }, + }; + const plans = [{ label: expectedPlanName }]; + + const infoName = info?.allowCreate?.name || ""; + const availableServices = [ + { label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, serviceName: "" }, + { label: commands.CMD_CREATE_SERVICE, serviceName: "" }, + ]; + const pickItems = [ + { description: "", label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName }, + { description: "", label: commands.CMD_CREATE_SERVICE }, + ]; + + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(pickItems, { + placeHolder: "promptA", + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(pickItems[0]); + + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves([{ label: expectedServiceName }]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves(plans); + + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.creating_service(expectedInstanceName, expectedServiceName, expectedPlanName), + cancellable: true, + }) + .resolves(expectedInstanceName); + + const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + expect(result).to.be.equal(infoName); + }); + + it("ok:: Bind to default service,when default service instance doesn't existed (createUpsInstance).", async () => { + const info: ServiceTypeInfo = { + name: "user-provided", + plan: "", + prompt: "", + tag: "mono", + allowCreate: { + name: "silent", + }, + }; + + const infoName = info?.allowCreate?.name || ""; + const availableServices = [ + { label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, serviceName: "" }, + { label: commands.CMD_CREATE_SERVICE, serviceName: "" }, + ]; + const pickItems = [ + { description: "", label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName }, + { description: "", label: commands.CMD_CREATE_SERVICE }, + ]; + + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(pickItems, { + placeHolder: "", + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(pickItems[0]); + + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(infoName)).resolves(); + cfLocalMock + .expects("cfCreateUpsInstance") + .withExactArgs({ + instanceName: infoName, + credentials: {}, + route_service_url: "", + syslog_drain_url: "", + tags: [info.tag], + }) + .resolves({ + name: infoName, + guid: "instance-guid", + type: "user-provided", + relationships: {}, + links: {}, + }); + + const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + expect(result).to.be.equal(infoName); + }); + }); + + describe("fetchServicePlanList", () => { + const plan = { + label: "test", + guid: "plan-guid", + description: "", + service_offering: { guid: "service-guid", name: "service-name", description: "service-description" }, + }; + + it("ok:: on success", async () => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([plan]); + vscodeWindowMock.expects("showWarningMessage").never(); + expect(await commands.fetchServicePlanList()).to.deep.equal([plan]); + }); + + it("ok:: not logged in, perform loggin", async () => { + const errorMessage = messages.login; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .rejects(new Error(errorMessage)); + cfLocalMock.expects("cfGetTarget").rejects(); + vscodeCommandsMock.expects("executeCommand").withExactArgs("cf.login", true).resolves(OK); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .resolves([plan]); + expect(await commands.fetchServicePlanList()).to.deep.equal([plan]); + }); + + it("fail:: not logged in, login canceled by user", async () => { + const errorMessage = messages.login; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .rejects(new Error(errorMessage)); + cfLocalMock.expects("cfGetTarget").rejects(); + vscodeCommandsMock.expects("executeCommand").withExactArgs("cf.login", true).resolves(undefined); + try { + await commands.fetchServicePlanList(); + fail("should fail"); + } catch (e) { + expect(e).to.be.equal(undefined); + } + }); + + it("fail:: not logged in, other error", async () => { + const errorMessage = "FAILED\nNo API endpoint set."; + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_service_plan_list, + cancellable: false, + }) + .rejects(new Error(errorMessage)); + vscodeCommandsMock.expects("executeCommand").never(); + try { + await commands.fetchServicePlanList(); + expect(true).to.be.equal(false); + } catch (e) { + expect(e.message).to.be.equal(errorMessage); + } + }); + }); + + describe("getInstanceName", () => { + const MORE_RESULTS = "More results..."; + const servicesPage2 = [{ serviceName: "serviceName2", label: "label2" }]; + const availableServices: ServiceInstanceInfo[] = [ + { serviceName: "serviceName1", label: "label1" }, + { label: MORE_RESULTS, serviceName: "more:2", alwaysShow: true }, + ]; + const items = [ + { label: availableServices[0].label, description: availableServices[0].serviceName }, + { label: availableServices[1].label, description: availableServices[1].serviceName }, + ]; + const allItems = [ + { label: availableServices[0].label, description: availableServices[0].serviceName }, + { label: servicesPage2[0].label, description: servicesPage2[0].serviceName }, + ]; + + it("ok:: success", async () => { + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(items, { + placeHolder: messages.select_service, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(items[1]); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.loading_services, + cancellable: true, + }) + .resolves(servicesPage2); + vscodeWindowMock + .expects("showQuickPick") + .withExactArgs(allItems, { + placeHolder: messages.select_service, + canPickMany: false, + matchOnDetail: true, + ignoreFocusOut: true, + }) + .resolves(items[0]); + const result = await commands.getInstanceName(availableServices); + expect(result).to.equal(items[0].label); + }); + + it("fail:: no avialable services", async () => { + vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found).resolves(); + const result = await commands.getInstanceName((undefined as unknown) as ServiceInstanceInfo[]); + expect(result).to.undefined; + }); + }); + + describe("cmdCreateUps", () => { + beforeEach(() => { + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves({ data: {} }); + }); + + it("fail:: cf connectivity canceled", async () => { + vscodeWindowMock.restore(); + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + vscodeWindowMock + .expects("withProgress") + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: messages.verify_cf_connectivity, + cancellable: false, + }) + .resolves(); + expect(await commands.cmdCreateUps()).to.be.undefined; + }); + + it("ok:: empty info data", async () => { + const instanceUpsName = "myUps"; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(instanceUpsName); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(instanceUpsName)); + cfLocalMock + .expects("cfCreateUpsInstance") + .withExactArgs({ + instanceName: instanceUpsName, + credentials: {}, + route_service_url: "", + syslog_drain_url: "", + tags: [], + }) + .resolves({ + name: instanceUpsName, + guid: "instance-guid", + type: "user-provided", + relationships: {}, + links: {}, + }); + const info: ServiceTypeInfo = { + name: "", + plan: "", + prompt: "", + tag: "", + }; + expect(await commands.cmdCreateUps(info)).to.be.equal(instanceUpsName); + }); + + it("fail:: no name provided", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(undefined); + cfLocalMock.expects("cfCreateUpsInstance").never(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("fail:: enter credentials canceled", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("name"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves(undefined); + cfLocalMock.expects("cfCreateUpsInstance").never(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("fail:: enter tags canceled", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("name"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves('{"user": "paswrd"}'); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(undefined); + cfLocalMock.expects("cfCreateUpsInstance").never(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("fail:: entering enter_sys_log_url canceled", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("name"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves('{"user": "paswrd"}'); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(undefined); + cfLocalMock.expects("cfCreateUpsInstance").never(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("fail:: entering enter_route_service_url canceled", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("name"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves('{"user": "paswrd"}'); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(undefined); + cfLocalMock.expects("cfCreateUpsInstance").never(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("fail:: no info data - unexpected response format", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceUpsName"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + const response = { label: "name", metadata: {} }; + vscodeWindowMock.expects("showInformationMessage").withExactArgs(_.toString(response)).resolves(); + cfLocalMock.expects("cfCreateUpsInstance").resolves(response); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("exception:: no info data, show error message", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceUpsName"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock.expects("showInformationMessage").never(); + const error = new Error("runtime error"); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message); + cfLocalMock.expects("cfCreateUpsInstance").rejects(error); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("exception:: no info data, exception no error thrown", async () => { + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves("instanceUpsName"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock.expects("showInformationMessage").never(); + vscodeWindowMock.expects("showErrorMessage").once().resolves(); + cfLocalMock.expects("cfCreateUpsInstance").throws(); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + + it("ok:: silent creation", async () => { + const info: ServiceTypeInfo = { + name: "", + plan: "", + prompt: "", + tag: "mono", + allowCreate: { + name: "silent", + }, + }; + const infoName = info?.allowCreate?.name || ""; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true, value: infoName }) + .resolves(infoName); + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(infoName)).resolves(); + cfLocalMock + .expects("cfCreateUpsInstance") + .withExactArgs({ + instanceName: infoName, + credentials: {}, + route_service_url: "", + syslog_drain_url: "", + tags: [info.tag], + }) + .resolves({ + name: infoName, + guid: "instance-guid", + type: "user-provided", + relationships: {}, + links: {}, + }); + expect(await commands.cmdCreateUps(info)).to.be.equal(infoName); + }); + + it("ok:: tags provided", async () => { + const info: ServiceTypeInfo = { + name: "", + plan: "", + prompt: "", + tag: "mono", + }; + const instanceUpsName = "myUps"; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(instanceUpsName); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true, value: info.tag }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_created(instanceUpsName)) + .resolves(); + cfLocalMock + .expects("cfCreateUpsInstance") + .withExactArgs({ + instanceName: instanceUpsName, + credentials: {}, + route_service_url: "", + syslog_drain_url: "", + tags: [], + }) + .resolves({ + name: instanceUpsName, + guid: "instance-guid", + type: "user-provided", + relationships: {}, + links: {}, + }); + expect(await commands.cmdCreateUps(info)).to.be.equal(instanceUpsName); + }); + + it("fail:: response isn't cf resource", async () => { + const instanceUpsName = "myUps"; + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .resolves(instanceUpsName); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ + prompt: messages.enter_credentials, + ignoreFocusOut: true, + value: stringify({}), + validateInput: validateParams(""), + }) + .resolves("{}"); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true }) + .resolves([]); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .resolves(""); + vscodeWindowMock + .expects("showInputBox") + .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .resolves(""); + const response: any = null; + vscodeWindowMock.expects("showInformationMessage").withExactArgs(_.toString(response)).resolves(); + cfLocalMock + .expects("cfCreateUpsInstance") + .withExactArgs({ + instanceName: instanceUpsName, + credentials: {}, + route_service_url: "", + syslog_drain_url: "", + tags: [], + }) + .resolves(response); + expect(await commands.cmdCreateUps()).to.be.equal(undefined); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/ext/mockUtil.ts b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockUtil.ts new file mode 100644 index 000000000..e35eefdcb --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockUtil.ts @@ -0,0 +1,40 @@ +import * as path from "path"; +import { dropRight, join, size, split } from "lodash"; +import { existsSync } from "fs"; + +const Module = require("module"); // eslint-disable-line @typescript-eslint/no-var-requires +const originalRequire = Module.prototype.require; + +export function clearModuleCache(testModulePath?: string): void { + if (testModulePath) { + const key = path.resolve(__dirname, "..", testModulePath); + if (require.cache[key]) { + delete require.cache[key]; + } + } +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function mockVscode(oVscodeMock: any, testModulePath?: string): void { + clearModuleCache(testModulePath); + + Module.prototype.require = function (request: any) { + if (request === "vscode") { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return oVscodeMock; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return originalRequire.apply(this, arguments); + }; +} + +export function recognisePackageJsonPath(cwd?: string): string | undefined { + let parts = split(cwd || __dirname, path.sep); + while (size(parts)) { + if (existsSync(path.resolve(path.join(...parts, "package.json")))) { + return join(parts, path.sep); + } + parts = dropRight(parts); + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts new file mode 100644 index 000000000..03998c876 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts @@ -0,0 +1,265 @@ +import * as _ from "lodash"; +import * as vscode from "vscode"; +import * as path from "path"; +import { platform } from "os"; +export const isWindows = platform().includes("win"); + +const testRegisteredCommands = {}; +export function getTestRegisteredCommands() { + return testRegisteredCommands; +} + +let privateOnDidChangeTreeData: any; +export function getTestPrivateOnDidChangeTreeData() { + return privateOnDidChangeTreeData; +} +let treeProvider: any; +export function getTestTreeProvider() { + return treeProvider; +} +const treeView: any = { + reveal: () => { + return; + }, +}; +export function getTestTreeView() { + return treeView; +} + +let aListeners: any[] = []; +export function getTestListeners() { + return aListeners; +} +export function setTestListeners(data: any[]) { + aListeners = data; +} + +let configs = {}; +export function setTestConfigs(data: any) { + configs = data; +} + +export enum ViewColumn { + Active = -1, + Beside = -2, + One = 1, + Two = 2, + Three = 3, + Four = 4, + Five = 5, + Six = 6, + Seven = 7, + Eight = 8, + Nine = 9, +} + +class Uri { + public static file(testpath: string): vscode.Uri { + return new Uri("file", "", testpath || (isWindows ? "\\" : "/"), "", ""); + } + public static parse(testpath: string): vscode.Uri { + return new Uri("http", "", testpath || (isWindows ? "\\" : "/"), "", ""); + } + public readonly path: string; + public readonly scheme: string; + public readonly authority: string; + public readonly query: string; + public readonly fragment: string; + public readonly fsPath: string; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private constructor(scheme: string, authority: string, testpath: string, query: string, fragment: string) { + this.fsPath = testpath; + this.scheme = scheme; + this.path = ""; + this.authority = ""; + this.query = ""; + this.fragment = ""; + } + public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri { + return new Uri("", "", change.path || "", "", ""); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public toString(skipEncoding?: boolean): string { + return this.fsPath; + } + public toJSON(): any { + return {}; + } +} + +class RelativePattern { + base: string; + pattern: string; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(base: any, pattern: string) { + this.base = base; + this.pattern = pattern; + } +} + +function _path2Uri(extPath: string): { uri: Uri; name: string; index: number } { + return _.set({ uri: Uri.file(extPath), name: extPath, index: 0 }, ["uri", "fsPath"], extPath); +} +const roots = [_path2Uri(path.resolve("workspace")), _path2Uri(path.resolve("project-test"))]; + +let selectedQuickPicks: any; +export function getTestSelectedQuickPicks() { + return selectedQuickPicks; +} +export function setTestSelectedQuickPicks(data: any) { + selectedQuickPicks = data; +} + +export const testVscode = { + Uri, + ViewColumn, + RelativePattern, + Progress: { + report: () => { + return; + }, + }, + ProgressLocation: { Window: 10, Notification: 15 }, + debug: { + startDebugging: () => Promise.resolve(), + }, + TreeItemCollapsibleState: { Collapsed: 1, Expanded: 2, None: 0 }, + commands: { + registerCommand: (id: string, cmd: any) => { + _.set(testRegisteredCommands, id, cmd); + return Promise.resolve(testRegisteredCommands); + }, + executeCommand: () => Promise.reject(), + }, + window: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + showTextDocument: (resourcePath: vscode.Uri) => { + return; + }, + showInformationMessage: () => Promise.resolve(), + showInputBox: () => Promise.resolve(), + showErrorMessage: () => Promise.reject(), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + registerTreeDataProvider: (id: string, provider: any) => { + return; + }, + showQuickPick: () => Promise.resolve(selectedQuickPicks), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + showWarningMessage: (message: string) => { + return; + }, + withProgress: ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options: vscode.ProgressOptions, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + task: ( + progress: vscode.Progress<{ message?: string; increment?: number }>, + token: vscode.CancellationToken + ) => Thenable + ) => Promise.resolve(), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createTreeView: (id: string, options: any) => { + treeProvider = options.treeDataProvider; + privateOnDidChangeTreeData = treeProvider.privateOnDidChangeTreeData; + privateOnDidChangeTreeData.fire = () => { + return; + }; + treeProvider.findTreeItem = () => Promise.resolve(); + treeProvider.matchItems = () => Promise.resolve([]); + return treeView; + }, + showOpenDialog: () => Promise.reject(), + // eslint-disable-next-line @typescript-eslint/no-unused-vars + createOutputChannel: (name: string) => { + return; + }, + createStatusBarItem: () => "", + createWebviewPanel: () => Promise.resolve(), + }, + workspace: { + rootPath: path.resolve("."), + workspaceFolders: roots, + getConfiguration: () => configs, + onDidChangeWorkspaceFolders: (listener: (e: any) => any) => { + aListeners.push(listener); + }, + onDidChangeConfiguration: () => { + return; + }, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onDidChangeTextDocument: (listener: any) => { + return; + }, + getWorkspaceFolder: (folderPath: vscode.Uri) => { + const fsPath = _.get(folderPath, "fsPath"); + return _.set( + { uri: testVscode.Uri.file(fsPath), name: _.last(_.split(fsPath, "\\")), index: 0 }, + ["uri", "fsPath"], + fsPath + ); + }, + findFiles: () => Promise.reject(), + }, + TreeItem: class MockTreeItem { + public readonly label?: string; + public contextValue?: string; + constructor(label?: string) { + if (label) { + this.label = label; + } + } + }, + Position: class MockPossition { + public readonly line: number; + public readonly character: number; + constructor(line: number, character: number) { + this.line = line; + this.character = character; + } + }, + Selection: class MockSelection { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(anchor: vscode.Position, active: vscode.Position) { + return; + } + }, + Range: class MockRange { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(begin: number, end: number) { + return; + } + }, + EventEmitter: class MockEventEmitter { + public event: any = { name: "test" }; + public fire() { + return; + } + }, + extensions: { + getExtension: () => { + return; + }, + }, + StatusBarAlignment: { Left: "left" }, + ThemeIcon: class MockThemeIcon { + static readonly File: vscode.ThemeIcon; + static readonly Folder: vscode.ThemeIcon; + readonly id: string; + readonly color?: vscode.ThemeColor; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(id: string, color?: vscode.ThemeColor) { + this.id = id; + } + }, + WebviewPanel: { + webview: { + onDidReceiveMessage: () => {}, + }, + }, +}; + +export function setTestRoots(projects: string[]) { + testVscode.workspace.workspaceFolders = _.map(projects, (project) => { + return _path2Uri(path.resolve(project)); + }); +} diff --git a/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts new file mode 100644 index 000000000..224a8c629 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts @@ -0,0 +1,626 @@ +import * as _ from "lodash"; +import * as fs from "fs"; +import * as path from "path"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode, recognisePackageJsonPath } from "./ext/mockUtil"; + +const statusBarItem = { show: () => "", hide: () => "" }; + +mockVscode(nsVsMock.testVscode, "src/extension.ts"); +mockVscode(nsVsMock.testVscode, "src/commands.ts"); +mockVscode(nsVsMock.testVscode, "src/cfViewCommands.ts"); +mockVscode(nsVsMock.testVscode, "src/run-configuration.ts"); +import * as extension from "../src/extension"; +import * as commands from "../src/commands"; +import * as cfViewCommands from "../src/cfViewCommands"; +import * as cflocal from "@sap/cf-tools/out/src/cf-local"; +import * as cflocalUnits from "@sap/cf-tools/out/src/utils"; +import * as loggerWrapper from "../src/logger/logger-wrapper"; +import { cfGetConfigFilePath, ITarget, OK } from "@sap/cf-tools"; +import { expect } from "chai"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; +import { CFTargetTI, CFView } from "../src/cfView"; +import type { ExtensionContext } from "vscode"; + +describe("extension unit test", () => { + const runConfigExtName = "sap.vscode-wing-run-config"; + let sandbox: SinonSandbox; + let windowMock: SinonMock; + let workspaceMock: SinonMock; + let viewCommandsMock: SinonMock; + let fsMock: SinonMock; + let mockCfLocal: SinonMock; + let mockCfLocalUnits: SinonMock; + let mockStatusBarItem: SinonMock; + let extensionsMock: SinonMock; + let loggerWrapperMock: SinonMock; + let isShowTarget: boolean; + const config = { + get: (key: string) => { + return key === "CloudFoundryTools.showTargetInformation" ? isShowTarget : undefined; + }, + }; + + before(() => { + sandbox = createSandbox(); + }); + + beforeEach(() => { + windowMock = sandbox.mock(nsVsMock.testVscode.window); + workspaceMock = sandbox.mock(nsVsMock.testVscode.workspace); + viewCommandsMock = sandbox.mock(cfViewCommands); + fsMock = sandbox.mock(fs); + mockCfLocal = sandbox.mock(cflocal); + mockCfLocalUnits = sandbox.mock(cflocalUnits); + mockStatusBarItem = sandbox.mock(statusBarItem); + extensionsMock = sandbox.mock(nsVsMock.testVscode.extensions); + loggerWrapperMock = sandbox.mock(loggerWrapper); + }); + + afterEach(() => { + windowMock.verify(); + workspaceMock.verify(); + viewCommandsMock.verify(); + fsMock.verify(); + mockCfLocal.verify(); + mockCfLocalUnits.verify(); + mockStatusBarItem.verify(); + extensionsMock.verify(); + loggerWrapperMock.verify(); + sandbox.restore(); + }); + + describe("deactivate", () => { + it("unwatchFile called", () => { + fsMock.expects("unwatchFile").withArgs(cfGetConfigFilePath()); + extension.deactivate(); + }); + + it("unwatchFile not called", () => { + mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); + fsMock.expects("unwatchFile").never(); + extension.deactivate(); + }); + }); + + describe("activate- fake createTreeView", () => { + let testContext: any; + const runConfigExtName = "sap.vscode-wing-run-config"; + + beforeEach(() => { + isShowTarget = true; + windowMock.expects("createTreeView").withArgs("cfView"); + testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; + loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); + mockStatusBarItem.expects("show"); + windowMock + .expects("createStatusBarItem") + .withExactArgs(nsVsMock.testVscode.StatusBarAlignment.Left, 100) + .returns(statusBarItem); + workspaceMock.expects("getConfiguration").returns(config); + }); + + it("ok:: cfConfigFilePath does not exist", async () => { + extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); + fsMock.expects("watchFile").never(); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + }); + + it("ok:: cfConfigFilePath exists", async () => { + extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + }); + + it("ok:: depended platform extension is defined but not activated", async () => { + const extRun = { + isActive: false, + activate: () => Promise.resolve(), + exports: { + registerDependency: () => "", + }, + }; + const mockExt = sandbox.mock(extRun); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .atLeast(1) + .resolves({ Name: "testName1" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .atLeast(1) + .resolves({ Name: "testName2" }); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); + mockExt.expects("activate").resolves(); + extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(extRun); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + mockExt.verify(); + }); + + it("ok:: depended platform extension is defined but can not be not activated", async () => { + const extRun = { + isActive: false, + activate: () => Promise.resolve(), + exports: { + registerDependency: () => "", + }, + }; + const mockExt = sandbox.mock(extRun); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .atLeast(1) + .resolves({ Name: "testName1" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .atLeast(1) + .resolves({ Name: "testName2" }); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); + mockExt.expects("activate").rejects(new Error("my error")); + extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(extRun); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + mockExt.verify(); + }); + }); + + describe("activate - depended platform extension is defined and activated", () => { + let testContext: any; + let mockCommands: SinonMock; + + beforeEach(async () => { + isShowTarget = false; + workspaceMock.expects("getConfiguration").returns(config); + mockCommands = sandbox.mock(commands); + testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; + loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); + windowMock + .expects("createStatusBarItem") + .withExactArgs(nsVsMock.testVscode.StatusBarAlignment.Left, 100) + .returns(statusBarItem); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); + mockStatusBarItem.expects("hide"); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns({ + isActive: true, + exports: { + registerDependency: () => "", + }, + }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for rest of initialization asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + }); + + afterEach(() => { + mockCommands.verify(); + }); + + it("ok:: depended platform extension is defined and activated, verify commands", () => { + _.each( + [ + "cf.login", + "cf.login.weak", + "cf.target.set", + "cf.services.create", + "cf.services.bind.local", + "cf.services.unbind", + "cf.services.bind", + "cf.services.binding.state", + "cf.set.orgspace", + "cf.targets.create", + "cf.target.delete", + "cf.targets.reload", + "cf.select.space", + "cf.deploy-service.api", + "cf.services.get-space-services", + "cf.services.get-ups-services", + "cf.services.get-services", + "cf.ups.create", + ], + (cmd) => { + expect(_.get(nsVsMock.getTestRegisteredCommands(), cmd)).to.be.ok; + } + ); + }); + + it("ok:: cmdLogin command triggered", async () => { + const targets = [new CFTargetTI({ label: "test-target", isCurrent: true, isDirty: false })]; + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + mockCommands.expects("cmdLogin").resolves(OK); + viewCommandsMock + .expects("cmdDeleteTarget") + .withExactArgs(targets[0], { silent: true, "skip-reload": true }) + .resolves(); + viewCommandsMock + .expects("execSaveTarget") + .withExactArgs(targets[0], { silent: true, "skip-reload": true }) + .resolves(); + viewCommandsMock.expects("execSetTarget").withExactArgs(targets[0], { silent: true }).resolves(); + await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); + }); + + it("ok:: cmdLogin command triggered and failed", async () => { + const targets = [new CFTargetTI({ label: "test-target", isCurrent: true, isDirty: false })]; + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + mockCommands.expects("cmdLogin").resolves("failed"); + viewCommandsMock.expects("cmdDeleteTarget").never(); + await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); + }); + + it("ok:: CFView created", () => { + expect(CFView.get()).to.be.ok; + }); + + it("ok:: cmdLogin command triggered, canceled", async () => { + mockCommands.expects("cmdLogin").resolves(); + await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); + }); + + it("ok:: cmdLogin command triggered, no active target", async () => { + sandbox.stub(nsVsMock.getTestTreeProvider(), "getCurrentTarget").returns(undefined); + mockCommands.expects("cmdLogin").resolves(OK); + await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); + }); + + it("ok:: 'cf.target.set' command triggered", (done) => { + const targets = [ + new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), + new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + ]; + const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + cfViewMock.expects("reveal").withExactArgs(targets[1], { select: true, focus: true, expand: true }).resolves(); + viewCommandsMock.expects("cmdSetCurrentTarget").withExactArgs(targets[0]).resolves(); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])(targets[0]); + setTimeout(() => { + cfViewMock.verify(); + done(); + }, 600); + }); + + it("ok:: 'cf.target.set' command triggered, reveal item not found", (done) => { + const targets = [ + new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), + new CFTargetTI({ label: "test-target-active", isCurrent: false, isDirty: false }), + ]; + const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + cfViewMock.expects("reveal").never(); + viewCommandsMock.expects("cmdSetCurrentTarget").withExactArgs(targets[0]).resolves(); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])(targets[0]); + setTimeout(() => { + cfViewMock.verify(); + done(); + }, 600); + }); + + it("ok:: 'cf.targets.create' command triggered", (done) => { + const targets = [ + new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), + new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + ]; + const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + cfViewMock.expects("reveal").withExactArgs(targets[1], { select: true, focus: true, expand: true }).resolves(); + mockCommands.expects("cmdSelectAndSaveTarget").resolves(targets[1].label); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "targets", "create"])(); + setTimeout(() => { + cfViewMock.verify(); + done(); + }, 600); + }); + + it("ok:: 'cf.targets.create' command triggered, creation canceled", (done) => { + const targets = [ + new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), + new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + ]; + const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); + sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + cfViewMock.expects("reveal").never(); + mockCommands.expects("cmdSelectAndSaveTarget").resolves(); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "targets", "create"])(); + setTimeout(() => { + cfViewMock.verify(); + done(); + }, 600); + }); + }); + + describe("onCFConfigFileChange", () => { + let testContext: any; + const target: ITarget = { + "api endpoint": "endpoint", + "api version": "3.2.1", + user: "test", + org: "org", + space: "space", + }; + + beforeEach(() => { + isShowTarget = false; + mockStatusBarItem.expects("hide"); + workspaceMock.expects("getConfiguration").returns(config); + windowMock.expects("createTreeView").withArgs("cfView"); + testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; + loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); + extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); + mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); + windowMock + .expects("createStatusBarItem") + .withExactArgs(nsVsMock.testVscode.StatusBarAlignment.Left, 100) + .returns(statusBarItem); + }); + + it("ok:: verify callback trigger, target updated", async () => { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "testName1" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + mockCfLocal.expects("cfGetTarget").resolves(target); + setTimeout(() => { + extension.onCFConfigFileChange(); + }, 100); + await new Promise((resolve) => setTimeout(resolve, 200)); + }); + + it("ok:: nothing updated", async () => { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + + setTimeout(() => { + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "testName1" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + mockCfLocal.expects("cfGetTarget").resolves(target); + extension.onCFConfigFileChange(); + }, 100); + + setTimeout(() => { + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "testName1" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + extension.onCFConfigFileChange(); + }, 300); + await new Promise((resolve) => setTimeout(resolve, 500)); + }); + + it("ok:: not targeted", async () => { + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + + setTimeout(() => { + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("OrganizationFields").resolves({ Name: "" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "" }); + mockCfLocal.expects("cfGetTarget").never(); + extension.onCFConfigFileChange(); + }, 100); + await new Promise((resolve) => setTimeout(resolve, 300)); + }); + + it("ok:: not logged in", async () => { + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "testName1" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName3" }); + /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ + await extension.activate(testContext).then(() => { + // wait for the tree view to be created asynchonously + return new Promise((resolve) => setTimeout(resolve, 100)); + }); + + setTimeout(() => { + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "testName1" }); + mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "" }); + mockCfLocal.expects("cfGetTarget").rejects(undefined); + extension.onCFConfigFileChange(); + }, 100); + await new Promise((resolve) => setTimeout(resolve, 300)); + }); + }); + + describe("callbackOnDidChangeConfiguration event", () => { + it("ok:: callbackOnDidChangeConfiguration - other section", () => { + const event = { + affectsConfiguration: (section: string) => { + return section === "RunConfigurations.loggingLevel"; + }, + }; + workspaceMock.expects("getConfiguration").never(); + extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + }); + + it("ok:: callbackOnDidChangeConfiguration - triggered, hide", () => { + const event = { + affectsConfiguration: (section: string) => { + return section === "CloudFoundryTools.showTargetInformation"; + }, + }; + workspaceMock.expects("getConfiguration").returns(config); + mockStatusBarItem.expects("hide"); + extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + }); + + it("ok:: callbackOnDidChangeConfiguration - triggered, show", () => { + const event = { + affectsConfiguration: (section: string) => { + return section === "CloudFoundryTools.showTargetInformation"; + }, + }; + isShowTarget = true; + workspaceMock.expects("getConfiguration").returns(config); + mockStatusBarItem.expects("show"); + extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + }); + }); + + describe("Common extension package definition", () => { + const jsonPath = recognisePackageJsonPath(__dirname); + const jsonPackage = JSON.parse( + fs.readFileSync(path.resolve(path.join(jsonPath ? jsonPath : "", "package.json")), { encoding: "utf8" }) + ); + + it("ok:: configuration title", () => { + expect(_.get(jsonPackage, ["contributes", "configuration", "title"])).to.be.equal("CloudFoundryTools"); + }); + + it("ok:: logging.loggingLevel desciption", () => { + expect( + _.get(jsonPackage, [ + "contributes", + "configuration", + "properties", + "CloudFoundryTools.logging.loggingLevel", + "description", + ]) + ).to.be.equal("The verbosity of logging. The Order is None < fatal < error < warn < info < debug < trace."); + }); + + it("ok:: logging.sourceLocationTracking desciption", () => { + expect( + _.get(jsonPackage, [ + "contributes", + "configuration", + "properties", + "CloudFoundryTools.logging.sourceLocationTracking", + "description", + ]) + ).to.be.equal( + "Should Source Code Location Info be added to log entries, DANGER - May be very slow, only use in debugging scenarios" + ); + }); + + it("ok:: showTargetInformation desciption", () => { + expect( + _.get(jsonPackage, [ + "contributes", + "configuration", + "properties", + "CloudFoundryTools.showTargetInformation", + "description", + ]) + ).to.be.equal("Display the current Cloud Foundry target information in the status bar"); + }); + + it("ok:: commandPalette content", () => { + const menus: any[] = _.get(jsonPackage, ["contributes", "menus", "commandPalette"]); + for (const menu of menus) { + expect( + [ + "cf.login.weak", + "cf.target.delete", + "cf.targets.create", + "cf.deploy-service.api", + "cf.target.set", + "cf.services.unbind", + "cf.services.bind", + "cf.services.binding.state", + "cf.services.get-space-services", + "cf.services.get-ups-services", + "cf.services.get-services", + ].includes(menu.command as string) + ).to.be.true; + expect(menu.when).to.be.equal("false"); + } + }); + + it("ok:: view/title content", () => { + const commands = _.get(jsonPackage, ["contributes", "menus", "view/title"]); + expect(_.find(commands, ["command", "cf.targets.create"])).to.be.deep.equal({ + command: "cf.targets.create", + when: "view == cfView", + group: "navigation@1", + }); + expect(_.find(commands, ["command", "cf.targets.reload"])).to.be.deep.equal({ + command: "cf.targets.reload", + when: "view == cfView", + group: "navigation@2", + }); + }); + + it("ok:: view/item/context content", () => { + const commands = _.get(jsonPackage, ["contributes", "menus", "view/item/context"]); + expect(_.find(commands, ["command", "cf.services.create"])).to.be.deep.equal({ + command: "cf.services.create", + when: "view == cfView && viewItem =~ /^services-active$/", + group: "inline", + }); + expect(_.find(commands, ["command", "cf.login"])).to.be.deep.equal({ + command: "cf.login", + when: "view == cfView && viewItem == cf-login-required", + group: "inline", + }); + expect(_.find(commands, ["command", "cf.services.bind.local"])).to.be.deep.equal({ + command: "cf.services.bind.local", + when: "view == cfView && viewItem == cf-service", + }); + let command = _.find(commands, (com) => { + return com.command === "cf.target.set" && com.group === "inline"; + }); + expect(command).to.be.deep.equal({ + command: "cf.target.set", + when: "view == cfView && viewItem =~ /^cf-target$|^cf-target-not-current$/", + group: "inline", + }); + command = _.find(commands, (com) => { + return com.command === "cf.target.set" && com.group === "appearance@1"; + }); + expect(command).to.be.deep.equal({ + command: "cf.target.set", + when: "view == cfView && viewItem =~ /^cf-target$/", + group: "appearance@1", + }); + expect(_.find(commands, ["command", "cf.target.delete"])).to.be.deep.equal({ + command: "cf.target.delete", + when: "view == cfView && viewItem =~ /^cf-target(-active)?$/", + group: "appearance@2", + }); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts new file mode 100644 index 000000000..449c160ee --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts @@ -0,0 +1,457 @@ +import { expect } from "chai"; +import { fail } from "assert"; +import * as proxyquire from "proxyquire"; +import { createSandbox, mock, SinonMock, SinonSandbox } from "sinon"; +import { cfendpoint } from "@sap/bas-sdk"; +import { OK, Organization, Space, SSOLoginOptions } from "@sap/cf-tools"; +import { RpcExtension } from "@sap-devx/webview-rpc/out.ext/rpc-extension"; +import * as nsVsMock from "./../ext/mockVscode"; +import { mockVscode } from "../ext/mockUtil"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import { messages } from "../../src/messages"; +import * as path from "path"; +import * as fs from "fs"; + +nsVsMock.setTestRoots(["/", "/some_path"]); + +mockVscode(nsVsMock.testVscode, "src/cfViewCommands.ts"); +mockVscode(nsVsMock.testVscode, "src/logger/logger-wrapper.ts"); +mockVscode(nsVsMock.testVscode, "src/commands.ts"); + +describe("loginTargetView tests", () => { + let sandbox: SinonSandbox; + let cfApiMock: SinonMock; + let cfFsMock: SinonMock; + let cfendpointMock: SinonMock; + let vscodeWindowMock: SinonMock; + let cfLocalMock: SinonMock; + let vscodeCommandsMock: SinonMock; + + const cfApiProxy = { + cfGetConfigFileField: () => Promise.reject(new Error("not implemented")), + cfGetAvailableOrgs: () => Promise.reject(new Error("not implemented")), + cfGetAvailableSpaces: () => Promise.reject(new Error("not implemented")), + cfSetOrgSpace: () => Promise.reject(new Error("not implemented")), + }; + let internal: any; + let loginTargetInstance: typeof internal.LoginTarget; + + const panelObj = { + viewType: "myViewType", + title: "My Webview Panel", + webview: { + onDidReceiveMessage: () => {}, + asWebviewUri: (uri: any) => uri, + onDidDispose: (callback: () => void) => { + // Simulate panel disposal + callback(); + }, + html: "", + }, + options: {}, // Provide appropriate mock data here + viewColumn: nsVsMock.testVscode.ViewColumn.One, + active: true, + visible: true, + onDidDispose: () => {}, + onDidChangeViewState: () => {}, // Provide appropriate mock data here + reveal: () => {}, + dispose: () => {}, + }; + + let internalModule: any; + const extensionPath = "api.sap.test.ondemand.com"; + + before(() => { + sandbox = createSandbox(); + internalModule = proxyquire("../../src/loginTargetView/loginTargetView", { + vscode: { + ...nsVsMock.testVscode, + "@noCallThru": true, + }, + "@sap/cf-tools": cfApiProxy, + "../extension": { + getPath: () => extensionPath, + "@noCallThru": true, + }, + "@noCallThru": true, + }); + internal = internalModule.internal; + }); + + beforeEach(() => { + cfApiMock = mock(cfApiProxy); + cfFsMock = mock(fs); + cfendpointMock = mock(cfendpoint); + cfLocalMock = sandbox.mock(cfLocal); + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + vscodeCommandsMock = sandbox.mock(nsVsMock.testVscode.commands); + }); + + afterEach(() => { + cfApiMock.verify(); + cfFsMock.verify(); + cfendpointMock.verify(); + cfLocalMock.verify(); + vscodeWindowMock.verify(); + vscodeCommandsMock.verify(); + sandbox.restore(); + }); + + const htmlContext = ""; + + describe("openLoginView", () => { + it("should open login view and handle login", async () => { + const panelMock = sandbox.mock(panelObj); + panelMock.expects("reveal").returns(undefined); + sandbox.stub(panelObj, "onDidDispose").yields(); + vscodeWindowMock + .expects("createWebviewPanel") + .withExactArgs("cfLogin", "Cloud Foundry Sign In", nsVsMock.testVscode.ViewColumn.Beside, { + enableScripts: true, + localResourceRoots: [nsVsMock.testVscode.Uri.file(path.join(extensionPath, "dist", "media"))], + }) + .returns(panelObj); + cfFsMock + .expects("readFileSync") + .withExactArgs(path.join(extensionPath, "dist", "media", "index.html"), "utf8") + .returns(htmlContext); + + // Stub the rpcInvokeStub for invoking long function with progress form + sandbox.stub(RpcExtension.prototype, "invoke").resolves(OK); + + expect(await internalModule.openLoginView({ isSplit: true })).to.equal(undefined); + }); + }); + + describe("getOrgs", () => { + it("should fetch orgs successfully", async () => { + const orgs: Organization[] = [{ label: "org", guid: "guid" }]; + loginTargetInstance = new internal.LoginTarget(panelObj); + + cfApiMock.expects("cfGetAvailableOrgs").resolves(orgs); + + const result = await loginTargetInstance.getOrgs(); + + expect(result).to.deep.equal(orgs); + }); + + it("should handle error when fetching orgs", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + cfApiMock.expects("cfGetAvailableOrgs").rejects(new Error("Failed to fetch orgs")); + + const result = await loginTargetInstance.getOrgs(); + + expect(result).to.deep.equal([]); + }); + }); + + describe("getTarget", () => { + it("should fetch target successfully", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + const api = { + user: "user", + space: "space", + }; + + const cfLogoutStub = sandbox.stub().resolves(api); + mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + + const result = await loginTargetInstance.getTarget(); + + expect(result.user).to.equal(api.user); + expect(result.space).to.equal(api.space); + }); + + it("should handle error when fetching target", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + const cfLogoutStub = sandbox.stub().rejects(new Error("Logout error")); + mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + + const result = await loginTargetInstance.getTarget(); + + expect(result).to.equal(undefined); + }); + }); + + describe("init", () => { + it("should initialize with target when logged in", async () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + + const cfGetTargetStub = sandbox + .stub(loginTargetInstance, "getTarget") + .resolves({ "api endpoint": extensionPath, org: "my-org", space: "my-space" }); + mockPrivateMethod(loginTargetInstance, cfGetTargetStub, "getTarget"); + + const cfDefaultLandscape = sandbox.stub().resolves(extensionPath); + mockPrivateMethod(loginTargetInstance, cfDefaultLandscape, "getCFDefaultLandscape"); + + const result = await loginTargetInstance.init(); + + expect(result.defaultEndpoint).to.equal(extensionPath); + expect(result.isLoggedIn).to.be.true; + expect(result.passcodeUrl).to.equal("login.sap.test.ondemand.com/passcode"); + expect(result.currentOrg).to.equal("my-org"); + expect(result.currentSpace).to.equal("my-space"); + }); + }); + + describe("getSpaces", () => { + it("should fetch spaces successfully", async () => { + const org = "org-1"; + const spaces: Space[] = [{ label: "space", guid: "guid", orgGUID: "orgGUID" }]; + loginTargetInstance = new internal.LoginTarget(panelObj); + + cfApiMock.expects("cfGetAvailableSpaces").withExactArgs(org).resolves(spaces); + + const result = await loginTargetInstance.getSpaces(org); + + expect(result).to.deep.equal(spaces); + }); + + it("should handle error when fetching spaces", async () => { + const org = "org-1"; + loginTargetInstance = new internal.LoginTarget(panelObj); + + cfApiMock.expects("cfGetAvailableSpaces").withExactArgs(org).rejects(new Error("Failed to fetch spaces")); + + const result = await loginTargetInstance.getSpaces(org); + + expect(result).to.deep.equal([]); + }); + }); + + describe("getSelectedTarget", () => { + it("should return selected target", () => { + const expectedTarget = { + "api endpoint": extensionPath, + org: "my-org", + space: "my-space", + }; + + const loginTargetInstance = new internal.LoginTarget(panelObj); + loginTargetInstance["currentTarget"] = expectedTarget; + + const result = loginTargetInstance.getSelectedTarget(); + + expect(result.org).to.deep.equal(expectedTarget.org); + expect(result.space).to.deep.equal(expectedTarget.space); + expect(result.endpoint).to.deep.equal(expectedTarget["api endpoint"]); + }); + + it("should return undefined when no target is selected", () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + loginTargetInstance["currentTarget"] = undefined; + + const result = loginTargetInstance.getSelectedTarget(); + + expect(result.org).to.be.undefined; + expect(result.space).to.be.undefined; + expect(result.endpoint).to.be.undefined; + }); + }); + + describe("logoutClick", () => { + it("logoutClick should fail and return false", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + const cfLogoutStub = sandbox.stub().rejects(new Error("Logout error")); + mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + + const result = await loginTargetInstance.logoutClick(); + + expect(result).to.be.false; + // Additional assertions could be made here if desired + }); + + it("should handle successful logout", async () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + + // Mock the cfLogout function to resolve successfully + const cfLogoutStub = sandbox.stub().resolves(); + loginTargetInstance.invokeLongFunctionWithProgressForm = cfLogoutStub; + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.logout_success).resolves(); + + const result = await loginTargetInstance.logoutClick(); + + // Check if cfLogout was called + expect(cfLogoutStub.calledOnce).to.be.true; + expect(result).to.be.true; + }); + }); + + describe("loginClick", () => { + it("should handle successful login", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + // Mock the long function (cfLogin) to return a success result + const cfLoginStub = sandbox.stub().resolves(OK); + mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + const showMessageStub = vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.login_success) + .resolves(); + + const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + + expect(result).to.be.true; + expect(showMessageStub.calledOnceWith(messages.login_success)).to.be.true; + expect(cfLoginStub.calledOnce).to.be.true; + }); + + it("should handle failed login and show error message", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + const loginResult = "Failed"; + const showMessageStub = vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.login_failed) + .resolves(); + + // Mock the long function (cfLogin) to return a failure result + const cfLoginStub = sandbox.stub().resolves(loginResult); + mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + + const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + + expect(result).to.be.false; + expect(showMessageStub.calledOnceWith(messages.login_failed)).to.be.true; + expect(cfLoginStub.calledOnce).to.be.true; + }); + + it("should handle login exception and show error message", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + // Mock the long function (cfLogin) to throw an error + const cfLoginStub = sandbox.stub().rejects(new Error("Login error")); + mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + + const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + + expect(result).to.be.false; + expect(cfLoginStub.calledOnce).to.be.true; + }); + }); + + describe("applyTarget", () => { + it("ok:: applyTarget - successful set org and space", async () => { + const org = "my-org"; + const space = "my-space"; + loginTargetInstance = new internal.LoginTarget(panelObj); + + // Mock the cfSetOrgSpace function to resolve successfully + const cfSetOrgSpaceStub = sandbox.stub().resolves(OK); + cfApiProxy.cfSetOrgSpace = cfSetOrgSpaceStub; + + vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + + const result = await loginTargetInstance.applyTarget(org, space); + + // Check if cfSetOrgSpace was called with the correct arguments + expect(cfSetOrgSpaceStub.calledOnceWith(org, space)).to.be.true; + expect(result).to.equal(OK); + }); + + it("should handle applyTarget exception and show error message", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + // Mock the cfSetOrgSpace function to resolve successfully + cfApiProxy.cfSetOrgSpace = sandbox.stub().throws("err"); + + const result = await loginTargetInstance.applyTarget(null, null); + + expect(result).to.equal("Error"); + }); + }); + + describe("openPasscodeLink", () => { + it("should open passcode link in vscode", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + + vscodeCommandsMock.expects("executeCommand").resolves(); + + await loginTargetInstance.openPasscodeLink(extensionPath); + }); + }); + + describe("invokeLongFunctionWithProgressForm", () => { + it("should invoke long function with progress form", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + const longFunctionStub = sandbox.stub().resolves("result"); + const rpcInvokeStub = sandbox.stub(RpcExtension.prototype, "invoke"); + + const result = await loginTargetInstance.invokeLongFunctionWithProgressForm(longFunctionStub, "arg1", "arg2"); + + expect(result).to.equal("result"); + expect(rpcInvokeStub.calledTwice).to.be.true; + expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])).to.be.true; + expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])).to.be.true; + }); + + it("should handle error when invoking long function", async () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + const longFunctionStub = sandbox.stub().rejects(new Error("Function failed")); + const rpcInvokeStub = sandbox.stub(RpcExtension.prototype, "invoke"); + + try { + await loginTargetInstance.invokeLongFunctionWithProgressForm(longFunctionStub, "arg1", "arg2"); + fail("Should have thrown an error"); + } catch (error) { + expect(error.message).to.equal("Function failed"); + expect(rpcInvokeStub.calledTwice).to.be.true; + expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])).to.be.true; + expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])).to.be.true; + } + }); + }); + + describe("getCFDefaultLandscape", () => { + const envEndpoint = "https://my.env.endpoint"; + const fileEndpoint = "https://my.test.endpoint"; + + it("ok:: getCFDefaultLandscape - 'Target' is defined in .cf config file - return it first", async () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(fileEndpoint); + expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal(fileEndpoint); + }); + + it("ok:: getCFDefaultLandscape - 'Target' is undefined in .cf config file - fetch from env", async () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(); + cfendpointMock.expects("getCFEndpoint").resolves(envEndpoint); + expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal(envEndpoint); + }); + + it("fail:: getCFDefaultLandscape - cfGetConfigFileField is rejected", () => { + const loginTargetInstance = new internal.LoginTarget(panelObj); + const error = new Error("cfGetConfigFileField rejected"); + cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").rejects(error); + return loginTargetInstance + .getCFDefaultLandscape() + .then(() => { + fail("should fail"); + }) + .catch((e: Error) => { + expect(e.message).to.be.equal(error.message); + }); + }); + }); + + describe("calculatePasscodeUrl", () => { + it("ok:: calculatePasscodeUrl", () => { + loginTargetInstance = new internal.LoginTarget(panelObj); + expect(loginTargetInstance.calculatePasscodeUrl(extensionPath)).to.be.equal( + "login.sap.test.ondemand.com/passcode" + ); + }); + }); + + function mockPrivateMethod( + loginTargetInstance: typeof internal.LoginTarget, + cfLoginStub: any, + nameofPrivateMethod: string + ) { + // Call the private method through the instance + return (loginTargetInstance[nameofPrivateMethod] = cfLoginStub); + } +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts new file mode 100644 index 000000000..0972e15bb --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts @@ -0,0 +1,23 @@ +import { expect } from "chai"; +import { messages } from "../src/messages"; + +describe("messages unit test", () => { + it("messages", () => { + expect(messages.service_created("test")).to.be.not.undefined; + expect(messages.activation_extension("test")).to.be.not.undefined; + expect(messages.extension_active("test")).to.be.not.undefined; + expect(messages.targeting("testOrg", "testSpace")).to.be.not.undefined; + expect(messages.targeting("testOrg", "testSpace")).to.be.not.undefined; + expect(messages.no_service_plan_info_found("testPlan", "testService")).to.be.not.undefined; + expect(messages.no_services_instance_byname_found("testService")).to.be.not.undefined; + expect(messages.no_services_found("my-plan {1}")).to.be.ok; + expect(messages.service_bound_successful("planName")).to.be.ok; + expect(messages.service_instances_list_incomplete(["s-1", "s-2"])).to.be.equal( + "Could not get the details of the following service instances: 's-1,s-2'. Try to verify the specified service instances, then log in again to Cloud Foundry." + ); + expect(messages.service_instances_list_incomplete(["s-1", "s-2", "s-3", "s-4"])).to.be.equal( + "Could not get the details of some service instances. Try to verify the service instances, then log in again to Cloud Foundry." + ); + expect(messages.target_created("my-target")).to.be.ok; + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.envNoVCAP b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envNoVCAP new file mode 100644 index 000000000..9be943f4b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envNoVCAP @@ -0,0 +1 @@ +CHISEL_USER=chisel-devx \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest new file mode 100644 index 000000000..1e3d1795a --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest @@ -0,0 +1,6 @@ +TUNNEL_PARAM=31111:10.255.008.222:35555 +CHISEL_URL=https://chisel6bb53d2a.test.hana.ondemand.com +CHISEL_USER=chisel-xxx +CHISEL_PASSWORD=6test5 +VCAP_SERVICES={"naha":[{"name":"hdi-test-instance","instance_name":"hdi-test-instance","label":"hana","tags":["database","relational","mta-resource-name:bookshop-hdi-container"],"plan":"hdi-shared"}],"hanatest":[{"name":"hana-1","instance_name":"hana-1","label":"hanatest","tags":["mta-resource-name:hana1-mta"]},{"name":"hana-2","instance_name":"hana-2","label":"hanatest","tags":["mta-resource-name:hana2-mta"]}],"xsuaa":[{"name":"xsuaa-myser","instance_name":"xsuaa-myser","label":"xsuaa","tags":["xsuaa"],"plan":"application"}]} +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_test" : "hdi-test-instance"}] \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest_readonly b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest_readonly new file mode 100644 index 000000000..a934934ab --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.envtest_readonly @@ -0,0 +1,5 @@ +TUNNEL_PARAM=30041:10.253.111.222:30041 +CHISEL_URL=https://chisel6bb53d2a-11111-a998.example.hana.ondemand.com +CHISEL_USER=chisel-user +VCAP_SERVICES={"hana":[{"name":"hdi-test-instance","instance_name":"hdi-test-instance","label":"hana","tags":["hana","database","relational","mta-resource-name:bookshop-hdi-container"],"plan":"hdi-shared"}]} +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_name_for_DefaultDB" : "hdi-test-instance"}] \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.testInValidVCAP b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testInValidVCAP new file mode 100644 index 000000000..639da7f67 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testInValidVCAP @@ -0,0 +1,5 @@ +TUNNEL_PARAM=30041:10.253.222.111:30041 +CHISEL_URL=https://chisel6bb53d2a-6429e0cba998.test.ondemand.com +CHISEL_USER=chisel-user +VCAP_SERVICES={"test":"} +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_name_for_DefaultDB" : "hdi-test-instance"}] diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnv b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnv new file mode 100644 index 000000000..035561cbf --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnv @@ -0,0 +1,5 @@ +TUNNEL_PARAM=30041:10.253.108.214:30041 +CHISEL_URL=https://chisel6bb53d2a-b9f6-44f8-b70a-6429e0cba998.cfapps.sap.hana.ondemand.com +CHISEL_USER=chisel-user +VCAP_SERVICES={"hana":[{"name":"hdi-test-instance","instance_name":"hdi-test-instance","label":"hana","tags":["hana","database","relational","mta-resource-name:bookshop-hdi-container"],"plan":"hdi-shared"}]} +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_name_for_DefaultDB" : "hdi-test-instance"}] diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotes b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotes new file mode 100644 index 000000000..329794c23 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotes @@ -0,0 +1,5 @@ +TUNNEL_PARAM=30041:10.253.108.214:30041 +CHISEL_URL=https://chisel6bb53d2a-b9f6-44f8-b70a-6429e0cba998.cfapps.sap.hana.ondemand.com +CHISEL_USER=chisel-user +VCAP_SERVICES='{"hana":[{"name":"hdi-test-instance","instance_name":"hdi-test-instance","label":"hana","tags":["hana","database","relational","mta-resource-name:bookshop-hdi-container"],"plan":"hdi-shared"}]}' +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_name_for_DefaultDB" : "hdi-test-instance"}] diff --git a/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotesAndSpaces b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotesAndSpaces new file mode 100644 index 000000000..b08f8ca17 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/resources/.testValidEnvVCAPWithQuotesAndSpaces @@ -0,0 +1,5 @@ +TUNNEL_PARAM=30041:10.253.108.214:30041 +CHISEL_URL=https://chisel6bb53d2a-b9f6-44f8-b70a-6429e0cba998.cfapps.sap.hana.ondemand.com +CHISEL_USER=chisel-user +VCAP_SERVICES= '{"hana":[{"name":"hdi-test-instance","instance_name":"hdi-test-instance","label":"hana","tags":["hana","database","relational","mta-resource-name:bookshop-hdi-container"],"plan":"hdi-shared"}]}' +JBP_CONFIG_RESOURCE_CONFIGURATION=[tomcat/webapps/ROOT/META-INF/context.xml: {"service_name_for_DefaultDB" : "hdi-test-instance"}] diff --git a/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts new file mode 100644 index 000000000..b249b5f87 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts @@ -0,0 +1,319 @@ +import { expect, assert } from "chai"; +import * as _ from "lodash"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; + +mockVscode(nsVsMock.testVscode, "src/run-configuration.ts"); +import * as cfViewCommands from "../src/cfViewCommands"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import * as chisel from "../src/chisel"; +import { DependencyHandler } from "../src/run-configuration"; +import * as utils from "../src/utils"; +import { IBindContext, ConfigurationTarget, ConfigMetadata, BindState } from "@sap/wing-run-config-types"; +import { messages } from "../src/messages"; +import { createSandbox, SinonMock, SinonSandbox } from "sinon"; + +describe("run-configuration tests package", () => { + let sandbox: SinonSandbox; + let vscodeWindowMock: SinonMock; + + before(() => { + sandbox = createSandbox(); + }); + + beforeEach(() => { + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + }); + + afterEach(() => { + vscodeWindowMock.verify(); + sandbox.restore(); + }); + + describe("DependencyHandler scope", () => { + const instances = ["testInstance"]; + const bindContext: IBindContext = { + runnableId: "", + configData: { + config: { + data: ConfigMetadata.get(ConfigurationTarget.launch), + }, + }, + envPath: nsVsMock.testVscode.Uri.file("envPath"), + depContext: { + type: "hdi_type", + displayName: "", + bindable: true, + displayType: "dysplayType", + dependencyHandlerId: "dependencyHandlerId", + data: { + resourceTag: "res-tag", + resourceName: "res-name", + }, + }, + }; + + it("ok:: constructor", () => { + const id = "test.Handler.id"; + expect(new DependencyHandler(id).getId()).to.be.equal(id); + }); + + it("ok:: 'getBindState' - cloud type", async () => { + const getEnvResourcesResult = { + vcapObject: { + hdi_type: BindState.cloud, + }, + isQuotedVcap: false, + }; + sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).resolves(getEnvResourcesResult); + expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.cloud); + }); + + it("ok:: 'getBindState' - not defined", async () => { + const getEnvResourcesResult = { + vcapObject: { + type: BindState.cloud, + }, + isQuotedVcap: false, + }; + sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).resolves(getEnvResourcesResult); + expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.notbound); + }); + + it("ok:: 'getBindState' - exception error, resolved to not bound", async () => { + const error = new Error("some error"); + sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).throws(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.notbound); + }); + + it("ok:: 'bind' - no instance", async () => { + sandbox.stub(cfViewCommands, "bindLocalService").resolves([]); + expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.equal(undefined); + }); + + it("ok:: 'bind' - exception", async () => { + const error = new Error("my error"); + sandbox.stub(cfViewCommands, "bindLocalService").rejects(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.equal(undefined); + }); + + it("ok:: 'bind' succeedded", async () => { + sandbox.stub(cfViewCommands, "bindLocalService").resolves(["testInstance"]); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + + expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.deep.equal({ + configData: bindContext.configData, + resource: { name: "testInstance", type: "resourceType" }, + }); + }); + + it("ok:: 'bind' - with tag", async () => { + const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); + const resourceName: string = _.get(bindContext, "depContext.data.resourceName"); + const serviceType = [ + { + name: bindContext.depContext.type, + plan: _.get(bindContext.depContext, ["data", "plan"], ""), + tag: resourceTag ? resourceTag + resourceName : "", + prompt: "", + }, + ]; + sandbox + .stub(cfViewCommands, "bindLocalService") + .withArgs(serviceType, bindContext.envPath) + .resolves(["testInstance"]); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + + expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.deep.equal({ + configData: bindContext.configData, + resource: { name: "testInstance", type: "resourceType" }, + }); + }); + + it("ok:: 'bind' - no tag", async () => { + const serviceType = [ + { + name: bindContext.depContext.type, + plan: _.get(bindContext.depContext, ["data", "plan"], ""), + tag: "", + prompt: "", + }, + ]; + const copyContext = _.cloneDeep(bindContext); + copyContext.depContext.data = {}; + sandbox + .stub(cfViewCommands, "bindLocalService") + .withArgs(serviceType, bindContext.envPath) + .resolves(["testInstance"]); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + + expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + configData: copyContext.configData, + resource: { name: "testInstance", type: "resourceType" }, + }); + }); + + it("ok:: 'bind' - create chisel task - return undefined", async () => { + sandbox + .stub(chisel, "checkAndCreateChiselTask") + .withArgs(bindContext.envPath.fsPath, instances.join("&")) + .resolves(undefined); + sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + + const copyContext = _.cloneDeep(bindContext); + copyContext.depContext.data = { isCreateChiselTask: true }; + expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + configData: copyContext.configData, + resource: { name: "testInstance", type: "resourceType" }, + }); + }); + + it("ok:: 'bind' - create chisel task - no depended tasks", async () => { + const chiselTask = { label: "chiselLabel", data: { context: "some" } }; + sandbox + .stub(chisel, "checkAndCreateChiselTask") + .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .resolves(chiselTask); + sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "ahana" }); + const copyContext = _.cloneDeep(bindContext); + copyContext.depContext.data = { isCreateChiselTask: true }; + + copyContext.configData.dependentTasks = undefined; + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs( + `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselTask.label}'` + ) + .resolves(); + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext, { "skip-reload": false }) + ).to.be.deep.equal({ + configData: copyContext.configData, + resource: { + name: "testInstance", + type: "ahana", + data: { chiselTask }, + }, + }); + }); + + it("ok:: 'bind' - create chisel task - no depended tasks, create chisel task is not required explicitly", async () => { + const chiselTask = { label: "chiselLabel", data: { context: "some" } }; + sandbox + .stub(chisel, "checkAndCreateChiselTask") + .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .resolves(chiselTask); + sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "hanatrial" }); + const copyContext = _.cloneDeep(bindContext); + + copyContext.configData.dependentTasks = undefined; + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs( + `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselTask.label}'` + ) + .resolves(); + expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + configData: copyContext.configData, + resource: { + name: "testInstance", + type: "hanatrial", + data: { chiselTask }, + }, + }); + }); + + it("ok:: 'bind' - create chisel task - no depended tasks, silent mode required", async () => { + const chiselTask = { label: "chiselLabel", data: { context: "some" } }; + sandbox + .stub(chisel, "checkAndCreateChiselTask") + .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .resolves(chiselTask); + sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + const copyContext = _.cloneDeep(bindContext); + copyContext.depContext.data = { isCreateChiselTask: true }; + + copyContext.configData.dependentTasks = undefined; + vscodeWindowMock.expects("showInformationMessage").never(); + expect(await new DependencyHandler("test.Handler.id").bind(copyContext, { silent: true })).to.be.deep.equal({ + configData: copyContext.configData, + resource: { + name: "testInstance", + type: "resourceType", + data: { chiselTask }, + }, + }); + }); + + it("ok:: 'bind' - create chisel task - depended tasks", async () => { + const chiselLabel = "chiselLabel"; + const tsk = { name: "task" }; + const chiselJson = { label: chiselLabel, data: { context: "some" } }; + sandbox + .stub(chisel, "checkAndCreateChiselTask") + .withArgs(bindContext.envPath.fsPath, instances.join("&")) + .resolves(chiselJson); + sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); + sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + const copyContext = _.cloneDeep(bindContext); + copyContext.configData.config.data.envFile = undefined; + copyContext.configData.dependentTasks = [tsk]; + copyContext.depContext.data = { isCreateChiselTask: true }; + + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs( + `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselLabel}'` + ) + .resolves(); + expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + configData: copyContext.configData, + resource: { name: "testInstance", type: "resourceType", data: { chiselTask: chiselJson } }, + }); + assert.deepEqual(copyContext.configData.dependentTasks, [tsk, chiselJson]); + }); + + it("ok:: 'unbind' succeddeed", async () => { + const configurationData = { config: { data: {}, type: ConfigurationTarget.launch }, dependentTasks: [{}] }; + configurationData.dependentTasks = []; + const property = { resourceName: "propName", envPath: "env Path", resourceData: { label: "resource-type" } }; + sandbox.stub(utils, "removeResourceFromEnv").resolves(property); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_unbound_successful(property.resourceName)) + .resolves(); + assert.deepEqual(await new DependencyHandler("test.Handler.id").unbind(bindContext), { + configData: configurationData, + resource: { name: "propName", type: "resource-type", data: { label: "resource-type" } }, + }); + }); + + it("ok:: 'unbind' - empty property", async () => { + const configurationData = { config: { data: {}, type: ConfigurationTarget.launch }, dependentTasks: [{}] }; + configurationData.dependentTasks = []; + const property = { resourceName: "", envPath: "", resourceData: {} }; + sandbox.stub(utils, "removeResourceFromEnv").resolves(property); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_unbound_successful(_.get(property, "resourceName"))) + .resolves(); + assert.deepEqual(await new DependencyHandler("test.Handler.id").unbind(bindContext), { + configData: configurationData, + resource: { name: "", type: "", data: {} }, + }); + }); + + it("ok:: 'unbind' - exception", async () => { + const error = new Error("my error"); + sandbox.stub(utils, "removeResourceFromEnv").rejects(error); + vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + expect(await new DependencyHandler("test.Handler.id").unbind(bindContext)).to.be.equal(undefined); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts new file mode 100644 index 000000000..04c4aa6ea --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts @@ -0,0 +1,679 @@ +import { expect, assert } from "chai"; +import * as sinon from "sinon"; +import * as path from "path"; +import * as fs from "fs"; +import * as fsSync from "fs"; +import _ = require("lodash"); +import { stringify } from "comment-json"; +import { fail } from "assert"; +import * as nsVsMock from "./ext/mockVscode"; +import { mockVscode } from "./ext/mockUtil"; + +mockVscode(nsVsMock.testVscode, "src/utils.ts"); +import * as utils from "../src/utils"; +import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; +import { ServiceInstanceInfo, eFilters, ITarget } from "@sap/cf-tools"; +import * as PropertiesReader from "properties-reader"; +import * as messages from "../src/messages"; + +describe("utils unit tests", () => { + let sandbox: sinon.SinonSandbox; + let vscodeWorkspaceMock: sinon.SinonMock; + let fsMock: sinon.SinonMock; + + before(() => { + sandbox = sinon.createSandbox(); + }); + + beforeEach(() => { + vscodeWorkspaceMock = sandbox.mock(nsVsMock.testVscode.workspace); + fsMock = sandbox.mock(fs.promises); + }); + + afterEach(() => { + vscodeWorkspaceMock.verify(); + fsMock.verify(); + sandbox.restore(); + }); + + describe("toText", () => { + it("ok:: 'name' attribute defined", () => { + const name = "test name"; + const e = new Error(); + e.name = name; + expect(utils.toText(e)).to.be.equal(name); + }); + + it("ok:: 'message' attribute defined", () => { + const message = "test description"; + const e = new Error(message); + expect(utils.toText(e)).to.be.equal(message); + }); + + it("ok:: neither 'message' and 'name' attribute is defined", () => { + const e = new Error(); + e.stack = "stack trace"; + expect(utils.toText(e)).to.be.equal(_.toString(e)); + }); + }); + + describe("getEnvResources", () => { + it("ok:: sanity with valid VCAP_SERVICES value", async () => { + sandbox.stub(fsSync, "existsSync").returns(true); + const envFilePath: string = path.join(__dirname, "resources", ".testValidEnv"); + const envResources = await utils.getEnvResources(envFilePath); + const vcapServicesObj = envResources.vcapObject; + expect(vcapServicesObj).to.not.be.empty; + expect(vcapServicesObj.hana).to.exist; + }); + + it("ok:: sanity with valid VCAP_SERVICES value surrounded with single quotes", async () => { + sandbox.stub(fsSync, "existsSync").returns(true); + const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotes"); + const envResources = await utils.getEnvResources(envFilePath); + const vcapServicesObj = envResources.vcapObject; + expect(vcapServicesObj).to.not.be.empty; + expect(vcapServicesObj.hana).to.exist; + }); + + it("ok:: sanity with valid VCAP_SERVICES value surrounded with single quotes and spaces", async () => { + sandbox.stub(fsSync, "existsSync").returns(true); + const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotesAndSpaces"); + const envResources = await utils.getEnvResources(envFilePath); + const vcapServicesObj = envResources.vcapObject; + expect(vcapServicesObj).to.not.be.empty; + expect(vcapServicesObj.hana).to.exist; + }); + + it("fail:: invalid VCAP_SERVICES value in env file", async () => { + sandbox.stub(fsSync, "existsSync").returns(true); + const envFilePath: string = path.join(__dirname, "resources", ".testInValidVCAP"); + let vcapServicesObj; + try { + const envResources = await utils.getEnvResources(envFilePath); + vcapServicesObj = envResources.vcapObject; + fail("test should fail here"); + } catch (e) { + expect(vcapServicesObj).to.be.undefined; + } + }); + + it("ok:: '.env' file does not exist", async () => { + sandbox.stub(fsSync, "existsSync").returns(false); + const envFilePath: string = path.join(__dirname, "resources", ".notExists"); + expect((await utils.getEnvResources(envFilePath)).vcapObject).to.be.null; + }); + + it("ok:: '.env' file without VCAP_SERVICES key", async () => { + sandbox.stub(fsSync, "existsSync").returns(true); + const envFilePath: string = path.join(__dirname, "resources", ".envNoVCAP"); + expect((await utils.getEnvResources(envFilePath)).vcapObject).to.be.null; + }); + }); + + describe("Tests for removeResourceFromEnv when VCAP_SERVICES surrounded with quotes", () => { + const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotes"); + const propReader = PropertiesReader(envFilePath); + let vcapServicesObjBeforeChange: any; + before(() => { + sandbox = sinon.createSandbox(); + vcapServicesObjBeforeChange = propReader.get(utils.ENV_VCAP_RESOURCES); + }); + + afterEach(async () => { + sandbox.restore(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + propReader.set(utils.ENV_VCAP_RESOURCES, vcapServicesObjBeforeChange); + await propReader.save(envFilePath); + }); + + it("ok:: remove resource when VCAP is surrounded with single quotes and leave single quotes after removal", async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "hana", + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + const vcapServicesObjAfterChange: string | null = PropertiesReader(envFilePath).getRaw(utils.ENV_VCAP_RESOURCES); + + if (vcapServicesObjAfterChange) { + expect(vcapServicesObjAfterChange).to.not.include("hana"); + expect(_.startsWith(vcapServicesObjAfterChange, "'")).to.be.true; + expect(_.endsWith(vcapServicesObjAfterChange, "'")).to.be.true; + } else { + expect(fail("VCAP_SERVICES must exists and be empty after removing the last dependency")); + } + }); + }); + + describe("Tests for removeResourceFromEnv", () => { + const envFilePath: string = path.join(__dirname, "resources", ".envtest"); + const propReader = PropertiesReader(envFilePath); + let vcapServicesObjBeforeChange: any; + before(() => { + sandbox = sinon.createSandbox(); + vcapServicesObjBeforeChange = propReader.get(utils.ENV_VCAP_RESOURCES); + }); + + afterEach(async () => { + sandbox.restore(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + propReader.set(utils.ENV_VCAP_RESOURCES, vcapServicesObjBeforeChange); + await propReader.save(envFilePath); + }); + + // does not work on linux ?! - clarification needed + it("Test removeResourceFromEnv - exception thrown", async () => { + const bindContext: any = { + envPath: { + fsPath: envFilePath + "_readonly", + }, + depContext: { + type: "hana", + data: { + resourceTag: "tag-not-found:", + }, + displayName: "bookshop-hdi-container", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + fsSync.chmodSync(bindContext.envPath.fsPath, 0o444); + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + fail("test should fail here"); + } catch (e) { + // ... + } + }); + + it("ok:: remove resource when it is not tagged & verify it is left unquoted as it was.", async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "xsuaa", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + const envPropertiesAfterChange = PropertiesReader(envFilePath); + const vcapProperty: string = envPropertiesAfterChange.getRaw(utils.ENV_VCAP_RESOURCES) ?? ""; + expect(vcapProperty).to.not.include("xsuaa"); + expect(vcapProperty).to.include('"instance_name":"hdi-test-instance"'); + expect(vcapProperty).to.not.include("xsuaa"); + + expect(_.startsWith(vcapProperty, "'")).to.be.false; + expect(_.endsWith(vcapProperty, "'")).to.be.false; + }); + + it("ok:: remove resource when it is not tagged by wrong type", async () => { + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "_xsuaa", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + assert.deepEqual(PropertiesReader(envFilePath).get(utils.ENV_VCAP_RESOURCES), vcapServicesObjBeforeChange); + }); + + it("ok:: remove resource when it has a tag", async () => { + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "hana", + data: { + resourceTag: "mta-resource-name:", + resourceName: "bookshop-hdi-container", + }, + displayName: "bookshop-hdi-container", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + const envPropertiesAfterChange = PropertiesReader(envFilePath); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include( + '"instance_name":"hdi-test-instance"' + ); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("xsuaa"); + }); + + it("ok:: remove resource when it has a tag, resource type has more then 1 instance (e.g. two dependencies bound to different instances)", async () => { + let envPropertiesAfterChange; + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "hanatest", + data: { + resourceTag: "mta-resource-name:", + resourceName: "hana1-mta", + }, + displayName: "hana1-mta", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + envPropertiesAfterChange = PropertiesReader(envFilePath); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("hanatest"); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include('"instance_name":"hana-2"'); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-1"'); + + // Another remove should remove this key completely and not leave an empty array + const bindContext2: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "hanatest", + data: { + resourceTag: "mta-resource-name:", + resourceName: "hana2-mta", + }, + displayName: "hana2-mta", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext2); + envPropertiesAfterChange = PropertiesReader(envFilePath); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include("hanatest"); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-2"'); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-1"'); + }); + + it("ok:: remove resource when it has a tag but it has not been found", async () => { + const bindContext: any = { + envPath: { + fsPath: envFilePath, + }, + depContext: { + type: "hana", + data: { + resourceTag: "tag-not-found:", + }, + displayName: "bookshop-hdi-container", + }, + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + await utils.removeResourceFromEnv(bindContext); + const envPropertiesAfterChange = PropertiesReader(envFilePath); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include('"instance_name":"hdi-test-instance"'); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("xsuaa"); + }); + }); + + describe("API for creation service", () => { + const plan = "application"; + it("generateParams4Service - xsuaa, plan - application", () => { + const data = utils.generateParams4Service("xsuaa", plan); + expect(_.startsWith(data.xsappname as string, "xsuaa_")).to.be.true; + expect(data["tenant-mode"]).to.be.equal("dedicated"); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + expect(_.size(data["oauth2-configuration"]["redirect-uris"])).to.be.equal(1); + }); + + it("generateParams4Service - xsuaa, plan - other", () => { + assert.deepEqual(utils.generateParams4Service("xsauu", "other"), {}); + }); + + it("validateParams - other : valid", () => { + expect(utils.validateParams("other")('{ "key" : "value" }')).to.be.undefined; + }); + + it("validateParams - other : incorrect", () => { + expect(_.size(utils.validateParams("other")('{ "key" :: "value" }'))).to.be.gt(1); + }); + + it("validateParams - xsuaa : valid", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be.undefined; + }); + + it("validateParams - xsuaa : invalid xsappname", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + xsuaaData.xsappname = "xs.ass_345"; + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : padded xsappname", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + xsuaaData.xsappname = " "; + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : missing xsappname", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + delete xsuaaData.xsappname; + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : long xsappname", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + xsuaaData.xsappname = _.repeat("x", 103); + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : invalid tenant-mode", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + xsuaaData["tenant-mode"] = "lite"; + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : invalid redirect-uris", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + xsuaaData["oauth2-configuration"]["redirect-uris"] = ["localhost://*", "localhos://*"]; + expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + }); + + it("validateParams - xsuaa : missing redirect-uris", () => { + const xsuaaData = utils.generateParams4Service("xsuaa", plan); + delete xsuaaData["oauth2-configuration"]["redirect-uris"]; + expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be.undefined; + }); + }); + + describe("suite for getAllServiceInstances", () => { + const services: ServiceInstanceInfo[] = [ + { label: "s1", serviceName: "hana", plan: "s-plan-1", plan_guid: "s-plan-guid-1" }, + { label: "s1", serviceName: "hana", plan: "s-plan-2", plan_guid: "s-plan-guid-2" }, + ]; + const ups = [ + { label: "ups1", serviceName: "user-provided-service", credentials: { tags: ["monodb", "hana"] } }, + { label: "ups2", serviceName: "user-provided-service", credentials: { tags: ["david"] } }, + ]; + + it("ok:: there are both kind of services", async () => { + sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getAllServiceInstances({ ups: { isShow: true } }), _.concat(services, ups)); + }); + + it("ok:: contain 'managed' and 'ups' filtered services", async () => { + sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getAllServiceInstances({ ups: { tag: "/.*dav.*/" } }), _.concat(services, ups[1])); + }); + + it("ok:: ups services are not required", async () => { + sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getAllServiceInstances(), services); + }); + + it("ok:: there are ups filtered services", async () => { + const opts = { + query: { + filters: [ + { key: eFilters.broker_catalog_ids, value: "test" }, + { key: eFilters.space_guids, value: "testSpace" }, + ], + }, + ups: { tag: "monodb" }, + }; + const expectedQuery = _.cloneDeep(opts.query); + expectedQuery.filters = [{ key: eFilters.space_guids, value: "testSpace" }]; + sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); + sandbox.stub(cfLocal, "cfGetUpsInstances").withArgs(expectedQuery).resolves(ups); + assert.deepEqual(await utils.getAllServiceInstances(opts), _.concat(services, ups[0])); + }); + }); + + describe("getUpsServiceInstances", () => { + const ups = [ + { label: "ups1", serviceName: "user-provided-service", credentials: { tags: ["monodb", "hana"] } }, + { label: "ups2", serviceName: "user-provided-service", credentials: { tags: ["david"] } }, + { label: "ups3", serviceName: "user-provided-service", credentials: { tags: "hana" } }, + { label: "wrong-credential-tags", serviceName: "user-provided-service", credentials: { tags: 2 } }, + { + label: "wrong-credential-tags2", + serviceName: "user-provided-service", + credentials: { tags: [2, { data: "hana" }] }, + }, + { label: "wrong-credential", serviceName: "user-provided-service" }, + ]; + + it("ok:: get all user provides services, tags not specified", async () => { + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getUpsServiceInstances(), ups); + }); + + it("ok:: get all user provides services filtered by credential tag", async () => { + const options = { credentials: { tag: "hana" } }; + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getUpsServiceInstances(options), [ups[0], ups[2]]); + }); + + it("ok:: get all user provides services filtered by credential tag as regex expression", async () => { + const options = { credentials: { tag: "/.*mono|vid.*/" } }; + sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); + assert.deepEqual(await utils.getUpsServiceInstances(options), [ups[0], ups[1]]); + }); + }); + + const UTF8 = "utf8"; + const EOL = require("os").EOL; + const GITIGNORE = ".gitignore"; + + describe("updateGitIgnoreList suite", () => { + const project = { uri: nsVsMock.testVscode.Uri.file(path.resolve("myProject")) }; + const ignorePattern = new nsVsMock.testVscode.RelativePattern(project, GITIGNORE); + const gitIgnoreFile = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/${GITIGNORE}`)); + + it("ok:: '.env' pattern added", async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const gitIgnoreContent = _.join([" # empty line"], EOL); + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); + vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([gitIgnoreFile]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const patterns = _.split(gitIgnoreContent, EOL); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + fsMock + .expects("writeFile") + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .resolves(undefined); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + it("ok:: '.env' pattern not added - already exists", async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const gitIgnoreContent = _.join([" # empty line", "subFolder/.env"], EOL); + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); + vscodeWorkspaceMock.expects("findFiles").exactly(2).returns([testEnv]); + fsMock.expects("readFile").resolves(gitIgnoreContent); + fsMock.expects("writeFile").never(); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + it("ok:: empty project", async () => { + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(undefined); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + it("ok:: '.env' pattern added, .gitIgnore file not exist", async () => { + const gitIgnoreContent = ""; + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); + vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([]); + fsMock.expects("open").withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w").resolves(undefined); + fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const patterns = _.split(gitIgnoreContent, EOL); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + fsMock + .expects("writeFile") + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .resolves(undefined); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + it("fail:: '.env' pattern NOT added, .gitIgnore file not exist, creation failed", async () => { + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); + vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([]); + fsMock.expects("open").withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w").throws(new Error("access denied")); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + it("fail:: '.env' pattern NOT added, writeFile failed", async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const gitIgnoreContent = _.join([" # empty line"], EOL); + const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); + vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([gitIgnoreFile]); + fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const patterns = _.split(gitIgnoreContent, EOL); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + fsMock + .expects("writeFile") + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .throws(new Error("access denied")); + await utils.updateGitIgnoreList(testEnv.fsPath); + }); + + describe("notifyWhenServicesInfoResultIncomplete method", () => { + let vscodeWindowMock: sinon.SinonMock; + + beforeEach(() => { + vscodeWindowMock = sandbox.mock(nsVsMock.testVscode.window); + }); + + afterEach(() => { + vscodeWindowMock.verify(); + }); + + it("ok:: serviceInstanceInfo list is fullfilled", async () => { + const infos: ServiceInstanceInfo[] = [ + { + label: "instance-1", + serviceName: "type-1", + plan: "plan-1", + }, + { + label: "instance-2", + serviceName: "type-2", + plan: "plan-2", + }, + ]; + expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + }); + + it("ok:: serviceInstanceInfo list is empty", async () => { + const list = Promise.resolve([]); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + expect(await utils.notifyWhenServicesInfoResultIncomplete(list)).to.be.deep.equal([]); + }); + + it("ok:: serviceInstanceInfo list is incomplete", async () => { + const infos: ServiceInstanceInfo[] = [ + { + label: "instance-1", + serviceName: "unknown", + plan: "plan-1", + }, + ]; + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.messages.service_instances_list_incomplete(_.map(infos, "label"))) + .resolves(); + expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + }); + + it("ok:: serviceInstanceInfo list is incomplete more than 3 instances", async () => { + const infos: ServiceInstanceInfo[] = [ + { + label: "instance-1", + serviceName: "unknown", + plan: "plan-1", + }, + { + label: "instance-2", + serviceName: "type-2", + plan: "unknown", + }, + { + label: "instance-3", + serviceName: "type-3", + plan: "unknown", + }, + { + label: "instance-4", + serviceName: "type-4", + plan: "unknown", + }, + ]; + vscodeWindowMock + .expects("showWarningMessage") + .withExactArgs(messages.messages.service_instances_list_incomplete(_.map(infos, "label"))) + .resolves(); + expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + }); + }); + + describe("verifyCFTarget", () => { + const target: ITarget = { + "api endpoint": "endPoint", + org: "test-org", + space: "test-space", + user: "user", + "api version": "3.100.0", + }; + + it("ok:: validate", async () => { + sandbox.stub(cfLocal, "cfGetTarget").resolves(target); + expect(await utils.examCFTarget("", ["user"], false)).to.be.deep.equal(target); + }); + + it("error:: invalid key requested", async () => { + sandbox.stub(cfLocal, "cfGetTarget").resolves(target); + const error = "invalid key"; + try { + await utils.examCFTarget(error, ["user", "wrong"], false); + fail("should fail"); + } catch (e) { + expect(e.message).to.be.equal(error); + } + }); + + it("ok:: weak mode", async () => { + const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0" }; + sandbox.stub(cfLocal, "cfGetTarget").resolves(t); + expect(await utils.examCFTarget("", ["api endpoint"], true)).to.be.deep.equal(t); + }); + + it("exception:: throws error", async () => { + const text = "error text"; + sandbox.stub(cfLocal, "cfGetTarget").throws(new Error(text)); + try { + await utils.examCFTarget("", [], false); + fail("should fail"); + } catch (e) { + expect(e.message).to.be.equal(text); + } + }); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/backend/tsconfig.json b/projects/cloud-foundry-tools/packages/backend/tsconfig.json new file mode 100644 index 000000000..ded225140 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "rootDir": ".", + "rootDirs": ["./src", "./tests"], + "useUnknownInCatchVariables": false + } +} diff --git a/projects/cloud-foundry-tools/packages/backend/webpack.config.js b/projects/cloud-foundry-tools/packages/backend/webpack.config.js new file mode 100644 index 000000000..7bfaa1534 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/backend/webpack.config.js @@ -0,0 +1,31 @@ +const path = require("path"); +const baseConfig = require("../../webpack.config.vscode.base"); + +const config = Object.assign({}, baseConfig, { + entry: "./out/src/extension.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "extension.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, + module: { + // https://webpack.js.org/configuration/module/#modulenoparse + // used to avoid transforming native require usage in `optional-require` implementation + noParse: /native-require\.(js|ts)$/, + }, + // 📖 -> https://webpack.js.org/configuration/externals/ + externals: { + // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed. + vscode: "commonjs vscode", + // To enable bundling for @sap/artifact-management + // fsevents is a macos file system event library that is compiled during installation. + fsevents: "commonjs fsevents", + }, + node: { + // needed to bundle artifact-management successfully + __dirname: false, + }, +}); + +module.exports = config; diff --git a/projects/cloud-foundry-tools/packages/frontend/.vscode/launch.json b/projects/cloud-foundry-tools/packages/frontend/.vscode/launch.json new file mode 100644 index 000000000..7155e5192 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "vuejs: chrome", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}/src", + "breakOnLoad": true, + "sourceMapPathOverrides": { + "webpack:///src/*": "${webRoot}/*" + }, + "preLaunchTask": "serve" + } + ] +} diff --git a/projects/cloud-foundry-tools/packages/frontend/.vscode/tasks.json b/projects/cloud-foundry-tools/packages/frontend/.vscode/tasks.json new file mode 100644 index 000000000..f44649a7f --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "serve", + "type": "npm", + "script": "serve", + "isBackground": true, + "problemMatcher": { + "pattern": { + "regexp": "^(.*)$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^(.*)$", + "endsPattern": "^(.*)DONE(.*)$" + } + } + } + ] +} diff --git a/projects/cloud-foundry-tools/packages/frontend/CHANGELOG.md b/projects/cloud-foundry-tools/packages/frontend/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/projects/cloud-foundry-tools/packages/frontend/CHANGELOG.old.md b/projects/cloud-foundry-tools/packages/frontend/CHANGELOG.old.md new file mode 100644 index 000000000..d674f491b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/CHANGELOG.old.md @@ -0,0 +1,160 @@ +# Historic Changelog + +> This file contains the changelog history from the standalone [SAP/cloud-foundry-tools](https://github.com/SAP/cloud-foundry-tools) repository, prior to migration into this monorepo. New changes are recorded in this package's `CHANGELOG.md`. + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [8.1.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.2) (2026-06-21) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [8.1.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.1) (2026-06-21) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +# [8.1.0](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.2...v8.1.0) (2026-02-11) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [8.0.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.1...v8.0.2) (2025-12-09) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [8.0.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.0...v8.0.1) (2025-08-24) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +# [8.0.0](https://github.com/SAP/cloud-foundry-tools/compare/v7.5.2...v8.0.0) (2025-08-20) + +### Features + +- package name and publisher are changed ([#304](https://github.com/SAP/cloud-foundry-tools/issues/304)) ([33e586d](https://github.com/SAP/cloud-foundry-tools/commit/33e586d67270602128d3c4c17179ac632a7c82a5)) + +### BREAKING CHANGES + +- VS Code Marketplace Publishing Support + +## [7.5.2](https://github.com/SAP/cloud-foundry-tools/compare/v7.5.1...v7.5.2) (2025-08-06) + +### Bug Fixes + +- organization dropdown wrapping ([5e69cf8](https://github.com/SAP/cloud-foundry-tools/commit/5e69cf8408cfa2f7eb45e5fc3ebaee38842568ef)) +- sign in button disabled state ([208eb68](https://github.com/SAP/cloud-foundry-tools/commit/208eb680372ce54fd176eabcd88c49a8f97bafde)) +- space dropdown wrapping ([b7e9f4f](https://github.com/SAP/cloud-foundry-tools/commit/b7e9f4fb6bf71ca50497b3e1f0d1daf702756674)) + +## [7.5.1](https://github.com/SAP/cloud-foundry-tools/compare/v7.5.0...v7.5.1) (2025-06-24) + +### Bug Fixes + +- `paste` is not happening in cloud foundry `sign in` page ([#301](https://github.com/SAP/cloud-foundry-tools/issues/301)) ([0385390](https://github.com/SAP/cloud-foundry-tools/commit/0385390328284840464673a665778c2e7a8511df)) +- prevent webview default `contextmenu` ([#300](https://github.com/SAP/cloud-foundry-tools/issues/300)) ([e79a1cb](https://github.com/SAP/cloud-foundry-tools/commit/e79a1cbdb2702e060ada5909aff7d8e15675c3a1)) + +# [7.5.0](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.5...v7.5.0) (2025-02-10) + +### Bug Fixes + +- adding back the missing clip icon in the passphrase input field ([63aa214](https://github.com/SAP/cloud-foundry-tools/commit/63aa21498c7778224f38d9b1a0c4954557b42c77)) +- address live demo comments ([0ae1abb](https://github.com/SAP/cloud-foundry-tools/commit/0ae1abbb647a4c191afbdebbe3caecc78b49ed12)) +- icon inside vscode-button added id for codicon link ([aa530e6](https://github.com/SAP/cloud-foundry-tools/commit/aa530e6601234449bf488c92dd46ac65725c3c96)) +- reverting selection of first org in list ([a839de7](https://github.com/SAP/cloud-foundry-tools/commit/a839de7c2d9c426b684434b0490cc5f8615a55be)) +- updating user facing messages ([8421d55](https://github.com/SAP/cloud-foundry-tools/commit/8421d55d7cfc32fd16d49992546673ab287d0458)) + +### Features + +- devxcore-3010 cf missing next steps after signing in to cf adding org and space ([ae116e4](https://github.com/SAP/cloud-foundry-tools/commit/ae116e4e81bd4310c60e070d77c6052783f4f29f)) +- migrate to vscode elements ([22547a5](https://github.com/SAP/cloud-foundry-tools/commit/22547a5a9e0c3425212c8220f17ee7ba0469bbce)) + +## [7.4.5](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.4...v7.4.5) (2024-10-14) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.4.4](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.3...v7.4.4) (2024-05-30) + +### Bug Fixes + +- failed to login to CF with SSO passcode starting with a hyphen ([#293](https://github.com/SAP/cloud-foundry-tools/issues/293)) ([9bb5e3a](https://github.com/SAP/cloud-foundry-tools/commit/9bb5e3adfef757238cf01c0d83190af957349b0e)) + +## [7.4.3](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.2...v7.4.3) (2024-05-05) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.4.2](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.1...v7.4.2) (2023-11-26) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.4.1](https://github.com/SAP/cloud-foundry-tools/compare/v7.4.0...v7.4.1) (2023-11-12) + +### Bug Fixes + +- sorted orgs and spaces + tests ([#277](https://github.com/SAP/cloud-foundry-tools/issues/277)) ([459801a](https://github.com/SAP/cloud-foundry-tools/commit/459801a3bd2a8bb796fef4a3e0fcb6a74d9ab9f6)) + +# [7.4.0](https://github.com/SAP/cloud-foundry-tools/compare/v7.3.4...v7.4.0) (2023-10-03) + +### Features + +- migrate to vite.js and upgrade deps ([#271](https://github.com/SAP/cloud-foundry-tools/issues/271)) ([6e4381c](https://github.com/SAP/cloud-foundry-tools/commit/6e4381cd889da81424da5ce0e11bd5b78b15121c)) +- migrating to Vue3 ([#258](https://github.com/SAP/cloud-foundry-tools/issues/258)) ([1bbe976](https://github.com/SAP/cloud-foundry-tools/commit/1bbe9764f3af8989d15cd5ba48cb706fc97c83d3)) + +## [7.3.4](https://github.com/SAP/cloud-foundry-tools/compare/v7.3.3...v7.3.4) (2023-08-09) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.3.3](https://github.com/SAP/cloud-foundry-tools/compare/v7.3.2...v7.3.3) (2023-07-09) + +### Bug Fixes + +- validate CF endpoint URL for security ([#262](https://github.com/SAP/cloud-foundry-tools/issues/262)) ([c675000](https://github.com/SAP/cloud-foundry-tools/commit/c675000514ced213ff21f6c4534161435b2ce794)) + +## [7.3.2](https://github.com/SAP/cloud-foundry-tools/compare/v7.3.1...v7.3.2) (2023-07-06) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.3.1](https://github.com/SAP/cloud-foundry-tools/compare/v7.3.0...v7.3.1) (2023-07-04) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +# [7.3.0](https://github.com/SAP/cloud-foundry-tools/compare/v7.2.2...v7.3.0) (2023-07-04) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +## [7.2.2](https://github.com/SAP/cloud-foundry-tools/compare/v7.2.1...v7.2.2) (2023-06-29) + +### Bug Fixes + +- sign out button in fiori themes ([#259](https://github.com/SAP/cloud-foundry-tools/issues/259)) ([d4bfa2e](https://github.com/SAP/cloud-foundry-tools/commit/d4bfa2e4d8f0dd6e130ca7a8f1865fffd7ece3d5)) + +## [7.2.1](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.2.0...v7.2.1) (2023-02-15) + +**Note:** Version bump only for package cloud-foundry-tools-frontend + +# [7.1.0](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.0.4...v7.1.0) (2022-11-16) + +### Bug Fixes + +- [DEVXCORE-2589] fix type of endpoint control ([#245](https://github.com/sap-staging/cloud-foundry-tools/issues/245)) ([48ef31d](https://github.com/sap-staging/cloud-foundry-tools/commit/48ef31dc61478bd56190edfc14c3587111732a74)) +- make dropdown control searchable ([#248](https://github.com/sap-staging/cloud-foundry-tools/issues/248)) ([126d27b](https://github.com/sap-staging/cloud-foundry-tools/commit/126d27b7c24da77fc91bf3fac7b59e771a9e5c74)) + +## [7.0.4](https://github.com/sap-staging/cloud-foundry-tools/compare/v7.0.1...v7.0.4) (2022-11-03) + +### Bug Fixes + +- [DEVXBUGS-10581] CF sign doesn't detect the last used API endpoint ([#243](https://github.com/sap-staging/cloud-foundry-tools/issues/243)) ([a2b7c93](https://github.com/sap-staging/cloud-foundry-tools/commit/a2b7c93b24db17bd292d6b30dfc3310f67680d47)) +- adding tooltips to target ([e4f53e4](https://github.com/sap-staging/cloud-foundry-tools/commit/e4f53e4009971ef61f1b5305fcba4b784fb3d5dd)) +- bugs ([dc5d257](https://github.com/sap-staging/cloud-foundry-tools/commit/dc5d2574c86d1f9b1bca01ea44983c1e97151988)) +- remove the important from the style ([a426e5c](https://github.com/sap-staging/cloud-foundry-tools/commit/a426e5cb88df128c623c08dc4cedfcc98d9376a6)) +- review fixes ([cb0361d](https://github.com/sap-staging/cloud-foundry-tools/commit/cb0361d25817a06fbe8379e583f9cbda6eb2fa05)) + +## [7.0.1](https://github.com/SAP/cloud-foundry-tools/compare/v7.0.0...v7.0.1) (2022-06-29) + +### Bug Fixes + +- leftovers ([#225](https://github.com/SAP/cloud-foundry-tools/issues/225)) ([c6cedcb](https://github.com/SAP/cloud-foundry-tools/commit/c6cedcb0a67f40f3040d258b442128cb19fc70cf)) + +# [7.0.0](https://github.com/SAP/cloud-foundry-tools/compare/v6.5.0...v7.0.0) (2022-06-28) + +### Features + +- support 2fa in the cf tools ui ([#224](https://github.com/SAP/cloud-foundry-tools/issues/224)) ([5ed4d20](https://github.com/SAP/cloud-foundry-tools/commit/5ed4d20a88b86df180cf98db3dbf615e5e20ddda)) diff --git a/projects/cloud-foundry-tools/packages/frontend/babel.config.js b/projects/cloud-foundry-tools/packages/frontend/babel.config.js new file mode 100644 index 000000000..fa911e233 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: ["@babel/preset-env", "@babel/preset-typescript"], + plugins: ["@babel/plugin-transform-modules-commonjs"], +}; diff --git a/projects/cloud-foundry-tools/packages/frontend/codicon.css b/projects/cloud-foundry-tools/packages/frontend/codicon.css new file mode 100644 index 000000000..aac4b12d1 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/codicon.css @@ -0,0 +1,553 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@font-face { + font-family: "codicon"; + font-display: block; + src: url("./codicon.ttf?f6ab4abcfdcac2bb563bb55b9602bc7f") format("truetype"); +} + +.codicon[class*='codicon-'] { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; +} + +/*--------------------- + * Modifiers + *-------------------*/ + +@keyframes codicon-spin { + 100% { + transform:rotate(360deg); + } +} + +.codicon-sync.codicon-modifier-spin, +.codicon-loading.codicon-modifier-spin, +.codicon-gear.codicon-modifier-spin { + /* Use steps to throttle FPS to reduce CPU usage */ + animation: codicon-spin 1.5s steps(30) infinite; +} + +.codicon-modifier-disabled { + opacity: 0.5; +} + +/* custom speed & easing for loading icon */ +.codicon-loading { + animation-duration: 1s !important; + animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67) !important; +} + +/*--------------------- + * Icons + *-------------------*/ + +.codicon-add:before { content: "\ea60" } +.codicon-plus:before { content: "\ea60" } +.codicon-gist-new:before { content: "\ea60" } +.codicon-repo-create:before { content: "\ea60" } +.codicon-lightbulb:before { content: "\ea61" } +.codicon-light-bulb:before { content: "\ea61" } +.codicon-repo:before { content: "\ea62" } +.codicon-repo-delete:before { content: "\ea62" } +.codicon-gist-fork:before { content: "\ea63" } +.codicon-repo-forked:before { content: "\ea63" } +.codicon-git-pull-request:before { content: "\ea64" } +.codicon-git-pull-request-abandoned:before { content: "\ea64" } +.codicon-record-keys:before { content: "\ea65" } +.codicon-keyboard:before { content: "\ea65" } +.codicon-tag:before { content: "\ea66" } +.codicon-tag-add:before { content: "\ea66" } +.codicon-tag-remove:before { content: "\ea66" } +.codicon-person:before { content: "\ea67" } +.codicon-person-follow:before { content: "\ea67" } +.codicon-person-outline:before { content: "\ea67" } +.codicon-person-filled:before { content: "\ea67" } +.codicon-git-branch:before { content: "\ea68" } +.codicon-git-branch-create:before { content: "\ea68" } +.codicon-git-branch-delete:before { content: "\ea68" } +.codicon-source-control:before { content: "\ea68" } +.codicon-mirror:before { content: "\ea69" } +.codicon-mirror-public:before { content: "\ea69" } +.codicon-star:before { content: "\ea6a" } +.codicon-star-add:before { content: "\ea6a" } +.codicon-star-delete:before { content: "\ea6a" } +.codicon-star-empty:before { content: "\ea6a" } +.codicon-comment:before { content: "\ea6b" } +.codicon-comment-add:before { content: "\ea6b" } +.codicon-alert:before { content: "\ea6c" } +.codicon-warning:before { content: "\ea6c" } +.codicon-search:before { content: "\ea6d" } +.codicon-search-save:before { content: "\ea6d" } +.codicon-log-out:before { content: "\ea6e" } +.codicon-sign-out:before { content: "\ea6e" } +.codicon-log-in:before { content: "\ea6f" } +.codicon-sign-in:before { content: "\ea6f" } +.codicon-eye:before { content: "\ea70" } +.codicon-eye-unwatch:before { content: "\ea70" } +.codicon-eye-watch:before { content: "\ea70" } +.codicon-circle-filled:before { content: "\ea71" } +.codicon-primitive-dot:before { content: "\ea71" } +.codicon-close-dirty:before { content: "\ea71" } +.codicon-debug-breakpoint:before { content: "\ea71" } +.codicon-debug-breakpoint-disabled:before { content: "\ea71" } +.codicon-debug-hint:before { content: "\ea71" } +.codicon-primitive-square:before { content: "\ea72" } +.codicon-edit:before { content: "\ea73" } +.codicon-pencil:before { content: "\ea73" } +.codicon-info:before { content: "\ea74" } +.codicon-issue-opened:before { content: "\ea74" } +.codicon-gist-private:before { content: "\ea75" } +.codicon-git-fork-private:before { content: "\ea75" } +.codicon-lock:before { content: "\ea75" } +.codicon-mirror-private:before { content: "\ea75" } +.codicon-close:before { content: "\ea76" } +.codicon-remove-close:before { content: "\ea76" } +.codicon-x:before { content: "\ea76" } +.codicon-repo-sync:before { content: "\ea77" } +.codicon-sync:before { content: "\ea77" } +.codicon-clone:before { content: "\ea78" } +.codicon-desktop-download:before { content: "\ea78" } +.codicon-beaker:before { content: "\ea79" } +.codicon-microscope:before { content: "\ea79" } +.codicon-vm:before { content: "\ea7a" } +.codicon-device-desktop:before { content: "\ea7a" } +.codicon-file:before { content: "\ea7b" } +.codicon-file-text:before { content: "\ea7b" } +.codicon-more:before { content: "\ea7c" } +.codicon-ellipsis:before { content: "\ea7c" } +.codicon-kebab-horizontal:before { content: "\ea7c" } +.codicon-mail-reply:before { content: "\ea7d" } +.codicon-reply:before { content: "\ea7d" } +.codicon-organization:before { content: "\ea7e" } +.codicon-organization-filled:before { content: "\ea7e" } +.codicon-organization-outline:before { content: "\ea7e" } +.codicon-new-file:before { content: "\ea7f" } +.codicon-file-add:before { content: "\ea7f" } +.codicon-new-folder:before { content: "\ea80" } +.codicon-file-directory-create:before { content: "\ea80" } +.codicon-trash:before { content: "\ea81" } +.codicon-trashcan:before { content: "\ea81" } +.codicon-history:before { content: "\ea82" } +.codicon-clock:before { content: "\ea82" } +.codicon-folder:before { content: "\ea83" } +.codicon-file-directory:before { content: "\ea83" } +.codicon-symbol-folder:before { content: "\ea83" } +.codicon-logo-github:before { content: "\ea84" } +.codicon-mark-github:before { content: "\ea84" } +.codicon-github:before { content: "\ea84" } +.codicon-terminal:before { content: "\ea85" } +.codicon-console:before { content: "\ea85" } +.codicon-repl:before { content: "\ea85" } +.codicon-zap:before { content: "\ea86" } +.codicon-symbol-event:before { content: "\ea86" } +.codicon-error:before { content: "\ea87" } +.codicon-stop:before { content: "\ea87" } +.codicon-variable:before { content: "\ea88" } +.codicon-symbol-variable:before { content: "\ea88" } +.codicon-array:before { content: "\ea8a" } +.codicon-symbol-array:before { content: "\ea8a" } +.codicon-symbol-module:before { content: "\ea8b" } +.codicon-symbol-package:before { content: "\ea8b" } +.codicon-symbol-namespace:before { content: "\ea8b" } +.codicon-symbol-object:before { content: "\ea8b" } +.codicon-symbol-method:before { content: "\ea8c" } +.codicon-symbol-function:before { content: "\ea8c" } +.codicon-symbol-constructor:before { content: "\ea8c" } +.codicon-symbol-boolean:before { content: "\ea8f" } +.codicon-symbol-null:before { content: "\ea8f" } +.codicon-symbol-numeric:before { content: "\ea90" } +.codicon-symbol-number:before { content: "\ea90" } +.codicon-symbol-structure:before { content: "\ea91" } +.codicon-symbol-struct:before { content: "\ea91" } +.codicon-symbol-parameter:before { content: "\ea92" } +.codicon-symbol-type-parameter:before { content: "\ea92" } +.codicon-symbol-key:before { content: "\ea93" } +.codicon-symbol-text:before { content: "\ea93" } +.codicon-symbol-reference:before { content: "\ea94" } +.codicon-go-to-file:before { content: "\ea94" } +.codicon-symbol-enum:before { content: "\ea95" } +.codicon-symbol-value:before { content: "\ea95" } +.codicon-symbol-ruler:before { content: "\ea96" } +.codicon-symbol-unit:before { content: "\ea96" } +.codicon-activate-breakpoints:before { content: "\ea97" } +.codicon-archive:before { content: "\ea98" } +.codicon-arrow-both:before { content: "\ea99" } +.codicon-arrow-down:before { content: "\ea9a" } +.codicon-arrow-left:before { content: "\ea9b" } +.codicon-arrow-right:before { content: "\ea9c" } +.codicon-arrow-small-down:before { content: "\ea9d" } +.codicon-arrow-small-left:before { content: "\ea9e" } +.codicon-arrow-small-right:before { content: "\ea9f" } +.codicon-arrow-small-up:before { content: "\eaa0" } +.codicon-arrow-up:before { content: "\eaa1" } +.codicon-bell:before { content: "\eaa2" } +.codicon-bold:before { content: "\eaa3" } +.codicon-book:before { content: "\eaa4" } +.codicon-bookmark:before { content: "\eaa5" } +.codicon-debug-breakpoint-conditional-unverified:before { content: "\eaa6" } +.codicon-debug-breakpoint-conditional:before { content: "\eaa7" } +.codicon-debug-breakpoint-conditional-disabled:before { content: "\eaa7" } +.codicon-debug-breakpoint-data-unverified:before { content: "\eaa8" } +.codicon-debug-breakpoint-data:before { content: "\eaa9" } +.codicon-debug-breakpoint-data-disabled:before { content: "\eaa9" } +.codicon-debug-breakpoint-log-unverified:before { content: "\eaaa" } +.codicon-debug-breakpoint-log:before { content: "\eaab" } +.codicon-debug-breakpoint-log-disabled:before { content: "\eaab" } +.codicon-briefcase:before { content: "\eaac" } +.codicon-broadcast:before { content: "\eaad" } +.codicon-browser:before { content: "\eaae" } +.codicon-bug:before { content: "\eaaf" } +.codicon-calendar:before { content: "\eab0" } +.codicon-case-sensitive:before { content: "\eab1" } +.codicon-check:before { content: "\eab2" } +.codicon-checklist:before { content: "\eab3" } +.codicon-chevron-down:before { content: "\eab4" } +.codicon-chevron-left:before { content: "\eab5" } +.codicon-chevron-right:before { content: "\eab6" } +.codicon-chevron-up:before { content: "\eab7" } +.codicon-chrome-close:before { content: "\eab8" } +.codicon-chrome-maximize:before { content: "\eab9" } +.codicon-chrome-minimize:before { content: "\eaba" } +.codicon-chrome-restore:before { content: "\eabb" } +.codicon-circle-outline:before { content: "\eabc" } +.codicon-debug-breakpoint-unverified:before { content: "\eabc" } +.codicon-circle-slash:before { content: "\eabd" } +.codicon-circuit-board:before { content: "\eabe" } +.codicon-clear-all:before { content: "\eabf" } +.codicon-clippy:before { content: "\eac0" } +.codicon-close-all:before { content: "\eac1" } +.codicon-cloud-download:before { content: "\eac2" } +.codicon-cloud-upload:before { content: "\eac3" } +.codicon-code:before { content: "\eac4" } +.codicon-collapse-all:before { content: "\eac5" } +.codicon-color-mode:before { content: "\eac6" } +.codicon-comment-discussion:before { content: "\eac7" } +.codicon-credit-card:before { content: "\eac9" } +.codicon-dash:before { content: "\eacc" } +.codicon-dashboard:before { content: "\eacd" } +.codicon-database:before { content: "\eace" } +.codicon-debug-continue:before { content: "\eacf" } +.codicon-debug-disconnect:before { content: "\ead0" } +.codicon-debug-pause:before { content: "\ead1" } +.codicon-debug-restart:before { content: "\ead2" } +.codicon-debug-start:before { content: "\ead3" } +.codicon-debug-step-into:before { content: "\ead4" } +.codicon-debug-step-out:before { content: "\ead5" } +.codicon-debug-step-over:before { content: "\ead6" } +.codicon-debug-stop:before { content: "\ead7" } +.codicon-debug:before { content: "\ead8" } +.codicon-device-camera-video:before { content: "\ead9" } +.codicon-device-camera:before { content: "\eada" } +.codicon-device-mobile:before { content: "\eadb" } +.codicon-diff-added:before { content: "\eadc" } +.codicon-diff-ignored:before { content: "\eadd" } +.codicon-diff-modified:before { content: "\eade" } +.codicon-diff-removed:before { content: "\eadf" } +.codicon-diff-renamed:before { content: "\eae0" } +.codicon-diff:before { content: "\eae1" } +.codicon-discard:before { content: "\eae2" } +.codicon-editor-layout:before { content: "\eae3" } +.codicon-empty-window:before { content: "\eae4" } +.codicon-exclude:before { content: "\eae5" } +.codicon-extensions:before { content: "\eae6" } +.codicon-eye-closed:before { content: "\eae7" } +.codicon-file-binary:before { content: "\eae8" } +.codicon-file-code:before { content: "\eae9" } +.codicon-file-media:before { content: "\eaea" } +.codicon-file-pdf:before { content: "\eaeb" } +.codicon-file-submodule:before { content: "\eaec" } +.codicon-file-symlink-directory:before { content: "\eaed" } +.codicon-file-symlink-file:before { content: "\eaee" } +.codicon-file-zip:before { content: "\eaef" } +.codicon-files:before { content: "\eaf0" } +.codicon-filter:before { content: "\eaf1" } +.codicon-flame:before { content: "\eaf2" } +.codicon-fold-down:before { content: "\eaf3" } +.codicon-fold-up:before { content: "\eaf4" } +.codicon-fold:before { content: "\eaf5" } +.codicon-folder-active:before { content: "\eaf6" } +.codicon-folder-opened:before { content: "\eaf7" } +.codicon-gear:before { content: "\eaf8" } +.codicon-gift:before { content: "\eaf9" } +.codicon-gist-secret:before { content: "\eafa" } +.codicon-gist:before { content: "\eafb" } +.codicon-git-commit:before { content: "\eafc" } +.codicon-git-compare:before { content: "\eafd" } +.codicon-compare-changes:before { content: "\eafd" } +.codicon-git-merge:before { content: "\eafe" } +.codicon-github-action:before { content: "\eaff" } +.codicon-github-alt:before { content: "\eb00" } +.codicon-globe:before { content: "\eb01" } +.codicon-grabber:before { content: "\eb02" } +.codicon-graph:before { content: "\eb03" } +.codicon-gripper:before { content: "\eb04" } +.codicon-heart:before { content: "\eb05" } +.codicon-home:before { content: "\eb06" } +.codicon-horizontal-rule:before { content: "\eb07" } +.codicon-hubot:before { content: "\eb08" } +.codicon-inbox:before { content: "\eb09" } +.codicon-issue-reopened:before { content: "\eb0b" } +.codicon-issues:before { content: "\eb0c" } +.codicon-italic:before { content: "\eb0d" } +.codicon-jersey:before { content: "\eb0e" } +.codicon-json:before { content: "\eb0f" } +.codicon-kebab-vertical:before { content: "\eb10" } +.codicon-key:before { content: "\eb11" } +.codicon-law:before { content: "\eb12" } +.codicon-lightbulb-autofix:before { content: "\eb13" } +.codicon-link-external:before { content: "\eb14" } +.codicon-link:before { content: "\eb15" } +.codicon-list-ordered:before { content: "\eb16" } +.codicon-list-unordered:before { content: "\eb17" } +.codicon-live-share:before { content: "\eb18" } +.codicon-loading:before { content: "\eb19" } +.codicon-location:before { content: "\eb1a" } +.codicon-mail-read:before { content: "\eb1b" } +.codicon-mail:before { content: "\eb1c" } +.codicon-markdown:before { content: "\eb1d" } +.codicon-megaphone:before { content: "\eb1e" } +.codicon-mention:before { content: "\eb1f" } +.codicon-milestone:before { content: "\eb20" } +.codicon-mortar-board:before { content: "\eb21" } +.codicon-move:before { content: "\eb22" } +.codicon-multiple-windows:before { content: "\eb23" } +.codicon-mute:before { content: "\eb24" } +.codicon-no-newline:before { content: "\eb25" } +.codicon-note:before { content: "\eb26" } +.codicon-octoface:before { content: "\eb27" } +.codicon-open-preview:before { content: "\eb28" } +.codicon-package:before { content: "\eb29" } +.codicon-paintcan:before { content: "\eb2a" } +.codicon-pin:before { content: "\eb2b" } +.codicon-play:before { content: "\eb2c" } +.codicon-run:before { content: "\eb2c" } +.codicon-plug:before { content: "\eb2d" } +.codicon-preserve-case:before { content: "\eb2e" } +.codicon-preview:before { content: "\eb2f" } +.codicon-project:before { content: "\eb30" } +.codicon-pulse:before { content: "\eb31" } +.codicon-question:before { content: "\eb32" } +.codicon-quote:before { content: "\eb33" } +.codicon-radio-tower:before { content: "\eb34" } +.codicon-reactions:before { content: "\eb35" } +.codicon-references:before { content: "\eb36" } +.codicon-refresh:before { content: "\eb37" } +.codicon-regex:before { content: "\eb38" } +.codicon-remote-explorer:before { content: "\eb39" } +.codicon-remote:before { content: "\eb3a" } +.codicon-remove:before { content: "\eb3b" } +.codicon-replace-all:before { content: "\eb3c" } +.codicon-replace:before { content: "\eb3d" } +.codicon-repo-clone:before { content: "\eb3e" } +.codicon-repo-force-push:before { content: "\eb3f" } +.codicon-repo-pull:before { content: "\eb40" } +.codicon-repo-push:before { content: "\eb41" } +.codicon-report:before { content: "\eb42" } +.codicon-request-changes:before { content: "\eb43" } +.codicon-rocket:before { content: "\eb44" } +.codicon-root-folder-opened:before { content: "\eb45" } +.codicon-root-folder:before { content: "\eb46" } +.codicon-rss:before { content: "\eb47" } +.codicon-ruby:before { content: "\eb48" } +.codicon-save-all:before { content: "\eb49" } +.codicon-save-as:before { content: "\eb4a" } +.codicon-save:before { content: "\eb4b" } +.codicon-screen-full:before { content: "\eb4c" } +.codicon-screen-normal:before { content: "\eb4d" } +.codicon-search-stop:before { content: "\eb4e" } +.codicon-server:before { content: "\eb50" } +.codicon-settings-gear:before { content: "\eb51" } +.codicon-settings:before { content: "\eb52" } +.codicon-shield:before { content: "\eb53" } +.codicon-smiley:before { content: "\eb54" } +.codicon-sort-precedence:before { content: "\eb55" } +.codicon-split-horizontal:before { content: "\eb56" } +.codicon-split-vertical:before { content: "\eb57" } +.codicon-squirrel:before { content: "\eb58" } +.codicon-star-full:before { content: "\eb59" } +.codicon-star-half:before { content: "\eb5a" } +.codicon-symbol-class:before { content: "\eb5b" } +.codicon-symbol-color:before { content: "\eb5c" } +.codicon-symbol-constant:before { content: "\eb5d" } +.codicon-symbol-enum-member:before { content: "\eb5e" } +.codicon-symbol-field:before { content: "\eb5f" } +.codicon-symbol-file:before { content: "\eb60" } +.codicon-symbol-interface:before { content: "\eb61" } +.codicon-symbol-keyword:before { content: "\eb62" } +.codicon-symbol-misc:before { content: "\eb63" } +.codicon-symbol-operator:before { content: "\eb64" } +.codicon-symbol-property:before { content: "\eb65" } +.codicon-wrench:before { content: "\eb65" } +.codicon-wrench-subaction:before { content: "\eb65" } +.codicon-symbol-snippet:before { content: "\eb66" } +.codicon-tasklist:before { content: "\eb67" } +.codicon-telescope:before { content: "\eb68" } +.codicon-text-size:before { content: "\eb69" } +.codicon-three-bars:before { content: "\eb6a" } +.codicon-thumbsdown:before { content: "\eb6b" } +.codicon-thumbsup:before { content: "\eb6c" } +.codicon-tools:before { content: "\eb6d" } +.codicon-triangle-down:before { content: "\eb6e" } +.codicon-triangle-left:before { content: "\eb6f" } +.codicon-triangle-right:before { content: "\eb70" } +.codicon-triangle-up:before { content: "\eb71" } +.codicon-twitter:before { content: "\eb72" } +.codicon-unfold:before { content: "\eb73" } +.codicon-unlock:before { content: "\eb74" } +.codicon-unmute:before { content: "\eb75" } +.codicon-unverified:before { content: "\eb76" } +.codicon-verified:before { content: "\eb77" } +.codicon-versions:before { content: "\eb78" } +.codicon-vm-active:before { content: "\eb79" } +.codicon-vm-outline:before { content: "\eb7a" } +.codicon-vm-running:before { content: "\eb7b" } +.codicon-watch:before { content: "\eb7c" } +.codicon-whitespace:before { content: "\eb7d" } +.codicon-whole-word:before { content: "\eb7e" } +.codicon-window:before { content: "\eb7f" } +.codicon-word-wrap:before { content: "\eb80" } +.codicon-zoom-in:before { content: "\eb81" } +.codicon-zoom-out:before { content: "\eb82" } +.codicon-list-filter:before { content: "\eb83" } +.codicon-list-flat:before { content: "\eb84" } +.codicon-list-selection:before { content: "\eb85" } +.codicon-selection:before { content: "\eb85" } +.codicon-list-tree:before { content: "\eb86" } +.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" } +.codicon-debug-breakpoint-function:before { content: "\eb88" } +.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } +.codicon-debug-stackframe-active:before { content: "\eb89" } +.codicon-debug-stackframe-dot:before { content: "\eb8a" } +.codicon-debug-stackframe:before { content: "\eb8b" } +.codicon-debug-stackframe-focused:before { content: "\eb8b" } +.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } +.codicon-symbol-string:before { content: "\eb8d" } +.codicon-debug-reverse-continue:before { content: "\eb8e" } +.codicon-debug-step-back:before { content: "\eb8f" } +.codicon-debug-restart-frame:before { content: "\eb90" } +.codicon-debug-alt:before { content: "\eb91" } +.codicon-call-incoming:before { content: "\eb92" } +.codicon-call-outgoing:before { content: "\eb93" } +.codicon-menu:before { content: "\eb94" } +.codicon-expand-all:before { content: "\eb95" } +.codicon-feedback:before { content: "\eb96" } +.codicon-group-by-ref-type:before { content: "\eb97" } +.codicon-ungroup-by-ref-type:before { content: "\eb98" } +.codicon-account:before { content: "\eb99" } +.codicon-bell-dot:before { content: "\eb9a" } +.codicon-debug-console:before { content: "\eb9b" } +.codicon-library:before { content: "\eb9c" } +.codicon-output:before { content: "\eb9d" } +.codicon-run-all:before { content: "\eb9e" } +.codicon-sync-ignored:before { content: "\eb9f" } +.codicon-pinned:before { content: "\eba0" } +.codicon-github-inverted:before { content: "\eba1" } +.codicon-server-process:before { content: "\eba2" } +.codicon-server-environment:before { content: "\eba3" } +.codicon-pass:before { content: "\eba4" } +.codicon-issue-closed:before { content: "\eba4" } +.codicon-stop-circle:before { content: "\eba5" } +.codicon-play-circle:before { content: "\eba6" } +.codicon-record:before { content: "\eba7" } +.codicon-debug-alt-small:before { content: "\eba8" } +.codicon-vm-connect:before { content: "\eba9" } +.codicon-cloud:before { content: "\ebaa" } +.codicon-merge:before { content: "\ebab" } +.codicon-export:before { content: "\ebac" } +.codicon-graph-left:before { content: "\ebad" } +.codicon-magnet:before { content: "\ebae" } +.codicon-notebook:before { content: "\ebaf" } +.codicon-redo:before { content: "\ebb0" } +.codicon-check-all:before { content: "\ebb1" } +.codicon-pinned-dirty:before { content: "\ebb2" } +.codicon-pass-filled:before { content: "\ebb3" } +.codicon-circle-large-filled:before { content: "\ebb4" } +.codicon-circle-large-outline:before { content: "\ebb5" } +.codicon-combine:before { content: "\ebb6" } +.codicon-gather:before { content: "\ebb6" } +.codicon-table:before { content: "\ebb7" } +.codicon-variable-group:before { content: "\ebb8" } +.codicon-type-hierarchy:before { content: "\ebb9" } +.codicon-type-hierarchy-sub:before { content: "\ebba" } +.codicon-type-hierarchy-super:before { content: "\ebbb" } +.codicon-git-pull-request-create:before { content: "\ebbc" } +.codicon-run-above:before { content: "\ebbd" } +.codicon-run-below:before { content: "\ebbe" } +.codicon-notebook-template:before { content: "\ebbf" } +.codicon-debug-rerun:before { content: "\ebc0" } +.codicon-workspace-trusted:before { content: "\ebc1" } +.codicon-workspace-untrusted:before { content: "\ebc2" } +.codicon-workspace-unknown:before { content: "\ebc3" } +.codicon-terminal-cmd:before { content: "\ebc4" } +.codicon-terminal-debian:before { content: "\ebc5" } +.codicon-terminal-linux:before { content: "\ebc6" } +.codicon-terminal-powershell:before { content: "\ebc7" } +.codicon-terminal-tmux:before { content: "\ebc8" } +.codicon-terminal-ubuntu:before { content: "\ebc9" } +.codicon-terminal-bash:before { content: "\ebca" } +.codicon-arrow-swap:before { content: "\ebcb" } +.codicon-copy:before { content: "\ebcc" } +.codicon-person-add:before { content: "\ebcd" } +.codicon-filter-filled:before { content: "\ebce" } +.codicon-wand:before { content: "\ebcf" } +.codicon-debug-line-by-line:before { content: "\ebd0" } +.codicon-inspect:before { content: "\ebd1" } +.codicon-layers:before { content: "\ebd2" } +.codicon-layers-dot:before { content: "\ebd3" } +.codicon-layers-active:before { content: "\ebd4" } +.codicon-compass:before { content: "\ebd5" } +.codicon-compass-dot:before { content: "\ebd6" } +.codicon-compass-active:before { content: "\ebd7" } +.codicon-azure:before { content: "\ebd8" } +.codicon-issue-draft:before { content: "\ebd9" } +.codicon-git-pull-request-closed:before { content: "\ebda" } +.codicon-git-pull-request-draft:before { content: "\ebdb" } +.codicon-debug-all:before { content: "\ebdc" } +.codicon-debug-coverage:before { content: "\ebdd" } +.codicon-run-errors:before { content: "\ebde" } +.codicon-folder-library:before { content: "\ebdf" } +.codicon-debug-continue-small:before { content: "\ebe0" } +.codicon-beaker-stop:before { content: "\ebe1" } +.codicon-graph-line:before { content: "\ebe2" } +.codicon-graph-scatter:before { content: "\ebe3" } +.codicon-pie-chart:before { content: "\ebe4" } +.codicon-bracket:before { content: "\eb0f" } +.codicon-bracket-dot:before { content: "\ebe5" } +.codicon-bracket-error:before { content: "\ebe6" } +.codicon-lock-small:before { content: "\ebe7" } +.codicon-azure-devops:before { content: "\ebe8" } +.codicon-verified-filled:before { content: "\ebe9" } +.codicon-newline:before { content: "\ebea" } +.codicon-layout:before { content: "\ebeb" } +.codicon-layout-activitybar-left:before { content: "\ebec" } +.codicon-layout-activitybar-right:before { content: "\ebed" } +.codicon-layout-panel-left:before { content: "\ebee" } +.codicon-layout-panel-center:before { content: "\ebef" } +.codicon-layout-panel-justify:before { content: "\ebf0" } +.codicon-layout-panel-right:before { content: "\ebf1" } +.codicon-layout-panel:before { content: "\ebf2" } +.codicon-layout-sidebar-left:before { content: "\ebf3" } +.codicon-layout-sidebar-right:before { content: "\ebf4" } +.codicon-layout-statusbar:before { content: "\ebf5" } +.codicon-layout-menubar:before { content: "\ebf6" } +.codicon-layout-centered:before { content: "\ebf7" } +.codicon-target:before { content: "\ebf8" } +.codicon-indent:before { content: "\ebf9" } +.codicon-record-small:before { content: "\ebfa" } +.codicon-error-small:before { content: "\ebfb" } +.codicon-arrow-circle-down:before { content: "\ebfc" } +.codicon-arrow-circle-left:before { content: "\ebfd" } +.codicon-arrow-circle-right:before { content: "\ebfe" } +.codicon-arrow-circle-up:before { content: "\ebff" } diff --git a/projects/cloud-foundry-tools/packages/frontend/codicon.ttf b/projects/cloud-foundry-tools/packages/frontend/codicon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2f5dbfcc76de9fe983e8eb2b5aaf163e1aa32740 GIT binary patch literal 70800 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 literal 0 HcmV?d00001 diff --git a/projects/cloud-foundry-tools/packages/frontend/index.html b/projects/cloud-foundry-tools/packages/frontend/index.html new file mode 100644 index 000000000..db52fe922 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/index.html @@ -0,0 +1,18 @@ + + + + + + + + + Cloud Foundry Sign-in + + + +
+ + + diff --git a/projects/cloud-foundry-tools/packages/frontend/jest.config.js b/projects/cloud-foundry-tools/packages/frontend/jest.config.js new file mode 100644 index 000000000..dc8a0a69f --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/jest.config.js @@ -0,0 +1,38 @@ +module.exports = { + verbose: true, + testRegex: "(/test/(.*).(test|spec)).[jt]sx?$", + collectCoverage: true, + coverageProvider: "v8", + collectCoverageFrom: [ + "src/**/*.{js,vue}", + "!**/node_modules/**", + "!/src/main.js", + "!/src/directives/**", + "!/src/styles/**", + ], + moduleNameMapper: { + "^@/(.*)$": "/src/$1", + }, + moduleFileExtensions: ["js", "vue", "json"], + transformIgnorePatterns: ["/node_modules/(?!(.+)/)"], + modulePaths: ["/src", "/node_modules"], + transform: { + "^.+\\.vue$": "@vue/vue3-jest", + "^.+\\.tsx?$": "ts-jest", + "^.+\\.js$": "babel-jest", + "^.+\\.jsx?$": "babel-jest", + }, + setupFilesAfterEnv: ["/jest.setup.js"], + testEnvironmentOptions: { + customExportConditions: ["node", "node-addons"], + }, + testEnvironment: "jsdom", + coverageThreshold: { + global: { + branches: 85, + functions: 80, + lines: 87, + statements: 87, + }, + }, +}; diff --git a/projects/cloud-foundry-tools/packages/frontend/jest.setup.js b/projects/cloud-foundry-tools/packages/frontend/jest.setup.js new file mode 100644 index 000000000..d3433d9a2 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/jest.setup.js @@ -0,0 +1,12 @@ +import "@testing-library/jest-dom"; // If you're using Vue Test Utils with Vue 3 + +// Mock window.matchMedia +window.matchMedia = jest.fn().mockImplementation((query) => { + return { + matches: query === "(min-width: 768px)", // Adjust as needed + media: query, + onchange: null, + addListener: jest.fn(), + removeListener: jest.fn(), + }; +}); diff --git a/projects/cloud-foundry-tools/packages/frontend/package.json b/projects/cloud-foundry-tools/packages/frontend/package.json new file mode 100644 index 000000000..7f40153e8 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/package.json @@ -0,0 +1,62 @@ +{ + "name": "cloud-foundry-tools-frontend", + "displayName": "Cloud Foundry Tools Sign Frontend", + "version": "8.1.2", + "private": true, + "description": "Frontend for the cf sign-in", + "license": "Apache 2.0", + "publisher": "SAP", + "scripts": { + "build": "vite build", + "build-dev": "vite build --mode development", + "ci": "npm-run-all clean build test", + "ci:artifacts_only": "npm-run-all clean build", + "clean": "rimraf ./dist ./coverage", + "dev": "vite", + "format:fix": "prettier --print-width 120 --write \"**/*.@(js|ts|json|md|vue)\" --ignore-path=.gitignore", + "format:validate": "prettier --print-width 120 --check \"**/*.@(js|ts|json|md|vue)\" --ignore-path=.gitignore", + "serve": "vite preview", + "test": "jest -w 1 --colors" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ], + "eslintConfig": { + "env": { + "es2022": true, + "jest": true, + "node": true + }, + "extends": [ + "plugin:vue/vue3-recommended", + "eslint:recommended", + "prettier" + ], + "rules": { + "vue/attribute-hyphenation": "off" + }, + "root": true + }, + "dependencies": { + "@sap-devx/webview-rpc": "0.4.1", + "@vscode-elements/elements": "1.16.1", + "vue": "3.3.8", + "vue-loader": "17.2.2", + "vue-loading-overlay": "6.0.3" + }, + "devDependencies": { + "@babel/preset-env": "^7.22.20", + "@babel/preset-typescript": "^7.23.0", + "@testing-library/jest-dom": "^6.6.3", + "@vitejs/plugin-vue": "^5.2.0", + "@vue/compiler-sfc": "3.3.8", + "@vue/test-utils": "2.4.1", + "@vue/vue3-jest": "29.2.6", + "eslint-plugin-vue": "9.18.1", + "jest": "29.7.0", + "jest-environment-jsdom": "^29.7.0", + "ts-jest": "29.1.1", + "vite": "^6.0.6" + } +} diff --git a/projects/cloud-foundry-tools/packages/frontend/src/App.vue b/projects/cloud-foundry-tools/packages/frontend/src/App.vue new file mode 100644 index 000000000..a40c5c4d6 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/App.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue new file mode 100644 index 000000000..e8d42e442 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue @@ -0,0 +1,26 @@ + + + + diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue new file mode 100644 index 000000000..33a4f4582 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue @@ -0,0 +1,377 @@ + + + + + + diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue new file mode 100644 index 000000000..693ecd228 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue @@ -0,0 +1,249 @@ + + + + + diff --git a/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js b/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js new file mode 100644 index 000000000..1f1f4754b --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js @@ -0,0 +1,141 @@ +export default { + updateTooltip(el, { value, modifiers }) { + if (typeof value === "string") { + // we can pass either a string + el.setAttribute("data-v-tooltip", value); + + // this check if when v-tooltip receives string with .arrow modifier + if (modifiers.arrow) { + el.style.setProperty("--v-tooltip-arrow-display", "inline"); + } + } else { + // or an object + if (value.text) { + el.setAttribute("data-v-tooltip", value.text); + } + if (value.displayArrow || modifiers.arrow) { + // if there is a prop global: true then mutate the :root css variables + // otherwise it adds given variables to the element, which makes it possible to be different than others + const targetEl = value.global ? document.documentElement : el; + targetEl.style.setProperty("--v-tooltip-arrow-display", "inline"); + } + if (value.theme) { + // if there is a prop global: true then mutate the :root css variables + // otherwise it adds given variables to the element, which makes it possible to be different than others + const targetEl = value.global ? document.documentElement : el; + for (const [key, val] of Object.entries(value.theme)) { + if (key === "placement") { + switch (val) { + case "top": + targetEl.style.setProperty("--v-tooltip-left", "50%"); + targetEl.style.setProperty("--v-tooltip-top", "0%"); + targetEl.style.setProperty("--v-tooltip-translate", "translate(-50%, -110%)"); + if (value.displayArrow || modifiers.arrow) { + targetEl.style.setProperty( + "--v-tooltip-arrow-border-color", + "var(--v-tooltip-background-color) transparent transparent transparent" + ); + targetEl.style.setProperty( + "--v-tooltip-arrow-top", + "calc(var(--v-tooltip-top) - var(--v-tooltip-top-offset) + 8px)" + ); + } + break; + case "bottom": + targetEl.style.setProperty("--v-tooltip-left", "50%"); + targetEl.style.setProperty("--v-tooltip-top", "100%"); + targetEl.style.setProperty("--v-tooltip-translate", "translate(-50%, 10%)"); + if (value.displayArrow || modifiers.arrow) { + targetEl.style.setProperty( + "--v-tooltip-arrow-border-color", + "transparent transparent var(--v-tooltip-background-color) transparent" + ); + targetEl.style.setProperty( + "--v-tooltip-arrow-top", + "calc(var(--v-tooltip-top) - var(--v-tooltip-top-offset) - 7px)" + ); + } + break; + case "left": + targetEl.style.setProperty("--v-tooltip-left", "0%"); + targetEl.style.setProperty("--v-tooltip-top", "50%"); + targetEl.style.setProperty("--v-tooltip-translate", "translate(-110%, -50%)"); + if (value.displayArrow || modifiers.arrow) { + targetEl.style.setProperty( + "--v-tooltip-arrow-border-color", + "transparent transparent transparent var(--v-tooltip-background-color)" + ); + targetEl.style.setProperty("--v-tooltip-arrow-top", "calc(var(--v-tooltip-top)"); + targetEl.style.setProperty( + "--v-tooltip-arrow-left", + "calc( var(--v-tooltip-left) - var(--v-tooltip-left-offset) + 1.5px)" + ); + } + break; + case "right": + targetEl.style.setProperty("--v-tooltip-left", "100%"); + targetEl.style.setProperty("--v-tooltip-top", "50%"); + targetEl.style.setProperty("--v-tooltip-translate", "translate(3%, -50%)"); + if (value.displayArrow || modifiers.arrow) { + targetEl.style.setProperty( + "--v-tooltip-arrow-border-color", + "transparent var(--v-tooltip-background-color) transparent transparent" + ); + targetEl.style.setProperty("--v-tooltip-arrow-top", "calc(var(--v-tooltip-top)"); + targetEl.style.setProperty( + "--v-tooltip-arrow-left", + "calc( var(--v-tooltip-left) - var(--v-tooltip-left-offset) - 2px)" + ); + } + break; + default: + break; + } + } else if (key === "offset" && !value.global) { + for (const direction of val) { + if (direction === "left") { + targetEl.style.setProperty( + "--v-tooltip-left-offset", + `-${targetEl.scrollWidth - targetEl.clientWidth}px` + ); + } else if (direction === "right") { + targetEl.style.setProperty( + "--v-tooltip-left-offset", + `${targetEl.scrollWidth - targetEl.clientWidth}px` + ); + } else if (direction === "top") { + targetEl.style.setProperty( + "--v-tooltip-top-offset", + `-${targetEl.scrollHeight - targetEl.clientHeight}px` + ); + } else if (direction === "bottom") { + targetEl.style.setProperty( + "--v-tooltip-top-offset", + `${targetEl.scrollHeight - targetEl.clientHeight}px` + ); + } + } + } else { + targetEl.style.setProperty(`--v-tooltip-${key}`, val); + } + } + } + } + }, + // hooks + mounted(el, { value, dir, modifiers }) { + // v-tooltips with global prop won't show the tooltip + // also object notation without text prop won't show neither + if (typeof value === "object" && !value.global && value.text) { + el.classList.add("data-v-tooltip"); + } else if (typeof value === "string") { + el.classList.add("data-v-tooltip"); + } + + // to use functions in Vue's directives which are inside this object, we can't use this, we have to use dir, which is the directive object + dir.updateTooltip(el, { value, modifiers }); + }, + updated(el, { value, dir, modifiers }) { + dir.updateTooltip(el, { value, modifiers }); + }, +}; diff --git a/projects/cloud-foundry-tools/packages/frontend/src/main.js b/projects/cloud-foundry-tools/packages/frontend/src/main.js new file mode 100644 index 000000000..597549a43 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/main.js @@ -0,0 +1,11 @@ +import { createApp } from "vue"; +import CFSignIn from "./App.vue"; +import "./styles/tooltip.css"; +import tooltip from "./directives/tooltip.js"; + +const app = createApp(CFSignIn); + +// register all directives +app.directive("tooltip", tooltip); + +app.mount("#app"); diff --git a/projects/cloud-foundry-tools/packages/frontend/src/styles/tooltip.css b/projects/cloud-foundry-tools/packages/frontend/src/styles/tooltip.css new file mode 100644 index 000000000..aefb93248 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/src/styles/tooltip.css @@ -0,0 +1,83 @@ +:root { + --v-tooltip-left: 50%; + --v-tooltip-top: 0%; + --v-tooltip-translate: translate(-50%, -110%); + --v-tooltip-left-offset: 0px; + --v-tooltip-top-offset: 0px; + --v-tooltip-width: max-content; + --v-tooltip-background-color: var(--vscode-editor-background, white); + --v-tooltip-color: var(--vscode-foreground, black); + --v-tooltip-border-radius: 0.4rem; + --v-tooltip-padding: 0.6em; + --v-tooltip-font-size: 0.7rem; + --v-tooltip-font-family: var(--vscode-font-family, Roboto, Avenir, Helvetica, Arial, sans-serif); + --v-tooltip-border: 0.1rem solid var(--v-tooltip-color); + --v-tooltip-box-shadow: none; + --v-tooltip-transition-duration: 0.25s; + --v-tooltip-transition-delay: 0.3s; + --v-tooltip-transition-timing-function: ease; + --v-tooltip-arrow-display: none; + --v-tooltip-arrow-border-color: var(--v-tooltip-background-color) + transparent transparent transparent; + --v-tooltip-arrow-top: calc( + var(--v-tooltip-top) - var(--v-tooltip-top-offset) + 8px + ); + --v-tooltip-arrow-left: calc( + var(--v-tooltip-left) - var(--v-tooltip-left-offset) + ); +} +.data-v-tooltip { + position: relative; +} +.data-v-tooltip::after { + z-index: 2147483647; + content: attr(data-v-tooltip); + position: absolute; + left: calc(var(--v-tooltip-left) - var(--v-tooltip-left-offset)); + top: calc(var(--v-tooltip-top) - var(--v-tooltip-top-offset)); + transform: var(--v-tooltip-translate); + width: var(--v-tooltip-width); + background-color: var(--v-tooltip-background-color); + color: var(--v-tooltip-color); + border-radius: var(--v-tooltip-border-radius); + padding: var(--v-tooltip-padding); + font-size: var(--v-tooltip-font-size); + font-family: var(--v-tooltip-font-family); + border: var(--v-tooltip-border); + box-shadow: var(--v-tooltip-box-shadow); + transition-property: all; + transition-duration: var(--v-tooltip-transition-duration); + transition-delay: 0s; + transition-timing-function: var(--v-tooltip-transition-timing-function); + opacity: 0; + visibility: hidden; + pointer-events: none; +} +/* arrow */ +.data-v-tooltip::before { + z-index: 2147483647; + content: ""; + position: absolute; + display: var(--v-tooltip-arrow-display); + left: var(--v-tooltip-arrow-left); + top: var(--v-tooltip-arrow-top); + transform: var(--v-tooltip-translate); + border-width: 5px; + border-style: solid; + border-color: var(--v-tooltip-arrow-border-color); + transition-property: all; + transition-duration: var(--v-tooltip-transition-duration); + transition-delay: 0s; + transition-timing-function: var(--v-tooltip-transition-timing-function); + opacity: 0; + visibility: hidden; + pointer-events: none; +} +.data-v-tooltip:hover::after, +.data-v-tooltip:hover::before { + transition-duration: var(--v-tooltip-transition-duration); + transition-delay: var(--v-tooltip-transition-delay); + transition-timing-function: var(--v-tooltip-transition-timing-function); + opacity: 1; + visibility: visible; +} \ No newline at end of file diff --git a/projects/cloud-foundry-tools/packages/frontend/test/App.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/App.spec.js new file mode 100644 index 000000000..27bdb8818 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/test/App.spec.js @@ -0,0 +1,184 @@ +import { expect } from "chai"; +import { shallowMount } from "@vue/test-utils"; +import App from "../src/App"; + +jest.mock("@sap-devx/webview-rpc/out.browser/rpc-browser", () => ({ + RpcBrowser: jest.fn().mockImplementation(() => ({ + invoke: jest.fn(), + registerMethod: jest.fn(), + })), +})); + +// Mock RpcBrowserWebSockets class +jest.mock("@sap-devx/webview-rpc/out.browser/rpc-browser-ws", () => { + class MockRpcBrowserWebSockets { + constructor(ws, logger) { + this.ws = ws; + this.logger = logger; + this.eventListeners = {}; + + // Simulate the behavior of the real constructor + this.ws.addEventListener = (eventName, callback) => { + this.eventListeners[eventName] = callback; + }; + + // Initialize an empty invoke method + this.invoke = jest.fn(); + } + + registerMethod() {} + } + + return { + RpcBrowserWebSockets: MockRpcBrowserWebSockets, + }; +}); + +jest.mock("../src/components/CFHeader.vue", () => ({ + name: "CFHeader", + template: "
Mocked CFHeader
", // Replace with your desired mock template +})); + +// Mock the CFSignin.vue module +jest.mock("../src/components/CFSignin.vue", () => ({ + name: "CFSignin", + template: "
Mocked CFSignin
", // Replace with your desired mock template +})); + +// Stub is used to avoid console warnings of vscode custom elements +const global = { + stubs: { + VscodeProgressRing: { + template: "
", + }, + }, +}; + +describe("App.vue", () => { + // Mock the rpc object with the required methods + const mockRpc = { + invoke: jest.fn(() => Promise.resolve({})), + registerMethod: jest.fn(), + }; + + it("renders without errors", () => { + // Mount the component with the mocked rpc object and target prop + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + initialTarget: {}, + }; + }, + global, + }); + + // Assert that the component exists without errors + expect(wrapper.exists()).to.be.true; + }); + + it("updates isLoggedIn when updateIsLoggedIn is called", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + initialTarget: {}, + }; + }, + global, + }); + expect(wrapper.vm.isLoggedIn).to.be.false; // Initial state + + // Call the updateIsLoggedIn method with a value of true + wrapper.vm.updateIsLoggedIn(true); + + // Check that isLoggedIn has been updated to true + expect(wrapper.vm.isLoggedIn).to.be.true; + }); + + it("updates orgAndSpaceSet when updateOrgAndSpaceSet is called", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + initialTarget: {}, + }; + }, + global, + }); + expect(wrapper.vm.orgAndSpaceSet).to.be.false; // Initial state + + // Call the updateOrgAndSpaceSet method with a value of true + wrapper.vm.updateOrgAndSpaceSet(true); + + // Check that isLoggedIn has been updated to true + expect(wrapper.vm.orgAndSpaceSet).to.be.true; + }); + + it("renders the correct HTML structure", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + initialTarget: {}, + }; + }, + global, + }); + const expectedHtml = ``; + expect(wrapper.html().replace(/\s/g, "")).to.equal(expectedHtml); + }); + + it("computes isLoadingColor as true", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + initialTarget: {}, + }; + }, + global, + }); + + // Check that the computed property returns true + expect(wrapper.vm.isLoadingColor).to.be.true; + }); + + it("sets showBusyIndicator to the specified value", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + showBusyIndicator: false, // Initialize with a specific value + initialTarget: {}, + }; + }, + global, + }); + + // Call the setBusyIndicator method with a value of true + wrapper.vm.setBusyIndicator(true); + + // Check that the showBusyIndicator data property is now true + expect(wrapper.vm.showBusyIndicator).to.be.true; + }); + + it("sets showBusyIndicator to the specified value (false)", () => { + const wrapper = shallowMount(App, { + data() { + return { + rpc: mockRpc, // Provide the mock rpc object + showBusyIndicator: true, // Initialize with a specific value + initialTarget: {}, + }; + }, + global, + }); + + // Call the setBusyIndicator method with a value of false + wrapper.vm.setBusyIndicator(false); + + // Check that the showBusyIndicator data property is now false + expect(wrapper.vm.showBusyIndicator).to.be.false; + }); +}); diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js new file mode 100644 index 000000000..56cb956e9 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js @@ -0,0 +1,57 @@ +import CFHeader from "../../src/components/CFHeader.vue"; +import { expect } from "chai"; +import { mount } from "@vue/test-utils"; + +jest.spyOn(window, "getComputedStyle").mockImplementation(() => ({ + getPropertyValue: (property) => { + if (property === "--vscode-descriptionForeground") { + return "#717171"; // Mock the CSS variable value + } + return ""; + }, +})); + +const global = { + stubs: { + VscodeDivider: { + template: "
", + }, + }, +}; + +describe("CFHeader.vue", () => { + it("renders without errors", () => { + const wrapper = mount(CFHeader, { + global, + }); + expect(wrapper.exists()).to.be.true; + }); + + it("renders the component with correct content", () => { + const wrapper = mount(CFHeader, { global }); + expect(wrapper.text()).contain("Cloud Foundry Sign In and Targets"); + expect(wrapper.text()).contain( + "Provide your credentials to sign in to the Cloud Foundry environment and then select the target org and space." + ); + expect(wrapper.find('[role="separator"]').exists()).to.be.true; + }); + + it("applies the correct subtitle-field style", () => { + const wrapper = mount(CFHeader, { global }); + + // Access the element with class "subtitle-field" + const subtitleField = wrapper.find(".subtitle-field").element; + + // Get the computed style of the element + const computedStyle = window.getComputedStyle(subtitleField); + + // Assert the computed style properties + expect(computedStyle.getPropertyValue("--vscode-descriptionForeground")).to.equal("#717171"); + }); + + it("renders the correct HTML structure", () => { + const wrapper = mount(CFHeader, { global }); + const expectedHtml = `
CloudFoundrySignInandTargetsProvideyourcredentialstosignintotheCloudFoundryenvironmentandthenselectthetargetorgandspace.


`; + expect(wrapper.html().replace(/\s/g, "")).to.equal(expectedHtml); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js new file mode 100644 index 000000000..381ac99db --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js @@ -0,0 +1,425 @@ +import { expect } from "chai"; +import { shallowMount } from "@vue/test-utils"; +import CFSignin from "../../src/components/CFSignin.vue"; + +const mockTooltipDirective = jest.fn(); + +const global = { + directives: { + // Use the mockTooltipDirective for v-tooltip + tooltip: mockTooltipDirective, + }, + stubs: { + VscodeLink: { + template: "
", + }, + VscodeRadioGroup: { + template: "
", + }, + VscodeRadio: { + template: "", + }, + VscodeTextfield: { + template: "
", + }, + VscodeButton: { + template: "", + }, + VscodeIcon: { + template: "", + }, + }, +}; + +describe("CFSignin.vue", () => { + it("renders without errors", () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: {}, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + expect(wrapper.exists()).to.be.true; + }); + + it("initializes with default data values", () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Check initial data values + expect(wrapper.vm.endpoint).to.equal("https://example.com"); + expect(wrapper.vm.passcode).to.equal(""); + expect(wrapper.vm.username).to.equal(""); + expect(wrapper.vm.password).to.equal(""); + expect(wrapper.vm.isCFEndpointValid).to.be.true; + expect(wrapper.vm.disableButton).to.be.true; + }); + + it("disables the Sign in button when no input is provided", async () => { + //avoid showing warning for custom element in console log. + jest.spyOn(console, "warn").mockImplementation(() => { + return; + }); + + const wrapper = shallowMount(CFSignin, { + // Do not mock button + global: { + directives: { + // Use the mockTooltipDirective for v-tooltip + tooltip: mockTooltipDirective, + }, + stubs: { + VscodeLink: { + template: "
", + }, + VscodeRadioGroup: { + template: "
", + }, + VscodeRadio: { + template: "", + }, + VscodeTextField: { + template: "
", + }, + }, + }, + props: { + target: { + defaultEndpoint: "", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Confirm that the Sign in button is initially not visible + expect(wrapper.find("vscode-button").isVisible()).to.be.false; + }); + + it("handles successful sign-in with valid credentials", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: false, + }, + rpc: { + invoke: jest.fn().mockResolvedValue(true), // Mock RPC to return success + }, + orgAndSpaceSet: false, + }, + }); + // Set endPoint to be the default one + wrapper.vm.setEndpoint({ target: { value: "" } }); + + // Set SSO target to be Credentials + wrapper.vm.setSSO({ target: { value: "Credentials" } }); + + // Set valid username and password or passcode + wrapper.setData({ username: "validUsername", password: "validPassword" }); + + const invokeSpy = jest.spyOn(wrapper.props().rpc, "invoke"); + + // Trigger the SigninClicked method + await wrapper.vm.SigninClicked(); + + // Assert that authentication failed message is not displayed and isLoggedIn is set to true + expect(wrapper.vm.authFailed).to.equal(""); + expect(wrapper.vm.isLoggedIn).to.be.true; + // Check that loginClick was called with correct params + expect(invokeSpy.mock.calls[0][0]).to.be.equals("loginClick"); + expect(invokeSpy.mock.calls[0][1][0]).to.deep.equal({ + endpoint: "https://example.com", + user: "validUsername", + password: "validPassword", + }); + }); + + it("handles successful sign-in with valid SSO", async () => { + const endpoint = "https://example.com"; + const passcode = "-_validPasscode"; + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: endpoint, + isLoggedIn: false, + }, + rpc: { + invoke: async (command, args) => { + expect(command).to.equal("loginClick"); + expect(args[0]).to.deep.equal({ + endpoint: endpoint, + ssoPasscode: `"${passcode}"`, + }); + return true; + }, + }, + orgAndSpaceSet: false, + }, + }); + + // Set SSO authentication method + wrapper.vm.setSSO({ target: { value: "SSO" } }); + + wrapper.vm.passcode = passcode; + + // Trigger the SigninClicked method + await wrapper.vm.SigninClicked(); + + // Assert that authentication failed message is not displayed and isLoggedIn is set to true + expect(wrapper.vm.authFailed).to.equal(""); + expect(wrapper.vm.isLoggedIn).to.be.true; + }); + + it("handles unsuccessful sign-in with invalid credentials", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: false, + }, + rpc: { + invoke: jest.fn().mockResolvedValue(false), // Mock RPC to return failure + }, + orgAndSpaceSet: false, + }, + }); + + // Set invalid username and password or passcode + wrapper.setData({ username: "invalidUsername", password: "invalidPassword" }); + + // Trigger the SigninClicked method + await wrapper.vm.SigninClicked(); + + // Assert that authentication failed message is displayed and isLoggedIn remains false + expect(wrapper.vm.authFailed).not.to.equal(""); + expect(wrapper.vm.isLoggedIn).to.be.false; + }); + + it("handles successful sign-out", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: true, + }, + rpc: { + invoke: jest.fn().mockResolvedValue(true), // Mock RPC to return success + }, + orgAndSpaceSet: false, + }, + }); + + // Trigger the SignoutClicked method + await wrapper.vm.SignoutClicked(); + + // Assert that isLoggedIn is set to false after sign-out + expect(wrapper.vm.isLoggedIn).to.be.false; + }); + + it("sets SSO or Credentials authentication method", () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Assert that ssoOrCredentials is set to "SSO" by default + expect(wrapper.vm.ssoOrCredentials).to.equal("SSO"); + + // Set Credentials authentication method + wrapper.vm.setSSO({ target: { value: "Credentials" } }); + + // Assert that ssoOrCredentials is set to "Credentials" + expect(wrapper.vm.ssoOrCredentials).to.equal("Credentials"); + }); + + it("should set correct button state when conditions are met", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Set enabled credentials data + await wrapper.setData({ + endpoint: "https://example.com", // Set a valid endpoint + isCFEndpointValid: true, + ssoOrCredentials: "Credentials", + username: "user", + password: "password", + }); + + // Trigger the btnStatus method + await wrapper.vm.btnStatus(); + + // Check that the button is enabled + expect(wrapper.vm.disableButton).to.be.false; + + // Set enabled sso data + await wrapper.setData({ + endpoint: "https://example.com", // Set a valid endpoint + isCFEndpointValid: true, + ssoOrCredentials: "SSO", + passcode: "passcode", + }); + + // Trigger the btnStatus method + await wrapper.vm.btnStatus(); + + // Check that the button is enabled + expect(wrapper.vm.disableButton).to.be.false; + + // Set disabled sso data + await wrapper.setData({ + endpoint: "https://example.com", // Set a valid endpoint + isCFEndpointValid: true, + ssoOrCredentials: "SSO", + passcode: "", + }); + + // Trigger the btnStatus method + await wrapper.vm.btnStatus(); + + // Check that the button is enabled + expect(wrapper.vm.disableButton).to.be.true; + }); + + it("should disable the button when conditions are not met", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Set some initial values to trigger button disabling + await wrapper.setData({ + endpoint: "", // Set an empty endpoint + isCFEndpointValid: false, + ssoOrCredentials: "Credentials", + username: "", + password: "", + }); + + // Trigger the btnStatus method + await wrapper.vm.btnStatus(); + + // Check that the button is disabled + expect(wrapper.vm.disableButton).to.be.true; + }); + + it("should set CF endpoint and validate it", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "", + isLoggedIn: false, + }, + rpc: {}, + orgAndSpaceSet: false, + }, + }); + + // Set a valid CF endpoint + const validEndpoint = "https://example.com"; + await wrapper.vm.setEndpoint({ target: { value: validEndpoint } }); + + // Check if endpoint is correctly set + expect(wrapper.vm.endpoint).to.be.equals(validEndpoint); + + // Set an invalid CF endpoint + const invalidEndpoint = "invalid-url"; + await wrapper.vm.setEndpoint({ target: { value: invalidEndpoint } }); + + // Check if endpoint is correctly set + expect(wrapper.vm.endpoint).to.be.equals(invalidEndpoint); + }); + + it("handles successful open passcode link", async () => { + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: true, + }, + rpc: { + invoke: jest.fn().mockResolvedValue(true), // Mock RPC to return success + }, + orgAndSpaceSet: false, + }, + }); + + const invokeSpy = jest.spyOn(wrapper.props().rpc, "invoke"); + + // Trigger the method that should call openPasscodeLink + await wrapper.vm.openPasscodeLink(); + + // Check that openPasscodeLink was called + expect(invokeSpy.mock.calls[0][0]).to.be.equals("openPasscodeLink"); + }); + + it("handles successful paste", async () => { + // Mock the clipboard API + navigator.clipboard = { + readText: jest.fn().mockResolvedValue("mocked-passcode"), + }; + + const wrapper = shallowMount(CFSignin, { + global, + props: { + target: { + defaultEndpoint: "https://example.com", + isLoggedIn: true, + }, + rpc: { + invoke: jest.fn().mockResolvedValue(true), // Mock RPC to return success + }, + orgAndSpaceSet: false, + }, + }); + + // Trigger the paste method + wrapper.vm.paste(); + + // Assert the passcode is set correctly + wrapper.vm.$nextTick(() => { + expect(wrapper.vm.passcode).to.be.equals("mocked-passcode"); + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js new file mode 100644 index 000000000..74630d797 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js @@ -0,0 +1,687 @@ +import CFTarget from "../../src/components/CFTarget.vue"; +import { expect } from "chai"; +import { shallowMount } from "@vue/test-utils"; + +jest.spyOn(window, "getComputedStyle").mockImplementation(() => ({ + getPropertyValue: (property) => { + if (property === "--vscode-descriptionForeground") { + return "#717171"; // Mock the CSS variable value + } + return ""; + }, +})); + +// Stub is used to avoid console warnings of vscode custom elements +const global = { + stubs: { + VscodeDivider: { + template: "
", + }, + VscodeOption: { + template: "
", + }, + VscodeButton: { + template: "", + }, + VscodeSingleSelect: { + template: "
", + }, + }, +}; + +describe("CFTarget.vue", () => { + it("renders without errors", () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: {}, + isLoggedIn: true, // Provide isLoggedIn value as needed + }, + global, + }); + expect(wrapper.exists()).to.be.true; + }); + + it("invokes setTarget method on Apply button click", async () => { + const rpcMock = { + invoke: jest.fn().mockResolvedValue(), // Mock the applyTarget method + }; + //avoid showing warning for custom element in console log. + jest.spyOn(console, "warn").mockImplementation(() => { + return; + }); + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: rpcMock, + isLoggedIn: true, + }, + global: { + stubs: { + VscodeDivider: { + template: "
", + }, + VscodeOption: { + template: "
", + }, + VscodeDropdown: { + template: "
", + }, + }, + }, + }); + + // Simulate selecting an organization and space + wrapper.setData({ + selectedOrg: { label: "Org Label" }, + selectedSpace: { label: "Space Label" }, + }); + + // Simulate clicking the "Apply" button + await wrapper.find("vscode-button").trigger("click"); + + // Add more assertions for component state updates + expect(wrapper.vm.areOrgAndSpaceSet).to.be.true; // Ensure it's set to true + expect(wrapper.vm.currentOrg).to.equal("Org Label"); // Ensure it's updated with the selected organization + expect(wrapper.vm.currentSpace).to.equal("Space Label"); // Ensure it's updated with the selected space + }); + + it("displays certain elements when logged in", () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: {}, + isLoggedIn: true, // Logged in + }, + global, + }); + + // Assert that certain elements are visible + expect(wrapper.find(".cloud-foundry-target").isVisible()).to.be.true; + expect(wrapper.vm.loggedInVisibility).to.equal(""); + }); + + it("hides certain elements when not logged in", () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: {}, + rpc: {}, + isLoggedIn: false, // Simulate not being logged in + }, + global, + }); + + // Assert that certain elements are hidden + expect(wrapper.vm.orgAndSpaceSetVisibility).to.equal("none"); + expect(wrapper.vm.loggedInVisibility).to.equal("none"); + }); + + it("populates organization dropdown", async () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: { + invoke: jest.fn().mockResolvedValue([]), // Mock the getOrgs and getSpaces methods + }, + isLoggedIn: true, + }, + global, + }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Assert that the organization dropdown is populated correctly + const orgDropdown = wrapper.find(".cf-drop-down"); // Adjust the selector as needed + expect(orgDropdown.exists()).to.be.true; + }); + + it("populates space dropdown when an organization is selected", async () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: { + invoke: jest.fn().mockResolvedValue([]), // Mock the getOrgs and getSpaces methods + }, + isLoggedIn: true, + }, + global, + }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Simulate selecting an organization + await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Assert that the space dropdown is populated correctly + const spaceDropdown = wrapper.find(".cf-drop-down"); // Adjust the selector as needed + expect(spaceDropdown.exists()).to.be.true; + }); + + it("populates organization and space dropdowns", async () => { + const rpcMock = { + invoke: jest.fn().mockResolvedValue([]), // Mock the getOrgs and getSpaces methods + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: rpcMock, + isLoggedIn: true, + }, + global, + }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Assert that the organization and space dropdowns are populated correctly + const orgDropdown = wrapper.find(".cf-drop-down"); // Adjust the selector as needed + const spaceDropdown = wrapper.findAll(".cf-drop-down"); // Adjust the selector as needed + expect(orgDropdown.exists()).to.be.true; + expect(spaceDropdown.length).to.equal(2); // You may need to adjust this based on your component's structure + }); + + it("sets currentOrg and currentSpace to undefined when target is undefined", async () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: undefined, + rpc: { + invoke: jest.fn().mockResolvedValue([]), // Mock the getOrgs and getSpaces methods + }, + isLoggedIn: true, + }, + global, + }); + await wrapper.vm.$nextTick(); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ currentOrg: "", currentSpace: "" }); + + // // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.currentOrg).to.equal(undefined); + expect(wrapper.vm.currentSpace).to.equal(undefined); + }); + + it("does select Organizaion when Organizaion is returned from selectedTarget", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([ + { guid: "47423742", label: "bbbb" }, + { guid: "32432423", label: "aaaaa" }, + ]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "32432423", org: "aaaaa" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Assert orgs got sorted + const orgsList = wrapper.vm.$data.orgs; + expect(orgsList[0]?.label).equal("aaaaa"); + expect(orgsList[1]?.label).equal("bbbb"); + expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423", selected: true }); + }); + + it("does not select Organizaion when multiple Organizaions returned and unknown Organizaion from selectedTarget", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([ + { guid: "47423742", label: "bbbb" }, + { guid: "32432423", label: "aaaaa" }, + ]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + { + label: "space2", + guid: "yyyyyyyy", + orgGUID: "47423742", + }, + ]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedOrg).to.deep.equal({}); + expect(wrapper.vm.selectedSpace).to.deep.equal({}); + }); + + it("does select Organizaion and no Space when one Organizaion is returned but multiple Spaces and unknown org from selectedTarget", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + { + label: "space2", + guid: "yyyyyyyy", + orgGUID: "47423742", + }, + ]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + expect(wrapper.vm.selectedSpace).to.deep.equal({}); + }); + + it("does select Organisaion and Space when one Organisation and one Space and unknown org from selectedTarget", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + ]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "xxxxxxxx", label: "space1", selected: false }); + }); + + it("does select Organisaion and Space when one Organisation and multiple Space and known org from selectedTarget", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "32432423", org: "aaaaa", space: "space1" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + { + label: "space2", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + ]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423", selected: true }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "xxxxxxxx", label: "space1", selected: true }); + }); + + it("does show error message when no Organisations are returned", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + await wrapper.setData({ + orgMissing: true, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Trigger watch by changing isLoggedIn prop + expect(wrapper.vm.selectedOrg).to.deep.equal({}); + expect(wrapper.vm.selectedSpace).to.deep.equal({}); + expect(wrapper.text()).contain("There is no Org defined for this landscape."); + }); + + it("does show error message when there is Organisaion but no Spaces are returned", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + await wrapper.setData({ + spaceMissing: true, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.selectedSpace).to.deep.equal({}); + expect(wrapper.text()).contain("There is no Space defined for the org you selected."); + }); + + it("updates selectedOrg when an organization is selected", async () => { + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", // Set your desired currentOrg value + currentSpace: "ExampleSpace", // Set your desired currentSpace value + }, + rpc: {}, + isLoggedIn: true, + }, + global, + }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Simulate selecting an organization + await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + + // Assert that selectedOrg is updated correctly + expect(wrapper.vm.selectedOrg).to.deep.equal({ guid: "org-guid", label: "Org Label" }); + }); + + it("selects a space when an organization is selected", async () => { + // Mock the RPC object with a custom implementation for getSpaces + const rpcMock = { + invoke: jest.fn((method, args) => { + if (method === "getSpaces" && args[0] === "org-guid") { + return Promise.resolve([ + { guid: "space-guid-1", label: "Space 1" }, + { guid: "space-guid-2", label: "Space 2" }, + ]); + } + return Promise.resolve([]); + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "ExampleOrg", + }, + rpc: rpcMock, + isLoggedIn: true, + }, + global, + }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Simulate selecting an organization + await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + // Assert that the space dropdown is populated correctly + const spaceDropdown = wrapper.find(".cf-drop-down"); + expect(spaceDropdown.exists()).to.be.true; + + // Simulate selecting a space + await wrapper.setData({ selectedSpace: { guid: "space-guid-1", label: "Space 1" } }); + + // Assert that selectedSpace is updated correctly + expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "space-guid-1", label: "Space 1" }); + }); + + it("update selectedOrg and clears selected space when Organization is changed", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([ + { guid: "32432423", label: "aaaaa" }, + { guid: "47423742", label: "bbbb" }, + ]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + }, + ]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + wrapper.vm.updateSelectedOrg({ target: { value: "bbbb" } }); + + expect(wrapper.vm.selectedOrg).to.deep.equal({ guid: "47423742", label: "bbbb", selected: false }); + expect(wrapper.vm.selectedSpace).to.deep.equal({}); + }); + + it("update selectedSpace when new space is passed", async () => { + const rpcMock = { + invoke: jest.fn().mockImplementation((funcName) => { + if (funcName === "getOrgs") { + return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); + } else if (funcName === "getSelectedTarget") { + return Promise.resolve({ guid: "00000000", org: "cccccc" }); + } else if (funcName === "getSpaces") { + return Promise.resolve([]); + } + }), + }; + + const wrapper = shallowMount(CFTarget, { + props: { + target: { + currentOrg: "aaaaa", + }, + rpc: rpcMock, + isLoggedIn: false, + }, + global, + }); + + await wrapper.setData({ + spaces: [ + { + label: "space1", + guid: "xxxxxxxx", + orgGUID: "32432423", + selected: true, + }, + { + label: "space2", + guid: "yyyyyyyy", + orgGUID: "47423742", + }, + ], + }); + // Trigger watch by changing isLoggedIn prop + await wrapper.setProps({ isLoggedIn: true }); + + // Wait for Vue to update the component + await wrapper.vm.$nextTick(); + + wrapper.vm.updateSelectedSpace({ target: { value: "space2" } }); + + expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "yyyyyyyy", label: "space2", orgGUID: "47423742" }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/frontend/vite.config.js b/projects/cloud-foundry-tools/packages/frontend/vite.config.js new file mode 100644 index 000000000..d021f8772 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/frontend/vite.config.js @@ -0,0 +1,32 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +const path = require("path"); + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + vue({ + template: { + compilerOptions: { + // Instruct the compiler to treat UI5 Web Components and VSCode Elements Web Components as custom elements. + isCustomElement: (tag) => tag.startsWith("ui5-") || tag.startsWith("vscode-"), + }, + }, + }), + { + name: "html-transform", + transformIndexHtml(html) { + // Add id="vscode-codicon-stylesheet" to the existing link tag + return html.replace(//, (match) => + match.replace(" This file contains the changelog history from the standalone [SAP/cloud-foundry-tools](https://github.com/SAP/cloud-foundry-tools) repository, prior to migration into this monorepo. New changes are recorded in this package's `CHANGELOG.md`. + +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [8.1.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.2) (2026-06-21) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [8.1.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.1.0...v8.1.1) (2026-06-21) + +**Note:** Version bump only for package vscode-wing-cf-tools + +# [8.1.0](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.2...v8.1.0) (2026-02-11) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [8.0.2](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.1...v8.0.2) (2025-12-09) + +**Note:** Version bump only for package vscode-wing-cf-tools + +## [8.0.1](https://github.com/SAP/cloud-foundry-tools/compare/v8.0.0...v8.0.1) (2025-08-24) + +**Note:** Version bump only for package vscode-wing-cf-tools diff --git a/projects/cloud-foundry-tools/packages/wrapper/README.md b/projects/cloud-foundry-tools/packages/wrapper/README.md new file mode 100644 index 000000000..9af482727 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/wrapper/README.md @@ -0,0 +1,5 @@ +# Cloud Foundry Tools Wrapper Extension + +> **⚠️ This extension is deprecated.** + +Please use [`saposs.vscode-cf-tools`](https://marketplace.visualstudio.com/items?itemName=saposs.vscode-cf-tools) instead. diff --git a/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js new file mode 100644 index 000000000..3f6741d6d --- /dev/null +++ b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js @@ -0,0 +1,9 @@ +const baseNycConfig = require("../../nyc.config"); + +module.exports = Object.assign(baseNycConfig, { + branches: 100, + lines: 100, + functions: 100, + statements: 100, + include: ["**/src/**"], +}); diff --git a/projects/cloud-foundry-tools/packages/wrapper/package.json b/projects/cloud-foundry-tools/packages/wrapper/package.json new file mode 100644 index 000000000..e54ca3fd6 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/wrapper/package.json @@ -0,0 +1,70 @@ +{ + "name": "vscode-wing-cf-tools", + "displayName": "Cloud Foundry Tools", + "version": "8.1.2", + "private": true, + "description": "Wrapper for vscode extension of Cloud Foundry tools", + "categories": [ + "Other" + ], + "bugs": { + "url": "https://github.com/SAP/cloud-foundry-tools.git/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/SAP/cloud-foundry-tools.git" + }, + "license": "Apache 2.0", + "publisher": "SAP", + "main": "./dist/extension.js", + "scripts": { + "build": "npm-run-all clean compile webpack-dev", + "bundle": "webpack --mode production", + "ci": "npm-run-all clean compile coverage bundle package", + "ci:artifacts_only": "npm-run-all clean compile webpack-dev package", + "clean": "npm-run-all clean:dist clean:out clean:vsix", + "clean:dist": "rimraf ./dist", + "clean:out": "rimraf ./out", + "clean:vsix": "rimraf ./*.vsix", + "compile": "tsc -p ./", + "coverage": "nyc mocha --colors", + "package": "vsce package --yarn", + "test": "mocha --colors", + "watch": "tsc -watch -p ./", + "webpack-dev": "webpack --mode development", + "webpack-dev:watch": "webpack --mode development --watch" + }, + "activationEvents": [ + "onStartupFinished" + ], + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "config": { + "commitizen": { + "path": "cz-conventional-changelog" + } + }, + "extensionDependencies": [ + "saposs.vscode-cf-tools" + ], + "devDependencies": { + "@types/node": "^14.14.6", + "@types/sinon": "^17.0.3", + "@types/vscode": "^1.43.0", + "@vscode-logging/types": "^2.0.0", + "mocha": "^9.2.2", + "sinon": "^19.0.2", + "ts-loader": "^9.2.8", + "ts-node": "^10.7.0", + "vsce": "1.100.1", + "webpack": "^5.94.0", + "webpack-cli": "^4.9.2" + }, + "engines": { + "vscode": "^1.50.0" + }, + "icon": "resources/icon.png" +} diff --git a/projects/cloud-foundry-tools/packages/wrapper/resources/icon.png b/projects/cloud-foundry-tools/packages/wrapper/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8ee6692f7f7d2a1dd85a310439d940027e6a59 GIT binary patch literal 7254 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 { + let sandbox: sinon.SinonSandbox; + + before(() => { + sandbox = createSandbox(); + }); + + beforeEach(() => {}); + + afterEach(() => { + sandbox.restore(); + }); + + describe("activate", () => { + it("activation test", () => { + expect(activate()).to.be.undefined; + }); + }); + + describe("deactivate", () => { + it("deactivation test", () => { + expect(deactivate()).to.be.undefined; + }); + }); +}); diff --git a/projects/cloud-foundry-tools/packages/wrapper/tsconfig.json b/projects/cloud-foundry-tools/packages/wrapper/tsconfig.json new file mode 100644 index 000000000..ded225140 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/wrapper/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "rootDir": ".", + "rootDirs": ["./src", "./tests"], + "useUnknownInCatchVariables": false + } +} diff --git a/projects/cloud-foundry-tools/packages/wrapper/webpack.config.js b/projects/cloud-foundry-tools/packages/wrapper/webpack.config.js new file mode 100644 index 000000000..7bfaa1534 --- /dev/null +++ b/projects/cloud-foundry-tools/packages/wrapper/webpack.config.js @@ -0,0 +1,31 @@ +const path = require("path"); +const baseConfig = require("../../webpack.config.vscode.base"); + +const config = Object.assign({}, baseConfig, { + entry: "./out/src/extension.js", + output: { + path: path.resolve(__dirname, "dist"), + filename: "extension.js", + libraryTarget: "commonjs2", + devtoolModuleFilenameTemplate: "../[resource-path]", + }, + module: { + // https://webpack.js.org/configuration/module/#modulenoparse + // used to avoid transforming native require usage in `optional-require` implementation + noParse: /native-require\.(js|ts)$/, + }, + // 📖 -> https://webpack.js.org/configuration/externals/ + externals: { + // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed. + vscode: "commonjs vscode", + // To enable bundling for @sap/artifact-management + // fsevents is a macos file system event library that is compiled during installation. + fsevents: "commonjs fsevents", + }, + node: { + // needed to bundle artifact-management successfully + __dirname: false, + }, +}); + +module.exports = config; From b46d850dedd02a1c75247b5d5f9fffdaaf2efab2 Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:14:08 +0200 Subject: [PATCH 02/11] fix(cloud-foundry-tools): wire package into pnpm monorepo toolchain - add projects/cloud-foundry-tools/tsconfig.base.json (extends root base, skipLibCheck) - backend: declare @types/rimraf (yarn-hoisted in source, pnpm needs it explicit) - frontend: declare lodash (used but undeclared, relied on yarn hoist) - repository fields point at the monorepo - regenerate lockfile Signed-off-by: Jacob Kreyenbuehl --- pnpm-lock.yaml | 2166 +++++++++++++---- .../packages/backend/package.json | 4 +- .../packages/frontend/package.json | 6 + .../cloud-foundry-tools/tsconfig.base.json | 6 + 4 files changed, 1736 insertions(+), 446 deletions(-) create mode 100644 projects/cloud-foundry-tools/tsconfig.base.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67254903d..d661ebee2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -570,6 +570,194 @@ importers: specifier: 27.5.1 version: 27.5.1(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3))(utf-8-validate@5.0.10) + projects/cloud-foundry-tools/packages/backend: + dependencies: + '@sap-devx/webview-rpc': + specifier: 0.4.1 + version: 0.4.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) + '@sap/bas-sdk': + specifier: ^3.13.2 + version: 3.13.7(bufferutil@4.1.0)(utf-8-validate@5.0.10) + '@sap/cf-tools': + specifier: ^3.2.2 + version: 3.3.1 + '@sap/swa-for-sapbas-vsx': + specifier: ^2.0.7 + version: 2.0.16(bufferutil@4.1.0)(utf-8-validate@5.0.10) + '@sap/wing-run-config-types': + specifier: ^4.2.0 + version: 4.2.0 + '@vscode-logging/types': + specifier: 2.0.0 + version: 2.0.0 + '@vscode-logging/wrapper': + specifier: 2.0.0 + version: 2.0.0 + cheerio: + specifier: ^1.0.0 + version: 1.2.0 + comment-json: + specifier: ^4.2.2 + version: 4.2.3 + lodash: + specifier: 4.17.21 + version: 4.17.21 + properties-reader: + specifier: 2.3.0 + version: 2.3.0 + devDependencies: + '@types/cheerio': + specifier: ^0.22.35 + version: 0.22.35 + '@types/comment-json': + specifier: 2.4.2 + version: 2.4.2 + '@types/node': + specifier: ^14.14.6 + version: 14.18.63 + '@types/properties-reader': + specifier: ^2.1.3 + version: 2.1.3 + '@types/proxyquire': + specifier: ^1.3.28 + version: 1.3.31 + '@types/rimraf': + specifier: 3.0.0 + version: 3.0.0 + '@types/sinon': + specifier: ^17.0.3 + version: 17.0.4 + '@types/vscode': + specifier: ^1.43.0 + version: 1.75.0 + clean-webpack-plugin: + specifier: ^4.0.0 + version: 4.0.0(webpack@5.106.2) + mocha: + specifier: ^9.2.2 + version: 9.2.2 + proxyquire: + specifier: 2.1.3 + version: 2.1.3 + sinon: + specifier: ^19.0.2 + version: 19.0.5 + standard-version: + specifier: ^9.5.0 + version: 9.5.0 + temp-dir: + specifier: ^2.0.0 + version: 2.0.0 + ts-loader: + specifier: ^9.2.8 + version: 9.6.2(loader-utils@2.0.4)(typescript@5.7.3)(webpack@5.106.2) + ts-node: + specifier: ^10.7.0 + version: 10.9.2(@types/node@14.18.63)(typescript@5.7.3) + vsce: + specifier: 1.100.1 + version: 1.100.1 + webpack: + specifier: ^5.94.0 + version: 5.106.2(webpack-cli@4.10.0) + webpack-cli: + specifier: ^4.9.2 + version: 4.10.0(webpack@5.106.2) + + projects/cloud-foundry-tools/packages/frontend: + dependencies: + '@sap-devx/webview-rpc': + specifier: 0.4.1 + version: 0.4.1(bufferutil@4.1.0)(utf-8-validate@5.0.10) + '@vscode-elements/elements': + specifier: 1.16.1 + version: 1.16.1 + lodash: + specifier: 4.17.21 + version: 4.17.21 + vue: + specifier: 3.3.8 + version: 3.3.8(typescript@5.7.3) + vue-loader: + specifier: 17.2.2 + version: 17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.3.8(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) + vue-loading-overlay: + specifier: 6.0.3 + version: 6.0.3(vue@3.3.8(typescript@5.7.3)) + devDependencies: + '@babel/preset-env': + specifier: ^7.22.20 + version: 7.29.7(@babel/core@7.29.0) + '@babel/preset-typescript': + specifier: ^7.23.0 + version: 7.29.7(@babel/core@7.29.0) + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.10.0(@testing-library/dom@9.3.4) + '@vitejs/plugin-vue': + specifier: ^5.2.0 + version: 5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.3.8(typescript@5.7.3)) + '@vue/compiler-sfc': + specifier: 3.3.8 + version: 3.3.8 + '@vue/test-utils': + specifier: 2.4.1 + version: 2.4.1(@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3)))(vue@3.3.8(typescript@5.7.3)) + '@vue/vue3-jest': + specifier: 29.2.6 + version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.3.8(typescript@5.7.3)) + eslint-plugin-vue: + specifier: 9.18.1 + version: 9.18.1(eslint@8.57.1) + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + ts-jest: + specifier: 29.1.1 + version: 29.1.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3) + vite: + specifier: ^6.0.6 + version: 6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) + + projects/cloud-foundry-tools/packages/wrapper: + devDependencies: + '@types/node': + specifier: ^14.14.6 + version: 14.18.63 + '@types/sinon': + specifier: ^17.0.3 + version: 17.0.4 + '@types/vscode': + specifier: ^1.43.0 + version: 1.75.0 + '@vscode-logging/types': + specifier: ^2.0.0 + version: 2.0.10 + mocha: + specifier: ^9.2.2 + version: 9.2.2 + sinon: + specifier: ^19.0.2 + version: 19.0.5 + ts-loader: + specifier: ^9.2.8 + version: 9.6.2(loader-utils@2.0.4)(typescript@5.7.3)(webpack@5.106.2) + ts-node: + specifier: ^10.7.0 + version: 10.9.2(@types/node@14.18.63)(typescript@5.7.3) + vsce: + specifier: 1.100.1 + version: 1.100.1 + webpack: + specifier: ^5.94.0 + version: 5.106.2(webpack-cli@4.10.0) + webpack-cli: + specifier: ^4.9.2 + version: 4.10.0(webpack@5.106.2) + projects/feature-toggle-node/packages/feature-toggle-node: dependencies: node-cache: @@ -879,7 +1067,7 @@ importers: version: 5.9.55 '@sap-devx/inquirer-gui': specifier: 3.0.0 - version: 3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + version: 3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(webpack-plugin-vuetify@2.0.1) '@sap-devx/inquirer-gui-file-browser-plugin': specifier: 3.0.0 version: 3.0.0(typescript@5.7.3) @@ -906,7 +1094,7 @@ importers: version: 6.0.6(vue@3.5.34(typescript@5.7.3)) vuetify: specifier: ^3.3.10 - version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) devDependencies: '@babel/preset-env': specifier: ^7.9.5 @@ -1272,7 +1460,7 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) vue: specifier: ^3.3.4 version: 3.5.34(typescript@5.7.3) @@ -1321,7 +1509,7 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) vue: specifier: ^3.3.4 version: 3.5.34(typescript@5.7.3) @@ -1468,7 +1656,7 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) vue: specifier: ^3.3.4 version: 3.5.34(typescript@5.7.3) @@ -2329,22 +2517,22 @@ importers: version: 5.9.55 '@sap-devx/inquirer-gui': specifier: 3.4.14 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) '@sap-devx/inquirer-gui-auto-complete-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3)(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0)) '@sap-devx/inquirer-gui-file-browser-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) '@sap-devx/inquirer-gui-folder-browser-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) '@sap-devx/inquirer-gui-label-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3) '@sap-devx/inquirer-gui-login-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) '@sap-devx/inquirer-gui-radio-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3) @@ -2374,7 +2562,7 @@ importers: version: 6.0.6(vue@3.5.34(typescript@5.7.3)) vuetify: specifier: ^3.5.7 - version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) + version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3)) devDependencies: '@babel/core': specifier: ^7.22.0 @@ -2456,11 +2644,11 @@ importers: dependencies: yeoman-environment: specifier: 3.19.3 - version: 3.19.3(@types/node@22.20.1) + version: 3.19.3(@types/node@14.18.63) devDependencies: '@types/yeoman-environment': specifier: 2.10.11 - version: 2.10.11(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4) + version: 2.10.11(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4) packages: @@ -2728,10 +2916,6 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} @@ -2965,12 +3149,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.29.7': resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} @@ -4275,6 +4453,10 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@hutson/parse-repository-url@3.0.2': + resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} + engines: {node: '>=6.9.0'} + '@inquirer/ansi@2.0.7': resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==} engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'} @@ -5429,6 +5611,9 @@ packages: '@sap/cf-tools@3.2.0': resolution: {integrity: sha512-iD/ttsBzKAqLShPuB4FsNcO6d5/Bl0gMSb/MXw2lYOGZE91IJ8PQDk1OF27fJ4e5ycK33ZkuD2xGxJygCBF7wQ==} + '@sap/cf-tools@3.3.1': + resolution: {integrity: sha512-hGrpVEoTxUC3IdfwCE30Pm4JV+hNXrJDfYzu7VspZglEZSPnut+MzCnzodRacFzRJ8qAP0MPvrr5PCFMmuBckA==} + '@sap/mta-lib@1.7.4': resolution: {integrity: sha512-NBtcVKipYrw9uCiWwH2QVfaVeJC3OV6spAE29TsUKjGzqUrSzMkFi8FtsikvDDDThtysjmcvWN8SDvkK3eQxmg==} engines: {node: '>=8.0.0'} @@ -5442,6 +5627,10 @@ packages: '@sap/swa-for-sapbas-vsx@2.0.4': resolution: {integrity: sha512-jG3PU68go30PvCtBfuzjAuWn3eY+2BNuk+v9KqbjUKYEI4ZyxucmrXTF7nrlcosUzUuKnO2//ujJ9T/IE1Wbgw==} + '@sap/wing-run-config-types@4.2.0': + resolution: {integrity: sha512-4pwFxv6APiiWkVZRs1nLrQgYu91ojIK0qEKgXEIhJfGiUQyuHmCatBC3bblUOtd1wMTjbtwE2MsxOK9dBToziA==} + engines: {vscode: ^1.36.0} + '@sap_oss/task_contrib_types@3.1.0': resolution: {integrity: sha512-kqGkb8hNCbqv05qY37peOtQFsjRoSbJvwTi+PAAkzfUrGEmKinJWXAVqGIoPKM1KYCSNWDEpSU7uEjoYXykrJg==} @@ -5584,12 +5773,21 @@ packages: '@sinonjs/fake-timers@11.3.1': resolution: {integrity: sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==} + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} + '@sinonjs/fake-timers@8.1.0': resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==} '@sinonjs/samsam@8.0.0': resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} + '@sinonjs/samsam@8.0.3': + resolution: {integrity: sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==} + '@sinonjs/text-encoding@0.7.3': resolution: {integrity: sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==} deprecated: |- @@ -5637,6 +5835,13 @@ packages: vitest: optional: true + '@testing-library/jest-dom@6.10.0': + resolution: {integrity: sha512-HQwu0KaB2zyT0iLzBL+8CLyZDL3KlZlZJ+2iyc9uCUnlJVskJU/UlPuVCyIPhtukjPQdT2QNoR5nCP5FqTmmDQ==} + engines: {node: '>=22', npm: '>=6', yarn: '>=1'} + deprecated: Incorrect minor release with breaking changes (Node >=22 and required @testing-library/dom peer). Use 6.9.1 for the 6.x line, or upgrade to 7.0.0. + peerDependencies: + '@testing-library/dom': '>=10 <11' + '@testing-library/vue@8.0.1': resolution: {integrity: sha512-l51ZEpjTQ6glq3wM+asQ1GbKJMGcxwgHEygETx0aCRN4TjFEGvMZy4YdWKs/y7bu4bmLrxcxhbEPP7iPSW/2OQ==} engines: {node: '>=14'} @@ -5950,6 +6155,9 @@ packages: '@types/prettier@2.7.3': resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + '@types/properties-reader@2.1.3': + resolution: {integrity: sha512-k4fBDScfZ9xQjIrZm0HcJlyWVZ5ltE9W8N2AIecsgFXfg5REhKKHEwmpRvSQvEPWnIEsL67K70P1tx7kGo+cjQ==} + '@types/proxyquire@1.3.31': resolution: {integrity: sha512-uALowNG2TSM1HNPMMOR0AJwv4aPYPhqB0xlEhkeRTMuto5hjoSPZkvgu1nbPUkz3gEPAHv4sy4DmKsurZiEfRQ==} @@ -5965,6 +6173,9 @@ packages: '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/rimraf@3.0.0': + resolution: {integrity: sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==} + '@types/rimraf@3.0.2': resolution: {integrity: sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==} @@ -6207,6 +6418,9 @@ packages: resolution: {integrity: sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==} engines: {node: '>=18.0.0'} + '@ungap/promise-all-settled@1.1.2': + resolution: {integrity: sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==} + '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} @@ -6234,6 +6448,9 @@ packages: '@vscode-elements/elements@1.11.0': resolution: {integrity: sha512-vC1QDaelqERypHinavJbe4Bl4/g66CVJWKFWFXZyp1seBvTxkgscayDTiE//H95V8b75BgLPDAS1y3pbSIKXag==} + '@vscode-elements/elements@1.16.1': + resolution: {integrity: sha512-AhNPWjEAmPvbya2mWdr/wxx+Iqrg96xFDZr6uM35Q7QjSDwewxI38WdCakXclUZCY2PhedMtbcNXRSdcBl5yIQ==} + '@vscode-logging/logger@2.0.10': resolution: {integrity: sha512-Mg8aW5Bkim3p4vF2S+n5eEKzg/OBSdS9iom3GmmBUGtmZ07WlOROGYm+2i2sN0R05wnqoafuJbN9LeCwMuZIbw==} @@ -6246,12 +6463,18 @@ packages: '@vscode-logging/types@0.1.4': resolution: {integrity: sha512-uxuHQfpX9RbkgSj5unJFmciXRczyFSaAI2aA829MYYkE8jxlhZLRLoiJLymTNiojNVdV7fFE3CILF5Q6M+EBsA==} + '@vscode-logging/types@2.0.0': + resolution: {integrity: sha512-P42r5SPYeJKgMDYb5Ez9rjPlpnGEZ1eDFVjT0azxueaJ65iE259hpROmvSPUd80HAALn9/59L+CgcGLmwZcCmg==} + '@vscode-logging/types@2.0.10': resolution: {integrity: sha512-Y8Qn0Xc7ej8BC+cbDAi5LUKJs3qMGni0zUGZgqhDrXq3sQS5SQ2KG4sk320Si2rdL/nqivJ/gaEAcxD61m1+3w==} '@vscode-logging/types@2.0.8': resolution: {integrity: sha512-9mc5+Iob1BqJ00QExLwdz4+w0A1odSYjvzpKYfP0lVzr7Tdf4iWGspLADyTSC7DtJVQQ+tu411cBju+cYJxE/g==} + '@vscode-logging/wrapper@2.0.0': + resolution: {integrity: sha512-XA8T61WL3vsrfEMUog1km8USEgnD/3H2qXk7fizFzuEFqNRGpVq9AYjxSUuBMsffIdtwfAOFYZJ8HIwzy+cGjA==} + '@vscode-logging/wrapper@2.0.9': resolution: {integrity: sha512-kTx73Ev1O85oS51s6CnIep/F02uwTGrpuUZMi5/uMaxcU8AdzYWpHVeD1T1BuvInaQhHdADzVnF2gpehKYUbog==} @@ -6470,6 +6693,9 @@ packages: '@vue/compiler-core@3.3.4': resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==} + '@vue/compiler-core@3.3.8': + resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==} + '@vue/compiler-core@3.4.3': resolution: {integrity: sha512-u8jzgFg0EDtSrb/hG53Wwh1bAOQFtc1ZCegBpA/glyvTlgHl+tq13o1zvRfLbegYUw/E4mSTGOiCnAJ9SJ+lsg==} @@ -6479,6 +6705,9 @@ packages: '@vue/compiler-dom@3.3.4': resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==} + '@vue/compiler-dom@3.3.8': + resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==} + '@vue/compiler-dom@3.4.3': resolution: {integrity: sha512-oGF1E9/htI6JWj/lTJgr6UgxNCtNHbM6xKVreBWeZL9QhRGABRVoWGAzxmtBfSOd+w0Zi5BY0Es/tlJrN6WgEg==} @@ -6488,6 +6717,9 @@ packages: '@vue/compiler-sfc@3.3.4': resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} + '@vue/compiler-sfc@3.3.8': + resolution: {integrity: sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==} + '@vue/compiler-sfc@3.4.3': resolution: {integrity: sha512-NuJqb5is9I4uzv316VRUDYgIlPZCG8D+ARt5P4t5UDShIHKL25J3TGZAUryY/Aiy0DsY7srJnZL5ryB6DD63Zw==} @@ -6497,6 +6729,9 @@ packages: '@vue/compiler-ssr@3.3.4': resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==} + '@vue/compiler-ssr@3.3.8': + resolution: {integrity: sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==} + '@vue/compiler-ssr@3.4.3': resolution: {integrity: sha512-wnYQtMBkeFSxgSSQbYGQeXPhQacQiog2c6AlvMldQH6DB+gSXK/0F6DVXAJfEiuBSgBhUc8dwrrG5JQcqwalsA==} @@ -6509,24 +6744,41 @@ packages: '@vue/reactivity-transform@3.3.4': resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==} + '@vue/reactivity-transform@3.3.8': + resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==} + + '@vue/reactivity@3.3.8': + resolution: {integrity: sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==} + '@vue/reactivity@3.4.3': resolution: {integrity: sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==} '@vue/reactivity@3.5.34': resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} + '@vue/runtime-core@3.3.8': + resolution: {integrity: sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==} + '@vue/runtime-core@3.4.3': resolution: {integrity: sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==} '@vue/runtime-core@3.5.34': resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} + '@vue/runtime-dom@3.3.8': + resolution: {integrity: sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==} + '@vue/runtime-dom@3.4.3': resolution: {integrity: sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==} '@vue/runtime-dom@3.5.34': resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} + '@vue/server-renderer@3.3.8': + resolution: {integrity: sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==} + peerDependencies: + vue: 3.3.8 + '@vue/server-renderer@3.4.3': resolution: {integrity: sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==} peerDependencies: @@ -6540,6 +6792,9 @@ packages: '@vue/shared@3.3.4': resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==} + '@vue/shared@3.3.8': + resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==} + '@vue/shared@3.4.3': resolution: {integrity: sha512-rIwlkkP1n4uKrRzivAKPZIEkHiuwY5mmhMJ2nZKCBLz8lTUlE73rQh4n1OnnMurXt1vcUNyH4ZPfdh8QweTjpQ==} @@ -6655,6 +6910,12 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@webpack-cli/configtest@1.2.0': + resolution: {integrity: sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==} + peerDependencies: + webpack: 4.x.x || 5.x.x + webpack-cli: 4.x.x + '@webpack-cli/configtest@2.1.1': resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} engines: {node: '>=14.15.0'} @@ -6669,6 +6930,11 @@ packages: webpack: ^5.82.0 webpack-cli: 6.x.x + '@webpack-cli/info@1.5.0': + resolution: {integrity: sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==} + peerDependencies: + webpack-cli: 4.x.x + '@webpack-cli/info@2.0.2': resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} engines: {node: '>=14.15.0'} @@ -6683,6 +6949,15 @@ packages: webpack: ^5.82.0 webpack-cli: 6.x.x + '@webpack-cli/serve@1.7.0': + resolution: {integrity: sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==} + peerDependencies: + webpack-cli: 4.x.x + webpack-dev-server: '*' + peerDependenciesMeta: + webpack-dev-server: + optional: true + '@webpack-cli/serve@2.0.5': resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} engines: {node: '>=14.15.0'} @@ -6823,6 +7098,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + add-stream@1.0.0: + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + address@1.2.2: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} @@ -7612,6 +7890,10 @@ packages: resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==} engines: {node: '>=0.10.0'} + camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + camelcase@2.1.1: resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} engines: {node: '>=0.10.0'} @@ -7724,6 +8006,10 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} + chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -7769,6 +8055,12 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + clean-webpack-plugin@4.0.0: + resolution: {integrity: sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w==} + engines: {node: '>=10.0.0'} + peerDependencies: + webpack: '>=4.0.0 <6.0.0' + cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -7978,6 +8270,10 @@ packages: resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} engines: {node: '>= 6'} + comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + common-ancestor-path@1.0.1: resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} @@ -8005,6 +8301,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + condense-newlines@0.2.1: resolution: {integrity: sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==} engines: {node: '>=0.10.0'} @@ -8203,19 +8503,97 @@ packages: continuation-local-storage@3.2.1: resolution: {integrity: sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==} + conventional-changelog-angular@5.0.13: + resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} + engines: {node: '>=10'} + conventional-changelog-angular@7.0.0: resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} engines: {node: '>=16'} + conventional-changelog-atom@2.0.8: + resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-codemirror@2.0.8: + resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-config-spec@2.1.0: + resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} + + conventional-changelog-conventionalcommits@4.6.3: + resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} + engines: {node: '>=10'} + conventional-changelog-conventionalcommits@7.0.2: resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} engines: {node: '>=16'} + conventional-changelog-core@4.2.4: + resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} + engines: {node: '>=10'} + deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead. + + conventional-changelog-ember@2.0.9: + resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-eslint@3.0.9: + resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-express@2.0.6: + resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-jquery@3.0.11: + resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-jshint@2.0.9: + resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} + engines: {node: '>=10'} + deprecated: This preset is deprecated. Please use conventional-changelog-conventionalcommits or conventional-changelog-angular instead. + + conventional-changelog-preset-loader@2.3.4: + resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} + engines: {node: '>=10'} + + conventional-changelog-writer@5.0.1: + resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} + engines: {node: '>=10'} + hasBin: true + + conventional-changelog@3.1.25: + resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} + engines: {node: '>=10'} + + conventional-commits-filter@2.0.7: + resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} + engines: {node: '>=10'} + + conventional-commits-parser@3.2.4: + resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} + engines: {node: '>=10'} + hasBin: true + conventional-commits-parser@5.0.0: resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} engines: {node: '>=16'} hasBin: true + conventional-recommended-bump@6.1.0: + resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} + engines: {node: '>=10'} + hasBin: true + convert-source-map@1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} @@ -8556,6 +8934,15 @@ packages: supports-color: optional: true + debug@4.3.3: + resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -8590,6 +8977,10 @@ packages: decache@4.6.2: resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==} + decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -8709,6 +9100,10 @@ packages: resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==} engines: {node: '>=0.10.0'} + del@4.1.1: + resolution: {integrity: sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==} + engines: {node: '>=6'} + delay@5.0.0: resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} engines: {node: '>=10'} @@ -8794,10 +9189,18 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} + diff@5.0.0: + resolution: {integrity: sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==} + engines: {node: '>=0.3.1'} + diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} + diff@7.0.0: + resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} + engines: {node: '>=0.3.1'} + diff@9.0.0: resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} engines: {node: '>=0.3.1'} @@ -8828,6 +9231,9 @@ packages: dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-converter@0.2.0: resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} @@ -8885,6 +9291,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dotgitignore@2.1.0: + resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} + engines: {node: '>=6'} + download-stats@0.3.4: resolution: {integrity: sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==} engines: {node: '>=0.10.0'} @@ -9248,6 +9658,12 @@ packages: peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + eslint-plugin-vue@9.18.1: + resolution: {integrity: sha512-7hZFlrEgg9NIzuVik2I9xSnJA5RsmOfueYgsUGUokEDLJ1LHtxO0Pl4duje1BriZ/jDWb+44tcIlC3yi0tdlZg==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 + eslint-plugin-vue@9.33.0: resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} engines: {node: ^14.17.0 || >=16.0.0} @@ -9619,6 +10035,10 @@ packages: resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} engines: {node: '>=0.10.0'} + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + find-up@3.0.0: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} @@ -9874,6 +10294,11 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-pkg-repo@4.2.1: + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} + hasBin: true + get-port@7.0.0: resolution: {integrity: sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==} engines: {node: '>=16'} @@ -9929,12 +10354,31 @@ packages: resolution: {integrity: sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==} engines: {node: '>=4'} + git-raw-commits@2.0.11: + resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} + engines: {node: '>=10'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. + hasBin: true + git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true + git-remote-origin-url@2.0.0: + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} + + git-semver-tags@4.1.1: + resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} + engines: {node: '>=10'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. + hasBin: true + + gitconfiglocal@1.0.0: + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -10045,6 +10489,10 @@ packages: resolution: {integrity: sha512-JmsqJalahxxgW8V2ecSQ2G7UjPlI9cpKdrkG9KoNiXhd/YslXOTEB0cViENWUznuovIuNT+FkMbraDGjr4FCUg==} engines: {node: '>=20'} + globby@6.1.0: + resolution: {integrity: sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==} + engines: {node: '>=0.10.0'} + globby@8.0.2: resolution: {integrity: sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==} engines: {node: '>=4'} @@ -10084,6 +10532,10 @@ packages: resolution: {integrity: sha512-c5NDCWO0XiXuJAhOegMiNotkDmgORN+VNo3+YHMhWpoWG/u2+8im8byqsOe3/myI9YcC//plRdqGa2AE3Qsdjw==} engines: {node: '>=8.0.0'} + growl@1.10.5: + resolution: {integrity: sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==} + engines: {node: '>=4.x'} + gzip-size@6.0.0: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} @@ -10108,6 +10560,10 @@ packages: engines: {node: '>=6'} deprecated: this library is no longer supported + hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -10536,6 +10992,10 @@ packages: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} + interpret@2.2.0: + resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} + engines: {node: '>= 0.10'} + interpret@3.1.1: resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} engines: {node: '>=10.13.0'} @@ -10728,6 +11188,18 @@ packages: is-object@1.0.2: resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-in-cwd@2.1.0: + resolution: {integrity: sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==} + engines: {node: '>=6'} + + is-path-inside@2.1.0: + resolution: {integrity: sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==} + engines: {node: '>=6'} + is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} @@ -10823,6 +11295,10 @@ packages: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} + is-text-path@1.0.1: + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} + is-text-path@2.0.0: resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} engines: {node: '>=8'} @@ -11693,6 +12169,10 @@ packages: resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} engines: {node: '>=8.9.0'} + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -11741,6 +12221,9 @@ packages: lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + lodash.ismatch@4.4.0: + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + lodash.isnumber@3.0.3: resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} @@ -11921,6 +12404,10 @@ packages: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} + map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + map-visit@1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} engines: {node: '>=0.10.0'} @@ -12014,6 +12501,10 @@ packages: resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} engines: {node: '>=0.10.0'} + meow@8.1.2: + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} + merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} @@ -12142,6 +12633,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@4.2.1: + resolution: {integrity: sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==} + engines: {node: '>=10'} + minimatch@5.1.6: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} @@ -12158,6 +12653,10 @@ packages: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} + minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + minimist@1.2.0: resolution: {integrity: sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==} @@ -12256,6 +12755,11 @@ packages: engines: {node: '>= 14.0.0'} hasBin: true + mocha@9.2.2: + resolution: {integrity: sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==} + engines: {node: '>= 12.0.0'} + hasBin: true + mock-require@3.0.3: resolution: {integrity: sha512-lLzfLHcyc10MKQnNUCv7dMcoY/2Qxd6wJfbqCcVk3LDb8An4hF6ohk5AztrvgKhJCqj36uyzi/p5se+tvyD+Wg==} engines: {node: '>=4.3.0'} @@ -12267,6 +12771,10 @@ packages: mock-spawn@0.2.6: resolution: {integrity: sha512-cMSfGmQlTiCF54a7taY1cUVXuuEKqDR8vLrfN7pI4LCskxwRedupMQB7o5d5Hg5L6rctvtyZJwUxMl3czTd4tg==} + modify-values@1.0.1: + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} + module-alias@2.3.4: resolution: {integrity: sha512-bOclZt8hkpuGgSSoG07PKmvzTizROilUTvLNyrMqvlC9snhs7y7GzjNWAVbISIOlhCP1T14rH1PDAV9iNyBq/w==} @@ -12321,6 +12829,11 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.1: + resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -12363,6 +12876,9 @@ packages: nise@6.0.0: resolution: {integrity: sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==} + nise@6.1.5: + resolution: {integrity: sha512-SnRDPDBjxZZoU2n0+gzzLtSvo1OZo7j6jnbXsoh3AFxEGhaFU7ZF0TmefuKERq79wxR2U+MPn7ArW+Tl+clC3A==} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -12468,6 +12984,10 @@ packages: normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + normalize-package-data@5.0.0: resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -12820,6 +13340,10 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -12832,6 +13356,10 @@ packages: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + p-locate@3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -12904,6 +13432,10 @@ packages: resolution: {integrity: sha512-tb3/zIwbU6Z9RMDxZM3/UsyL5LpIUQj7Drq7iXWG9ilPpzyGG28EEFRRrGTsxHf3sOSOiQEiwevQH/VWtHbZfg==} engines: {node: '>=18.19.0'} + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -13043,6 +13575,9 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-is-inside@1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} @@ -13076,6 +13611,9 @@ packages: path-to-regexp@6.2.2: resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + path-type@1.1.0: resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} engines: {node: '>=0.10.0'} @@ -13521,6 +14059,10 @@ packages: resolution: {integrity: sha512-CgVcr8MwGoBKK24r9TwHfZkLLaNFHQ6y4wgT9w/XzdpacOOi5ciH4hcuLechSDAwXsfrGQtI2JTutY2djOx2Ow==} engines: {node: '>=10'} + properties-reader@2.3.0: + resolution: {integrity: sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw==} + engines: {node: '>=14'} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -13567,6 +14109,14 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + q@1.5.1: + resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} + engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + qjobs@1.2.0: resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==} engines: {node: '>=0.9'} @@ -13600,6 +14150,10 @@ packages: queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + quick-lru@4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -13672,6 +14226,10 @@ packages: resolution: {integrity: sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==} engines: {node: '>=16'} + read-pkg-up@3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + read-pkg-up@4.0.0: resolution: {integrity: sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==} engines: {node: '>=6'} @@ -13746,6 +14304,10 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} + rechoir@0.7.1: + resolution: {integrity: sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==} + engines: {node: '>= 0.10'} + rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} @@ -14374,6 +14936,9 @@ packages: sinon@18.0.1: resolution: {integrity: sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==} + sinon@19.0.5: + resolution: {integrity: sha512-r15s9/s+ub/d4bxNXqIUmwp6imVSdTorIRaxoecYjqTVLZ8RuoXr/4EDGwIBo6Waxn7f2gnURX9zuhAfCwaF6Q==} + sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -14546,10 +15111,16 @@ packages: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} + split2@3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + split@1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -14603,6 +15174,11 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + standard-version@9.5.0: + resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} + engines: {node: '>=10'} + hasBin: true + static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} @@ -14704,6 +15280,10 @@ packages: resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} engines: {node: '>=4'} + stringify-package@1.0.1: + resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} + deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json + strip-ansi@3.0.1: resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} engines: {node: '>=0.10.0'} @@ -14927,6 +15507,10 @@ packages: text-decoder@1.2.7: resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + text-extensions@1.9.0: + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} + text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -14971,6 +15555,9 @@ packages: through2@3.0.2: resolution: {integrity: sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==} + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -15083,6 +15670,10 @@ packages: resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} engines: {node: '>=0.10.0'} + trim-newlines@3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + trim-right@1.0.1: resolution: {integrity: sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==} engines: {node: '>=0.10.0'} @@ -15285,6 +15876,10 @@ packages: resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} engines: {node: '>=4'} + type-fest@0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -15356,6 +15951,9 @@ packages: typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -15560,6 +16158,10 @@ packages: url@0.11.0: resolution: {integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==} + url@0.11.4: + resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} + engines: {node: '>= 0.4'} + use@3.1.1: resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==} engines: {node: '>=0.10.0'} @@ -15879,6 +16481,18 @@ packages: vue-template-compiler: optional: true + vue-loader@17.2.2: + resolution: {integrity: sha512-aqNvKJvnz2A/6VWeJZodAo8XLoAlVwBv+2Z6dama+LHsAF+P/xijQ+OfWrxIs0wcGSJduvdzvTuATzXbNKkpiw==} + peerDependencies: + '@vue/compiler-sfc': '*' + vue: '*' + webpack: ^4.1.0 || ^5.0.0-0 + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + vue: + optional: true + vue-loader@17.4.2: resolution: {integrity: sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==} peerDependencies: @@ -15912,6 +16526,14 @@ packages: vue-template-es2015-compiler@1.9.1: resolution: {integrity: sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==} + vue@3.3.8: + resolution: {integrity: sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + vue@3.4.3: resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==} peerDependencies: @@ -16023,6 +16645,26 @@ packages: engines: {node: '>=8'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + webpack-cli@4.10.0: + resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + '@webpack-cli/generators': '*' + '@webpack-cli/migrate': '*' + webpack: 4.x.x || 5.x.x + webpack-bundle-analyzer: '*' + webpack-dev-server: '*' + peerDependenciesMeta: + '@webpack-cli/generators': + optional: true + '@webpack-cli/migrate': + optional: true + webpack-bundle-analyzer: + optional: true + webpack-dev-server: + optional: true + webpack-cli@5.1.4: resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} engines: {node: '>=14.15.0'} @@ -16274,6 +16916,9 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + workerpool@6.2.0: + resolution: {integrity: sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==} + workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} @@ -16445,6 +17090,10 @@ packages: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} + yargs-parser@20.2.4: + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -16807,7 +17456,7 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: '@babel/compat-data': 7.29.3 - '@babel/helper-validator-option': 7.27.1 + '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -16975,8 +17624,6 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-validator-option@7.29.7': {} '@babel/helper-wrap-function@7.28.6': @@ -17066,7 +17713,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) transitivePeerDependencies: @@ -17076,7 +17723,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -17092,37 +17739,37 @@ snapshots: '@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0)': @@ -17130,20 +17777,20 @@ snapshots: '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) transitivePeerDependencies: @@ -17153,7 +17800,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -17165,12 +17812,12 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: @@ -17180,7 +17827,7 @@ snapshots: '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: @@ -17200,7 +17847,7 @@ snapshots: '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17218,11 +17865,6 @@ snapshots: '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 @@ -17235,32 +17877,32 @@ snapshots: '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: @@ -17270,7 +17912,7 @@ snapshots: '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: @@ -17291,7 +17933,7 @@ snapshots: '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17311,7 +17953,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -17328,7 +17970,7 @@ snapshots: '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17338,7 +17980,7 @@ snapshots: '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17367,7 +18009,7 @@ snapshots: '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) '@babel/traverse': 7.29.7 transitivePeerDependencies: @@ -17388,7 +18030,7 @@ snapshots: '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/template': 7.29.7 '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.0)': @@ -17400,7 +18042,7 @@ snapshots: '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -17417,7 +18059,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17428,7 +18070,7 @@ snapshots: '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17457,7 +18099,7 @@ snapshots: '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17472,7 +18114,7 @@ snapshots: '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color @@ -17489,7 +18131,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -17511,7 +18153,7 @@ snapshots: '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17526,7 +18168,7 @@ snapshots: '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17537,7 +18179,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -17553,7 +18195,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -17569,7 +18211,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 '@babel/traverse': 7.29.7 transitivePeerDependencies: @@ -17589,7 +18231,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color @@ -17605,7 +18247,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17616,7 +18258,7 @@ snapshots: '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17647,7 +18289,7 @@ snapshots: '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -17676,7 +18318,7 @@ snapshots: '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17703,7 +18345,7 @@ snapshots: '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17713,7 +18355,7 @@ snapshots: '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-regenerator@7.29.8(@babel/core@7.29.0)': dependencies: @@ -17729,7 +18371,7 @@ snapshots: '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17751,7 +18393,7 @@ snapshots: '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17761,7 +18403,7 @@ snapshots: '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color @@ -17777,7 +18419,7 @@ snapshots: '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17787,7 +18429,7 @@ snapshots: '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17797,7 +18439,7 @@ snapshots: '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17818,7 +18460,7 @@ snapshots: '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17835,7 +18477,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18003,7 +18645,7 @@ snapshots: '@babel/preset-modules@0.1.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.29.0) '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) '@babel/types': 7.29.7 @@ -18060,7 +18702,7 @@ snapshots: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 '@babel/types': 7.29.7 debug: 4.4.3 @@ -18677,6 +19319,8 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@hutson/parse-repository-url@3.0.2': {} + '@inquirer/ansi@2.0.7': {} '@inquirer/checkbox@5.2.1(@types/node@14.18.63)': @@ -18688,16 +19332,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/checkbox@5.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/confirm@6.1.1(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18705,14 +19339,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/confirm@6.1.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/core@11.2.1(@types/node@14.18.63)': dependencies: '@inquirer/ansi': 2.0.7 @@ -18725,19 +19351,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/core@11.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - cli-width: 4.1.0 - fast-wrap-ansi: 0.2.2 - mute-stream: 3.0.0 - signal-exit: 4.1.0 - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/editor@5.2.2(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18746,15 +19359,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/editor@5.2.2(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/external-editor': 3.0.3(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/expand@5.1.1(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18762,13 +19366,12 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/expand@5.1.1(@types/node@22.20.1)': + '@inquirer/external-editor@1.0.3(@types/node@14.18.63)': dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) + chardet: 2.1.1 + iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 22.20.1 - optional: true + '@types/node': 14.18.63 '@inquirer/external-editor@1.0.3(@types/node@22.20.1)': dependencies: @@ -18784,14 +19387,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/external-editor@3.0.3(@types/node@22.20.1)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/figures@2.0.7': {} '@inquirer/input@5.1.2(@types/node@14.18.63)': @@ -18801,14 +19396,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/input@5.1.2(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/number@4.1.1(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18816,14 +19403,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/number@4.1.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/password@5.1.1(@types/node@14.18.63)': dependencies: '@inquirer/ansi': 2.0.7 @@ -18832,15 +19411,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/password@5.1.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/prompts@8.5.2(@types/node@14.18.63)': dependencies: '@inquirer/checkbox': 5.2.1(@types/node@14.18.63) @@ -18856,22 +19426,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/prompts@8.5.2(@types/node@22.20.1)': - dependencies: - '@inquirer/checkbox': 5.2.1(@types/node@22.20.1) - '@inquirer/confirm': 6.1.1(@types/node@22.20.1) - '@inquirer/editor': 5.2.2(@types/node@22.20.1) - '@inquirer/expand': 5.1.1(@types/node@22.20.1) - '@inquirer/input': 5.1.2(@types/node@22.20.1) - '@inquirer/number': 4.1.1(@types/node@22.20.1) - '@inquirer/password': 5.1.1(@types/node@22.20.1) - '@inquirer/rawlist': 5.3.1(@types/node@22.20.1) - '@inquirer/search': 4.2.1(@types/node@22.20.1) - '@inquirer/select': 5.2.1(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/rawlist@5.3.1(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18879,14 +19433,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/rawlist@5.3.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/search@4.2.1(@types/node@14.18.63)': dependencies: '@inquirer/core': 11.2.1(@types/node@14.18.63) @@ -18895,15 +19441,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/search@4.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/select@5.2.1(@types/node@14.18.63)': dependencies: '@inquirer/ansi': 2.0.7 @@ -18913,25 +19450,10 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - '@inquirer/select@5.2.1(@types/node@22.20.1)': - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/figures': 2.0.7 - '@inquirer/type': 4.0.7(@types/node@22.20.1) - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@inquirer/type@4.0.7(@types/node@14.18.63)': optionalDependencies: '@types/node': 14.18.63 - '@inquirer/type@4.0.7(@types/node@22.20.1)': - optionalDependencies: - '@types/node': 22.20.1 - optional: true - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -18965,7 +19487,7 @@ snapshots: '@jest/console@27.5.1': dependencies: '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 jest-message-util: 27.5.1 jest-util: 27.5.1 @@ -18974,7 +19496,7 @@ snapshots: '@jest/console@28.1.3': dependencies: '@jest/types': 28.1.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 jest-message-util: 28.1.3 jest-util: 28.1.3 @@ -18983,7 +19505,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -18996,7 +19518,7 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.8.1 @@ -19026,6 +19548,41 @@ snapshots: - ts-node - utf-8-validate + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 14.18.63 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3))': dependencies: '@jest/console': 29.7.0 @@ -19033,14 +19590,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) + jest-config: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -19065,14 +19622,14 @@ snapshots: dependencies: '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 27.5.1 '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -19090,7 +19647,7 @@ snapshots: dependencies: '@jest/types': 27.5.1 '@sinonjs/fake-timers': 8.1.0 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-message-util: 27.5.1 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -19099,7 +19656,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -19126,7 +19683,7 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit: 0.1.2 @@ -19157,7 +19714,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit: 0.1.2 @@ -19286,7 +19843,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/yargs': 16.0.11 chalk: 4.1.2 @@ -19295,7 +19852,7 @@ snapshots: '@jest/schemas': 28.1.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -19304,7 +19861,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -20219,98 +20776,98 @@ snapshots: dependencies: strip-ansi: 6.0.0 vite-plugin-css-injected-by-js: 3.5.2(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0)) - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript - vite '@sap-devx/inquirer-gui-file-browser-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-file-browser-plugin@3.0.1(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-file-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-file-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) - vue: 3.5.34(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-folder-browser-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-folder-browser-plugin@3.0.1(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-folder-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-folder-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) - vue: 3.5.34(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-label-plugin@3.0.2(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-label-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-login-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-login-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-login-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) - vue: 3.5.34(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-radio-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-tiles-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-tiles-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui@3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))))': + '@sap-devx/inquirer-gui@3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(webpack-plugin-vuetify@2.0.1)': dependencies: lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + vue: 3.4.3(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) transitivePeerDependencies: - typescript - vite-plugin-vuetify @@ -20320,32 +20877,20 @@ snapshots: dependencies: lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)) - transitivePeerDependencies: - - typescript - - vite-plugin-vuetify - - webpack-plugin-vuetify - - '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))': - dependencies: - '@vscode-elements/elements': 1.11.0 - lodash: 4.18.1 - strip-ansi: 6.0.0 - vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) + vue: 3.4.3(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)) transitivePeerDependencies: - typescript - vite-plugin-vuetify - webpack-plugin-vuetify - '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))': + '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)': dependencies: '@vscode-elements/elements': 1.11.0 lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) + vue: 3.4.3(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3)) transitivePeerDependencies: - typescript - vite-plugin-vuetify @@ -20495,6 +21040,13 @@ snapshots: properties-reader: 2.2.0 url: 0.11.0 + '@sap/cf-tools@3.3.1': + dependencies: + comment-json: 4.2.5 + lodash: 4.18.1 + properties-reader: 2.3.0 + url: 0.11.4 + '@sap/mta-lib@1.7.4': dependencies: cross-spawn: 7.0.6 @@ -20534,6 +21086,8 @@ snapshots: - supports-color - utf-8-validate + '@sap/wing-run-config-types@4.2.0': {} + '@sap_oss/task_contrib_types@3.1.0': {} '@sec-ant/readable-stream@0.4.1': {} @@ -20713,6 +21267,14 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@15.4.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers@8.1.0': dependencies: '@sinonjs/commons': 1.8.6 @@ -20723,6 +21285,11 @@ snapshots: lodash.get: 4.4.2 type-detect: 4.1.0 + '@sinonjs/samsam@8.0.3': + dependencies: + '@sinonjs/commons': 3.0.1 + type-detect: 4.1.0 + '@sinonjs/text-encoding@0.7.3': {} '@so-ric/colorspace@1.1.6': @@ -20772,6 +21339,16 @@ snapshots: '@types/jest': 29.5.14 jest: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) + '@testing-library/jest-dom@6.10.0(@testing-library/dom@9.3.4)': + dependencies: + '@adobe/css-tools': 4.5.0 + '@testing-library/dom': 9.3.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + '@testing-library/vue@8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))': dependencies: '@babel/runtime': 7.26.0 @@ -20841,7 +21418,7 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/types': 7.29.7 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -20867,7 +21444,7 @@ snapshots: '@types/bonjour@3.5.13': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/chai-as-promised@7.1.8': dependencies: @@ -20877,30 +21454,30 @@ snapshots: '@types/cheerio@0.22.35': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/comment-json@1.1.1': {} '@types/comment-json@2.4.2': dependencies: - comment-json: 4.1.1 + comment-json: 4.2.3 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 4.17.35 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/connect@3.4.35': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/conventional-commits-parser@5.0.0': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/cors@2.8.13': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/cors@2.8.19': dependencies: @@ -20939,7 +21516,7 @@ snapshots: '@types/express-serve-static-core@4.17.35': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 '@types/send': 0.17.1 @@ -20958,11 +21535,11 @@ snapshots: '@types/fs-extra@5.0.5': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/fs-extra@9.0.12': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/fs-extra@9.0.13': dependencies: @@ -20971,11 +21548,11 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 3.0.5 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/html-minifier-terser@6.1.0': {} @@ -20983,7 +21560,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/http-terminator@2.0.5': dependencies: @@ -21031,7 +21608,7 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -21043,11 +21620,11 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/keyv@3.1.4': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/lodash-es@4.17.12': dependencies: @@ -21082,12 +21659,12 @@ snapshots: '@types/node-fetch@2.6.13': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 form-data: 4.0.5 '@types/node-forge@1.3.14': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/node@12.20.55': {} @@ -21117,7 +21694,7 @@ snapshots: '@types/npm-registry-fetch@8.0.9': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/node-fetch': 2.6.13 '@types/npm-package-arg': 6.1.4 '@types/npmlog': 7.0.0 @@ -21125,7 +21702,7 @@ snapshots: '@types/npmlog@7.0.0': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/object-hash@2.2.1': {} @@ -21135,6 +21712,8 @@ snapshots: '@types/prettier@2.7.3': {} + '@types/properties-reader@2.1.3': {} + '@types/proxyquire@1.3.31': {} '@types/qs@6.9.7': {} @@ -21143,14 +21722,19 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/retry@0.12.0': {} + '@types/rimraf@3.0.0': + dependencies: + '@types/glob': 7.2.0 + '@types/node': 14.18.63 + '@types/rimraf@3.0.2': dependencies: '@types/glob': 7.2.0 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/sarif@2.1.7': {} @@ -21161,7 +21745,7 @@ snapshots: '@types/send@0.17.1': dependencies: '@types/mime': 1.3.2 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/serve-index@1.9.4': dependencies: @@ -21170,7 +21754,7 @@ snapshots: '@types/serve-static@1.15.1': dependencies: '@types/mime': 3.0.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/shimmer@1.2.0': {} @@ -21186,11 +21770,11 @@ snapshots: '@types/sockjs@0.3.36': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/ssri@7.1.5': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/stack-utils@2.0.3': {} @@ -21206,7 +21790,7 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/tmp@0.2.6': {} @@ -21223,7 +21807,7 @@ snapshots: '@types/vinyl@2.0.12': dependencies: '@types/expect': 1.20.4 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/vscode@1.51.0': {} @@ -21239,11 +21823,11 @@ snapshots: '@types/ws@7.4.7': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/ws@8.18.1': dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/yargs-parser@21.0.3': {} @@ -21263,15 +21847,15 @@ snapshots: dependencies: '@types/node': 22.20.1 - '@types/yeoman-environment@2.10.11(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4)': + '@types/yeoman-environment@2.10.11(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4)': dependencies: '@types/diff': 8.0.0 '@types/inquirer': 8.2.13 '@types/mem-fs': 2.2.0 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/text-table': 0.2.5 '@types/vinyl': 2.0.12 - '@types/yeoman-generator': 6.0.0(@types/node@22.20.1)(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4) + '@types/yeoman-generator': 6.0.0(@types/node@14.18.63)(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4) chalk: 4.1.2 commander: 9.5.0 execa: 5.1.1 @@ -21283,9 +21867,9 @@ snapshots: - react-native-b4a - supports-color - '@types/yeoman-generator@6.0.0(@types/node@22.20.1)(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4)': + '@types/yeoman-generator@6.0.0(@types/node@14.18.63)(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4)': dependencies: - yeoman-generator: 8.2.2(@types/node@22.20.1)(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4) + yeoman-generator: 8.2.2(@types/node@14.18.63)(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4) transitivePeerDependencies: - '@types/node' - '@yeoman/types' @@ -21479,6 +22063,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@ungap/promise-all-settled@1.1.2': {} + '@ungap/structured-clone@1.3.1': {} '@vitejs/plugin-vue@4.5.2(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': @@ -21496,6 +22082,11 @@ snapshots: vite: 5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0) vue: 3.4.3(typescript@5.7.3) + '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.3.8(typescript@5.7.3))': + dependencies: + vite: 6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) + vue: 3.3.8(typescript@5.7.3) + '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': dependencies: vite: 6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) @@ -21505,6 +22096,10 @@ snapshots: dependencies: lit: 3.3.3 + '@vscode-elements/elements@1.16.1': + dependencies: + lit: 3.3.3 + '@vscode-logging/logger@2.0.10': dependencies: '@vscode-logging/types': 2.0.10 @@ -21521,7 +22116,7 @@ snapshots: '@vscode-logging/logger@2.0.9': dependencies: - '@vscode-logging/types': 2.0.8 + '@vscode-logging/types': 2.0.10 fast-safe-stringify: 2.1.1 fs-extra: 11.2.0 lodash: 4.18.1 @@ -21537,10 +22132,19 @@ snapshots: '@vscode-logging/types@0.1.4': {} + '@vscode-logging/types@2.0.0': {} + '@vscode-logging/types@2.0.10': {} '@vscode-logging/types@2.0.8': {} + '@vscode-logging/wrapper@2.0.0': + dependencies: + '@vscode-logging/logger': 2.0.10 + '@vscode-logging/types': 2.0.10 + transitivePeerDependencies: + - supports-color + '@vscode-logging/wrapper@2.0.9': dependencies: '@vscode-logging/logger': 2.0.9 @@ -21681,7 +22285,7 @@ snapshots: dependencies: '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@babel/template': 7.29.7 '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 @@ -21708,7 +22312,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.29.7 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-helper-vue-jsx-merge-props': 1.4.0 html-tags: 2.0.0 lodash.kebabcase: 4.1.1 @@ -21724,7 +22328,7 @@ snapshots: '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.0) '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.0) '@babel/preset-env': 7.29.7(@babel/core@7.29.0) '@babel/runtime': 7.26.0 @@ -21758,27 +22362,27 @@ snapshots: '@vue/babel-sugar-composition-api-inject-h@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-sugar-composition-api-render-instance@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-sugar-functional-vue@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-sugar-inject-h@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-sugar-v-model@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-helper-vue-jsx-merge-props': 1.4.0 '@vue/babel-plugin-transform-vue-jsx': 1.4.0(@babel/core@7.29.0) camelcase: 5.3.1 @@ -21790,7 +22394,7 @@ snapshots: '@vue/babel-sugar-v-on@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) '@vue/babel-plugin-transform-vue-jsx': 1.4.0(@babel/core@7.29.0) camelcase: 5.3.1 transitivePeerDependencies: @@ -22196,6 +22800,13 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.3.8': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.3.8 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-core@3.4.3': dependencies: '@babel/parser': 7.29.8 @@ -22217,6 +22828,11 @@ snapshots: '@vue/compiler-core': 3.3.4 '@vue/shared': 3.3.4 + '@vue/compiler-dom@3.3.8': + dependencies: + '@vue/compiler-core': 3.3.8 + '@vue/shared': 3.3.8 + '@vue/compiler-dom@3.4.3': dependencies: '@vue/compiler-core': 3.4.3 @@ -22240,6 +22856,19 @@ snapshots: postcss: 8.5.15 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.3.8': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.3.8 + '@vue/compiler-dom': 3.3.8 + '@vue/compiler-ssr': 3.3.8 + '@vue/reactivity-transform': 3.3.8 + '@vue/shared': 3.3.8 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + '@vue/compiler-sfc@3.4.3': dependencies: '@babel/parser': 7.29.8 @@ -22254,7 +22883,7 @@ snapshots: '@vue/compiler-sfc@3.5.34': dependencies: - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@vue/compiler-core': 3.5.34 '@vue/compiler-dom': 3.5.34 '@vue/compiler-ssr': 3.5.34 @@ -22269,6 +22898,11 @@ snapshots: '@vue/compiler-dom': 3.3.4 '@vue/shared': 3.3.4 + '@vue/compiler-ssr@3.3.8': + dependencies: + '@vue/compiler-dom': 3.3.8 + '@vue/shared': 3.3.8 + '@vue/compiler-ssr@3.4.3': dependencies: '@vue/compiler-dom': 3.4.3 @@ -22354,6 +22988,18 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.21 + '@vue/reactivity-transform@3.3.8': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.3.8 + '@vue/shared': 3.3.8 + estree-walker: 2.0.2 + magic-string: 0.30.21 + + '@vue/reactivity@3.3.8': + dependencies: + '@vue/shared': 3.3.8 + '@vue/reactivity@3.4.3': dependencies: '@vue/shared': 3.4.3 @@ -22362,6 +23008,11 @@ snapshots: dependencies: '@vue/shared': 3.5.34 + '@vue/runtime-core@3.3.8': + dependencies: + '@vue/reactivity': 3.3.8 + '@vue/shared': 3.3.8 + '@vue/runtime-core@3.4.3': dependencies: '@vue/reactivity': 3.4.3 @@ -22372,6 +23023,12 @@ snapshots: '@vue/reactivity': 3.5.34 '@vue/shared': 3.5.34 + '@vue/runtime-dom@3.3.8': + dependencies: + '@vue/runtime-core': 3.3.8 + '@vue/shared': 3.3.8 + csstype: 3.2.3 + '@vue/runtime-dom@3.4.3': dependencies: '@vue/runtime-core': 3.4.3 @@ -22385,12 +23042,25 @@ snapshots: '@vue/shared': 3.5.34 csstype: 3.2.3 + '@vue/server-renderer@3.3.8(vue@3.3.8(typescript@5.7.3))': + dependencies: + '@vue/compiler-ssr': 3.3.8 + '@vue/shared': 3.3.8 + vue: 3.3.8(typescript@5.7.3) + '@vue/server-renderer@3.4.3(vue@3.4.3(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.4.3 '@vue/shared': 3.4.3 vue: 3.4.3(typescript@5.7.3) + '@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3))': + dependencies: + '@vue/compiler-ssr': 3.5.34 + '@vue/shared': 3.5.34 + vue: 3.3.8(typescript@5.7.3) + optional: true + '@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.5.34 @@ -22406,10 +23076,20 @@ snapshots: '@vue/shared@3.3.4': {} + '@vue/shared@3.3.8': {} + '@vue/shared@3.4.3': {} '@vue/shared@3.5.34': {} + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3)))(vue@3.3.8(typescript@5.7.3))': + dependencies: + js-beautify: 1.14.9 + vue: 3.3.8(typescript@5.7.3) + vue-component-type-helpers: 1.8.4 + optionalDependencies: + '@vue/server-renderer': 3.5.34(vue@3.3.8(typescript@5.7.3)) + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3))': dependencies: js-beautify: 1.14.9 @@ -22449,10 +23129,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.3.8(typescript@5.7.3))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 2.4.2 + convert-source-map: 1.8.0 + css-tree: 2.3.1 + jest: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + source-map: 0.5.6 + tsconfig: 7.0.0 + vue: 3.3.8(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + transitivePeerDependencies: + - supports-color + '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) babel-jest: 29.7.0(@babel/core@7.29.0) chalk: 2.4.2 convert-source-map: 1.8.0 @@ -22469,7 +23166,7 @@ snapshots: '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.5.34(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) babel-jest: 29.7.0(@babel/core@7.29.0) chalk: 2.4.2 convert-source-map: 1.8.0 @@ -22490,7 +23187,7 @@ snapshots: find-cache-dir: 3.3.2 upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) '@vuetify/loader-shared@2.1.2(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9)': dependencies: @@ -22502,7 +23199,7 @@ snapshots: dependencies: upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) '@webassemblyjs/ast@1.14.1': dependencies: @@ -22580,6 +23277,11 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@webpack-cli/configtest@1.2.0(webpack-cli@4.10.0)(webpack@5.106.2)': + dependencies: + webpack: 5.106.2(webpack-cli@4.10.0) + webpack-cli: 4.10.0(webpack@5.106.2) + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.106.2)': dependencies: webpack: 5.106.2(webpack-cli@5.1.4) @@ -22595,6 +23297,11 @@ snapshots: webpack: 5.104.1(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.104.1) + '@webpack-cli/info@1.5.0(webpack-cli@4.10.0)': + dependencies: + envinfo: 7.14.0 + webpack-cli: 4.10.0(webpack@5.106.2) + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.106.2)': dependencies: webpack: 5.106.2(webpack-cli@5.1.4) @@ -22610,6 +23317,10 @@ snapshots: webpack: 5.104.1(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.104.1) + '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0)': + dependencies: + webpack-cli: 4.10.0(webpack@5.106.2) + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.106.2)': dependencies: webpack: 5.106.2(webpack-cli@5.1.4) @@ -22642,20 +23353,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@yeoman/adapter@4.0.2(@types/node@22.20.1)': - dependencies: - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/prompts': 8.5.2(@types/node@22.20.1) - chalk: 5.6.2 - inquirer: 13.4.3(@types/node@22.20.1) - log-symbols: 7.0.1 - ora: 9.4.1 - p-queue: 9.3.1 - text-table: 0.2.0 - transitivePeerDependencies: - - '@types/node' - optional: true - '@yeoman/conflicter@4.1.0(@types/node@14.18.63)(@yeoman/types@1.11.1(@types/node@14.18.63)(@yeoman/adapter@4.0.2(@types/node@14.18.63))(mem-fs@4.1.4))(mem-fs@4.1.4)': dependencies: '@types/node': 14.18.63 @@ -22694,13 +23391,6 @@ snapshots: '@yeoman/adapter': 4.0.2(@types/node@14.18.63) mem-fs: 4.1.4 - '@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4)': - dependencies: - '@types/node': 22.20.1 - optionalDependencies: - '@yeoman/adapter': 4.0.2(@types/node@22.20.1) - mem-fs: 4.1.4 - JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -22763,6 +23453,8 @@ snapshots: acorn@8.16.0: {} + add-stream@1.0.0: {} + address@1.2.2: {} agent-base@6.0.2: @@ -23200,7 +23892,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-plugin-utils': 7.29.7 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -23747,6 +24439,12 @@ snapshots: camelcase: 2.1.1 map-obj: 1.0.1 + camelcase-keys@6.2.2: + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + camelcase@2.1.1: {} camelcase@5.3.1: {} @@ -23897,6 +24595,18 @@ snapshots: undici: 7.25.0 whatwg-mimetype: 4.0.0 + chokidar@3.5.3: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -23941,6 +24651,11 @@ snapshots: clean-stack@2.2.0: {} + clean-webpack-plugin@4.0.0(webpack@5.106.2): + dependencies: + del: 4.1.1 + webpack: 5.106.2(webpack-cli@4.10.0) + cli-cursor@2.1.0: dependencies: restore-cursor: 2.0.0 @@ -24117,7 +24832,7 @@ snapshots: comment-json@4.1.0: dependencies: array-timsort: 1.0.3 - core-util-is: 1.0.2 + core-util-is: 1.0.3 esprima: 4.0.1 has-own-prop: 2.0.0 repeat-string: 1.6.1 @@ -24138,6 +24853,14 @@ snapshots: has-own-prop: 2.0.0 repeat-string: 1.6.1 + comment-json@4.2.5: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + common-ancestor-path@1.0.1: {} common-ancestor-path@2.0.0: {} @@ -24169,6 +24892,13 @@ snapshots: concat-map@0.0.1: {} + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + condense-newlines@0.2.1: dependencies: extend-shallow: 2.0.1 @@ -24215,14 +24945,115 @@ snapshots: async-listener: 0.6.10 emitter-listener: 1.1.2 + conventional-changelog-angular@5.0.13: + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + conventional-changelog-angular@7.0.0: dependencies: compare-func: 2.0.0 + conventional-changelog-atom@2.0.8: + dependencies: + q: 1.5.1 + + conventional-changelog-codemirror@2.0.8: + dependencies: + q: 1.5.1 + + conventional-changelog-config-spec@2.1.0: {} + + conventional-changelog-conventionalcommits@4.6.3: + dependencies: + compare-func: 2.0.0 + lodash: 4.18.1 + q: 1.5.1 + conventional-changelog-conventionalcommits@7.0.2: dependencies: compare-func: 2.0.0 + conventional-changelog-core@4.2.4: + dependencies: + add-stream: 1.0.0 + conventional-changelog-writer: 5.0.1 + conventional-commits-parser: 3.2.4 + dateformat: 3.0.3 + get-pkg-repo: 4.2.1 + git-raw-commits: 2.0.11 + git-remote-origin-url: 2.0.0 + git-semver-tags: 4.1.1 + lodash: 4.18.1 + normalize-package-data: 3.0.3 + q: 1.5.1 + read-pkg: 3.0.0 + read-pkg-up: 3.0.0 + through2: 4.0.2 + + conventional-changelog-ember@2.0.9: + dependencies: + q: 1.5.1 + + conventional-changelog-eslint@3.0.9: + dependencies: + q: 1.5.1 + + conventional-changelog-express@2.0.6: + dependencies: + q: 1.5.1 + + conventional-changelog-jquery@3.0.11: + dependencies: + q: 1.5.1 + + conventional-changelog-jshint@2.0.9: + dependencies: + compare-func: 2.0.0 + q: 1.5.1 + + conventional-changelog-preset-loader@2.3.4: {} + + conventional-changelog-writer@5.0.1: + dependencies: + conventional-commits-filter: 2.0.7 + dateformat: 3.0.3 + handlebars: 4.7.9 + json-stringify-safe: 5.0.1 + lodash: 4.18.1 + meow: 8.1.2 + semver: 6.3.1 + split: 1.0.1 + through2: 4.0.2 + + conventional-changelog@3.1.25: + dependencies: + conventional-changelog-angular: 5.0.13 + conventional-changelog-atom: 2.0.8 + conventional-changelog-codemirror: 2.0.8 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-changelog-core: 4.2.4 + conventional-changelog-ember: 2.0.9 + conventional-changelog-eslint: 3.0.9 + conventional-changelog-express: 2.0.6 + conventional-changelog-jquery: 3.0.11 + conventional-changelog-jshint: 2.0.9 + conventional-changelog-preset-loader: 2.3.4 + + conventional-commits-filter@2.0.7: + dependencies: + lodash.ismatch: 4.4.0 + modify-values: 1.0.1 + + conventional-commits-parser@3.2.4: + dependencies: + JSONStream: 1.3.5 + is-text-path: 1.0.1 + lodash: 4.18.1 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + conventional-commits-parser@5.0.0: dependencies: JSONStream: 1.3.5 @@ -24230,6 +25061,17 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + conventional-recommended-bump@6.1.0: + dependencies: + concat-stream: 2.0.0 + conventional-changelog-preset-loader: 2.3.4 + conventional-commits-filter: 2.0.7 + conventional-commits-parser: 3.2.4 + git-raw-commits: 2.0.11 + git-semver-tags: 4.1.1 + meow: 8.1.2 + q: 1.5.1 + convert-source-map@1.8.0: dependencies: safe-buffer: 5.1.2 @@ -24346,6 +25188,21 @@ snapshots: dependencies: capture-stack-trace: 1.0.2 + create-jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)): dependencies: '@jest/types': 29.6.3 @@ -24623,6 +25480,12 @@ snapshots: optionalDependencies: supports-color: 8.1.1 + debug@4.3.3(supports-color@8.1.1): + dependencies: + ms: 2.1.2 + optionalDependencies: + supports-color: 8.1.1 + debug@4.3.4: dependencies: ms: 2.1.2 @@ -24643,6 +25506,11 @@ snapshots: dependencies: callsite: 1.0.0 + decamelize-keys@1.1.1: + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + decamelize@1.2.0: {} decamelize@2.0.0: @@ -24759,6 +25627,16 @@ snapshots: is-descriptor: 1.0.4 isobject: 3.0.1 + del@4.1.1: + dependencies: + '@types/glob': 7.2.0 + globby: 6.1.0 + is-path-cwd: 2.2.0 + is-path-in-cwd: 2.1.0 + p-map: 2.1.0 + pify: 4.0.1 + rimraf: 2.7.1 + delay@5.0.0: {} delayed-stream@1.0.0: {} @@ -24815,8 +25693,12 @@ snapshots: diff@4.0.4: {} + diff@5.0.0: {} + diff@5.2.0: {} + diff@7.0.0: {} + diff@9.0.0: {} diffie-hellman@5.0.3: @@ -24848,6 +25730,8 @@ snapshots: dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dom-converter@0.2.0: dependencies: utila: 0.4.0 @@ -24916,6 +25800,11 @@ snapshots: dotenv@16.4.7: {} + dotgitignore@2.1.0: + dependencies: + find-up: 3.0.0 + minimatch: 3.1.2 + download-stats@0.3.4: dependencies: JSONStream: 1.3.5 @@ -25018,7 +25907,7 @@ snapshots: engine.io@6.6.7(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@types/cors': 2.8.13 - '@types/node': 22.20.1 + '@types/node': 14.18.63 '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 @@ -25423,6 +26312,19 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-plugin-vue@9.18.1(eslint@8.57.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + eslint: 8.57.1 + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@8.57.1) + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - supports-color + eslint-plugin-vue@9.33.0(eslint@8.57.1): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) @@ -26000,6 +26902,10 @@ snapshots: path-exists: 2.1.0 pinkie-promise: 2.0.1 + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + find-up@3.0.0: dependencies: locate-path: 3.0.0 @@ -26294,6 +27200,13 @@ snapshots: get-package-type@0.1.0: {} + get-pkg-repo@4.2.1: + dependencies: + '@hutson/parse-repository-url': 3.0.2 + hosted-git-info: 4.0.2 + through2: 2.0.5 + yargs: 16.2.0 + get-port@7.0.0: {} get-proto@1.0.1: @@ -26344,12 +27257,34 @@ snapshots: got: 7.1.0 is-plain-obj: 1.1.0 + git-raw-commits@2.0.11: + dependencies: + dargs: 7.0.0 + lodash: 4.18.1 + meow: 8.1.2 + split2: 3.2.2 + through2: 4.0.2 + git-raw-commits@4.0.0: dependencies: dargs: 8.1.0 meow: 12.1.1 split2: 4.2.0 + git-remote-origin-url@2.0.0: + dependencies: + gitconfiglocal: 1.0.0 + pify: 2.3.0 + + git-semver-tags@4.1.1: + dependencies: + meow: 8.1.2 + semver: 6.3.1 + + gitconfiglocal@1.0.0: + dependencies: + ini: 1.3.8 + github-from-package@0.0.0: optional: true @@ -26506,6 +27441,14 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.4.0 + globby@6.1.0: + dependencies: + array-union: 1.0.2 + glob: 7.2.0 + object-assign: 4.1.1 + pify: 2.3.0 + pinkie-promise: 2.0.1 + globby@8.0.2: dependencies: array-union: 1.0.2 @@ -26583,6 +27526,8 @@ snapshots: grouped-queue@2.1.0: {} + growl@1.10.5: {} + gzip-size@6.0.0: dependencies: duplexer: 0.1.2 @@ -26614,6 +27559,8 @@ snapshots: ajv: 6.15.0 har-schema: 2.0.0 + hard-rejection@2.1.0: {} + has-ansi@2.0.0: dependencies: ansi-regex: 2.1.1 @@ -27041,19 +27988,6 @@ snapshots: optionalDependencies: '@types/node': 14.18.63 - inquirer@13.4.3(@types/node@22.20.1): - dependencies: - '@inquirer/ansi': 2.0.7 - '@inquirer/core': 11.2.1(@types/node@22.20.1) - '@inquirer/prompts': 8.5.2(@types/node@22.20.1) - '@inquirer/type': 4.0.7(@types/node@22.20.1) - mute-stream: 3.0.0 - run-async: 4.0.6 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 22.20.1 - optional: true - inquirer@7.3.3: dependencies: ansi-escapes: 4.3.2 @@ -27070,9 +28004,9 @@ snapshots: strip-ansi: 6.0.0 through: 2.3.8 - inquirer@8.2.7(@types/node@22.20.1): + inquirer@8.2.7(@types/node@14.18.63): dependencies: - '@inquirer/external-editor': 1.0.3(@types/node@22.20.1) + '@inquirer/external-editor': 1.0.3(@types/node@14.18.63) ansi-escapes: 4.3.2 chalk: 4.1.2 cli-cursor: 3.1.0 @@ -27098,6 +28032,8 @@ snapshots: interpret@1.4.0: {} + interpret@2.2.0: {} + interpret@3.1.1: {} invariant@2.2.4: @@ -27264,6 +28200,16 @@ snapshots: is-object@1.0.2: {} + is-path-cwd@2.2.0: {} + + is-path-in-cwd@2.1.0: + dependencies: + is-path-inside: 2.1.0 + + is-path-inside@2.1.0: + dependencies: + path-is-inside: 1.0.2 + is-path-inside@3.0.3: {} is-path-inside@4.0.0: {} @@ -27334,6 +28280,10 @@ snapshots: has-symbols: 1.1.0 safe-regex-test: 1.1.0 + is-text-path@1.0.1: + dependencies: + text-extensions: 1.9.0 + is-text-path@2.0.0: dependencies: text-extensions: 2.4.0 @@ -27563,7 +28513,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -27588,7 +28538,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.2 @@ -27629,6 +28579,25 @@ snapshots: - ts-node - utf-8-validate + jest-cli@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + exit: 0.1.2 + import-local: 3.0.3 + jest-config: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) @@ -27682,6 +28651,68 @@ snapshots: - supports-color - utf-8-validate + jest-config@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.0 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 14.18.63 + ts-node: 10.9.2(@types/node@14.18.63)(typescript@5.7.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.0 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 14.18.63 + ts-node: 10.9.2(@types/node@22.20.1)(typescript@5.7.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-config@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)): dependencies: '@babel/core': 7.29.0 @@ -27763,7 +28794,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 27.5.1 jest-util: 27.5.1 jsdom: 16.7.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -27779,7 +28810,7 @@ snapshots: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 20.0.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) @@ -27793,7 +28824,7 @@ snapshots: '@jest/environment': 27.5.1 '@jest/fake-timers': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 27.5.1 jest-util: 27.5.1 @@ -27802,7 +28833,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -27816,7 +28847,7 @@ snapshots: dependencies: '@jest/types': 27.5.1 '@types/graceful-fs': 4.1.9 - '@types/node': 22.20.1 + '@types/node': 14.18.63 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -27833,7 +28864,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.20.1 + '@types/node': 14.18.63 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -27851,7 +28882,7 @@ snapshots: '@jest/source-map': 27.5.1 '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 co: 4.6.0 expect: 27.5.1 @@ -27942,12 +28973,12 @@ snapshots: jest-mock@27.5.1: dependencies: '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@27.5.1): @@ -28011,7 +29042,7 @@ snapshots: '@jest/test-result': 27.5.1 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 emittery: 0.8.1 graceful-fs: 4.2.11 @@ -28040,7 +29071,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -28095,7 +29126,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.3 @@ -28123,7 +29154,7 @@ snapshots: jest-serializer@27.5.1: dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 graceful-fs: 4.2.11 jest-snapshot@27.5.1: @@ -28183,7 +29214,7 @@ snapshots: jest-util@27.5.1: dependencies: '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -28192,7 +29223,7 @@ snapshots: jest-util@28.1.3: dependencies: '@jest/types': 28.1.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -28201,7 +29232,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -28240,7 +29271,7 @@ snapshots: dependencies: '@jest/test-result': 27.5.1 '@jest/types': 27.5.1 - '@types/node': 22.20.1 + '@types/node': 14.18.63 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 27.5.1 @@ -28250,7 +29281,7 @@ snapshots: dependencies: '@jest/test-result': 28.1.3 '@jest/types': 28.1.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.10.2 @@ -28261,7 +29292,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.20.1 + '@types/node': 14.18.63 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -28270,19 +29301,19 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@28.1.3: dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 22.20.1 + '@types/node': 14.18.63 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -28299,6 +29330,18 @@ snapshots: - ts-node - utf-8-validate + jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + '@jest/types': 29.6.3 + import-local: 3.0.3 + jest-cli: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) @@ -28823,6 +29866,11 @@ snapshots: emojis-list: 3.0.0 json5: 2.2.3 + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -28862,6 +29910,8 @@ snapshots: lodash.isinteger@4.0.4: {} + lodash.ismatch@4.4.0: {} + lodash.isnumber@3.0.3: {} lodash.isplainobject@4.0.6: {} @@ -29110,6 +30160,8 @@ snapshots: map-obj@1.0.1: {} + map-obj@4.3.0: {} + map-visit@1.0.0: dependencies: object-visit: 1.0.1 @@ -29287,6 +30339,20 @@ snapshots: redent: 1.0.0 trim-newlines: 1.0.0 + meow@8.1.2: + dependencies: + '@types/minimist': 1.2.5 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + merge-descriptors@1.0.1: {} merge-descriptors@1.0.3: {} @@ -29393,6 +30459,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@4.2.1: + dependencies: + brace-expansion: 1.1.11 + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 @@ -29409,6 +30479,12 @@ snapshots: dependencies: brace-expansion: 2.1.0 + minimist-options@4.1.0: + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + minimist@1.2.0: {} minimist@1.2.8: {} @@ -29536,6 +30612,33 @@ snapshots: yargs-parser: 20.2.9 yargs-unparser: 2.0.0 + mocha@9.2.2: + dependencies: + '@ungap/promise-all-settled': 1.1.2 + ansi-colors: 4.1.1 + browser-stdout: 1.3.1 + chokidar: 3.5.3 + debug: 4.3.3(supports-color@8.1.1) + diff: 5.0.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 7.2.0 + growl: 1.10.5 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 4.2.1 + ms: 2.1.3 + nanoid: 3.3.1 + serialize-javascript: 6.0.0 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + which: 2.0.2 + workerpool: 6.2.0 + yargs: 16.2.0 + yargs-parser: 20.2.4 + yargs-unparser: 2.0.0 + mock-require@3.0.3: dependencies: get-caller-file: 1.0.3 @@ -29547,6 +30650,8 @@ snapshots: dependencies: through: 2.3.8 + modify-values@1.0.1: {} + module-alias@2.3.4: {} module-details-from-path@1.0.3: {} @@ -29596,6 +30701,8 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nanoid@3.3.1: {} + nanoid@3.3.12: {} nanomatch@1.2.13: @@ -29647,6 +30754,13 @@ snapshots: just-extend: 6.2.0 path-to-regexp: 6.2.2 + nise@6.1.5: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 15.4.0 + just-extend: 6.2.0 + path-to-regexp: 8.4.2 + no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -29779,6 +30893,13 @@ snapshots: semver: 5.7.1 validate-npm-package-license: 3.0.4 + normalize-package-data@3.0.3: + dependencies: + hosted-git-info: 4.0.2 + is-core-module: 2.16.1 + semver: 7.6.3 + validate-npm-package-license: 3.0.4 + normalize-package-data@5.0.0: dependencies: hosted-git-info: 6.1.3 @@ -30256,6 +31377,10 @@ snapshots: p-finally@1.0.0: {} + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -30268,6 +31393,10 @@ snapshots: dependencies: yocto-queue: 1.1.1 + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + p-locate@3.0.0: dependencies: p-limit: 2.3.0 @@ -30340,6 +31469,8 @@ snapshots: '@types/node': 22.20.1 p-queue: 8.1.1 + p-try@1.0.0: {} + p-try@2.2.0: {} p-wait-for@3.2.0: @@ -30549,6 +31680,8 @@ snapshots: path-is-absolute@1.0.1: {} + path-is-inside@1.0.2: {} + path-key@2.0.1: {} path-key@3.1.1: {} @@ -30576,6 +31709,8 @@ snapshots: path-to-regexp@6.2.2: {} + path-to-regexp@8.4.2: {} + path-type@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -30972,6 +32107,10 @@ snapshots: dependencies: mkdirp: 1.0.4 + properties-reader@2.3.0: + dependencies: + mkdirp: 1.0.4 + proto-list@1.2.4: {} proxy-addr@2.0.7: @@ -31010,6 +32149,8 @@ snapshots: pure-rand@6.1.0: {} + q@1.5.1: {} + qjobs@1.2.0: {} qs@6.13.0: @@ -31036,6 +32177,8 @@ snapshots: dependencies: inherits: 2.0.4 + quick-lru@4.0.1: {} + quick-lru@5.1.1: {} randombytes@2.1.0: @@ -31122,6 +32265,11 @@ snapshots: read-pkg: 8.1.0 type-fest: 4.41.0 + read-pkg-up@3.0.0: + dependencies: + find-up: 2.1.0 + read-pkg: 3.0.0 + read-pkg-up@4.0.0: dependencies: find-up: 3.0.0 @@ -31193,14 +32341,14 @@ snapshots: readable-stream@1.0.34: dependencies: - core-util-is: 1.0.2 + core-util-is: 1.0.3 inherits: 2.0.4 isarray: 0.0.1 string_decoder: 0.10.31 readable-stream@2.3.8: dependencies: - core-util-is: 1.0.2 + core-util-is: 1.0.3 inherits: 2.0.4 isarray: 1.0.0 process-nextick-args: 2.0.1 @@ -31239,6 +32387,10 @@ snapshots: dependencies: resolve: 1.22.10 + rechoir@0.7.1: + dependencies: + resolve: 1.22.12 + rechoir@0.8.0: dependencies: resolve: 1.22.10 @@ -31379,7 +32531,7 @@ snapshots: dependencies: debug: 4.4.3 module-details-from-path: 1.0.3 - resolve: 1.22.10 + resolve: 1.22.12 transitivePeerDependencies: - supports-color @@ -31999,6 +33151,15 @@ snapshots: nise: 6.0.0 supports-color: 7.2.0 + sinon@19.0.5: + dependencies: + '@sinonjs/commons': 3.0.1 + '@sinonjs/fake-timers': 13.0.5 + '@sinonjs/samsam': 8.0.3 + diff: 7.0.0 + nise: 6.1.5 + supports-color: 7.2.0 + sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 @@ -32235,8 +33396,16 @@ snapshots: dependencies: extend-shallow: 3.0.2 + split2@3.2.2: + dependencies: + readable-stream: 3.6.2 + split2@4.2.0: {} + split@1.0.1: + dependencies: + through: 2.3.8 + sprintf-js@1.0.3: {} ssh-config@4.2.1: {} @@ -32296,6 +33465,23 @@ snapshots: stack-generator: 2.0.5 stacktrace-gps: 3.0.4 + standard-version@9.5.0: + dependencies: + chalk: 2.4.2 + conventional-changelog: 3.1.25 + conventional-changelog-config-spec: 2.1.0 + conventional-changelog-conventionalcommits: 4.6.3 + conventional-recommended-bump: 6.1.0 + detect-indent: 6.1.0 + detect-newline: 3.1.0 + dotgitignore: 2.1.0 + figures: 3.2.0 + find-up: 5.0.0 + git-semver-tags: 4.1.1 + semver: 7.6.3 + stringify-package: 1.0.1 + yargs: 16.2.0 + static-extend@0.1.2: dependencies: define-property: 0.2.5 @@ -32380,7 +33566,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.0 string-width@8.2.1: dependencies: @@ -32426,6 +33612,8 @@ snapshots: is-obj: 1.0.1 is-regexp: 1.0.0 + stringify-package@1.0.1: {} + strip-ansi@3.0.1: dependencies: ansi-regex: 2.1.1 @@ -32646,7 +33834,7 @@ snapshots: jest-worker: 27.5.1 schema-utils: 4.3.3 terser: 5.48.0 - webpack: 5.106.2(webpack-cli@5.1.4) + webpack: 5.106.2(webpack-cli@4.10.0) terser-webpack-plugin@5.5.0(webpack@5.89.0): dependencies: @@ -32681,6 +33869,8 @@ snapshots: transitivePeerDependencies: - react-native-b4a + text-extensions@1.9.0: {} + text-extensions@2.4.0: {} text-hex@1.0.0: {} @@ -32724,6 +33914,10 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + through@2.3.8: {} thunky@1.1.0: {} @@ -32819,6 +34013,8 @@ snapshots: trim-newlines@1.0.0: {} + trim-newlines@3.0.1: {} + trim-right@1.0.1: {} triple-beam@1.4.1: {} @@ -32854,6 +34050,23 @@ snapshots: '@types/jest': 27.5.2 babel-jest: 27.5.1(@babel/core@7.29.0) + ts-jest@29.1.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.7.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + ts-jest@29.1.1(@babel/core@7.29.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3): dependencies: bs-logger: 0.2.6 @@ -32926,7 +34139,7 @@ snapshots: picomatch: 4.0.4 source-map: 0.7.6 typescript: 5.7.3 - webpack: 5.106.2(webpack-cli@5.1.4) + webpack: 5.106.2(webpack-cli@4.10.0) optionalDependencies: loader-utils: 2.0.4 @@ -33032,6 +34245,8 @@ snapshots: type-detect@4.1.0: {} + type-fest@0.18.1: {} + type-fest@0.20.2: {} type-fest@0.21.3: {} @@ -33106,6 +34321,8 @@ snapshots: dependencies: is-typedarray: 1.0.0 + typedarray@0.0.6: {} + typescript@4.9.5: {} typescript@5.7.3: {} @@ -33276,6 +34493,11 @@ snapshots: punycode: 1.3.2 querystring: 0.2.0 + url@0.11.4: + dependencies: + punycode: 1.4.1 + qs: 6.14.2 + use@3.1.1: {} utf-8-validate@5.0.10: @@ -33391,18 +34613,6 @@ snapshots: transitivePeerDependencies: - supports-color - vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6): - dependencies: - '@vuetify/loader-shared': 2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - debug: 4.4.3 - upath: 2.0.1 - vite: 4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0) - vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) - transitivePeerDependencies: - - supports-color - optional: true - vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6): dependencies: '@vuetify/loader-shared': 2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) @@ -33421,7 +34631,7 @@ snapshots: upath: 2.0.1 vite: 6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0) vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) transitivePeerDependencies: - supports-color optional: true @@ -33459,6 +34669,21 @@ snapshots: sass: 1.68.0 terser: 5.48.0 + vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.63.1 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 14.18.63 + fsevents: 2.3.3 + jiti: 1.21.6 + sass: 1.104.0 + terser: 5.48.0 + vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0): dependencies: esbuild: 0.25.5 @@ -33649,6 +34874,16 @@ snapshots: - walrus - whiskers + vue-loader@17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.3.8(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): + dependencies: + chalk: 4.1.2 + hash-sum: 2.0.0 + watchpack: 2.5.1 + webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) + optionalDependencies: + '@vue/compiler-sfc': 3.3.8 + vue: 3.3.8(typescript@5.7.3) + vue-loader@17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: chalk: 4.1.2 @@ -33669,6 +34904,10 @@ snapshots: '@vue/compiler-sfc': 3.5.34 vue: 3.5.34(typescript@5.7.3) + vue-loading-overlay@6.0.3(vue@3.3.8(typescript@5.7.3)): + dependencies: + vue: 3.3.8(typescript@5.7.3) + vue-loading-overlay@6.0.3(vue@3.4.3(typescript@5.7.3)): dependencies: vue: 3.4.3(typescript@5.7.3) @@ -33689,6 +34928,16 @@ snapshots: vue-template-es2015-compiler@1.9.1: {} + vue@3.3.8(typescript@5.7.3): + dependencies: + '@vue/compiler-dom': 3.3.8 + '@vue/compiler-sfc': 3.3.8 + '@vue/runtime-dom': 3.3.8 + '@vue/server-renderer': 3.3.8(vue@3.3.8(typescript@5.7.3)) + '@vue/shared': 3.3.8 + optionalDependencies: + typescript: 5.7.3 + vue@3.4.3(typescript@5.7.3): dependencies: '@vue/compiler-dom': 3.4.3 @@ -33709,30 +34958,23 @@ snapshots: optionalDependencies: typescript: 5.7.3 - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)): dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 vite-plugin-vuetify: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9) - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3)): dependencies: - vue: 3.5.34(typescript@5.7.3) - optionalDependencies: - typescript: 5.7.3 - vite-plugin-vuetify: 2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)): - dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1): dependencies: - vue: 3.5.34(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) @@ -33745,6 +34987,14 @@ snapshots: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1): + dependencies: + vue: 3.5.34(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) + webpack-plugin-vuetify: 2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) + vuetify@3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)): dependencies: vue: 3.4.3(typescript@5.7.3) @@ -33820,6 +35070,22 @@ snapshots: deepmerge: 1.5.2 javascript-stringify: 2.1.0 + webpack-cli@4.10.0(webpack@5.106.2): + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@webpack-cli/configtest': 1.2.0(webpack-cli@4.10.0)(webpack@5.106.2) + '@webpack-cli/info': 1.5.0(webpack-cli@4.10.0) + '@webpack-cli/serve': 1.7.0(webpack-cli@4.10.0) + colorette: 2.0.16 + commander: 7.2.0 + cross-spawn: 7.0.6 + fastest-levenshtein: 1.0.12 + import-local: 3.0.3 + interpret: 2.2.0 + rechoir: 0.7.1 + webpack: 5.106.2(webpack-cli@4.10.0) + webpack-merge: 5.10.0 + webpack-cli@5.1.4(webpack@5.106.2): dependencies: '@discoveryjs/json-ext': 0.5.7 @@ -33945,7 +35211,7 @@ snapshots: null-loader: 4.0.1(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) webpack-sources@3.4.1: {} @@ -33988,6 +35254,39 @@ snapshots: - esbuild - uglify-js + webpack@5.106.2(webpack-cli@4.10.0): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.9 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.2 + chrome-trace-event: 1.0.3 + enhanced-resolve: 5.21.1 + es-module-lexer: 2.1.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + loader-runner: 4.3.2 + mime-db: 1.54.0 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.3 + terser-webpack-plugin: 5.5.0(webpack@5.106.2) + watchpack: 2.5.1 + webpack-sources: 3.4.1 + optionalDependencies: + webpack-cli: 4.10.0(webpack@5.106.2) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + webpack@5.106.2(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 @@ -34291,6 +35590,8 @@ snapshots: wordwrap@1.0.0: {} + workerpool@6.2.0: {} + workerpool@6.5.1: {} wrap-ansi@3.0.1: @@ -34314,7 +35615,7 @@ snapshots: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 - strip-ansi: 7.2.0 + strip-ansi: 7.1.0 wrappy@1.0.2: {} @@ -34413,6 +35714,8 @@ snapshots: camelcase: 5.3.1 decamelize: 1.2.0 + yargs-parser@20.2.4: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} @@ -34528,7 +35831,7 @@ snapshots: - supports-color optional: true - yeoman-environment@3.19.3(@types/node@22.20.1): + yeoman-environment@3.19.3(@types/node@14.18.63): dependencies: '@npmcli/arborist': 4.3.1 are-we-there-yet: 2.0.0 @@ -34546,7 +35849,7 @@ snapshots: find-up: 5.0.0 globby: 11.1.0 grouped-queue: 2.1.0 - inquirer: 8.2.7(@types/node@22.20.1) + inquirer: 8.2.7(@types/node@14.18.63) is-scoped: 2.1.0 isbinaryfile: 4.0.10 lodash: 4.18.1 @@ -34823,33 +36126,6 @@ snapshots: - react-native-b4a - supports-color - yeoman-generator@8.2.2(@types/node@22.20.1)(@yeoman/types@1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4))(mem-fs@4.1.4): - dependencies: - '@types/debug': 4.1.13 - '@types/lodash-es': 4.17.12 - '@yeoman/namespace': 2.1.0 - '@yeoman/types': 1.11.1(@types/node@22.20.1)(@yeoman/adapter@4.0.2(@types/node@22.20.1))(mem-fs@4.1.4) - chalk: 5.6.2 - debug: 4.4.3 - execa: 9.6.1 - latest-version: 9.0.0 - lodash-es: 4.18.1 - mem-fs: 4.1.4 - mem-fs-editor: 12.0.4(@types/node@22.20.1)(mem-fs@4.1.4) - minimist: 1.2.8 - read-package-up: 12.0.0 - semver: 7.8.1 - simple-git: 3.36.0 - sort-keys: 6.0.1 - text-table: 0.2.0 - type-fest: 5.8.0 - optionalDependencies: - '@types/node': 22.20.1 - transitivePeerDependencies: - - bare-abort-controller - - react-native-b4a - - supports-color - yn@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/projects/cloud-foundry-tools/packages/backend/package.json b/projects/cloud-foundry-tools/packages/backend/package.json index f20f97476..c3949735e 100644 --- a/projects/cloud-foundry-tools/packages/backend/package.json +++ b/projects/cloud-foundry-tools/packages/backend/package.json @@ -12,7 +12,8 @@ }, "repository": { "type": "git", - "url": "https://github.com/SAP/cloud-foundry-tools.git" + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/cloud-foundry-tools/packages/backend" }, "license": "Apache 2.0", "publisher": "SAPOSS", @@ -344,6 +345,7 @@ "@types/node": "^14.14.6", "@types/properties-reader": "^2.1.3", "@types/proxyquire": "^1.3.28", + "@types/rimraf": "3.0.0", "@types/sinon": "^17.0.3", "@types/vscode": "^1.43.0", "@vscode-logging/types": "^2.0.0", diff --git a/projects/cloud-foundry-tools/packages/frontend/package.json b/projects/cloud-foundry-tools/packages/frontend/package.json index 7f40153e8..b7b3627d5 100644 --- a/projects/cloud-foundry-tools/packages/frontend/package.json +++ b/projects/cloud-foundry-tools/packages/frontend/package.json @@ -41,6 +41,7 @@ "dependencies": { "@sap-devx/webview-rpc": "0.4.1", "@vscode-elements/elements": "1.16.1", + "lodash": "4.17.21", "vue": "3.3.8", "vue-loader": "17.2.2", "vue-loading-overlay": "6.0.3" @@ -58,5 +59,10 @@ "jest-environment-jsdom": "^29.7.0", "ts-jest": "29.1.1", "vite": "^6.0.6" + }, + "repository": { + "type": "git", + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/cloud-foundry-tools/packages/frontend" } } diff --git a/projects/cloud-foundry-tools/tsconfig.base.json b/projects/cloud-foundry-tools/tsconfig.base.json new file mode 100644 index 000000000..7871e0cf0 --- /dev/null +++ b/projects/cloud-foundry-tools/tsconfig.base.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "skipLibCheck": true + } +} From c9b870d40e9dbe1d4d251845a04484627a4acf96 Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:18:26 +0200 Subject: [PATCH 03/11] fix(cloud-foundry-tools): align build toolchain with monorepo - inline per-package nyc.config.js (source referenced repo-root ../../nyc.config) - copy webpack.config.vscode.base.js to project root; adapt module resolution to pnpm workspace-root node_modules (task-explorer precedent) - backend + wrapper: replace deprecated vsce@1.100.1 with @vscode/vsce@2.24.0 and 'vsce package --no-dependencies' (monorepo standard, no yarn dependency) - remove config.commitizen (broke pnpm env-var passing; commitlint runs at root) Signed-off-by: Jacob Kreyenbuehl --- pnpm-lock.yaml | 12 +++++------ .../packages/backend/nyc.config.js | 10 ++++++---- .../packages/backend/package.json | 9 ++------- .../packages/wrapper/nyc.config.js | 10 ++++++---- .../packages/wrapper/package.json | 12 ++++------- .../webpack.config.vscode.base.js | 20 +++++++++++++++++++ 6 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 projects/cloud-foundry-tools/webpack.config.vscode.base.js diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d661ebee2..39c03beef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -630,6 +630,9 @@ importers: '@types/vscode': specifier: ^1.43.0 version: 1.75.0 + '@vscode/vsce': + specifier: 2.24.0 + version: 2.24.0 clean-webpack-plugin: specifier: ^4.0.0 version: 4.0.0(webpack@5.106.2) @@ -654,9 +657,6 @@ importers: ts-node: specifier: ^10.7.0 version: 10.9.2(@types/node@14.18.63)(typescript@5.7.3) - vsce: - specifier: 1.100.1 - version: 1.100.1 webpack: specifier: ^5.94.0 version: 5.106.2(webpack-cli@4.10.0) @@ -736,6 +736,9 @@ importers: '@vscode-logging/types': specifier: ^2.0.0 version: 2.0.10 + '@vscode/vsce': + specifier: 2.24.0 + version: 2.24.0 mocha: specifier: ^9.2.2 version: 9.2.2 @@ -748,9 +751,6 @@ importers: ts-node: specifier: ^10.7.0 version: 10.9.2(@types/node@14.18.63)(typescript@5.7.3) - vsce: - specifier: 1.100.1 - version: 1.100.1 webpack: specifier: ^5.94.0 version: 5.106.2(webpack-cli@4.10.0) diff --git a/projects/cloud-foundry-tools/packages/backend/nyc.config.js b/projects/cloud-foundry-tools/packages/backend/nyc.config.js index 6000fd3ad..43c197269 100644 --- a/projects/cloud-foundry-tools/packages/backend/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/backend/nyc.config.js @@ -1,10 +1,12 @@ -const baseNycConfig = require("../../nyc.config"); - -module.exports = Object.assign(baseNycConfig, { +module.exports = { + reporter: ["text", "lcov"], + "check-coverage": true, + all: true, + extension: [".js", ".ts", ".vue"], branches: 90, lines: 95, functions: 91, statements: 95, include: ["**/src/**"], exclude: ["**/src/run-configuration-*", "**/src/sql-tools.ts", "**/src/logger/logger-wrapper.ts", "**/usage/*.ts"], -}); +}; diff --git a/projects/cloud-foundry-tools/packages/backend/package.json b/projects/cloud-foundry-tools/packages/backend/package.json index c3949735e..8023eabcd 100644 --- a/projects/cloud-foundry-tools/packages/backend/package.json +++ b/projects/cloud-foundry-tools/packages/backend/package.json @@ -33,7 +33,7 @@ "coverage": "nyc mocha --colors", "frontend:all": "cd ../frontend && npm-run-all clean build && cd ../backend && npm run frontend:copy", "frontend:copy": "npm-run-all clean:frontend && shx cp -r ../frontend/dist/. ./dist/media/", - "package": "vsce package --yarn", + "package": "vsce package --no-dependencies", "test": "mocha --colors", "watch": "tsc -watch -p ./", "webpack-dev": "webpack --mode development", @@ -321,11 +321,6 @@ "@commitlint/config-conventional" ] }, - "config": { - "commitizen": { - "path": "cz-conventional-changelog" - } - }, "dependencies": { "@sap-devx/webview-rpc": "0.4.1", "@sap/bas-sdk": "^3.13.2", @@ -349,6 +344,7 @@ "@types/sinon": "^17.0.3", "@types/vscode": "^1.43.0", "@vscode-logging/types": "^2.0.0", + "@vscode/vsce": "2.24.0", "clean-webpack-plugin": "^4.0.0", "mocha": "^9.2.2", "proxyquire": "2.1.3", @@ -357,7 +353,6 @@ "temp-dir": "^2.0.0", "ts-loader": "^9.2.8", "ts-node": "^10.7.0", - "vsce": "1.100.1", "webpack": "^5.94.0", "webpack-cli": "^4.9.2" }, diff --git a/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js index 3f6741d6d..7d4d99332 100644 --- a/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js @@ -1,9 +1,11 @@ -const baseNycConfig = require("../../nyc.config"); - -module.exports = Object.assign(baseNycConfig, { +module.exports = { + reporter: ["text", "lcov"], + "check-coverage": true, + all: true, + extension: [".js", ".ts", ".vue"], branches: 100, lines: 100, functions: 100, statements: 100, include: ["**/src/**"], -}); +}; diff --git a/projects/cloud-foundry-tools/packages/wrapper/package.json b/projects/cloud-foundry-tools/packages/wrapper/package.json index e54ca3fd6..8a8c53233 100644 --- a/projects/cloud-foundry-tools/packages/wrapper/package.json +++ b/projects/cloud-foundry-tools/packages/wrapper/package.json @@ -12,7 +12,8 @@ }, "repository": { "type": "git", - "url": "https://github.com/SAP/cloud-foundry-tools.git" + "url": "https://github.com/SAP/app-studio-toolkit.git", + "directory": "projects/cloud-foundry-tools/packages/wrapper" }, "license": "Apache 2.0", "publisher": "SAP", @@ -28,7 +29,7 @@ "clean:vsix": "rimraf ./*.vsix", "compile": "tsc -p ./", "coverage": "nyc mocha --colors", - "package": "vsce package --yarn", + "package": "vsce package --no-dependencies", "test": "mocha --colors", "watch": "tsc -watch -p ./", "webpack-dev": "webpack --mode development", @@ -42,11 +43,6 @@ "@commitlint/config-conventional" ] }, - "config": { - "commitizen": { - "path": "cz-conventional-changelog" - } - }, "extensionDependencies": [ "saposs.vscode-cf-tools" ], @@ -55,11 +51,11 @@ "@types/sinon": "^17.0.3", "@types/vscode": "^1.43.0", "@vscode-logging/types": "^2.0.0", + "@vscode/vsce": "2.24.0", "mocha": "^9.2.2", "sinon": "^19.0.2", "ts-loader": "^9.2.8", "ts-node": "^10.7.0", - "vsce": "1.100.1", "webpack": "^5.94.0", "webpack-cli": "^4.9.2" }, diff --git a/projects/cloud-foundry-tools/webpack.config.vscode.base.js b/projects/cloud-foundry-tools/webpack.config.vscode.base.js new file mode 100644 index 000000000..d21eb2511 --- /dev/null +++ b/projects/cloud-foundry-tools/webpack.config.vscode.base.js @@ -0,0 +1,20 @@ +const path = require("path"); + +const config = { + optimization: { + // The Default minimization options cause JavaScript runtime errors. + // Also we don't actually need to minimize as much (not targeted for browser). + // Rather we mostly need to reduce the number of fileSystem access requests + // by reducing the number of files packaged inside our VSCode extensions + minimize: false, + }, + target: "node", + devtool: "source-map", + resolve: { + // pnpm hoists deps to the workspace root; resolve from there first + modules: [path.resolve(__dirname, "../../node_modules"), "node_modules"], + extensions: [".js"], + }, +}; + +module.exports = config; From c641a0694dd55d95370778f64ca2cc22cfda09a5 Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:19:55 +0200 Subject: [PATCH 04/11] chore(cloud-foundry-tools): add ESLint overrides for migrated code Relax the same rule set the standalone repo carried (require-description, no-explicit-any, no-unused-expressions, unbound-method, etc.) scoped to projects/cloud-foundry-tools/**, plus prefer-spread/prefer-rest-params. Ignore dist/out/coverage build output. TODO: tighten incrementally. Signed-off-by: Jacob Kreyenbuehl --- .eslintrc.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.eslintrc.js b/.eslintrc.js index 70db9953e..839f41f04 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,6 +4,9 @@ module.exports = { ignorePatterns: [ "projects/task-explorer/**/dist/", "projects/task-explorer/**/coverage/", + "projects/cloud-foundry-tools/**/dist/", + "projects/cloud-foundry-tools/**/coverage/", + "projects/cloud-foundry-tools/**/out/", // Vendored compiled outputs of the RPC library bundled into rpc-example-ws for demo purposes. "projects/vscode-webview-rpc-lib/examples/rpc-example-ws/src/static/rpc/", ], @@ -322,6 +325,39 @@ module.exports = { "@typescript-eslint/only-throw-error": "off", }, }, + { + // cloud-foundry-tools was integrated with these rules relaxed for the migrated code. + // TODO: clean up violations and tighten incrementally. + files: ["projects/cloud-foundry-tools/**"], + rules: { + "eslint-comments/require-description": "off", + "no-unused-vars": "off", + "prefer-spread": "off", + "prefer-rest-params": "off", + }, + }, + { + // Additional TypeScript rules for cloud-foundry-tools packages. + // Legacy code migrated as-is; relaxed to match the historical behaviour. + // TODO: clean up violations and tighten incrementally. + files: ["projects/cloud-foundry-tools/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/unbound-method": "off", + "@typescript-eslint/prefer-promise-reject-errors": "off", + "@typescript-eslint/no-redundant-type-constituents": "off", + "@typescript-eslint/no-unnecessary-type-assertion": "off", + "@typescript-eslint/no-unsafe-enum-comparison": "off", + "@typescript-eslint/no-base-to-string": "off", + "@typescript-eslint/no-unsafe-function-type": "off", + "@typescript-eslint/no-this-alias": "off", + "@typescript-eslint/no-duplicate-type-constituents": "off", + }, + }, { // vscode-webview-rpc-lib was integrated with these rules relaxed for the migrated code. // TODO: clean up violations and tighten incrementally. From 79ce2c4ae091339da31264eb733232103b2e195f Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:22:06 +0200 Subject: [PATCH 05/11] style(cloud-foundry-tools): format with monorepo Prettier Reflow to the monorepo Prettier profile (default width) from the source's --print-width 120. Also disable the type-aware unsafe-* eslint rules for the project (source relaxed them; matches task-explorer precedent). Signed-off-by: Jacob Kreyenbuehl --- .eslintrc.js | 4 + .../packages/backend/nyc.config.js | 7 +- .../packages/backend/src/cfView.ts | 104 ++- .../packages/backend/src/cfViewCommands.ts | 245 ++++-- .../packages/backend/src/chisel.ts | 27 +- .../packages/backend/src/commands.ts | 338 ++++++-- .../packages/backend/src/extension.ts | 203 +++-- .../backend/src/logger/logger-wrapper.ts | 7 +- .../src/loginTargetView/loginTargetView.ts | 81 +- .../packages/backend/src/messages.ts | 81 +- .../backend/src/run-configuration-example.ts | 15 +- .../packages/backend/src/run-configuration.ts | 65 +- .../packages/backend/src/sql-tools.ts | 13 +- .../packages/backend/src/utils.ts | 170 +++- .../packages/backend/tests/cfView.spec.ts | 161 +++- .../backend/tests/cfViewCommands.spec.ts | 794 ++++++++++++++---- .../packages/backend/tests/chisel.spec.ts | 34 +- .../packages/backend/tests/commands-a.spec.ts | 185 +++- .../packages/backend/tests/commands.spec.ts | 532 +++++++++--- .../packages/backend/tests/ext/mockVscode.ts | 33 +- .../packages/backend/tests/extension.spec.ts | 313 +++++-- .../loginTargetView/loginTargetView.spec.ts | 179 +++- .../packages/backend/tests/messages.spec.ts | 14 +- .../backend/tests/run-configuration.spec.ts | 217 +++-- .../packages/backend/tests/utils.spec.ts | 392 +++++++-- .../packages/frontend/src/App.vue | 5 +- .../frontend/src/components/CFHeader.vue | 4 +- .../frontend/src/components/CFSignin.vue | 81 +- .../frontend/src/components/CFTarget.vue | 77 +- .../frontend/src/directives/tooltip.js | 30 +- .../frontend/test/components/CFHeader.spec.js | 4 +- .../frontend/test/components/CFSignin.spec.js | 5 +- .../frontend/test/components/CFTarget.spec.js | 91 +- .../packages/frontend/vite.config.js | 9 +- 34 files changed, 3560 insertions(+), 960 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 839f41f04..7b2de17e0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -346,7 +346,11 @@ module.exports = { "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-unused-expressions": "off", + "@typescript-eslint/no-unsafe-argument": "off", "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/unbound-method": "off", "@typescript-eslint/prefer-promise-reject-errors": "off", "@typescript-eslint/no-redundant-type-constituents": "off", diff --git a/projects/cloud-foundry-tools/packages/backend/nyc.config.js b/projects/cloud-foundry-tools/packages/backend/nyc.config.js index 43c197269..7cd43022a 100644 --- a/projects/cloud-foundry-tools/packages/backend/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/backend/nyc.config.js @@ -8,5 +8,10 @@ module.exports = { functions: 91, statements: 95, include: ["**/src/**"], - exclude: ["**/src/run-configuration-*", "**/src/sql-tools.ts", "**/src/logger/logger-wrapper.ts", "**/usage/*.ts"], + exclude: [ + "**/src/run-configuration-*", + "**/src/sql-tools.ts", + "**/src/logger/logger-wrapper.ts", + "**/usage/*.ts", + ], }; diff --git a/projects/cloud-foundry-tools/packages/backend/src/cfView.ts b/projects/cloud-foundry-tools/packages/backend/src/cfView.ts index aec732855..e3599cb93 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/cfView.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/cfView.ts @@ -1,24 +1,52 @@ import * as vscode from "vscode"; import * as path from "path"; import * as _ from "lodash"; -import { cfGetApps, cfGetServiceInstancesList, cfGetTargets, CFTarget } from "@sap/cf-tools"; +import { + cfGetApps, + cfGetServiceInstancesList, + cfGetTargets, + CFTarget, +} from "@sap/cf-tools"; let cfView: CFView; -export type CFTreeChildNode = CFMessageNode | CFService | CFApplication | CFFolder | CFTargetTI; +export type CFTreeChildNode = + | CFMessageNode + | CFService + | CFApplication + | CFFolder + | CFTargetTI; export class CFTargetTI extends vscode.TreeItem { public contextValue = `cf-target${ - _.includes(this.target.label, "(no targets)") ? "-notargets" : this.target.isCurrent ? "-active" : "" + _.includes(this.target.label, "(no targets)") + ? "-notargets" + : this.target.isCurrent + ? "-active" + : "" }`; constructor(public readonly target: CFTarget) { super( target.label, - target.isCurrent ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed + target.isCurrent + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.Collapsed ); this.tooltip = target.label; this.iconPath = { - light: path.join(__dirname, "..", "resources", "light", `target${this.target.isCurrent ? "-a" : ""}.svg`), - dark: path.join(__dirname, "..", "resources", "dark", `target${this.target.isCurrent ? "-a" : ""}.svg`), + light: path.join( + __dirname, + "..", + "resources", + "light", + `target${this.target.isCurrent ? "-a" : ""}.svg` + ), + dark: path.join( + __dirname, + "..", + "resources", + "dark", + `target${this.target.isCurrent ? "-a" : ""}.svg` + ), }; } @@ -26,7 +54,9 @@ export class CFTargetTI extends vscode.TreeItem { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore get description(): string { - return `${_.includes(this.contextValue, "-active") ? "(Active Target)" : ""}${this.target.isDirty ? "*" : ""}`; + return `${ + _.includes(this.contextValue, "-active") ? "(Active Target)" : "" + }${this.target.isDirty ? "*" : ""}`; } } @@ -61,7 +91,11 @@ export class CFService extends vscode.TreeItem { public iconPath = vscode.ThemeIcon.File; public contextValue = "cf-service"; - constructor(public readonly label: string, public readonly type: string, public readonly parent: CFFolder) { + constructor( + public readonly label: string, + public readonly type: string, + public readonly parent: CFFolder + ) { super(label, vscode.TreeItemCollapsibleState.None); } } @@ -70,7 +104,11 @@ export class CFApplication extends vscode.TreeItem { public iconPath = vscode.ThemeIcon.File; public contextValue = "cf-application"; - constructor(public readonly label: string, public readonly state: string, public readonly parent: CFFolder) { + constructor( + public readonly label: string, + public readonly state: string, + public readonly parent: CFFolder + ) { super(label, vscode.TreeItemCollapsibleState.None); } } @@ -78,7 +116,10 @@ export class CFApplication extends vscode.TreeItem { export class CFFolder extends vscode.TreeItem { public iconPath = vscode.ThemeIcon.Folder; - constructor(public readonly label: string, public readonly parent: CFTargetTI) { + constructor( + public readonly label: string, + public readonly parent: CFTargetTI + ) { super(label, vscode.TreeItemCollapsibleState.Collapsed); this.tooltip = `${label}`; } @@ -106,8 +147,9 @@ export class CFView implements vscode.TreeDataProvider { public readonly privateOnDidChangeTreeData: vscode.EventEmitter< vscode.TreeItem | undefined > = new vscode.EventEmitter(); - public readonly onDidChangeTreeData: vscode.Event = this.privateOnDidChangeTreeData - .event; + public readonly onDidChangeTreeData: vscode.Event< + vscode.TreeItem | undefined + > = this.privateOnDidChangeTreeData.event; public targets?: CFTargetTI[]; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -121,7 +163,9 @@ export class CFView implements vscode.TreeDataProvider { this.privateOnDidChangeTreeData.fire(undefined); } - public getParent(element: vscode.TreeItem): vscode.ProviderResult { + public getParent( + element: vscode.TreeItem + ): vscode.ProviderResult { return _.get(element, "parent") as vscode.TreeItem; } @@ -137,32 +181,54 @@ export class CFView implements vscode.TreeDataProvider { return Promise.resolve(element); } - public async getChildren(parent: vscode.TreeItem): Promise { + public async getChildren( + parent: vscode.TreeItem + ): Promise { if (parent) { if (parent instanceof CFTargetTI) { - return [new CFServicesFolder("Services", parent), new CFAppsFolder("Applications", parent)]; + return [ + new CFServicesFolder("Services", parent), + new CFAppsFolder("Applications", parent), + ]; } else if (/^(services|apps)/.test(parent.contextValue || "")) { if (/-active$/.test(parent.contextValue || "")) { try { return parent instanceof CFAppsFolder ? /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - _.map(await cfGetApps(), (app) => new CFApplication(app.name, _.get(app, "state"), parent)) + _.map( + await cfGetApps(), + (app) => + new CFApplication(app.name, _.get(app, "state"), parent) + ) : _.map( await cfGetServiceInstancesList(), - (service) => new CFService(service.label, service.serviceName, parent as CFFolder) + (service) => + new CFService( + service.label, + service.serviceName, + parent as CFFolder + ) ); } catch (e) { return [new CFLoginNode(parent as CFFolder)]; } } else { /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - return [new CFTargetNotCurrent(parent as CFFolder, _.get(parent, "parent.target.label"))]; + return [ + new CFTargetNotCurrent( + parent as CFFolder, + _.get(parent, "parent.target.label") + ), + ]; } } return []; } // return list of targets - this.targets = _.map(await cfGetTargets(), (target) => new CFTargetTI(target)); + this.targets = _.map( + await cfGetTargets(), + (target) => new CFTargetTI(target) + ); return this.targets; } diff --git a/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts b/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts index 13a04ec84..1532aaeed 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/cfViewCommands.ts @@ -1,6 +1,12 @@ import * as vscode from "vscode"; import * as path from "path"; -import { CFView, CFService, CFTargetTI, CFTargetNotCurrent, getTargetRoot } from "./cfView"; +import { + CFView, + CFService, + CFTargetTI, + CFTargetNotCurrent, + getTargetRoot, +} from "./cfView"; import { messages } from "./messages"; import * as https from "https"; import { @@ -88,7 +94,11 @@ async function execHttp(options: https.RequestOptions): Promise { }); }) .on("error", (err) => { - getModuleLogger(LOGGER_MODULE).error("execHttp: get failed", { error: toText(err) }, { host: options.host }); + getModuleLogger(LOGGER_MODULE).error( + "execHttp: get failed", + { error: toText(err) }, + { host: options.host } + ); reject(err.message); }); }); @@ -100,7 +110,9 @@ async function askUserForPath(): Promise { canSelectFiles: false, canSelectFolders: true, canSelectMany: false, - defaultUri: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri : undefined, + defaultUri: vscode.workspace.workspaceFolders + ? vscode.workspace.workspaceFolders[0].uri + : undefined, }); } @@ -111,7 +123,12 @@ async function getToBindInstances( tags: string[] ) { for (const serviceTypeInfo of serviceInfos) { - await updateInstanceNameAndTags(availableServices, serviceTypeInfo, instanceNames, tags); + await updateInstanceNameAndTags( + availableServices, + serviceTypeInfo, + instanceNames, + tags + ); } } @@ -141,12 +158,20 @@ async function doBind(opts: BindArgs) { ); const serviceNames = _.join(services, ","); if (!opts.options?.silent) { - void vscode.window.showInformationMessage(messages.service_bound_successful(serviceNames)); + void vscode.window.showInformationMessage( + messages.service_bound_successful(serviceNames) + ); } - getModuleLogger(LOGGER_MODULE).info("The service %s has been bound.", serviceNames); + getModuleLogger(LOGGER_MODULE).info( + "The service %s has been bound.", + serviceNames + ); } - const ups = _.filter(opts.instances, ["serviceName", eServiceTypes.user_provided]); + const ups = _.filter(opts.instances, [ + "serviceName", + eServiceTypes.user_provided, + ]); const services = _.difference(opts.instances, ups); if (_.size(services)) { const labels = _.map(services, "label"); @@ -163,7 +188,13 @@ async function doBind(opts: BindArgs) { } if (_.size(ups)) { const labels = _.map(ups, "label"); - const cb = () => cfBindLocalUps(opts.envPath.path.fsPath, labels, opts.tags, opts.options?.["quote-vcap"]); + const cb = () => + cfBindLocalUps( + opts.envPath.path.fsPath, + labels, + opts.tags, + opts.options?.["quote-vcap"] + ); await withProgress(cb, labels); } if (!opts.envPath.ignore) { @@ -171,7 +202,9 @@ async function doBind(opts: BindArgs) { } } -async function getServiceInstanceInfo(instanceName: string): Promise { +async function getServiceInstanceInfo( + instanceName: string +): Promise { const data = await cfGetInstanceMetadata(instanceName); return { label: _.get(data, "serviceName"), @@ -187,7 +220,9 @@ export async function cfDeployServiceAPI(urlPath: string): Promise { // https://api.cf. const deployServiceUrl = cfApi.replace("api.cf", "deploy-service.cfapps"); - const accessToken = (await cfGetAuthToken()).replace("\n", "").replace("\n", ""); // lgtm [js/incomplete-sanitization] + const accessToken = (await cfGetAuthToken()) + .replace("\n", "") + .replace("\n", ""); // lgtm [js/incomplete-sanitization] const urlObj = new URL(deployServiceUrl); @@ -207,7 +242,10 @@ export async function cfDeployServiceAPI(urlPath: string): Promise { throw new Error(messages.no_cf_api_endpoint); } -export async function cmdDeployServiceAPI(servicePath: string, message: string): Promise { +export async function cmdDeployServiceAPI( + servicePath: string, + message: string +): Promise { if (await verifyLoginRetry()) { return vscode.window.withProgress( { @@ -220,15 +258,25 @@ export async function cmdDeployServiceAPI(servicePath: string, message: string): } } -export async function execSetTarget(item: CFTargetTI, options?: CmdOptions): Promise { - const response: CliResult = await Cli.execute(["set-target", "-f", item.target.label]); +export async function execSetTarget( + item: CFTargetTI, + options?: CmdOptions +): Promise { + const response: CliResult = await Cli.execute([ + "set-target", + "-f", + item.target.label, + ]); if (response.exitCode !== 0) { if (!options?.silent) { void vscode.window.showErrorMessage(response.stdout); } - getModuleLogger(LOGGER_MODULE).error(`execSetTarget:: run 'set-target -f' with lable ${item.target.label} failed`, { - output: response.stdout, - }); + getModuleLogger(LOGGER_MODULE).error( + `execSetTarget:: run 'set-target -f' with lable ${item.target.label} failed`, + { + output: response.stdout, + } + ); } else { if (!options?.["skip-reload"]) { await cmdReloadTargets(); @@ -236,10 +284,16 @@ export async function execSetTarget(item: CFTargetTI, options?: CmdOptions): Pro } } -export async function execSaveTarget(item?: CFTargetTI, options?: CmdOptions): Promise { +export async function execSaveTarget( + item?: CFTargetTI, + options?: CmdOptions +): Promise { if (item?.contextValue !== "cf-target-notargets") { const response: CliResult = await Cli.execute( - _.concat(["save-target"], item?.target.label ? ["-f", item.target.label] : []) + _.concat( + ["save-target"], + item?.target.label ? ["-f", item.target.label] : [] + ) ); if (response.exitCode !== 0) { if (!options?.silent) { @@ -265,7 +319,12 @@ export async function cmdSetCurrentTarget( const currTarget = CFView.get().getCurrentTarget(); if (currTarget?.isDirty) { answer = await vscode.window - .showWarningMessage(messages.target_dirty_save(currTarget.label), YES, NO, CANCEL) + .showWarningMessage( + messages.target_dirty_save(currTarget.label), + YES, + NO, + CANCEL + ) .then((selection) => { if (selection === YES) { return execSaveTarget().then(() => selection); @@ -287,7 +346,9 @@ export async function cmdSetCurrentTarget( void vscode.window.showErrorMessage(toText(e)); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ getModuleLogger(LOGGER_MODULE).error( - `cmdSetCurrentTargetCommand with new target ${stringify(newTarget)} exception thrown`, + `cmdSetCurrentTargetCommand with new target ${stringify( + newTarget + )} exception thrown`, /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ { error: toText(e) } ); @@ -295,7 +356,10 @@ export async function cmdSetCurrentTarget( } } -export async function cmdDeleteTarget(item: CFTargetTI, options?: CmdOptions): Promise { +export async function cmdDeleteTarget( + item: CFTargetTI, + options?: CmdOptions +): Promise { const targetLabel = item.target.label; if (targetLabel === DEFAULT_TARGET) { return; @@ -306,14 +370,21 @@ export async function cmdDeleteTarget(item: CFTargetTI, options?: CmdOptions): P await cmdReloadTargets(); } if (!options?.silent) { - void vscode.window.showInformationMessage(messages.target_deleted(targetLabel)); + void vscode.window.showInformationMessage( + messages.target_deleted(targetLabel) + ); } - getModuleLogger(LOGGER_MODULE).debug(`cmdDeleteTarget:: command "delete-target" of ${targetLabel} succeeded.`); + getModuleLogger(LOGGER_MODULE).debug( + `cmdDeleteTarget:: command "delete-target" of ${targetLabel} succeeded.` + ); } else { void vscode.window.showErrorMessage(cliResult.stdout); - getModuleLogger(LOGGER_MODULE).error(`cmdSetCurrentTargetCommand:: run 'delete-target of ${targetLabel} failed`, { - output: cliResult.stdout, - }); + getModuleLogger(LOGGER_MODULE).error( + `cmdSetCurrentTargetCommand:: run 'delete-target of ${targetLabel} failed`, + { + output: cliResult.stdout, + } + ); } } @@ -327,26 +398,36 @@ export async function cmdGetSpaceServices( return getAvailableServices({ query, ups: { isShow: true } }, progressTitle); } -async function composeQueryToObtainInstances(serviceInfos: ServiceTypeInfo[]): Promise { +async function composeQueryToObtainInstances( + serviceInfos: ServiceTypeInfo[] +): Promise { let query; if (_.get(serviceInfos, ["0", "plan"])) { - query = padQuery((query as unknown) as IServiceQuery, [ - { key: eFilters.service_plan_names, value: resolveFilterValue(serviceInfos[0].plan) }, + query = padQuery(query as unknown as IServiceQuery, [ + { + key: eFilters.service_plan_names, + value: resolveFilterValue(serviceInfos[0].plan), + }, ]); } if (/*!_.size(plans) &&*/ _.get(serviceInfos, "[0].name")) { if (eServiceTypes.user_provided === serviceInfos[0].name) { - query = padQuery((query as unknown) as IServiceQuery, [ + query = padQuery(query as unknown as IServiceQuery, [ { key: eFilters.service_plan_names, value: "nothing-to-show" }, ]); // tricky - to show the only user-provided instances } else { const guids = _.map( await fetchServicePlanList({ - filters: [{ key: eFilters.service_offering_names, value: resolveFilterValue(serviceInfos[0].name) }], + filters: [ + { + key: eFilters.service_offering_names, + value: resolveFilterValue(serviceInfos[0].name), + }, + ], }), "guid" ); - query = padQuery((query as unknown) as IServiceQuery, [ + query = padQuery(query as unknown as IServiceQuery, [ { key: eFilters.service_plan_guids, value: _.join(guids) }, ]); } @@ -360,19 +441,36 @@ async function collectBindDetails( ): Promise { let details; - if (_.get(service, "contextValue") === "cf-service" && _.get(service, "label")) { + if ( + _.get(service, "contextValue") === "cf-service" && + _.get(service, "label") + ) { details = { - instances: [{ label: (service as CFService).label, serviceName: (service as CFService).type || "unknown" }], + instances: [ + { + label: (service as CFService).label, + serviceName: (service as CFService).type || "unknown", + }, + ], }; } else { // service is ServiceTypeInfo - const query = await composeQueryToObtainInstances(service as ServiceTypeInfo[]); - let availableServices = await getAvailableServices({ query, ups: _.get(service, ["0", "ups"]) }); + const query = await composeQueryToObtainInstances( + service as ServiceTypeInfo[] + ); + let availableServices = await getAvailableServices({ + query, + ups: _.get(service, ["0", "ups"]), + }); if (_.isEmpty(availableServices)) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - getModuleLogger(LOGGER_MODULE).debug(`No services found for plan ${_.get(service, "[0].plan")}`); + getModuleLogger(LOGGER_MODULE).debug( + `No services found for plan ${_.get(service, "[0].plan")}` + ); if (!_.find(service as ServiceTypeInfo[], "allowCreate")) { - void vscode.window.showInformationMessage(messages.no_services_instances_found); + void vscode.window.showInformationMessage( + messages.no_services_instances_found + ); return details; } } @@ -387,8 +485,14 @@ async function collectBindDetails( for (const serviceTypeInfo of serviceTypeInfos) { if (serviceTypeInfo.allowCreate) { // add 'create service' menu item - availableServices = _.concat([{ label: CMD_CREATE_SERVICE, serviceName: "" }], availableServices); - if (_.size(availableServices) && !_.isEmpty(serviceTypeInfo.allowCreate.name)) { + availableServices = _.concat( + [{ label: CMD_CREATE_SERVICE, serviceName: "" }], + availableServices + ); + if ( + _.size(availableServices) && + !_.isEmpty(serviceTypeInfo.allowCreate.name) + ) { // add 'Bind to the default service instance' menu item availableServices = _.concat( [ @@ -403,14 +507,26 @@ async function collectBindDetails( ); } } - await updateInstanceNameAndTags(availableServices, serviceTypeInfo, instanceNames, tags); + await updateInstanceNameAndTags( + availableServices, + serviceTypeInfo, + instanceNames, + tags + ); } } else { - let foundInstance = _.find(availableServices, ["label", requstedInstance]); + let foundInstance = _.find(availableServices, [ + "label", + requstedInstance, + ]); if (!foundInstance) { - foundInstance = await getServiceInstanceInfo(requstedInstance).catch(() => undefined); + foundInstance = await getServiceInstanceInfo(requstedInstance).catch( + () => undefined + ); if (!foundInstance) { - throw new Error(messages.no_services_instance_byname_found(requstedInstance)); + throw new Error( + messages.no_services_instance_byname_found(requstedInstance) + ); } availableServices.push(foundInstance); } @@ -420,9 +536,14 @@ async function collectBindDetails( if (_.size(instanceNames) > 0) { const serviceKeyNames: string[] = _.compact( - _.flatMap(serviceTypeInfos, (serviceTypeInfo) => serviceTypeInfo.serviceKeyName) + _.flatMap( + serviceTypeInfos, + (serviceTypeInfo) => serviceTypeInfo.serviceKeyName + ) + ); + const serviceKeyParams: unknown[] = _.compact( + _.map(serviceTypeInfos, "serviceKeyParam") ); - const serviceKeyParams: unknown[] = _.compact(_.map(serviceTypeInfos, "serviceKeyParam")); const instances = []; for (const name of instanceNames) { let serviceInstance = _.find(availableServices, ["label", name]); @@ -442,7 +563,9 @@ async function collectBindDetails( } else { const instanceName = await getInstanceName(availableServices); if (instanceName) { - details = { instances: [{ label: instanceName, serviceName: "unknown" }] }; + details = { + instances: [{ label: instanceName, serviceName: "unknown" }], + }; } } } @@ -480,7 +603,9 @@ export async function cmdBindLocal( return; } /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - filePath = vscode.Uri.file(path.join(_.get(uriArray, "[0].fsPath"), ".env")); + filePath = vscode.Uri.file( + path.join(_.get(uriArray, "[0].fsPath"), ".env") + ); } try { const bindDetails = await collectBindDetails(service, instanceName); @@ -495,7 +620,10 @@ export async function cmdBindLocal( }); const instanceName = _.get(_.head(bindDetails.instances), "label"); if (instanceName) { - const chiselTask = await checkAndCreateChiselTask(filePath.fsPath, instanceName); + const chiselTask = await checkAndCreateChiselTask( + filePath.fsPath, + instanceName + ); if (chiselTask) { await deleteChiselParamsFromFile(filePath.fsPath); } @@ -531,13 +659,19 @@ export async function bindLocalService( return []; } const query = await composeQueryToObtainInstances(serviceInfos); - const availableServices: ServiceInstanceInfo[] = await getAvailableServices({ query }); + const availableServices: ServiceInstanceInfo[] = await getAvailableServices( + { query } + ); if (_.isEmpty(availableServices)) { if (!_.isEmpty(_.get(serviceInfos, ["0", "plan"]))) { - getModuleLogger(LOGGER_MODULE).debug(`No service found for the plan ${serviceInfos[0].plan}`); + getModuleLogger(LOGGER_MODULE).debug( + `No service found for the plan ${serviceInfos[0].plan}` + ); } if (!_.find(serviceInfos, "allowCreate")) { - void vscode.window.showInformationMessage(messages.no_services_instances_found); + void vscode.window.showInformationMessage( + messages.no_services_instances_found + ); } return []; } @@ -547,7 +681,12 @@ export async function bindLocalService( if (isServiceTypeInfoInArray(serviceInfos)) { const instanceNames: string[] = []; const tags: string[] = []; - await getToBindInstances(serviceInfos, availableServices, instanceNames, tags); + await getToBindInstances( + serviceInfos, + availableServices, + instanceNames, + tags + ); const instances = _.reduce( instanceNames, (result, name) => { diff --git a/projects/cloud-foundry-tools/packages/backend/src/chisel.ts b/projects/cloud-foundry-tools/packages/backend/src/chisel.ts index 306658b45..303c0b6eb 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/chisel.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/chisel.ts @@ -27,12 +27,18 @@ function initReader(filePath: string): PropertiesReader.Reader | undefined { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function checkAndCreateChiselTask(filePath: string, name: string): any | undefined { +export function checkAndCreateChiselTask( + filePath: string, + name: string +): any | undefined { const envProperties = initReader(filePath); if (envProperties) { const chiselUrl = envProperties.get(ChiselKeys.CHISEL_URL); if (_.isEmpty(chiselUrl)) { - getModuleLogger(LOGGER_MODULE).debug("checkAndCreateChiselTask: empty chisel_url", { filePath: filePath }); + getModuleLogger(LOGGER_MODULE).debug( + "checkAndCreateChiselTask: empty chisel_url", + { filePath: filePath } + ); return undefined; } getModuleLogger(LOGGER_MODULE).debug( @@ -50,7 +56,9 @@ export function checkAndCreateChiselTask(filePath: string, name: string): any | "client", "--auth", // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - `${envProperties.get(ChiselKeys.CHISEL_USER)}:${envProperties.get(ChiselKeys.CHISEL_PASSWORD)}`, + `${envProperties.get(ChiselKeys.CHISEL_USER)}:${envProperties.get( + ChiselKeys.CHISEL_PASSWORD + )}`, chiselUrl, envProperties.get(ChiselKeys.TUNNEL_PARAM), ], @@ -73,7 +81,9 @@ function dropChiselProperties(propObj: any): Record { return properties; } -export async function deleteChiselParamsFromFile(filePath: string): Promise { +export async function deleteChiselParamsFromFile( + filePath: string +): Promise { const envProperties = initReader(filePath); if (!envProperties) { return false; @@ -83,16 +93,17 @@ export async function deleteChiselParamsFromFile(filePath: string): Promise { +function invokeLongFunctionWithProgress( + longFunction: any, + progressMessage: string +): Thenable { return vscode.window.withProgress( { location: vscode.ProgressLocation.Window, @@ -86,7 +92,11 @@ async function setCfTarget(message: string) { } else if (!target.space) { commandId = "cf.select.space"; } else { - getModuleLogger(LOGGER_MODULE).error("setCfTarget: cfGetTarget failed", { target: target }, { output: message }); + getModuleLogger(LOGGER_MODULE).error( + "setCfTarget: cfGetTarget failed", + { target: target }, + { output: message } + ); return Promise.reject(new Error(message)); } @@ -116,7 +126,11 @@ async function runBaseWithProgressAndLoginRetry( cancellable: isCancelable, // eslint-disable-next-line prefer-spread }, - (progress, token) => longFunction.apply(null, <[]>(_.isArray(args) ? _.concat(args, token) : [args, token])) + (progress, token) => + longFunction.apply( + null, + <[]>(_.isArray(args) ? _.concat(args, token) : [args, token]) + ) ); } catch (error) { // eslint-disable-next-line prefer-spread @@ -146,10 +160,18 @@ async function runWithProgressAndLoginRetry( longFunction: () => Promise, args?: any ): Promise { - return runBaseWithProgressAndLoginRetry(isCancelable, titleMessage, longFunction, onErrorSetCfTarget, args); + return runBaseWithProgressAndLoginRetry( + isCancelable, + titleMessage, + longFunction, + onErrorSetCfTarget, + args + ); } -export async function verifyLoginRetry(options?: { weak?: boolean }): Promise { +export async function verifyLoginRetry(options?: { + weak?: boolean; +}): Promise { let result: unknown; try { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -162,7 +184,11 @@ export async function verifyLoginRetry(options?: { weak?: boolean }): Promise { } } -async function verifyLoginRetryPartial(opts?: { endPoint?: string }): Promise { +async function verifyLoginRetryPartial(opts?: { + endPoint?: string; +}): Promise { let result: unknown; try { result = await runBaseWithProgressAndLoginRetry( @@ -195,7 +223,9 @@ async function verifyLoginRetryPartial(opts?: { endPoint?: string }): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument void vscode.window.showErrorMessage(toText(e)); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - getModuleLogger(LOGGER_MODULE).error("cmdCFSetOrgSpace failed", { exception: toText(e) }); + getModuleLogger(LOGGER_MODULE).error("cmdCFSetOrgSpace failed", { + exception: toText(e), + }); return ""; } } @@ -240,13 +272,19 @@ export async function cmdLogin( opts = opts ?? { isSplit: true, isLoginOnly: true }; - let result = weak ? (_.get(await pickCfTargetWithProgress(), "user") ? OK : undefined) : undefined; + let result = weak + ? _.get(await pickCfTargetWithProgress(), "user") + ? OK + : undefined + : undefined; if (!result) { const node = weak as unknown; if (node instanceof CFLoginNode) { // attempt to login from targets tree // eslint-disable-next-line @typescript-eslint/no-unsafe-call - const configJson = await cfGetConfigFileJson(_.get(getTargetRoot(node), ["target", "label"])); + const configJson = await cfGetConfigFileJson( + _.get(getTargetRoot(node), ["target", "label"]) + ); if (configJson) { endpoint = _.get(configJson, "Target"); space = _.get(configJson, ["SpaceFields", "Name"]); @@ -267,8 +305,13 @@ export async function cmdLogin( // Need only to set active target if (endpoint && org && space) { - await invokeLongFunctionWithProgress(cfSetOrgSpace.bind(undefined, org, space), messages.set_org_space); - void vscode.window.showInformationMessage(messages.success_set_org_space); + await invokeLongFunctionWithProgress( + cfSetOrgSpace.bind(undefined, org, space), + messages.set_org_space + ); + void vscode.window.showInformationMessage( + messages.success_set_org_space + ); getModuleLogger(LOGGER_MODULE).debug( "cmdCFSetOrgSpace: Organization <%s> and space <%s> have been set", org, @@ -286,7 +329,11 @@ export async function cmdLogin( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const errText = toText(e); void vscode.window.showErrorMessage(errText); - getModuleLogger(LOGGER_MODULE).error("cmdLogin failed", { weak: weak }, { exception: errText }); + getModuleLogger(LOGGER_MODULE).error( + "cmdLogin failed", + { weak: weak }, + { exception: errText } + ); return ""; } } @@ -298,30 +345,51 @@ export async function cmdSelectSpace(): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const errText = toText(e); void vscode.window.showErrorMessage(errText); - getModuleLogger(LOGGER_MODULE).error("cmdSelectSpace failed", { exception: errText }); + getModuleLogger(LOGGER_MODULE).error("cmdSelectSpace failed", { + exception: errText, + }); return ""; } } async function cmdCreateTarget(): Promise { - const targetName = await vscode.window.showInputBox({ prompt: messages.name_for_target, ignoreFocusOut: true }); + const targetName = await vscode.window.showInputBox({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }); if (targetName) { - const cliResult: CliResult = await Cli.execute(["save-target", "-f", targetName]); + const cliResult: CliResult = await Cli.execute([ + "save-target", + "-f", + targetName, + ]); if (cliResult.exitCode !== 0) { - getModuleLogger(LOGGER_MODULE).error("cmdCreateTarget : command 'save-target -f <%s>' failed", targetName, { - output: cliResult.stdout, - }); + getModuleLogger(LOGGER_MODULE).error( + "cmdCreateTarget : command 'save-target -f <%s>' failed", + targetName, + { + output: cliResult.stdout, + } + ); return Promise.reject(new Error(cliResult.stdout)); } await cmdReloadTargets(); - void vscode.window.showInformationMessage(messages.target_created(targetName)); - getModuleLogger(LOGGER_MODULE).debug("cmdCreateTarget: the <%s> target has been created", targetName); + void vscode.window.showInformationMessage( + messages.target_created(targetName) + ); + getModuleLogger(LOGGER_MODULE).debug( + "cmdCreateTarget: the <%s> target has been created", + targetName + ); return targetName; } } -function ask4ArbitraryParams(serviceInfo: ServiceInfo, plan: PlanInfo): Thenable { +function ask4ArbitraryParams( + serviceInfo: ServiceInfo, + plan: PlanInfo +): Thenable { const data = generateParams4Service(serviceInfo.label, plan.label); const value = stringify(data); const xsappname: string = _.get(data, "xsappname"); @@ -331,7 +399,10 @@ function ask4ArbitraryParams(serviceInfo: ServiceInfo, plan: PlanInfo): Thenable prompt: messages.create_service_enter_params, value, valueSelection: pos > 0 ? [pos, pos + _.size(xsappname)] : undefined, - validateInput: validateParams(_.get(serviceInfo, "label"), _.get(plan, "label")), + validateInput: validateParams( + _.get(serviceInfo, "label"), + _.get(plan, "label") + ), }); } @@ -346,8 +417,18 @@ async function onGetServicePlansFromCF( title || messages.loading_service_plan_list, cfGetServicePlansList as () => Promise, _.merge( - { filters: [{ key: eFilters.service_offering_guids, value: opt.serviceGuid }] }, - opt.planName ? { filters: [{ key: eFilters.names, value: resolveFilterValue(opt.planName) }] } : {} + { + filters: [ + { key: eFilters.service_offering_guids, value: opt.serviceGuid }, + ], + }, + opt.planName + ? { + filters: [ + { key: eFilters.names, value: resolveFilterValue(opt.planName) }, + ], + } + : {} ) ); } @@ -360,16 +441,30 @@ export async function onCreateService( progress: vscode.Progress<{ message?: string; increment?: number }>, cancelToken: vscode.CancellationToken ): Promise { - const response = await cfCreateService(planInfo.guid, instanceName, params || {}, tags || [], { - progress: progress, - cancelToken: cancelToken, - }); - void vscode.window.showInformationMessage(messages.service_created(response.name)); - getModuleLogger(LOGGER_MODULE).debug("onCreateService: the service has been created", { response: response }); + const response = await cfCreateService( + planInfo.guid, + instanceName, + params || {}, + tags || [], + { + progress: progress, + cancelToken: cancelToken, + } + ); + void vscode.window.showInformationMessage( + messages.service_created(response.name) + ); + getModuleLogger(LOGGER_MODULE).debug( + "onCreateService: the service has been created", + { response: response } + ); return response.name; } -async function createUpsInstance(name: string, info?: ServiceTypeInfo): Promise { +async function createUpsInstance( + name: string, + info?: ServiceTypeInfo +): Promise { let result; const quietMode = info?.tag && info.allowCreate?.name; const credentials = quietMode @@ -384,16 +479,25 @@ async function createUpsInstance(name: string, info?: ServiceTypeInfo): Promise< const tags = quietMode ? info?.tag : await vscode.window.showInputBox( - _.merge({ prompt: messages.enter_tags, ignoreFocusOut: true }, info?.tag ? { value: info.tag } : {}) + _.merge( + { prompt: messages.enter_tags, ignoreFocusOut: true }, + info?.tag ? { value: info.tag } : {} + ) ); if (undefined !== tags) { const sysLogUrl = quietMode ? "" - : await vscode.window.showInputBox({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }); + : await vscode.window.showInputBox({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }); if (undefined !== sysLogUrl) { const routeUrl = quietMode ? "" - : await vscode.window.showInputBox({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }); + : await vscode.window.showInputBox({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }); if (undefined !== routeUrl) { // canceled const response = await cfCreateUpsInstance({ @@ -404,11 +508,16 @@ async function createUpsInstance(name: string, info?: ServiceTypeInfo): Promise< tags: _.compact(_.split(tags, /[,\s]/)), }); void vscode.window.showInformationMessage( - isCFResource(response) ? messages.service_created(response.name) : _.toString(response) + isCFResource(response) + ? messages.service_created(response.name) + : _.toString(response) + ); + getModuleLogger(LOGGER_MODULE).debug( + "createUpsInstance: the service has been created", + { + response: response, + } ); - getModuleLogger(LOGGER_MODULE).debug("createUpsInstance: the service has been created", { - response: response, - }); result = response?.name; } } @@ -417,7 +526,10 @@ async function createUpsInstance(name: string, info?: ServiceTypeInfo): Promise< return result; } -async function createServiceInstance(name: string, info?: ServiceTypeInfo): Promise { +async function createServiceInstance( + name: string, + info?: ServiceTypeInfo +): Promise { const serviceName = info?.allowCreate?.serviceName || info?.name; const servicePlan = info?.allowCreate?.plan || info?.plan; const serviceTag = info?.allowCreate?.tag || info?.tag; @@ -426,7 +538,11 @@ async function createServiceInstance(name: string, info?: ServiceTypeInfo): Prom true, messages.loading_services, cfGetServices, - { filters: [{ key: eFilters.names, value: resolveFilterValue(serviceName) }] } + { + filters: [ + { key: eFilters.names, value: resolveFilterValue(serviceName) }, + ], + } ); if (_.size(servicesInfo)) { const serviceInfo = @@ -459,7 +575,11 @@ async function createServiceInstance(name: string, info?: ServiceTypeInfo): Prom return vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, - title: messages.creating_service(name, serviceInfo.label, planInfo.label), + title: messages.creating_service( + name, + serviceInfo.label, + planInfo.label + ), cancellable: true, }, (progress, cancelToken) => @@ -490,10 +610,17 @@ async function createServiceInstance(name: string, info?: ServiceTypeInfo): Prom } } -async function createService(isUps: boolean, info?: ServiceTypeInfo): Promise { +async function createService( + isUps: boolean, + info?: ServiceTypeInfo +): Promise { // first ask for service-name const options = _.merge( - { prompt: info?.allowCreate?.namePrompt ? `${info.allowCreate.namePrompt}` : messages.enter_service_name }, + { + prompt: info?.allowCreate?.namePrompt + ? `${info.allowCreate.namePrompt}` + : messages.enter_service_name, + }, info?.allowCreate?.name ? { value: `${info.allowCreate.name}` } : {}, { ignoreFocusOut: true } ); @@ -501,7 +628,9 @@ async function createService(isUps: boolean, info?: ServiceTypeInfo): Promise { +export async function cmdCreateService( + info?: ServiceTypeInfo +): Promise { if (await verifyLoginRetry()) { return createService(false, info); } } -export async function cmdCreateUps(info?: ServiceTypeInfo): Promise { +export async function cmdCreateUps( + info?: ServiceTypeInfo +): Promise { if (await verifyLoginRetry()) { return createService(true, info); } } -export async function fetchServicePlanList(query?: IServiceQuery): Promise { +export async function fetchServicePlanList( + query?: IServiceQuery +): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return runWithProgressAndLoginRetry(false, messages.loading_service_plan_list, cfGetServicePlansList, query); + return runWithProgressAndLoginRetry( + false, + messages.loading_service_plan_list, + cfGetServicePlansList, + query + ); } -export function getAvailableServices(opts?: DisplayServices, progressTitle?: string): Promise { +export function getAvailableServices( + opts?: DisplayServices, + progressTitle?: string +): Promise { return notifyWhenServicesInfoResultIncomplete( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - runWithProgressAndLoginRetry(true, progressTitle || messages.loading_services, getAllServiceInstances, opts) + runWithProgressAndLoginRetry( + true, + progressTitle || messages.loading_services, + getAllServiceInstances, + opts + ) ); } @@ -547,7 +695,12 @@ export async function getServiceInstances( ): Promise { return notifyWhenServicesInfoResultIncomplete( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - runWithProgressAndLoginRetry(true, progressTitle || messages.loading_services, cfGetServiceInstances, query) + runWithProgressAndLoginRetry( + true, + progressTitle || messages.loading_services, + cfGetServiceInstances, + query + ) ); } @@ -557,7 +710,12 @@ export async function getUserProvidedServiceInstances( ): Promise { return notifyWhenServicesInfoResultIncomplete( // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - runWithProgressAndLoginRetry(true, progressTitle || messages.loading_ups_services, getUpsServiceInstances, options) + runWithProgressAndLoginRetry( + true, + progressTitle || messages.loading_ups_services, + getUpsServiceInstances, + options + ) ); } @@ -582,7 +740,10 @@ async function askUserForServiceInstanceName( } const pickItems = _.map(availableServicesToShow, (s) => { - return { label: s.label, description: s.serviceName + (s.plan ? ` (${s.plan})` : "") }; + return { + label: s.label, + description: s.serviceName + (s.plan ? ` (${s.plan})` : ""), + }; }); return vscode.window @@ -610,7 +771,10 @@ async function checkForMoreServices( return false; } // it is more: - const nextPage = Number.parseInt(_.get(lastElem, 'serviceName.split(":")[1]') as string, 10); + const nextPage = Number.parseInt( + _.get(lastElem, 'serviceName.split(":")[1]') as string, + 10 + ); const nextAvailableServices = await getAvailableServices({ query: { page: nextPage }, @@ -636,9 +800,17 @@ export async function getInstanceName( availableServices: ServiceInstanceInfo[], serviceType?: ServiceTypeInfo ): Promise { - let instanceName = await askUserForServiceInstanceName(availableServices, serviceType); - while (await checkForMoreServices(instanceName, availableServices, serviceType)) { - instanceName = await askUserForServiceInstanceName(availableServices, serviceType); + let instanceName = await askUserForServiceInstanceName( + availableServices, + serviceType + ); + while ( + await checkForMoreServices(instanceName, availableServices, serviceType) + ) { + instanceName = await askUserForServiceInstanceName( + availableServices, + serviceType + ); } return instanceName; } @@ -654,7 +826,10 @@ export async function updateInstanceNameAndTags( instanceName = await (serviceTypeInfo?.name === eServiceTypes.user_provided ? cmdCreateUps(serviceTypeInfo) : cmdCreateService(serviceTypeInfo)); - } else if (instanceName === `${CMD_BIND_TO_DEFAULT_SERVICE}${serviceTypeInfo?.allowCreate?.name}`) { + } else if ( + instanceName === + `${CMD_BIND_TO_DEFAULT_SERVICE}${serviceTypeInfo?.allowCreate?.name}` + ) { const defaultInstance = _.find(availableServices, { label: serviceTypeInfo?.allowCreate?.name, plan: serviceTypeInfo?.allowCreate?.plan, @@ -663,9 +838,16 @@ export async function updateInstanceNameAndTags( if (defaultInstance) { instanceName = defaultInstance.label; } else { - instanceName = await (serviceTypeInfo?.name === eServiceTypes.user_provided - ? createUpsInstance(serviceTypeInfo?.allowCreate?.name || "", serviceTypeInfo) - : createServiceInstance(serviceTypeInfo?.allowCreate?.name || "", serviceTypeInfo)); + instanceName = await (serviceTypeInfo?.name === + eServiceTypes.user_provided + ? createUpsInstance( + serviceTypeInfo?.allowCreate?.name || "", + serviceTypeInfo + ) + : createServiceInstance( + serviceTypeInfo?.allowCreate?.name || "", + serviceTypeInfo + )); } } if (_.size(instanceName) > 0) { @@ -675,9 +857,15 @@ export async function updateInstanceNameAndTags( return instanceName; } -export function updateServicesOnCFPageSize(availableServices: ServiceInstanceInfo[]): void { +export function updateServicesOnCFPageSize( + availableServices: ServiceInstanceInfo[] +): void { if (_.size(availableServices) === CF_PAGE_SIZE) { - availableServices.push({ label: MORE_RESULTS, serviceName: "more:2", alwaysShow: true }); + availableServices.push({ + label: MORE_RESULTS, + serviceName: "more:2", + alwaysShow: true, + }); } } @@ -689,10 +877,17 @@ export async function cmdSelectAndSaveTarget(): Promise { const affairs = [ { id: "save", - label: messages.set_targets_save(target.org || "undefined", target.space || "undefined"), + label: messages.set_targets_save( + target.org || "undefined", + target.space || "undefined" + ), detail: messages.set_targets_save_details, }, - { id: "pick-save", label: messages.set_targets_pick_save, detail: messages.set_targets_pick_save_details }, + { + id: "pick-save", + label: messages.set_targets_pick_save, + detail: messages.set_targets_pick_save_details, + }, ]; let result; const action = await vscode.window.showQuickPick(affairs, { @@ -727,7 +922,10 @@ export async function cmdSelectAndSaveTarget(): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const errText = toText(e); void vscode.window.showErrorMessage(errText); - getModuleLogger(LOGGER_MODULE).error(`cmdSelectAndSaveTarget exception thrown`, { error: errText }); + getModuleLogger(LOGGER_MODULE).error( + `cmdSelectAndSaveTarget exception thrown`, + { error: errText } + ); } } diff --git a/projects/cloud-foundry-tools/packages/backend/src/extension.ts b/projects/cloud-foundry-tools/packages/backend/src/extension.ts index 6149b3905..763e9a4df 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/extension.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/extension.ts @@ -33,7 +33,13 @@ import { execSaveTarget, } from "./cfViewCommands"; -import { cfGetConfigFilePath, cfGetConfigFileField, ITarget, cfGetTarget, OK } from "@sap/cf-tools"; +import { + cfGetConfigFilePath, + cfGetConfigFileField, + ITarget, + cfGetTarget, + OK, +} from "@sap/cf-tools"; import * as fs from "fs"; import { DependencyHandler } from "./run-configuration"; import { IRunConfigRegistry } from "@sap/wing-run-config-types"; @@ -52,11 +58,18 @@ const onDidChangeTarget = targetChangedEventEmitter.event; async function updateStatusBar(): Promise { let isUpdated; const beforeText = _.get(cfStatusBarItem, "text"); - const results = await Promise.all([cfGetConfigFileField("OrganizationFields"), cfGetConfigFileField("SpaceFields")]); + const results = await Promise.all([ + cfGetConfigFileField("OrganizationFields"), + cfGetConfigFileField("SpaceFields"), + ]); const orgField: string = _.get(results, "[0].Name"); const spaceField: string = _.get(results, "[1].Name"); const isNoTarget = _.isEmpty(orgField) && _.isEmpty(spaceField); - const updatedText = `${isNoTarget ? messages.not_targeted : messages.targeting(orgField, spaceField)}`; + const updatedText = `${ + isNoTarget + ? messages.not_targeted + : messages.targeting(orgField, spaceField) + }`; if (beforeText !== updatedText) { _.set(cfStatusBarItem, "text", updatedText); @@ -89,7 +102,9 @@ export function onCFConfigFileChange(): void { } function displayTargetWhenAllowed(): void { - if (workspace.getConfiguration().get("CloudFoundryTools.showTargetInformation")) { + if ( + workspace.getConfiguration().get("CloudFoundryTools.showTargetInformation") + ) { cfStatusBarItem.show(); } else { cfStatusBarItem.hide(); @@ -97,13 +112,19 @@ function displayTargetWhenAllowed(): void { } // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function callbackOnDidChangeConfiguration(e: ConfigurationChangeEvent, context: ExtensionContext): void { +export function callbackOnDidChangeConfiguration( + e: ConfigurationChangeEvent, + context: ExtensionContext +): void { if (e.affectsConfiguration("CloudFoundryTools.showTargetInformation")) { displayTargetWhenAllowed(); } } -async function init(context: ExtensionContext, cfConfigFilePath: string): Promise { +async function init( + context: ExtensionContext, + cfConfigFilePath: string +): Promise { cfStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 100); context.subscriptions.push(cfStatusBarItem); @@ -111,7 +132,9 @@ async function init(context: ExtensionContext, cfConfigFilePath: string): Promis // cfStatusBarItem.command = {command: loginCmdId, arguments: [true], title: ""}; // start workarround --> remove following workarround in future theia releases /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - context.subscriptions.push(commands.registerCommand("cf.login.weak", cmdLogin.bind(null, true))); + context.subscriptions.push( + commands.registerCommand("cf.login.weak", cmdLogin.bind(null, true)) + ); cfStatusBarItem.command = "cf.login"; // end workarround @@ -123,32 +146,55 @@ async function init(context: ExtensionContext, cfConfigFilePath: string): Promis displayTargetWhenAllowed(); - let platformExtension = extensions.getExtension(runConfigExtName); + let platformExtension = + extensions.getExtension(runConfigExtName); if (platformExtension) { if (!platformExtension.isActive) { try { await platformExtension.activate(); } catch (e) { platformExtension = undefined; - getModuleLogger(LOGGER_MODULE).error("activate <%s> extension fails", runConfigExtName, { - exception: toText(new Error(e?.message as string)), - }); + getModuleLogger(LOGGER_MODULE).error( + "activate <%s> extension fails", + runConfigExtName, + { + exception: toText(new Error(e?.message as string)), + } + ); } } if (platformExtension) { - const genericDependencyHandler = new DependencyHandler("cf-tools-rsource-dependency"); + const genericDependencyHandler = new DependencyHandler( + "cf-tools-rsource-dependency" + ); platformExtension.exports.registerDependency(genericDependencyHandler); // eslint-disable-next-line @typescript-eslint/unbound-method - context.subscriptions.push(commands.registerCommand("cf.services.bind", genericDependencyHandler.bind)); + context.subscriptions.push( + commands.registerCommand( + "cf.services.bind", + genericDependencyHandler.bind + ) + ); // eslint-disable-next-line @typescript-eslint/unbound-method - context.subscriptions.push(commands.registerCommand("cf.services.unbind", genericDependencyHandler.unbind)); + context.subscriptions.push( + commands.registerCommand( + "cf.services.unbind", + genericDependencyHandler.unbind + ) + ); // eslint-disable-next-line @typescript-eslint/unbound-method context.subscriptions.push( - commands.registerCommand("cf.services.binding.state", genericDependencyHandler.getBindState) + commands.registerCommand( + "cf.services.binding.state", + genericDependencyHandler.getBindState + ) ); } } else { - getModuleLogger(LOGGER_MODULE).error("activate: the <%s> extension has not been set", runConfigExtName); + getModuleLogger(LOGGER_MODULE).error( + "activate: the <%s> extension has not been set", + runConfigExtName + ); } } @@ -158,32 +204,56 @@ export async function activate(context: ExtensionContext): Promise { const cfConfigFilePath: string = cfGetConfigFilePath(); treeDataProvider = new CFView(context, cfConfigFilePath); - const view = window.createTreeView("cfView", { treeDataProvider, showCollapseAll: true }); - - context.subscriptions.push( - commands.registerCommand("cf.login", (weak: boolean, target: boolean, extEndPoint: string | undefined) => { - return cmdLogin(weak, target, extEndPoint, { isSplit: !!weak }).then((result) => { - if (OK === result) { - const active = _.find(treeDataProvider.getTargets(), "target.isCurrent"); - if (active) { - // after re-login the target data become invalid -> perform re-create target - void cmdDeleteTarget(active, { "skip-reload": true, silent: true }).then(() => { - void execSaveTarget(active, { "skip-reload": true, silent: true }).then(() => { - void execSetTarget(active, { silent: true }); - }); - }); + const view = window.createTreeView("cfView", { + treeDataProvider, + showCollapseAll: true, + }); + + context.subscriptions.push( + commands.registerCommand( + "cf.login", + (weak: boolean, target: boolean, extEndPoint: string | undefined) => { + return cmdLogin(weak, target, extEndPoint, { isSplit: !!weak }).then( + (result) => { + if (OK === result) { + const active = _.find( + treeDataProvider.getTargets(), + "target.isCurrent" + ); + if (active) { + // after re-login the target data become invalid -> perform re-create target + void cmdDeleteTarget(active, { + "skip-reload": true, + silent: true, + }).then(() => { + void execSaveTarget(active, { + "skip-reload": true, + silent: true, + }).then(() => { + void execSetTarget(active, { silent: true }); + }); + }); + } + } + return result; } - } - return result; - }); - }) + ); + } + ) ); - context.subscriptions.push(workspace.onDidChangeConfiguration((e) => callbackOnDidChangeConfiguration(e, context))); + context.subscriptions.push( + workspace.onDidChangeConfiguration((e) => + callbackOnDidChangeConfiguration(e, context) + ) + ); function revealTargetItem(label?: string) { setTimeout(() => { - const treeItem = _.find(treeDataProvider.getTargets(), label ? ["label", label] : "target.isCurrent"); + const treeItem = _.find( + treeDataProvider.getTargets(), + label ? ["label", label] : "target.isCurrent" + ); if (treeItem) { void view.reveal(treeItem, { select: true, focus: true, expand: true }); } @@ -191,15 +261,26 @@ export async function activate(context: ExtensionContext): Promise { } context.subscriptions.push( - commands.registerCommand("cf.target.set", async (item: CFTargetTI | CFTargetNotCurrent) => { - await cmdSetCurrentTarget(item); - revealTargetItem(); - }) + commands.registerCommand( + "cf.target.set", + async (item: CFTargetTI | CFTargetNotCurrent) => { + await cmdSetCurrentTarget(item); + revealTargetItem(); + } + ) + ); + context.subscriptions.push( + commands.registerCommand("cf.services.create", cmdCreateService) + ); + context.subscriptions.push( + commands.registerCommand("cf.ups.create", cmdCreateUps) + ); + context.subscriptions.push( + commands.registerCommand("cf.services.bind.local", cmdBindLocal) + ); + context.subscriptions.push( + commands.registerCommand("cf.set.orgspace", cmdCFSetOrgSpace) ); - context.subscriptions.push(commands.registerCommand("cf.services.create", cmdCreateService)); - context.subscriptions.push(commands.registerCommand("cf.ups.create", cmdCreateUps)); - context.subscriptions.push(commands.registerCommand("cf.services.bind.local", cmdBindLocal)); - context.subscriptions.push(commands.registerCommand("cf.set.orgspace", cmdCFSetOrgSpace)); context.subscriptions.push( commands.registerCommand("cf.targets.create", async () => { const label = await cmdSelectAndSaveTarget(); @@ -208,13 +289,33 @@ export async function activate(context: ExtensionContext): Promise { } }) ); - context.subscriptions.push(commands.registerCommand("cf.target.delete", cmdDeleteTarget)); - context.subscriptions.push(commands.registerCommand("cf.targets.reload", cmdReloadTargets)); - context.subscriptions.push(commands.registerCommand("cf.select.space", cmdSelectSpace)); - context.subscriptions.push(commands.registerCommand("cf.deploy-service.api", cmdDeployServiceAPI)); - context.subscriptions.push(commands.registerCommand("cf.services.get-space-services", cmdGetSpaceServices)); - context.subscriptions.push(commands.registerCommand("cf.services.get-ups-services", cmdGetUpsServiceInstances)); - context.subscriptions.push(commands.registerCommand("cf.services.get-services", cmdGetServiceInstances)); + context.subscriptions.push( + commands.registerCommand("cf.target.delete", cmdDeleteTarget) + ); + context.subscriptions.push( + commands.registerCommand("cf.targets.reload", cmdReloadTargets) + ); + context.subscriptions.push( + commands.registerCommand("cf.select.space", cmdSelectSpace) + ); + context.subscriptions.push( + commands.registerCommand("cf.deploy-service.api", cmdDeployServiceAPI) + ); + context.subscriptions.push( + commands.registerCommand( + "cf.services.get-space-services", + cmdGetSpaceServices + ) + ); + context.subscriptions.push( + commands.registerCommand( + "cf.services.get-ups-services", + cmdGetUpsServiceInstances + ) + ); + context.subscriptions.push( + commands.registerCommand("cf.services.get-services", cmdGetServiceInstances) + ); // postpone the rest of initialization after the extension is activated setTimeout(() => { diff --git a/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts b/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts index ad45a11ad..aa913ce4e 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/logger/logger-wrapper.ts @@ -8,7 +8,8 @@ import { promisify } from "util"; * Note that the values of these configuration properties must match those defined in the package.json */ const LOGGING_LEVEL_CONFIG_PROP = "CloudFoundryTools.logging.loggingLevel"; -const SOURCE_TRACKING_CONFIG_PROP = "CloudFoundryTools.logging.sourceLocationTracking"; +const SOURCE_TRACKING_CONFIG_PROP = + "CloudFoundryTools.logging.sourceLocationTracking"; const readFile = promisify(readFileCallback); // On file load we initialize our logger to `NOOP_LOGGER` @@ -30,7 +31,9 @@ export function getModuleLogger(name: string): IChildLogger { } export async function initLogger(context: ExtensionContext): Promise { - const meta = JSON.parse(await readFile(resolve(context.extensionPath, "package.json"), "utf8")) as { + const meta = JSON.parse( + await readFile(resolve(context.extensionPath, "package.json"), "utf8") + ) as { displayName: string; }; diff --git a/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts b/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts index c138ea863..029fb3aba 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/loginTargetView/loginTargetView.ts @@ -25,7 +25,11 @@ import { toText } from "../utils"; import * as cheerio from "cheerio"; let loginTarget: LoginTarget | undefined; -let initTarget: { endpoint: string | undefined; org?: string | undefined; space?: string | undefined }; +let initTarget: { + endpoint: string | undefined; + org?: string | undefined; + space?: string | undefined; +}; let panel: vscode.WebviewPanel | undefined; let isLoginOnly: boolean | undefined; let cmdLoginResult: string | undefined; @@ -50,13 +54,22 @@ export function openLoginView( return new Promise((resolve, reject) => { try { - const split = opts.isSplit ? vscode.ViewColumn.Beside : vscode.ViewColumn.One; + const split = opts.isSplit + ? vscode.ViewColumn.Beside + : vscode.ViewColumn.One; panel = panel ? panel - : vscode.window.createWebviewPanel("cfLogin", "Cloud Foundry Sign In", split, { - enableScripts: true, - localResourceRoots: [vscode.Uri.file(join(extension.getPath(), "dist", "media"))], - }); + : vscode.window.createWebviewPanel( + "cfLogin", + "Cloud Foundry Sign In", + split, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.file(join(extension.getPath(), "dist", "media")), + ], + } + ); panel.reveal(); panel.onDidDispose(() => { resolve(cmdLoginResult); @@ -120,11 +133,16 @@ class LoginTarget { private async getTarget(): Promise { // TODO: need to pass the endpoint to check if logged-in to correct endpoint. try { - return (await this.invokeLongFunctionWithProgressForm(cfGetTarget, false)) as ITarget; + return (await this.invokeLongFunctionWithProgressForm( + cfGetTarget, + false + )) as ITarget; } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const errText = toText(e); - getModuleLogger(LOGGER_MODULE).error("getTarget failed", { exception: errText }); + getModuleLogger(LOGGER_MODULE).error("getTarget failed", { + exception: errText, + }); return undefined; } } @@ -162,7 +180,10 @@ class LoginTarget { async loginClick(payload: SSOLoginOptions | CredentialsLoginOptions) { try { - cmdLoginResult = await this.invokeLongFunctionWithProgressForm(cfLogin, payload); + cmdLoginResult = await this.invokeLongFunctionWithProgressForm( + cfLogin, + payload + ); if (OK !== cmdLoginResult) { void vscode.window.showErrorMessage(messages.login_failed); return false; @@ -190,7 +211,10 @@ class LoginTarget { getModuleLogger(LOGGER_MODULE).debug("executeLogout: logout succeeded"); return true; } catch (error) { - getModuleLogger(LOGGER_MODULE).error("executeLogout: logout error", error); + getModuleLogger(LOGGER_MODULE).error( + "executeLogout: logout error", + error + ); return false; } } @@ -206,11 +230,16 @@ class LoginTarget { getOrgs(): Promise { return cfGetAvailableOrgs() .then((orgs: Organization[]) => { - getModuleLogger(LOGGER_MODULE).debug("executeGetAvaliableOrgs: get avaliable orgs succeeded"); + getModuleLogger(LOGGER_MODULE).debug( + "executeGetAvaliableOrgs: get avaliable orgs succeeded" + ); return orgs; }) .catch((error) => { - getModuleLogger(LOGGER_MODULE).error("executeGetAvaliableOrgs: get avaliable orgs failed", error); + getModuleLogger(LOGGER_MODULE).error( + "executeGetAvaliableOrgs: get avaliable orgs failed", + error + ); return []; }); } @@ -218,11 +247,16 @@ class LoginTarget { getSpaces(org: string): Promise { return cfGetAvailableSpaces(org) .then((spaces: Space[]) => { - getModuleLogger(LOGGER_MODULE).debug("executeGetAvaliableSpaces: get avaliable spaces succeeded"); + getModuleLogger(LOGGER_MODULE).debug( + "executeGetAvaliableSpaces: get avaliable spaces succeeded" + ); return spaces; }) .catch((error) => { - getModuleLogger(LOGGER_MODULE).error("executeGetAvaliableSpaces: get avaliable spaces failed", error); + getModuleLogger(LOGGER_MODULE).error( + "executeGetAvaliableSpaces: get avaliable spaces failed", + error + ); return []; }); } @@ -232,21 +266,32 @@ class LoginTarget { await cfSetOrgSpace(org, space); cmdLoginResult = OK; void vscode.window.showInformationMessage(messages.success_set_org_space); - getModuleLogger(LOGGER_MODULE).debug("executeSetOrgSpace: set org & spaces succeeded"); + getModuleLogger(LOGGER_MODULE).debug( + "executeSetOrgSpace: set org & spaces succeeded" + ); panel?.dispose(); return cmdLoginResult; } catch (error) { cmdLoginResult = undefined; - getModuleLogger(LOGGER_MODULE).error("executeSetOrgSpace: set org & spaces failed", error); + getModuleLogger(LOGGER_MODULE).error( + "executeSetOrgSpace: set org & spaces failed", + error + ); return "Error"; } } openPasscodeLink(endpoint: string) { - void vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(this.calculatePasscodeUrl(endpoint))); + void vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse(this.calculatePasscodeUrl(endpoint)) + ); } - async invokeLongFunctionWithProgressForm(longFunction: Function, ...args: any): Promise { + async invokeLongFunctionWithProgressForm( + longFunction: Function, + ...args: any + ): Promise { try { await this.rpc.invoke("setBusyIndicator", [true]); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ diff --git a/projects/cloud-foundry-tools/packages/backend/src/messages.ts b/projects/cloud-foundry-tools/packages/backend/src/messages.ts index 90db86884..e3e4f7654 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/messages.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/messages.ts @@ -3,7 +3,16 @@ import * as _ from "lodash"; export const messages = { enter_cf_endpoint: "Enter Cloud Foundry endpoint", enter_user_email: "Enter e-mail address", - label_enter_password: `Enter ${_.reverse(["d", "r", "o", "w", "s", "s", "a", "p"]).join("")}`, + label_enter_password: `Enter ${_.reverse([ + "d", + "r", + "o", + "w", + "s", + "s", + "a", + "p", + ]).join("")}`, login: "You need to login to Cloud Foundry to continue.", loggin_in: "Logging in...", logout: "Logging out...", @@ -26,7 +35,8 @@ export const messages = { loading_services: "Loading Cloud Foundry services...", loading_ups_services: "Loading Cloud Foundry user provided services...", loading_service_plan_list: "Loading Cloud Foundry service plans...", - binding_service_to_file: "Binding the service instance to a local '.env' file...", + binding_service_to_file: + "Binding the service instance to a local '.env' file...", select_service: "Select a Cloud Foundry service instance", select_services: "Select a Cloud Foundry service", select_service_plan: "Select a Cloud Foundry service plan", @@ -43,35 +53,62 @@ export const messages = { set_targets_pick_save: `Create new ...`, set_targets_pick_save_details: `Create and save a new target`, set_targets_choose_the_operation: `Save your current target or specify a new one`, - no_services_instance_byname_found: (name: string): string => `Could not find the ${name} service instance`, + no_services_instance_byname_found: (name: string): string => + `Could not find the ${name} service instance`, login_failed: `Authentication failed.`, - authentication_failed: (result: string): string => `Authentication failed: ${result}`, - targeting: (orgField: string, spaceField: string): string => `Targeting CF ${orgField}/${spaceField}`, - service_created: (instanceName: string): string => `The ${instanceName} service has been created.`, - target_created: (targetName: string): string => `The '${targetName}' target has been created.`, - creating_service: (instanceName: string, servieInfoLabel: string, planInfoLabel: string): string => + authentication_failed: (result: string): string => + `Authentication failed: ${result}`, + targeting: (orgField: string, spaceField: string): string => + `Targeting CF ${orgField}/${spaceField}`, + service_created: (instanceName: string): string => + `The ${instanceName} service has been created.`, + target_created: (targetName: string): string => + `The '${targetName}' target has been created.`, + creating_service: ( + instanceName: string, + servieInfoLabel: string, + planInfoLabel: string + ): string => `Creating the ${instanceName}(${servieInfoLabel} - ${planInfoLabel}) service...`, - target_deleted: (targetLabel: string): string => `The '${targetLabel}' target has been deleted.`, - activation_extension: (extname: string): string => `Activating the "${extname}" extension`, - no_services_found: (plan: string): string => `No services were found for the '${plan}' service_plan`, - no_services_instances_found_for_type: (name: string): string => `No service instances found for the ${name} type`, - extension_active: (extname: string): string => `The "${extname}" extension is now active.`, - error_service_params_mandatory: (name: string): string => `You must provide a value for the ${name}`, + target_deleted: (targetLabel: string): string => + `The '${targetLabel}' target has been deleted.`, + activation_extension: (extname: string): string => + `Activating the "${extname}" extension`, + no_services_found: (plan: string): string => + `No services were found for the '${plan}' service_plan`, + no_services_instances_found_for_type: (name: string): string => + `No service instances found for the ${name} type`, + extension_active: (extname: string): string => + `The "${extname}" extension is now active.`, + error_service_params_mandatory: (name: string): string => + `You must provide a value for the ${name}`, error_service_params_max_length: (name: string, length: number): string => `The ${name} may be composed of up to ${length} characters`, - error_service_params_value_not_allowed: (name: string): string => `Invalid value for the ${name}`, - service_bound_successful: (service: string): string => `The ${service} service has been bound.`, - service_unbound_successful: (service: string): string => `The ${service} service has been unbound.`, + error_service_params_value_not_allowed: (name: string): string => + `Invalid value for the ${name}`, + service_bound_successful: (service: string): string => + `The ${service} service has been bound.`, + service_unbound_successful: (service: string): string => + `The ${service} service has been unbound.`, no_service_plan_info_found: (plan: string, service: string): string => `Could not find the ${plan} service plan info for the ${service} service`, - no_service_plans_found: (service: string): string => `Could not find a service plan for the ${service} service`, + no_service_plans_found: (service: string): string => + `Could not find a service plan for the ${service} service`, target_dirty_save: (name: string): string => `The ${name} target has been modified. Would you like to save the changes?`, service_instances_list_incomplete: (list: string[]): string => `Could not get the details of ${ - _.size(list) > 3 ? "some service instances" : "the following service instances: '" + list.join() + "'" - }. Try to verify the ${_.size(list) > 3 ? "" : "specified "}service instances, then log in again to Cloud Foundry.`, - target_setup_not_completed: (org: string | undefined, space: string | undefined): string => + _.size(list) > 3 + ? "some service instances" + : "the following service instances: '" + list.join() + "'" + }. Try to verify the ${ + _.size(list) > 3 ? "" : "specified " + }service instances, then log in again to Cloud Foundry.`, + target_setup_not_completed: ( + org: string | undefined, + space: string | undefined + ): string => `The CF target definition is incomplete - org='${org}', space='${space}'. Do you want to continue?`, - set_targets_save: (org: string, space: string): string => `Save current target (${org}/${space})`, + set_targets_save: (org: string, space: string): string => + `Save current target (${org}/${space})`, }; diff --git a/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts b/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts index 4c0d4e6ef..cd4f526c9 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/run-configuration-example.ts @@ -30,7 +30,15 @@ export class RunConfigContext implements IRunConfigContext { public displayName = "CF Tools Example"; public groupName = this.appId; public envPath: vscode.Uri = vscode.Uri.file( - path.join("C:", "Users", "your-i-number", "devx-wing", "test-run-config", "env", ".env") + path.join( + "C:", + "Users", + "your-i-number", + "devx-wing", + "test-run-config", + "env", + ".env" + ) ); public path: vscode.Uri; public dependencies = [new DependencyContext()]; @@ -56,7 +64,10 @@ export class RunConfigHandler implements IRunConfigHandler { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - public getRunConfigContext(runnableId: string, confData: IConfigurationData): Promise { + public getRunConfigContext( + runnableId: string, + confData: IConfigurationData + ): Promise { return Promise.resolve(new RunConfigContext(this.id, "Cf App")); } diff --git a/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts b/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts index 58bdd5585..62dc5c6fe 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/run-configuration.ts @@ -16,13 +16,16 @@ export class DependencyHandler implements types.IDependencyHandler { this.id = id; } - public async getBindState(bindContext: types.IBindContext): Promise { + public async getBindState( + bindContext: types.IBindContext + ): Promise { let bindState = types.BindState.notbound; try { if ( _.get( - (await getEnvResources(_.get(bindContext, ["envPath", "fsPath"]))).vcapObject, + (await getEnvResources(_.get(bindContext, ["envPath", "fsPath"]))) + .vcapObject, _.get(bindContext, ["depContext", "type"]) ) ) { @@ -32,7 +35,10 @@ export class DependencyHandler implements types.IDependencyHandler { /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ void vscode.window.showErrorMessage(toText(e)); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - getModuleLogger(DependencyHandler.MODULE_NAME).error("getBindState: processing failed", { exception: toText(e) }); + getModuleLogger(DependencyHandler.MODULE_NAME).error( + "getBindState: processing failed", + { exception: toText(e) } + ); } return bindState; } @@ -41,29 +47,46 @@ export class DependencyHandler implements types.IDependencyHandler { bindContext: types.IBindContext, options?: cfViewCommands.CmdOptions ): Promise { - const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); + const resourceTag: string = _.get( + bindContext, + "depContext.data.resourceTag" + ); const serviceType: cfLocal.ServiceTypeInfo[] = [ { name: _.get(bindContext.depContext, ["type"], ""), plan: _.get(bindContext.depContext, ["data", "plan"], ""), // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - tag: resourceTag ? resourceTag + _.get(bindContext, "depContext.data.resourceName") : "", + tag: resourceTag + ? resourceTag + _.get(bindContext, "depContext.data.resourceName") + : "", prompt: "", }, ]; try { - const instanceNames = await cfViewCommands.bindLocalService(serviceType, bindContext.envPath, options); + const instanceNames = await cfViewCommands.bindLocalService( + serviceType, + bindContext.envPath, + options + ); let chiselTask; if (_.size(instanceNames)) { // Get metadata of service instance by service name // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const instanceType: string = (await cfLocal.cfGetInstanceMetadata(_.get(instanceNames, "[0]"))).service; + const instanceType: string = ( + await cfLocal.cfGetInstanceMetadata(_.get(instanceNames, "[0]")) + ).service; // Create chisel task if neccessary - if (_.get(bindContext, "depContext.data.isCreateChiselTask") || /^hana(trial)?$/.test(instanceType)) { + if ( + _.get(bindContext, "depContext.data.isCreateChiselTask") || + /^hana(trial)?$/.test(instanceType) + ) { // Create it in dependent task const chiselTaskNameSuffix = instanceNames?.join("&") || ""; - chiselTask = await checkAndCreateChiselTask(bindContext.envPath?.fsPath, chiselTaskNameSuffix); + chiselTask = await checkAndCreateChiselTask( + bindContext.envPath?.fsPath, + chiselTaskNameSuffix + ); if (chiselTask) { if (!options?.silent) { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions @@ -98,7 +121,10 @@ export class DependencyHandler implements types.IDependencyHandler { /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ void vscode.window.showErrorMessage(toText(e)); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - getModuleLogger(DependencyHandler.MODULE_NAME).error("bind: processing failed", { exception: toText(e) }); + getModuleLogger(DependencyHandler.MODULE_NAME).error( + "bind: processing failed", + { exception: toText(e) } + ); } } @@ -106,13 +132,21 @@ export class DependencyHandler implements types.IDependencyHandler { bindContext: types.IBindContext, options?: cfViewCommands.CmdOptions ): Promise { - const configObject = new types.ConfigObject({}, types.ConfigurationTarget.launch); - const configurationData: types.IConfigurationData = { config: configObject, dependentTasks: [] }; + const configObject = new types.ConfigObject( + {}, + types.ConfigurationTarget.launch + ); + const configurationData: types.IConfigurationData = { + config: configObject, + dependentTasks: [], + }; try { const removedResourceDetails = await removeResourceFromEnv(bindContext); if (!options?.silent) { void vscode.window.showInformationMessage( - messages.service_unbound_successful(_.get(removedResourceDetails, "resourceName")) + messages.service_unbound_successful( + _.get(removedResourceDetails, "resourceName") + ) ); } getModuleLogger(DependencyHandler.MODULE_NAME).info( @@ -131,7 +165,10 @@ export class DependencyHandler implements types.IDependencyHandler { /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ void vscode.window.showErrorMessage(toText(e)); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - getModuleLogger(DependencyHandler.MODULE_NAME).error("unbind: processing failed", { exception: toText(e) }); + getModuleLogger(DependencyHandler.MODULE_NAME).error( + "unbind: processing failed", + { exception: toText(e) } + ); } } diff --git a/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts b/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts index a4e5e13f9..47060c096 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/sql-tools.ts @@ -6,10 +6,15 @@ import { parse } from "comment-json"; const CONNECTIONS = "connections"; function saveSettings(filePath: string, configuration: any) { - const sqltoolsSettings = vscode.workspace.getConfiguration("sqltools", vscode.Uri.file(filePath)); + const sqltoolsSettings = vscode.workspace.getConfiguration( + "sqltools", + vscode.Uri.file(filePath) + ); const connections: any[] = sqltoolsSettings.get(CONNECTIONS) || []; - const existingConnIndex = connections.findIndex((conn: any) => conn.name === configuration.name); + const existingConnIndex = connections.findIndex( + (conn: any) => conn.name === configuration.name + ); if (existingConnIndex >= 0) { connections[existingConnIndex] = configuration; } else { @@ -18,7 +23,9 @@ function saveSettings(filePath: string, configuration: any) { void sqltoolsSettings.update(CONNECTIONS, connections); } -export async function checkAndCreateSQLToolsSettings(filePath: string): Promise { +export async function checkAndCreateSQLToolsSettings( + filePath: string +): Promise { const envFileContent = await dataContentAsObject(filePath); const vcapServicesStr = _.get(envFileContent, "VCAP_SERVICES"); if (vcapServicesStr) { diff --git a/projects/cloud-foundry-tools/packages/backend/src/utils.ts b/projects/cloud-foundry-tools/packages/backend/src/utils.ts index e9db8d6cb..00209d31d 100644 --- a/projects/cloud-foundry-tools/packages/backend/src/utils.ts +++ b/projects/cloud-foundry-tools/packages/backend/src/utils.ts @@ -59,7 +59,9 @@ export function toText(e: Error): string { return _.get(e, "message") || _.get(e, "name", _.toString(e)); } -export function getEnvResources(envFilePath: string): Promise<{ vcapObject: any; isQuotedVcap: boolean }> { +export function getEnvResources( + envFilePath: string +): Promise<{ vcapObject: any; isQuotedVcap: boolean }> { try { let isQuotedVcap = false; if (existsSync(envFilePath)) { @@ -71,7 +73,10 @@ export function getEnvResources(envFilePath: string): Promise<{ vcapObject: any; vcapProperty = vcapProperty.substring(1, vcapProperty.length - 1); isQuotedVcap = true; } - return Promise.resolve({ vcapObject: JSON.parse(vcapProperty), isQuotedVcap }); + return Promise.resolve({ + vcapObject: JSON.parse(vcapProperty), + isQuotedVcap, + }); } else { getModuleLogger(LOGGER_MODULE).debug( "getEnvResources: the '.env' file is missing a key <%s>", @@ -81,9 +86,12 @@ export function getEnvResources(envFilePath: string): Promise<{ vcapObject: any; return Promise.resolve({ vcapObject: null, isQuotedVcap }); } } else { - getModuleLogger(LOGGER_MODULE).debug("getEnvResources: the '.env' file does not exist", { - filePath: envFilePath, - }); + getModuleLogger(LOGGER_MODULE).debug( + "getEnvResources: the '.env' file does not exist", + { + filePath: envFilePath, + } + ); return Promise.resolve({ vcapObject: null, isQuotedVcap }); } } catch (error) { @@ -98,13 +106,21 @@ export function getEnvResources(envFilePath: string): Promise<{ vcapObject: any; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function findServiceByResourceNameTag(vcapServices: any, yamlResourceName: string, resourceTag: string): any { +function findServiceByResourceNameTag( + vcapServices: any, + yamlResourceName: string, + resourceTag: string +): any { for (const key in vcapServices) { for (const service of vcapServices[key]) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call if ( service.tags && - service.tags.find((t: string) => t.startsWith(resourceTag) && t.substr(resourceTag.length) === yamlResourceName) + service.tags.find( + (t: string) => + t.startsWith(resourceTag) && + t.substr(resourceTag.length) === yamlResourceName + ) ) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [key, service]; @@ -129,9 +145,16 @@ export async function removeResourceFromEnv( let instanceName = ""; // If this is a tagged resource - remove by tag const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); - const resourceName: string = _.get(bindContext, "depContext.data.resourceName"); + const resourceName: string = _.get( + bindContext, + "depContext.data.resourceName" + ); if (!_.isEmpty(resourceTag)) { - const keyAndService = findServiceByResourceNameTag(vcapServicesObj, resourceName, resourceTag); + const keyAndService = findServiceByResourceNameTag( + vcapServicesObj, + resourceName, + resourceTag + ); if (!_.isEmpty(keyAndService)) { const resourceTypeKey = keyAndService[0]; instanceData = keyAndService[1]; @@ -146,9 +169,7 @@ export async function removeResourceFromEnv( delete vcapServicesObj[resourceTypeKey]; } } else { - getModuleLogger( - LOGGER_MODULE - ).debug( + getModuleLogger(LOGGER_MODULE).debug( "removeResourceFromEnv: the <%s> tagged <%s> resource was not found in the '.env' file", resourceTag, resourceName, @@ -170,14 +191,24 @@ export async function removeResourceFromEnv( // If VCAP_SERVICES was already wrapped with single quotes - maintain them when writing back const quote = envResources.isQuotedVcap ? "'" : ""; - envProperties.set(ENV_VCAP_RESOURCES, quote + JSON.stringify(vcapServicesObj) + quote); + envProperties.set( + ENV_VCAP_RESOURCES, + quote + JSON.stringify(vcapServicesObj) + quote + ); await envProperties.save(envFilePath); - return { resourceName: instanceName, envPath: envFilePath, resourceData: instanceData }; + return { + resourceName: instanceName, + envPath: envFilePath, + resourceData: instanceData, + }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function generateParams4Service(serviceLabel: string, plan: string): any { +export function generateParams4Service( + serviceLabel: string, + plan: string +): any { if ("xsuaa" === serviceLabel && "application" === plan) { return { xsappname: `xsuaa_${_.now()}`, @@ -205,8 +236,13 @@ export function generateParams4Service(serviceLabel: string, plan: string): any } function validateXsuaaTenantMode(value: string) { - if (!_.isUndefined(value) && !["shared", "dedicated", "external"].includes(_.trim(value))) { - throw new Error(messages.error_service_params_value_not_allowed("tenant-mode")); + if ( + !_.isUndefined(value) && + !["shared", "dedicated", "external"].includes(_.trim(value)) + ) { + throw new Error( + messages.error_service_params_value_not_allowed("tenant-mode") + ); } } @@ -214,7 +250,9 @@ function validateXsuaaOauth2Configuration(value: unknown) { for (const uri of _.get(value, "redirect-uris", [])) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument if (!_.startsWith(uri, "http") && !_.startsWith(uri, "localhost")) { - throw new Error(messages.error_service_params_value_not_allowed("redirect-uris")); + throw new Error( + messages.error_service_params_value_not_allowed("redirect-uris") + ); } } } @@ -228,7 +266,9 @@ function validateXsuaaName(value: string) { throw new Error(messages.error_service_params_max_length("xsappname", 100)); } if (/[^a-zA-Z0-9_\-"]/gm.test(value)) { - throw new Error(messages.error_service_params_value_not_allowed("xsappname")); + throw new Error( + messages.error_service_params_value_not_allowed("xsappname") + ); } } @@ -259,8 +299,13 @@ function validateParamsJson(value: string): TypeValidationResult { return result; } -export function validateParams(serviceLabel: string, plan?: string): (value: string) => TypeValidationResult { - return "xsuaa" === serviceLabel && "application" === plan ? validateParamsXsuaa : validateParamsJson; +export function validateParams( + serviceLabel: string, + plan?: string +): (value: string) => TypeValidationResult { + return "xsuaa" === serviceLabel && "application" === plan + ? validateParamsXsuaa + : validateParamsJson; } export function isRegexExpression(statement: string): boolean { @@ -268,27 +313,43 @@ export function isRegexExpression(statement: string): boolean { } export function composeFilterPattern(value: string): string { - return value ? (isRegexExpression(value) ? _.trim(value, "/") : `^${value}$`) : value; + return value + ? isRegexExpression(value) + ? _.trim(value, "/") + : `^${value}$` + : value; } -async function doGetUpsServiceInstances(query?: IServiceQuery, filterCredTag?: string): Promise { +async function doGetUpsServiceInstances( + query?: IServiceQuery, + filterCredTag?: string +): Promise { const upsServices = await cfGetUpsInstances(query); - const pattern = _.size(upsServices) && filterCredTag ? composeFilterPattern(filterCredTag) : undefined; + const pattern = + _.size(upsServices) && filterCredTag + ? composeFilterPattern(filterCredTag) + : undefined; const ups2Show = pattern ? upsServices.filter((service) => { // cretentials.tags can be 'string', 'string array' or 'array of objects' only - const tags = _.isString(service.credentials?.tags) ? [service.credentials.tags] : service.credentials?.tags; + const tags = _.isString(service.credentials?.tags) + ? [service.credentials.tags] + : service.credentials?.tags; return _.find(tags, (tag: string) => new RegExp(pattern).test(tag)); }) : upsServices; return ups2Show; } -export async function getUpsServiceInstances(options?: UpsServiceQueryOprions): Promise { +export async function getUpsServiceInstances( + options?: UpsServiceQueryOprions +): Promise { return doGetUpsServiceInstances(undefined, options?.credentials?.tag); } -export async function getAllServiceInstances(opts?: DisplayServices): Promise { +export async function getAllServiceInstances( + opts?: DisplayServices +): Promise { let ups2Show: ServiceInstanceInfo[] = []; if (opts?.ups?.isShow || opts?.ups?.tag) { const copyQuery = _.cloneDeep(opts.query); @@ -326,7 +387,12 @@ export async function updateGitIgnoreList(envPath: string): Promise { if ( isNotEmptyPattern(pattern) && _.includes( - _.map(await vscode.workspace.findFiles(new vscode.RelativePattern(project, pattern)), "fsPath"), + _.map( + await vscode.workspace.findFiles( + new vscode.RelativePattern(project, pattern) + ), + "fsPath" + ), envPath ) ) { @@ -335,30 +401,44 @@ export async function updateGitIgnoreList(envPath: string): Promise { } return false; }; - const ignoreFiles = await vscode.workspace.findFiles(new vscode.RelativePattern(project, GITIGNORE)); + const ignoreFiles = await vscode.workspace.findFiles( + new vscode.RelativePattern(project, GITIGNORE) + ); if (!_.size(ignoreFiles)) { try { // .gitignore file not exists -> create one - const filePath = path.normalize(path.join(project.uri.fsPath, GITIGNORE)); + const filePath = path.normalize( + path.join(project.uri.fsPath, GITIGNORE) + ); await fs.promises.open(filePath, "w"); ignoreFiles.push(vscode.Uri.file(filePath)); } catch (e) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - getModuleLogger(LOGGER_MODULE).error("updateGitIgnoreList: creation .gitignore file failed", { - exception: toText(new Error(e?.message as string)), - }); + getModuleLogger(LOGGER_MODULE).error( + "updateGitIgnoreList: creation .gitignore file failed", + { + exception: toText(new Error(e?.message as string)), + } + ); } } for (const file of ignoreFiles) { try { - const patterns = _.split(await fs.promises.readFile(file.fsPath, { encoding: UTF8 }), EOL); + const patterns = _.split( + await fs.promises.readFile(file.fsPath, { encoding: UTF8 }), + EOL + ); if (!(await isPatternFound(patterns))) { await fs.promises.writeFile( file.fsPath, _.join( _.concat(patterns, [ `# auto generated wildcard`, - _.replace(path.relative(project.uri.fsPath, envPath), /\\/g, "/"), + _.replace( + path.relative(project.uri.fsPath, envPath), + /\\/g, + "/" + ), ]), EOL ), @@ -377,7 +457,10 @@ export async function updateGitIgnoreList(envPath: string): Promise { } } -export async function writeProperties(filePath: string, properties: Record): Promise { +export async function writeProperties( + filePath: string, + properties: Record +): Promise { let text = ""; Object.keys(properties).forEach((key) => { const value = properties[key]; @@ -410,14 +493,20 @@ export function notifyWhenServicesInfoResultIncomplete( [] ); if (_.size(results)) { - void vscode.window.showWarningMessage(messages.service_instances_list_incomplete(results)); + void vscode.window.showWarningMessage( + messages.service_instances_list_incomplete(results) + ); } return infos; }); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export async function examCFTarget(errorMessage: string, keys: string[], weak: boolean): Promise { +export async function examCFTarget( + errorMessage: string, + keys: string[], + weak: boolean +): Promise { const target = await cfGetTarget(); if (weak) { return target; @@ -435,7 +524,10 @@ export async function examCFTarget(errorMessage: string, keys: string[], weak: b } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function invokeLongFunctionWithProgress(longFunction: any, progressMessage: string): Thenable { +export function invokeLongFunctionWithProgress( + longFunction: any, + progressMessage: string +): Thenable { return vscode.window.withProgress( { location: vscode.ProgressLocation.Window, diff --git a/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts index 83d360644..9f2ffe560 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/cfView.spec.ts @@ -38,7 +38,11 @@ describe("cfView tests", () => { const activePattern = "(Active Target)"; const contextPattern = "cf-target"; it("ok:: get description - active, not dirty", () => { - const item = new cfView.CFTargetTI({ label: "test", isCurrent: true, isDirty: false }); + const item = new cfView.CFTargetTI({ + label: "test", + isCurrent: true, + isDirty: false, + }); expect(item.description).to.be.equal(activePattern); expect(item.contextValue).to.be.equal(`${contextPattern}-active`); const parts = _.split(_.get(item, "iconPath.dark") as string, path.sep); @@ -46,7 +50,11 @@ describe("cfView tests", () => { }); it("ok:: get description - not active, is dirty", () => { - const item = new cfView.CFTargetTI({ label: "test", isCurrent: false, isDirty: true }); + const item = new cfView.CFTargetTI({ + label: "test", + isCurrent: false, + isDirty: true, + }); expect(item.description).to.be.equal("*"); expect(item.contextValue).to.be.equal(`${contextPattern}`); const parts = _.split(_.get(item, "iconPath.light") as string, path.sep); @@ -54,7 +62,11 @@ describe("cfView tests", () => { }); it("ok:: get description - (no targets), is active, is dirty", () => { - const item = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + const item = new cfView.CFTargetTI({ + label: "(no targets)", + isCurrent: true, + isDirty: true, + }); expect(item.description).to.be.equal(`*`); expect(item.contextValue).to.be.equal(`${contextPattern}-notargets`); expect(item.tooltip).to.be.equal(item.label); @@ -64,7 +76,11 @@ describe("cfView tests", () => { }); describe("Tree Nodes", () => { - const root = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + const root = new cfView.CFTargetTI({ + label: "(no targets)", + isCurrent: true, + isDirty: true, + }); const parent = new cfView.CFFolder("folder", root); it("ok:: CFLoginNode properties verifying", () => { @@ -82,7 +98,9 @@ describe("cfView tests", () => { const item = new cfView.CFTargetNotCurrent(parent, target); expect(item.contextValue).to.be.equal(`cf-target-not-current`); expect(item.label).to.be.equal(`Target needs to be active`); - expect(item.tooltip).to.be.equal(`Set the '${target}' as the active target to see details.`); + expect(item.tooltip).to.be.equal( + `Set the '${target}' as the active target to see details.` + ); const parts = _.split(_.get(item, "iconPath.light") as string, path.sep); expect(_.includes(parts, "info.svg")).to.be.true; expect(item.parent).to.be.equal(parent); @@ -110,7 +128,11 @@ describe("cfView tests", () => { it("ok:: CFServicesFolder properties verifying", () => { const name = "services"; - const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const parent = new cfView.CFTargetTI({ + label: "parent", + isCurrent: false, + isDirty: true, + }); const item = new cfView.CFServicesFolder(name, parent); expect(item.contextValue).to.be.equal(`services`); expect(item.label).to.be.equal(name); @@ -119,7 +141,11 @@ describe("cfView tests", () => { it("ok:: CFAppsFolder properties verifying", () => { const name = "apps"; - const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const parent = new cfView.CFTargetTI({ + label: "parent", + isCurrent: false, + isDirty: true, + }); const item = new cfView.CFAppsFolder(name, parent); expect(item.contextValue).to.be.equal(`apps`); expect(item.label).to.be.equal(name); @@ -128,35 +154,56 @@ describe("cfView tests", () => { }); describe("CFView ", () => { - const root = new cfView.CFTargetTI({ label: "(no targets)", isCurrent: true, isDirty: true }); + const root = new cfView.CFTargetTI({ + label: "(no targets)", + isCurrent: true, + isDirty: true, + }); const folder = new cfView.CFFolder("folder", root); let instance: cfView.CFView; // eslint-disable-next-line @typescript-eslint/no-explicit-any - const testContext: any = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; + const testContext: any = { + subscriptions: [], + logUri: { fsPath: path.resolve(__dirname) }, + }; beforeEach(() => { /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ instance = new cfView.CFView(testContext, ""); }); it("ok:: verify getParent of CFAppsFolder", () => { - const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const parent = new cfView.CFTargetTI({ + label: "parent", + isCurrent: false, + isDirty: true, + }); const item = new cfView.CFAppsFolder("name", parent); expect(instance.getParent(item)).to.be.equal(parent); }); it("ok:: verify getParent of CFServicesFolder", () => { - const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const parent = new cfView.CFTargetTI({ + label: "parent", + isCurrent: false, + isDirty: true, + }); const item = new cfView.CFServicesFolder("name", parent); expect(instance.getParent(item)).to.be.equal(parent); }); it("ok:: verify getParent of CFApplication", () => { - expect(instance.getParent(new cfView.CFApplication("name", "state", folder))).to.be.equal(folder); + expect( + instance.getParent(new cfView.CFApplication("name", "state", folder)) + ).to.be.equal(folder); }); it("ok:: verify getTreeItem of CFAppsFolder", async () => { - const parent = new cfView.CFTargetTI({ label: "parent", isCurrent: false, isDirty: true }); + const parent = new cfView.CFTargetTI({ + label: "parent", + isCurrent: false, + isDirty: true, + }); const item = new cfView.CFAppsFolder("name", parent); expect(await instance.getTreeItem(item)).to.be.equal(item); }); @@ -194,7 +241,9 @@ describe("cfView tests", () => { { label: "t-2", isCurrent: true, isDirty: false }, ]; cfLocalMock.expects("cfGetTargets").resolves(targets); - expect(await instance.getChildren((undefined as unknown) as TreeItem)).to.deep.equal( + expect( + await instance.getChildren(undefined as unknown as TreeItem) + ).to.deep.equal( _.map(targets, (target) => new cfView.CFTargetTI(target)) ); }); @@ -204,12 +253,17 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); const expected = [ new cfView.CFServicesFolder("Services", instance.targets[0]), new cfView.CFAppsFolder("Applications", instance.targets[0]), ]; - expect(await instance.getChildren(instance.targets[0])).to.deep.equal(expected); + expect(await instance.getChildren(instance.targets[0])).to.deep.equal( + expected + ); }); it("ok:: verify getChildren - target/services, not active", async () => { @@ -217,9 +271,20 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); - const parent = new cfView.CFServicesFolder("Services", instance.targets[0]); - const expected = [new cfView.CFTargetNotCurrent(parent, _.get(parent, "parent.target.label") as string)]; + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); + const parent = new cfView.CFServicesFolder( + "Services", + instance.targets[0] + ); + const expected = [ + new cfView.CFTargetNotCurrent( + parent, + _.get(parent, "parent.target.label") as string + ), + ]; expect(await instance.getChildren(parent)).to.deep.equal(expected); }); @@ -228,14 +293,24 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); - const parent = new cfView.CFServicesFolder("Services", instance.targets[1]); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); + const parent = new cfView.CFServicesFolder( + "Services", + instance.targets[1] + ); const services = [ { label: "s-1", serviceName: "name-1" }, { label: "s-2", serviceName: "name-2" }, ]; cfLocalMock.expects("cfGetServiceInstancesList").resolves(services); - const expected = _.map(services, (service) => new cfView.CFService(service.label, service.serviceName, parent)); + const expected = _.map( + services, + (service) => + new cfView.CFService(service.label, service.serviceName, parent) + ); expect(await instance.getChildren(parent)).to.deep.equal(expected); }); @@ -244,14 +319,20 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); const parent = new cfView.CFAppsFolder("Apps", instance.targets[1]); const apps = [ { name: "a-1", state: "STARTED" }, { name: "a-2", state: "STOPPED" }, ]; cfLocalMock.expects("cfGetApps").resolves(apps); - const expected = _.map(apps, (app) => new cfView.CFApplication(app.name, app.state, parent)); + const expected = _.map( + apps, + (app) => new cfView.CFApplication(app.name, app.state, parent) + ); expect(await instance.getChildren(parent)).to.deep.equal(expected); }); @@ -260,14 +341,21 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); const parent = new cfView.CFAppsFolder("Apps", instance.targets[1]); cfLocalMock.expects("cfGetApps").rejects(new Error("error")); - expect(await instance.getChildren(parent)).to.deep.equal([new cfView.CFLoginNode(parent)]); + expect(await instance.getChildren(parent)).to.deep.equal([ + new cfView.CFLoginNode(parent), + ]); }); it("ok:: verify getChildren - target/applications, incorrect hierachy", async () => { - expect(await instance.getChildren(new nsVsMock.testVscode.TreeItem("Apps"))).to.deep.equal([]); + expect( + await instance.getChildren(new nsVsMock.testVscode.TreeItem("Apps")) + ).to.deep.equal([]); }); it("ok:: verify getCurrentTarget, active is defined", () => { @@ -275,8 +363,13 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); - expect(instance.getCurrentTarget()).to.be.equal(instance.targets[1].target); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); + expect(instance.getCurrentTarget()).to.be.equal( + instance.targets[1].target + ); }); it("ok:: verify getCurrentTarget, active is not defined", () => { @@ -284,7 +377,10 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: false, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); expect(instance.getCurrentTarget()).to.be.equal(undefined); }); @@ -293,7 +389,10 @@ describe("cfView tests", () => { { label: "t-1", isCurrent: false, isDirty: false }, { label: "t-2", isCurrent: true, isDirty: false }, ]; - instance.targets = _.map(targets, (target) => new cfView.CFTargetTI(target)); + instance.targets = _.map( + targets, + (target) => new cfView.CFTargetTI(target) + ); expect(instance.getTargets()).to.deep.equal(instance.targets); }); }); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts index 69c874e4e..12d347f96 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/cfViewCommands.spec.ts @@ -29,7 +29,11 @@ import * as https from "https"; import * as fspath from "path"; import { messages } from "../src/messages"; import { fail } from "assert"; -import { DisplayServices, ServiceQueryOptions, UpsServiceQueryOprions } from "../src/utils"; +import { + DisplayServices, + ServiceQueryOptions, + UpsServiceQueryOprions, +} from "../src/utils"; import * as chisel from "../src/chisel"; import { createSandbox, SinonMock, SinonSandbox } from "sinon"; @@ -106,9 +110,14 @@ describe("cfViewCommands tests", () => { port: 443, method: "get", path: target.pathname, - headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + headers: { + Authorization: testToken.replace("\n", "").replace("\n", ""), + }, }; - cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + cfLocalUtilsMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves(ap); httpsMock.expects("get").withArgs(options).returns(result); cfLocalMock.expects("cfGetAuthToken").resolves(testToken); try { @@ -128,9 +137,14 @@ describe("cfViewCommands tests", () => { port: target.port, method: "get", path: urlPath, - headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + headers: { + Authorization: testToken.replace("\n", "").replace("\n", ""), + }, }; - cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + cfLocalUtilsMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves(ap); httpsMock.expects("get").withArgs(options).returns(result); cfLocalMock.expects("cfGetAuthToken").resolves(testToken); try { @@ -150,9 +164,14 @@ describe("cfViewCommands tests", () => { port: 80, method: "get", path: urlPath, - headers: { Authorization: testToken.replace("\n", "").replace("\n", "") }, + headers: { + Authorization: testToken.replace("\n", "").replace("\n", ""), + }, }; - cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(ap); + cfLocalUtilsMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves(ap); httpsMock.expects("get").withArgs(options).returns(result); cfLocalMock.expects("cfGetAuthToken").resolves(testToken); try { @@ -164,7 +183,10 @@ describe("cfViewCommands tests", () => { }); it("exception:: no target", async () => { - cfLocalUtilsMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves([]); + cfLocalUtilsMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves([]); try { await cfViewCommands.cfDeployServiceAPI(""); fail("should not reach here"); @@ -227,14 +249,22 @@ describe("cfViewCommands tests", () => { const message = "myMessage"; vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, title: message, cancellable: false }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Notification, + title: message, + cancellable: false, + }) .resolves(); await cfViewCommands.cmdDeployServiceAPI("", message); }); }); describe("cmdSetCurrentTarget scope", () => { - const currentTarget: CFTarget = { label: "current Target", isDirty: false, isCurrent: true }; + const currentTarget: CFTarget = { + label: "current Target", + isDirty: false, + isCurrent: true, + }; const localView = { getCurrentTarget: () => currentTarget, refresh: () => "", @@ -244,14 +274,21 @@ describe("cfViewCommands tests", () => { cliMock.expects("execute").never(); cfViewMock.expects("get").never(); await cfViewCommands.cmdSetCurrentTarget( - new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: true }) + new cfView.CFTargetTI({ + label: "my Target", + isDirty: false, + isCurrent: true, + }) ); }); it("ok:: target is set, reload called", async () => { const target = { label: "my Target", isDirty: false, isCurrent: false }; cfViewMock.expects("get").twice().returns(localView); - cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", target.label]) + .resolves(cliResult); await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); }); @@ -262,9 +299,17 @@ describe("cfViewCommands tests", () => { cfViewMock.expects("get").twice().returns(localView); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .withExactArgs( + messages.target_dirty_save(currentTarget.label), + "Yes", + "No", + "Cancel" + ) .resolves("No"); - cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", target.label]) + .resolves(cliResult); await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); }); @@ -275,9 +320,17 @@ describe("cfViewCommands tests", () => { cfViewMock.expects("get").returns(localView); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .withExactArgs( + messages.target_dirty_save(currentTarget.label), + "Yes", + "No", + "Cancel" + ) .resolves(undefined); - cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).never(); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", target.label]) + .never(); await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); }); @@ -288,10 +341,21 @@ describe("cfViewCommands tests", () => { cfViewMock.expects("get").twice().returns(localView); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .withExactArgs( + messages.target_dirty_save(currentTarget.label), + "Yes", + "No", + "Cancel" + ) .resolves("Yes"); - cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); - cliMock.expects("execute").withExactArgs(["save-target"]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", target.label]) + .resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["save-target"]) + .resolves(cliResult); await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); }); @@ -301,13 +365,25 @@ describe("cfViewCommands tests", () => { cfViewMock.expects("get").returns(localView); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_dirty_save(currentTarget.label), "Yes", "No", "Cancel") + .withExactArgs( + messages.target_dirty_save(currentTarget.label), + "Yes", + "No", + "Cancel" + ) .resolves("Yes"); const error = new Error("my error"); cliMock.expects("execute").withExactArgs(["save-target"]).throws(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); await cfViewCommands.cmdSetCurrentTarget( - new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: false }) + new cfView.CFTargetTI({ + label: "my Target", + isDirty: false, + isCurrent: false, + }) ); }); @@ -318,8 +394,14 @@ describe("cfViewCommands tests", () => { cfViewMock.expects("get").returns(localView); cliResult.exitCode = 1; cliResult.stdout = "any error"; - cliMock.expects("execute").withExactArgs(["set-target", "-f", target.label]).resolves(cliResult); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(cliResult.stdout).resolves(); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", target.label]) + .resolves(cliResult); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(cliResult.stdout) + .resolves(); await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetTI(target)); }); @@ -329,14 +411,20 @@ describe("cfViewCommands tests", () => { new cfView.CFTargetNotCurrent( new cfView.CFAppsFolder( "apps", - new cfView.CFTargetTI({ label: "my Target", isDirty: false, isCurrent: true }) + new cfView.CFTargetTI({ + label: "my Target", + isDirty: false, + isCurrent: true, + }) ) ) ); }); it("ok:: targets tree broken", async () => { - await cfViewCommands.cmdSetCurrentTarget(new cfView.CFTargetNotCurrent({ label: "label" } as cfView.CFFolder)); + await cfViewCommands.cmdSetCurrentTarget( + new cfView.CFTargetNotCurrent({ label: "label" } as cfView.CFFolder) + ); }); it("ok:: null received, targets tree broken", async () => { @@ -345,50 +433,80 @@ describe("cfViewCommands tests", () => { }); describe("execSetTarget scope", () => { - const item = new cfView.CFTargetTI({ label: "my Target", isCurrent: false, isDirty: true }); + const item = new cfView.CFTargetTI({ + label: "my Target", + isCurrent: false, + isDirty: true, + }); it("ok:: silent mode required", async () => { const cliResult = { exitCode: -1, stdout: "", stderr: "" }; - cliMock.expects("execute").withExactArgs(["set-target", "-f", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", item.target.label]) + .resolves(cliResult); vscodeWindowMock.expects("showErrorMessage").never(); await cfViewCommands.execSetTarget(item, { silent: true }); }); it("ok:: 'skip-reload' mode required", async () => { const cliResult = { exitCode: 0, stdout: "", stderr: "" }; - cliMock.expects("execute").withExactArgs(["set-target", "-f", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["set-target", "-f", item.target.label]) + .resolves(cliResult); cfViewMock.expects("get").never(); - await cfViewCommands.execSetTarget(item, { silent: true, "skip-reload": true }); + await cfViewCommands.execSetTarget(item, { + silent: true, + "skip-reload": true, + }); }); }); describe("execSaveTarget scope", () => { - const item = new cfView.CFTargetTI({ label: "my Target", isCurrent: false, isDirty: true }); + const item = new cfView.CFTargetTI({ + label: "my Target", + isCurrent: false, + isDirty: true, + }); it("ok:: error occured", async () => { const cliResult = { exitCode: -1, stdout: "", stderr: "" }; - cliMock.expects("execute").withExactArgs(["save-target", "-f", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["save-target", "-f", item.target.label]) + .resolves(cliResult); vscodeWindowMock.expects("showErrorMessage").resolves(); await cfViewCommands.execSaveTarget(item); }); it("ok:: silent mode required", async () => { const cliResult = { exitCode: -1, stdout: "", stderr: "" }; - cliMock.expects("execute").withExactArgs(["save-target", "-f", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["save-target", "-f", item.target.label]) + .resolves(cliResult); vscodeWindowMock.expects("showErrorMessage").never(); await cfViewCommands.execSaveTarget(item, { silent: true }); }); it("ok:: without args", async () => { const cliResult = { exitCode: 0, stdout: "", stderr: "" }; - cliMock.expects("execute").withExactArgs(["save-target"]).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["save-target"]) + .resolves(cliResult); await cfViewCommands.execSaveTarget(); }); it("ok:: 'no-targets' node received", async () => { cliMock.expects("execute").never(); await cfViewCommands.execSaveTarget( - new cfView.CFTargetTI({ label: "my (no targets)", isCurrent: false, isDirty: true }) + new cfView.CFTargetTI({ + label: "my (no targets)", + isCurrent: false, + isDirty: true, + }) ); }); }); @@ -397,19 +515,30 @@ describe("cfViewCommands tests", () => { const localView = { refresh: () => "", }; - const item = new cfView.CFTargetTI({ label: "other", isCurrent: true, isDirty: false }); + const item = new cfView.CFTargetTI({ + label: "other", + isCurrent: true, + isDirty: false, + }); it("ok:: default-target", async () => { cliMock.expects("execute").withArgs(["delete-target"]).never(); await cfViewCommands.cmdDeleteTarget( - new cfView.CFTargetTI({ label: DEFAULT_TARGET, isCurrent: true, isDirty: false }) + new cfView.CFTargetTI({ + label: DEFAULT_TARGET, + isCurrent: true, + isDirty: false, + }) ); }); it("ok:: other deleted", async () => { cliResult.exitCode = 0; cliResult.stdout = ""; - cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withArgs(["delete-target", item.target.label]) + .resolves(cliResult); cfViewMock.expects("get").returns(localView); vscodeWindowMock .expects("showInformationMessage") @@ -421,18 +550,30 @@ describe("cfViewCommands tests", () => { it("ok:: other deleted, silent and 'skip-reload' mode", async () => { cliResult.exitCode = 0; cliResult.stdout = ""; - cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withArgs(["delete-target", item.target.label]) + .resolves(cliResult); cfViewMock.expects("get").never(); vscodeWindowMock.expects("showInformationMessage").never(); - await cfViewCommands.cmdDeleteTarget(item, { silent: true, "skip-reload": true }); + await cfViewCommands.cmdDeleteTarget(item, { + silent: true, + "skip-reload": true, + }); }); it("ok:: other failure", async () => { cliResult.stdout = "some error during delete"; cliResult.exitCode = -1; - cliMock.expects("execute").withArgs(["delete-target", item.target.label]).resolves(cliResult); + cliMock + .expects("execute") + .withArgs(["delete-target", item.target.label]) + .resolves(cliResult); cfViewMock.expects("get").never(); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(cliResult.stdout).resolves(); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(cliResult.stdout) + .resolves(); await cfViewCommands.cmdDeleteTarget(item); }); }); @@ -451,14 +592,20 @@ describe("cfViewCommands tests", () => { filter.value = encodeURIComponent(filter.value); }); const arg = { query: modified, ups: { isShow: true } }; - commandsMock.expects("getAvailableServices").withExactArgs(arg, title).resolves(); + commandsMock + .expects("getAvailableServices") + .withExactArgs(arg, title) + .resolves(); await cfViewCommands.cmdGetSpaceServices(query, title); }); it("ok:: undefined query", async () => { const title = "progress title"; const arg: unknown = { query: undefined, ups: { isShow: true } }; - commandsMock.expects("getAvailableServices").withExactArgs(arg, title).resolves(); + commandsMock + .expects("getAvailableServices") + .withExactArgs(arg, title) + .resolves(); await cfViewCommands.cmdGetSpaceServices(undefined, title); }); }); @@ -472,8 +619,13 @@ describe("cfViewCommands tests", () => { { label: "s2", serviceName: "hana" }, { label: "s2", serviceName: "hanatrial" }, ]; - commandsMock.expects("getServiceInstances").withExactArgs(undefined, title).resolves(expServices); - expect(await cfViewCommands.cmdGetServiceInstances(undefined, title)).to.deep.equal(expServices); + commandsMock + .expects("getServiceInstances") + .withExactArgs(undefined, title) + .resolves(expServices); + expect( + await cfViewCommands.cmdGetServiceInstances(undefined, title) + ).to.deep.equal(expServices); }); it("ok:: retrieve 'hana 'services types with 'hdi-shared' plan", async () => { @@ -481,23 +633,41 @@ describe("cfViewCommands tests", () => { { label: "s1", serviceName: "hana", plan: "hdi-shared" }, { label: "s2", serviceName: "hana", plan: "hdi-shared" }, ]; - const serviceQueryOptions: ServiceQueryOptions = { name: "hana", plan: "hdi-shared" }; + const serviceQueryOptions: ServiceQueryOptions = { + name: "hana", + plan: "hdi-shared", + }; const plans: PlanInfo[] = [ { label: "hdi-shared", guid: "ABCD", description: "" }, { label: "lite", guid: "EFGH", description: "" }, ]; const serviceInstanceQuery = { - filters: [{ key: eFilters.service_plan_names, value: serviceQueryOptions.plan }], + filters: [ + { key: eFilters.service_plan_names, value: serviceQueryOptions.plan }, + ], per_page: CF_PAGE_SIZE, }; commandsMock .expects("fetchServicePlanList") - .withExactArgs({ filters: [{ key: eFilters.service_offering_names, value: serviceQueryOptions.name }] }) + .withExactArgs({ + filters: [ + { + key: eFilters.service_offering_names, + value: serviceQueryOptions.name, + }, + ], + }) .resolves(plans); serviceInstanceQuery.filters = _.concat(serviceInstanceQuery.filters, [ - { key: eFilters.service_plan_guids, value: _.join(_.map(plans, "guid")) }, + { + key: eFilters.service_plan_guids, + value: _.join(_.map(plans, "guid")), + }, ]); - commandsMock.expects("getServiceInstances").withExactArgs(serviceInstanceQuery, title).resolves(rspServices); + commandsMock + .expects("getServiceInstances") + .withExactArgs(serviceInstanceQuery, title) + .resolves(rspServices); await cfViewCommands.cmdGetServiceInstances(serviceQueryOptions, title); }); @@ -508,7 +678,10 @@ describe("cfViewCommands tests", () => { { label: "s2", serviceName: "hanatrial" }, ]; const serviceQueryOptions: ServiceQueryOptions = {}; - commandsMock.expects("getServiceInstances").withExactArgs(undefined, title).resolves(expServices); + commandsMock + .expects("getServiceInstances") + .withExactArgs(undefined, title) + .resolves(expServices); await cfViewCommands.cmdGetServiceInstances(serviceQueryOptions, title); }); }); @@ -517,15 +690,23 @@ describe("cfViewCommands tests", () => { const title = "progress title - retrieve all services"; it("ok:: verify calling 'getUserProvidedServiceInstances' happens with specified args", async () => { - const options: UpsServiceQueryOprions = { tag: "tags", credentials: { tag: "hana" } }; - commandsMock.expects("getUserProvidedServiceInstances").withExactArgs(options, title).resolves(); + const options: UpsServiceQueryOprions = { + tag: "tags", + credentials: { tag: "hana" }, + }; + commandsMock + .expects("getUserProvidedServiceInstances") + .withExactArgs(options, title) + .resolves(); await cfViewCommands.cmdGetUpsServiceInstances(options, title); }); }); describe("cmdBindLocal scope", () => { const path = nsVsMock.testVscode.Uri.file("some/path"); - const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + const service: ServiceTypeInfo[] = [ + { name: "", plan: "hdi-shared", prompt: "", tag: "" }, + ]; let origin: unknown; const plans: PlanInfo[] = [ { label: "hdi-shared", guid: "ABCD", description: "" }, @@ -536,7 +717,10 @@ describe("cfViewCommands tests", () => { { serviceName: "type2", label: "service2" }, ]; const opts: DisplayServices = { - query: { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }, + query: { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }, ups: _.get(service, ["0", "ups"]), }; @@ -548,7 +732,11 @@ describe("cfViewCommands tests", () => { _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[], + instanceNames: string[] + ) => { instanceNames.push(services[0].label); return Promise.resolve(instanceNames[0]); } @@ -571,12 +759,15 @@ describe("cfViewCommands tests", () => { defaultUri: nsVsMock.testVscode.workspace.workspaceFolders[0].uri, }) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, emptyPath)).to.be.undefined; + expect(await cfViewCommands.cmdBindLocal(service, emptyPath)).to.be + .undefined; }); it("ok:: path selected, service is CFService type, quote-vcap is false", async () => { const emptyPath = nsVsMock.testVscode.Uri.file(""); - sandbox.stub(nsVsMock.testVscode.workspace, "workspaceFolders").value(undefined); + sandbox + .stub(nsVsMock.testVscode.workspace, "workspaceFolders") + .value(undefined); vscodeWindowMock .expects("showOpenDialog") .withExactArgs({ @@ -592,10 +783,13 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(otherService.label)) .resolves(); - const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + const stubWithProgress = sandbox.stub( + nsVsMock.testVscode.window, + "withProgress" + ); expect( await cfViewCommands.cmdBindLocal( - (otherService as unknown) as cfView.CFService, + otherService as unknown as cfView.CFService, { path: emptyPath, ignore: true, @@ -624,12 +818,15 @@ describe("cfViewCommands tests", () => { cancellable: false, }); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be + .undefined; }); it("ok:: path selected, service is CFService type", async () => { const emptyPath = nsVsMock.testVscode.Uri.file(""); - sandbox.stub(nsVsMock.testVscode.workspace, "workspaceFolders").value(undefined); + sandbox + .stub(nsVsMock.testVscode.workspace, "workspaceFolders") + .value(undefined); vscodeWindowMock .expects("showOpenDialog") .withExactArgs({ @@ -645,12 +842,18 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(otherService.label)) .resolves(); - const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + const stubWithProgress = sandbox.stub( + nsVsMock.testVscode.window, + "withProgress" + ); expect( - await cfViewCommands.cmdBindLocal((otherService as unknown) as cfView.CFService, { - path: emptyPath, - ignore: true, - }) + await cfViewCommands.cmdBindLocal( + otherService as unknown as cfView.CFService, + { + path: emptyPath, + ignore: true, + } + ) ).deep.equal({ instanceName: otherService.label }); cfLocalMock @@ -672,24 +875,39 @@ describe("cfViewCommands tests", () => { cancellable: false, }); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be + .undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, no services found", async () => { - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.no_services_instances_found).resolves(); - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.no_services_instances_found) + .resolves(); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves([]); + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })) + .to.be.undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, no services found, creation allowed", async () => { - const cloneService = _.merge(_.cloneDeep(service[0]), { allowCreate: { name: "create" } }); - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + const cloneService = _.merge(_.cloneDeep(service[0]), { + allowCreate: { name: "create" }, + }); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves([]); commandsMock .expects("updateInstanceNameAndTags") .withExactArgs( [ { - label: commands.CMD_BIND_TO_DEFAULT_SERVICE + cloneService.allowCreate.name, + label: + commands.CMD_BIND_TO_DEFAULT_SERVICE + + cloneService.allowCreate.name, plan: cloneService.allowCreate.plan, serviceName: cloneService.allowCreate.serviceName ?? "", }, @@ -703,19 +921,31 @@ describe("cfViewCommands tests", () => { [] ) .resolves(undefined); - expect(await cfViewCommands.cmdBindLocal([cloneService], { path, ignore: true })).to.be.undefined; + expect( + await cfViewCommands.cmdBindLocal([cloneService], { + path, + ignore: true, + }) + ).to.be.undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, canceled", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set(commands, "updateInstanceNameAndTags", () => { return Promise.resolve(); }); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })) + .to.be.undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, type selected", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -728,13 +958,18 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }) + ).deep.equal({ instanceName: services[0].label, }); }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, required specific service", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); commandsMock.expects("updateInstanceNameAndTags").never(); vscodeWindowMock .expects("withProgress") @@ -748,30 +983,55 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, services[0].label)).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal( + service, + { path, ignore: true }, + services[0].label + ) + ).deep.equal({ instanceName: services[0].label, }); }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, required service not found", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); commandsMock.expects("updateInstanceNameAndTags").never(); - cfLocalMock.expects("cfGetInstanceMetadata").withExactArgs("not-existed").rejects(new Error("not found")); + cfLocalMock + .expects("cfGetInstanceMetadata") + .withExactArgs("not-existed") + .rejects(new Error("not found")); vscodeWindowMock .expects("showErrorMessage") .withArgs(messages.no_services_instance_byname_found("not-existed")) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, "not-existed")).to.be.undefined; + expect( + await cfViewCommands.cmdBindLocal( + service, + { path, ignore: true }, + "not-existed" + ) + ).to.be.undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, service not in list but found via direct lookup (CF eventual consistency)", async () => { // Simulate CF list API returning stale results: services[0] missing, only services[1] present - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([services[1]]); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves([services[1]]); commandsMock.expects("updateInstanceNameAndTags").never(); cfLocalMock .expects("cfGetInstanceMetadata") .withExactArgs(services[0].label) - .resolves({ serviceName: services[0].label, service: service[0].plan, plan_guid: "GUID" }); + .resolves({ + serviceName: services[0].label, + service: service[0].plan, + plan_guid: "GUID", + }); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -784,7 +1044,13 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, services[0].label)).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal( + service, + { path, ignore: true }, + services[0].label + ) + ).deep.equal({ instanceName: services[0].label, }); }); @@ -798,27 +1064,45 @@ describe("cfViewCommands tests", () => { { serviceName: eServiceTypes.user_provided, label: "ups1" }, { serviceName: eServiceTypes.user_provided, label: "ups2" }, ]; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[], + instanceNames: string[] + ) => { instanceNames.push(services[0].label); return Promise.resolve(instanceNames[0]); } ); - const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + const stubWithProgress = sandbox.stub( + nsVsMock.testVscode.window, + "withProgress" + ); vscodeWindowMock .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); expect( - await cfViewCommands.cmdBindLocal(service, { path, ignore: true }, undefined, { "quote-vcap": true }) + await cfViewCommands.cmdBindLocal( + service, + { path, ignore: true }, + undefined, + { "quote-vcap": true } + ) ).deep.equal({ instanceName: services[0].label, }); - cfLocalMock.expects("cfBindLocalUps").withExactArgs("some/path", [services[0].label], [], true).resolves(); + cfLocalMock + .expects("cfBindLocalUps") + .withExactArgs("some/path", [services[0].label], [], true) + .resolves(); expect(stubWithProgress.calledOnce).to.be.true; expect(stubWithProgress.args[0].length).to.be.equal(2); expect(stubWithProgress.args[0][0]).to.be.deep.equal({ @@ -827,7 +1111,8 @@ describe("cfViewCommands tests", () => { cancellable: false, }); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be + .undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no services, services found, ups selected", async () => { @@ -839,25 +1124,40 @@ describe("cfViewCommands tests", () => { { serviceName: eServiceTypes.user_provided, label: "ups1" }, { serviceName: eServiceTypes.user_provided, label: "ups2" }, ]; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[], + instanceNames: string[] + ) => { instanceNames.push(services[0].label); return Promise.resolve(instanceNames[0]); } ); - const stubWithProgress = sandbox.stub(nsVsMock.testVscode.window, "withProgress"); + const stubWithProgress = sandbox.stub( + nsVsMock.testVscode.window, + "withProgress" + ); vscodeWindowMock .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }) + ).deep.equal({ instanceName: services[0].label, }); - cfLocalMock.expects("cfBindLocalUps").withExactArgs("some/path", [services[0].label], [], undefined).resolves(); + cfLocalMock + .expects("cfBindLocalUps") + .withExactArgs("some/path", [services[0].label], [], undefined) + .resolves(); expect(stubWithProgress.calledOnce).to.be.true; expect(stubWithProgress.args[0].length).to.be.equal(2); expect(stubWithProgress.args[0][0]).to.be.deep.equal({ @@ -866,15 +1166,25 @@ describe("cfViewCommands tests", () => { cancellable: false, }); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ - expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be.undefined; + expect(await stubWithProgress.args[0][1]({} as any, {} as any)).to.be + .undefined; }); it("ok:: path selected, service is not ServiceTypeInfo type, no ups, services found, type selected", async () => { service[0].plan = plans[0].label; delete (service[0] as any).name; - opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); - commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[0].label); + opts.query = { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }; + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); + commandsMock + .expects("getInstanceName") + .withExactArgs(services) + .resolves(services[0].label); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -887,7 +1197,9 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[0].label)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }) + ).deep.equal({ instanceName: services[0].label, }); }); @@ -895,9 +1207,18 @@ describe("cfViewCommands tests", () => { it("ok:: path selected, service is not ServiceTypeInfo type, no ups, services found, type selection canceled", async () => { service[0].plan = plans[0].label; delete (service[0] as any).name; - opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); - commandsMock.expects("getInstanceName").withExactArgs(services).resolves(); + opts.query = { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }; + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); + commandsMock + .expects("getInstanceName") + .withExactArgs(services) + .resolves(); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -906,19 +1227,30 @@ describe("cfViewCommands tests", () => { cancellable: false, }) .never(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })) + .to.be.undefined; }); it("ok:: path selected, service is ServiceTypeInfo type, no ups, services found, selected new", async () => { service[0].plan = plans[0].label; service[0].name = ""; - opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + opts.query = { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }; const newService = "my-service"; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[], + instanceNames: string[] + ) => { instanceNames.push(newService); return Promise.resolve(instanceNames[0]); } @@ -926,7 +1258,11 @@ describe("cfViewCommands tests", () => { cfLocalMock .expects("cfGetInstanceMetadata") .withExactArgs(newService) - .resolves({ serviceName: newService, service: service[0].plan, plan_guid: "GUID" }); + .resolves({ + serviceName: newService, + service: service[0].plan, + plan_guid: "GUID", + }); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -939,7 +1275,9 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(newService)) .resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }) + ).deep.equal({ instanceName: newService, }); }); @@ -948,27 +1286,48 @@ describe("cfViewCommands tests", () => { service[0].plan = plans[0].label; service[0].name = ""; service[0].allowCreate = {}; - opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; + opts.query = { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }; let expectedServicesList; let servicePlans; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[] + ) => { expectedServicesList = availableServices; servicePlans = _.get(serviceTypeInfo, "plans"); return Promise.resolve(); } ); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).to.be.undefined; - expect(_.has(_.find(expectedServicesList, ["label", commands.CMD_CREATE_SERVICE]), "serviceName")).to.be.true; + expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })) + .to.be.undefined; + expect( + _.has( + _.find(expectedServicesList, ["label", commands.CMD_CREATE_SERVICE]), + "serviceName" + ) + ).to.be.true; expect(servicePlans).to.be.undefined; }); it("ok:: return chisel task", async () => { - opts.query = { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + opts.query = { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }; + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -989,9 +1348,17 @@ describe("cfViewCommands tests", () => { problemMatcher: "$chisel-client", args: ["client", "--auth"], }; - chiselMock.expects("checkAndCreateChiselTask").withExactArgs(path.fsPath, services[0].label).resolves(chiselTask); - chiselMock.expects("deleteChiselParamsFromFile").withExactArgs(path.fsPath).resolves(); - expect(await cfViewCommands.cmdBindLocal(service, { path, ignore: true })).deep.equal({ + chiselMock + .expects("checkAndCreateChiselTask") + .withExactArgs(path.fsPath, services[0].label) + .resolves(chiselTask); + chiselMock + .expects("deleteChiselParamsFromFile") + .withExactArgs(path.fsPath) + .resolves(); + expect( + await cfViewCommands.cmdBindLocal(service, { path, ignore: true }) + ).deep.equal({ instanceName: services[0].label, chiselTask: chiselTask, }); @@ -1016,7 +1383,11 @@ describe("cfViewCommands tests", () => { _.set( commands, "updateInstanceNameAndTags", - (availableServices: ServiceInstanceInfo[], serviceTypeInfo: ServiceTypeInfo[], instanceNames: string[]) => { + ( + availableServices: ServiceInstanceInfo[], + serviceTypeInfo: ServiceTypeInfo[], + instanceNames: string[] + ) => { instanceNames.push(services[1].label); return Promise.resolve(instanceNames[0]); } @@ -1027,27 +1398,45 @@ describe("cfViewCommands tests", () => { _.set(commands, "updateInstanceNameAndTags", origin); }); - const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + const service: ServiceTypeInfo[] = [ + { name: "", plan: "hdi-shared", prompt: "", tag: "" }, + ]; const opts: DisplayServices = { - query: { filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], per_page: CF_PAGE_SIZE }, + query: { + filters: [{ key: eFilters.service_plan_names, value: service[0].plan }], + per_page: CF_PAGE_SIZE, + }, }; const envPath = nsVsMock.testVscode.Uri.file("root/folder"); it("ok:: path is empty", async () => { const path = nsVsMock.testVscode.Uri.file(""); - expect(await cfViewCommands.bindLocalService(service, { path, ignore: true })).to.be.empty; + expect( + await cfViewCommands.bindLocalService(service, { path, ignore: true }) + ).to.be.empty; }); it("ok:: path defined, plan required, no service selected", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); _.set(commands, "updateInstanceNameAndTags", () => { return Promise.resolve([]); }); - expect(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true })).to.be.undefined; + expect( + await cfViewCommands.bindLocalService(service, { + path: envPath, + ignore: true, + }) + ).to.be.undefined; }); it("ok:: path defined, plan required, service selected", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -1060,13 +1449,20 @@ describe("cfViewCommands tests", () => { .expects("showInformationMessage") .withExactArgs(messages.service_bound_successful(services[1].label)) .resolves(); - assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ - services[1].label, - ]); + assert.deepEqual( + await cfViewCommands.bindLocalService(service, { + path: envPath, + ignore: true, + }), + [services[1].label] + ); }); it("ok:: path defined, plan required, service selected, silent mode required", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -1080,15 +1476,25 @@ describe("cfViewCommands tests", () => { .withExactArgs(messages.service_bound_successful(services[1].label)) .never(); assert.deepEqual( - await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }, { silent: true }), + await cfViewCommands.bindLocalService( + service, + { path: envPath, ignore: true }, + { silent: true } + ), [services[1].label] ); }); it("ok:: serviceInfo is not array, service selected", async () => { vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(undefined); - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); - commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[1].label); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); + commandsMock + .expects("getInstanceName") + .withExactArgs(services) + .resolves(services[1].label); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -1102,34 +1508,70 @@ describe("cfViewCommands tests", () => { .withExactArgs(messages.service_bound_successful(services[1].label)) .resolves(); delete (service[0] as any).name; - assert.deepEqual(await cfViewCommands.bindLocalService(service, envPath), [services[1].label]); + assert.deepEqual( + await cfViewCommands.bindLocalService(service, envPath), + [services[1].label] + ); }); it("ok:: serviceInfo is not array, select service canceled", async () => { - const service: ServiceTypeInfo[] = [{ name: "", plan: "hdi-shared", prompt: "", tag: "" }]; + const service: ServiceTypeInfo[] = [ + { name: "", plan: "hdi-shared", prompt: "", tag: "" }, + ]; delete (service[0] as any).name; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); - commandsMock.expects("getInstanceName").withExactArgs(services).resolves(); - expect(await cfViewCommands.bindLocalService(service, envPath)).to.be.undefined; + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); + commandsMock + .expects("getInstanceName") + .withExactArgs(services) + .resolves(); + expect(await cfViewCommands.bindLocalService(service, envPath)).to.be + .undefined; }); it("ok:: serviceInfo is array, no services found", async () => { service[0].name = ""; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves([]); vscodeWindowMock.expects("withProgress").never(); - expect(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true })).to.be.empty; + expect( + await cfViewCommands.bindLocalService(service, { + path: envPath, + ignore: true, + }) + ).to.be.empty; }); it("ok:: serviceInfo is array, no services found, plan is not provided", async () => { - const copyService: ServiceTypeInfo[] = [{ name: "", plan: "", prompt: "", tag: "" }]; + const copyService: ServiceTypeInfo[] = [ + { name: "", plan: "", prompt: "", tag: "" }, + ]; const opts: DisplayServices = { query: undefined }; - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves([]); - expect(await cfViewCommands.bindLocalService(copyService, { path: envPath, ignore: true })).to.be.empty; + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves([]); + expect( + await cfViewCommands.bindLocalService(copyService, { + path: envPath, + ignore: true, + }) + ).to.be.empty; }); it("show error:: serviceInfo is not array, service selected, doBind rejected", async () => { - commandsMock.expects("getAvailableServices").withExactArgs(opts).resolves(services); - commandsMock.expects("getInstanceName").withExactArgs(services).resolves(services[1].label); + commandsMock + .expects("getAvailableServices") + .withExactArgs(opts) + .resolves(services); + commandsMock + .expects("getInstanceName") + .withExactArgs(services) + .resolves(services[1].label); const error = new Error("error"); vscodeWindowMock .expects("withProgress") @@ -1139,7 +1581,10 @@ describe("cfViewCommands tests", () => { cancellable: false, }) .rejects(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); delete (service[0] as any).name; await cfViewCommands.bindLocalService(service, envPath); }); @@ -1149,16 +1594,34 @@ describe("cfViewCommands tests", () => { service[0].name = " hana , xsuaa "; commandsMock .expects("fetchServicePlanList") - .withExactArgs(_.cloneDeep({ filters: [{ key: eFilters.service_offering_names, value: "hana,xsuaa" }] })) + .withExactArgs( + _.cloneDeep({ + filters: [ + { key: eFilters.service_offering_names, value: "hana,xsuaa" }, + ], + }) + ) .resolves(plans); const query: IServiceQuery = { - filters: [{ key: eFilters.service_plan_guids, value: _.join(_.map(plans, "guid")) }], + filters: [ + { + key: eFilters.service_plan_guids, + value: _.join(_.map(plans, "guid")), + }, + ], per_page: CF_PAGE_SIZE, }; - commandsMock.expects("getAvailableServices").withExactArgs({ query }).resolves(services); - assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ - services[1].label, - ]); + commandsMock + .expects("getAvailableServices") + .withExactArgs({ query }) + .resolves(services); + assert.deepEqual( + await cfViewCommands.bindLocalService(service, { + path: envPath, + ignore: true, + }), + [services[1].label] + ); }); it("ok:: serviceInfo is array, service name - ups only requested, no services found", async () => { @@ -1169,13 +1632,22 @@ describe("cfViewCommands tests", () => { { serviceName: eServiceTypes.user_provided, label: "label2" }, ]; const query: IServiceQuery = { - filters: [{ key: eFilters.service_plan_names, value: "nothing-to-show" }], + filters: [ + { key: eFilters.service_plan_names, value: "nothing-to-show" }, + ], per_page: CF_PAGE_SIZE, }; - commandsMock.expects("getAvailableServices").withExactArgs({ query }).resolves(upsServices); - assert.deepEqual(await cfViewCommands.bindLocalService(service, { path: envPath, ignore: true }), [ - services[1].label, - ]); + commandsMock + .expects("getAvailableServices") + .withExactArgs({ query }) + .resolves(upsServices); + assert.deepEqual( + await cfViewCommands.bindLocalService(service, { + path: envPath, + ignore: true, + }), + [services[1].label] + ); }); }); }); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts index 1b00ca61e..ab0913de1 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/chisel.spec.ts @@ -11,7 +11,10 @@ import { mockVscode } from "./ext/mockUtil"; mockVscode(nsVsMock.testVscode, "src/chisel.ts"); import * as utils from "../src/utils"; -import { deleteChiselParamsFromFile, checkAndCreateChiselTask } from "../src/chisel"; +import { + deleteChiselParamsFromFile, + checkAndCreateChiselTask, +} from "../src/chisel"; describe("chisel unit tests", () => { const serviceName = "chisel-service-name"; @@ -58,21 +61,26 @@ describe("chisel unit tests", () => { }; it("ok:: verify created chisel task structure", async () => { await utils.writeProperties(envFilePath, envWithChisel); - expect(await checkAndCreateChiselTask(envFilePath, serviceName)).deep.equal(expectedChiselTask); + expect( + await checkAndCreateChiselTask(envFilePath, serviceName) + ).deep.equal(expectedChiselTask); }); it("ok:: create 'undefined' when chisel params not appear in the environment file", async () => { await utils.writeProperties(envFilePath, {}); - expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be + .undefined; }); it("ok:: create 'undefined' chisel URL is empty", async () => { await utils.writeProperties(envFilePath, { CHISEL_URL: "" }); - expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be + .undefined; }); it("ok:: create 'undefined' when .env file does not exists", async () => { - expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be.undefined; + expect(await checkAndCreateChiselTask(envFilePath, serviceName)).to.be + .undefined; }); it("ok:: create chisel task and ignore from invalid format text in the .env file", async () => { @@ -85,7 +93,9 @@ describe("chisel unit tests", () => { "VCAP_SERVICES= VCAP_SERVICES_VALUE"; await fs.promises.writeFile(envFilePath, text); - expect(await checkAndCreateChiselTask(envFilePath, serviceName)).deep.equal(expectedChiselTask); + expect( + await checkAndCreateChiselTask(envFilePath, serviceName) + ).deep.equal(expectedChiselTask); }); }); @@ -97,7 +107,9 @@ describe("chisel unit tests", () => { await utils.writeProperties(envFilePath, envWithChisel); expect(await deleteChiselParamsFromFile(envFilePath)).to.be.true; const actualProperties = PropertiesReader(envFilePath); - expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + expect(actualProperties.getAllProperties()).deep.equal( + expectedProperties + ); }); it("ok:: return true and ignored from empty properties when override the .env file", async () => { @@ -113,14 +125,18 @@ describe("chisel unit tests", () => { await fs.promises.writeFile(envFilePath, text); expect(await deleteChiselParamsFromFile(envFilePath)).to.be.true; const actualProperties = PropertiesReader(envFilePath); - expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + expect(actualProperties.getAllProperties()).deep.equal( + expectedProperties + ); }); it("ok:: return false when chisel params does not exists in .env file", async () => { await utils.writeProperties(envFilePath, expectedProperties); expect(await deleteChiselParamsFromFile(envFilePath)).to.be.false; const actualProperties = PropertiesReader(envFilePath); - expect(actualProperties.getAllProperties()).deep.equal(expectedProperties); + expect(actualProperties.getAllProperties()).deep.equal( + expectedProperties + ); }); it("ok:: return false when .env file does not exists ", async () => { diff --git a/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts index eb3a26d85..12fc8c77f 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/commands-a.spec.ts @@ -82,7 +82,12 @@ describe("commands unit tests", () => { const errorMessage = "login"; openLoginViewMock .expects("openLoginView") - .withExactArgs({ isSplit: true, isLoginOnly: true }, "https://example.com", "", "") + .withExactArgs( + { isSplit: true, isLoginOnly: true }, + "https://example.com", + "", + "" + ) .resolves(undefined); let expErr: any = null; const error = new Error(errorMessage); @@ -96,7 +101,9 @@ describe("commands unit tests", () => { it("should handle undefined error gracefully", async () => { const err = new Error("test"); let expErr: any = null; - await internal.onErrorCfLogin(err).catch((errorMessage: Error) => (expErr = errorMessage)); + await internal + .onErrorCfLogin(err) + .catch((errorMessage: Error) => (expErr = errorMessage)); // Assert that console.error was not called expect(expErr.message).to.exist; @@ -112,7 +119,11 @@ describe("commands unit tests", () => { user: "user", "api version": "3.100.0", }; - const cliResult: CliResult = { stdout: "some text", stderr: "", exitCode: 0 }; + const cliResult: CliResult = { + stdout: "some text", + stderr: "", + exitCode: 0, + }; it("ok:: show selection dialog - canceled", async () => { vscodeWindowMock @@ -132,7 +143,11 @@ describe("commands unit tests", () => { label: messages.set_targets_save(org, space), detail: messages.set_targets_save_details, }, - { id: "pick-save", label: messages.set_targets_pick_save, detail: messages.set_targets_pick_save_details }, + { + id: "pick-save", + label: messages.set_targets_pick_save, + detail: messages.set_targets_pick_save_details, + }, ]; vscodeWindowMock .expects("showQuickPick") @@ -186,16 +201,24 @@ describe("commands unit tests", () => { //...cmdCreateTarget vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(label); const saveTargetParams = ["save-target", "-f", label]; - cliMock.expects("execute").withExactArgs(saveTargetParams).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(saveTargetParams) + .resolves(cliResult); cfViewMock.expects("get").returns({ refresh: () => { return; }, }); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.target_created(label)); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.target_created(label)); // expect(await commands.cmdSelectAndSaveTarget()).to.be.equals(label); }); @@ -214,7 +237,10 @@ describe("commands unit tests", () => { //...cmdCreateTarget canceled vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(); // expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; @@ -230,11 +256,19 @@ describe("commands unit tests", () => { }) .resolves({}); vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); - const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0" }; + const t: ITarget = { + "api endpoint": "endPoint", + user: "user", + "api version": "3.100.0", + }; cfLocalMock.expects("cfGetTarget").resolves(t); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .withExactArgs( + messages.target_setup_not_completed(t.org, t.space), + "OK", + "Cancel" + ) .resolves("Cancel"); expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; }); @@ -249,11 +283,20 @@ describe("commands unit tests", () => { }) .resolves({}); vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); - const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0", org: "test-org" }; + const t: ITarget = { + "api endpoint": "endPoint", + user: "user", + "api version": "3.100.0", + org: "test-org", + }; cfLocalMock.expects("cfGetTarget").resolves(t); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .withExactArgs( + messages.target_setup_not_completed(t.org, t.space), + "OK", + "Cancel" + ) .resolves("Cancel"); expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; }); @@ -268,16 +311,28 @@ describe("commands unit tests", () => { }) .resolves({}); vscodeWindowMock.expects("showQuickPick").resolves({ id: "save" }); - const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0", space: "test-space" }; + const t: ITarget = { + "api endpoint": "endPoint", + user: "user", + "api version": "3.100.0", + space: "test-space", + }; cfLocalMock.expects("cfGetTarget").resolves(t); vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.target_setup_not_completed(t.org, t.space), "OK", "Cancel") + .withExactArgs( + messages.target_setup_not_completed(t.org, t.space), + "OK", + "Cancel" + ) .resolves("OK"); //...cmdCreateTarget canceled vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(); // expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; @@ -298,10 +353,17 @@ describe("commands unit tests", () => { //...cmdCreateTarget vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(label); const saveTargetParams = ["save-target", "-f", label]; - const cli: CliResult = { exitCode: 1, stdout: "create target command error", stderr: "" }; + const cli: CliResult = { + exitCode: 1, + stdout: "create target command error", + stderr: "", + }; cliMock.expects("execute").withExactArgs(saveTargetParams).resolves(cli); // vscodeWindowMock @@ -325,9 +387,16 @@ describe("commands unit tests", () => { const selectedEndPoint = "other-end-point"; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_cf_endpoint, + value: target["api endpoint"], + ignoreFocusOut: true, + }) .resolves(selectedEndPoint); - cliMock.expects("execute").withExactArgs(["api", selectedEndPoint], undefined, undefined).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["api", selectedEndPoint], undefined, undefined) + .resolves(cliResult); // ...cmdCFSetOrgSpace vscodeWindowMock .expects("withProgress") @@ -339,7 +408,10 @@ describe("commands unit tests", () => { .resolves({ data: {} }); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.getting_orgs, + }) .resolves(orgs); vscodeWindowMock .expects("showQuickPick") @@ -352,7 +424,10 @@ describe("commands unit tests", () => { .resolves(orgs[1]); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_spaces }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.getting_spaces, + }) .resolves(spaces); vscodeWindowMock .expects("showQuickPick") @@ -363,15 +438,24 @@ describe("commands unit tests", () => { ignoreFocusOut: true, }) .resolves(spaces[0]); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.success_set_org_space) + .resolves(); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.set_org_space }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.set_org_space, + }) .resolves(); //...cmdCreateTarget canceled vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(); // expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; @@ -391,9 +475,16 @@ describe("commands unit tests", () => { const selectedEndPoint = "other-end-point"; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_cf_endpoint, + value: target["api endpoint"], + ignoreFocusOut: true, + }) .resolves(selectedEndPoint); - cliMock.expects("execute").withExactArgs(["api", selectedEndPoint], undefined, undefined).resolves(cliResult); + cliMock + .expects("execute") + .withExactArgs(["api", selectedEndPoint], undefined, undefined) + .resolves(cliResult); // ...cmdCFSetOrgSpace fails vscodeWindowMock .expects("withProgress") @@ -405,7 +496,10 @@ describe("commands unit tests", () => { .resolves({ data: {} }); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.getting_orgs, + }) .resolves(orgs); vscodeWindowMock .expects("showQuickPick") @@ -433,7 +527,11 @@ describe("commands unit tests", () => { cfLocalMock.expects("cfGetTarget").resolves(target); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_cf_endpoint, + value: target["api endpoint"], + ignoreFocusOut: true, + }) .resolves(); expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; }); @@ -451,7 +549,11 @@ describe("commands unit tests", () => { cfLocalMock.expects("cfGetTarget").resolves(target); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_cf_endpoint, value: target["api endpoint"], ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_cf_endpoint, + value: target["api endpoint"], + ignoreFocusOut: true, + }) .resolves(target["api endpoint"]); // ...cmdCFSetOrgSpace vscodeWindowMock @@ -464,7 +566,10 @@ describe("commands unit tests", () => { .resolves({ data: {} }); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_orgs }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.getting_orgs, + }) .resolves(orgs); vscodeWindowMock .expects("showQuickPick") @@ -477,7 +582,10 @@ describe("commands unit tests", () => { .resolves(orgs[1]); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.getting_spaces }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.getting_spaces, + }) .resolves(spaces); vscodeWindowMock .expects("showQuickPick") @@ -488,15 +596,24 @@ describe("commands unit tests", () => { ignoreFocusOut: true, }) .resolves(spaces[0]); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.success_set_org_space) + .resolves(); vscodeWindowMock .expects("withProgress") - .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Window, title: messages.set_org_space }) + .withArgs({ + location: nsVsMock.testVscode.ProgressLocation.Window, + title: messages.set_org_space, + }) .resolves(); //...cmdCreateTarget canceled vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.name_for_target, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.name_for_target, + ignoreFocusOut: true, + }) .resolves(); // expect(await commands.cmdSelectAndSaveTarget()).to.be.undefined; diff --git a/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts index 82a38cf10..e4ed23a34 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/commands.spec.ts @@ -7,7 +7,13 @@ import { mockVscode } from "./ext/mockUtil"; mockVscode(nsVsMock.testVscode, "src/commands.ts"); import * as commands from "../src/commands"; import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; -import { ServiceTypeInfo, ServiceInstanceInfo, Cli, CF_PAGE_SIZE, OK } from "@sap/cf-tools"; +import { + ServiceTypeInfo, + ServiceInstanceInfo, + Cli, + CF_PAGE_SIZE, + OK, +} from "@sap/cf-tools"; import { messages } from "../src/messages"; import * as cfLocalUtils from "@sap/cf-tools/out/src/utils"; import * as loginTargetView from "../src/loginTargetView/loginTargetView"; @@ -65,7 +71,11 @@ describe("commands unit tests", () => { }); describe("cmdLogin", () => { - const target = new cfView.CFTargetTI({ label: "target", isCurrent: true, isDirty: false }); + const target = new cfView.CFTargetTI({ + label: "target", + isCurrent: true, + isDirty: false, + }); const parent = new cfView.CFFolder("parent", target); const node = new cfView.CFLoginNode(parent); @@ -176,16 +186,24 @@ describe("commands unit tests", () => { it("fail:: instanceName is not specified", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(); expect(await commands.cmdCreateService()).to.be.undefined; }); it("fail:: withProgress is called, servicesInfo is empty", async () => { - vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_services_instances_found); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -199,11 +217,18 @@ describe("commands unit tests", () => { }); it("fail:: withProgress is called, servicesInfo is not empty, planInfo is empty", async () => { - const info = { service_plans_url: "test_service_plans_url", label: "testLabel", ignoreFocusOut: true }; + const info = { + service_plans_url: "test_service_plans_url", + label: "testLabel", + ignoreFocusOut: true, + }; vscodeWindowMock.expects("showQuickPick").resolves(info); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -232,7 +257,10 @@ describe("commands unit tests", () => { vscodeWindowMock.expects("showQuickPick").resolves(undefined); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -249,7 +277,10 @@ describe("commands unit tests", () => { vscodeWindowMock.expects("showQuickPick").never(); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -282,7 +313,11 @@ describe("commands unit tests", () => { .expects("withProgress") .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, - title: messages.creating_service("instanceName", "myService", "plan1"), + title: messages.creating_service( + "instanceName", + "myService", + "plan1" + ), cancellable: true, }) .resolves(); @@ -294,7 +329,10 @@ describe("commands unit tests", () => { vscodeWindowMock.expects("showQuickPick").never(); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -316,22 +354,32 @@ describe("commands unit tests", () => { const service = { name: "myService", plan: "wrong", tag: "", prompt: "" }; vscodeWindowMock .expects("showErrorMessage") - .withExactArgs(messages.no_service_plan_info_found(service.plan, "myService")) + .withExactArgs( + messages.no_service_plan_info_found(service.plan, "myService") + ) .resolves(); expect(await commands.cmdCreateService(service)).to.be.undefined; }); it("ok:: withProgress is called, servicesInfo is not empty, planInfo is not empty", async () => { + vscodeWindowMock.expects("showQuickPick").resolves({ + service_plans_url: "test_service_plans_url", + label: "testServiceInfoLabel", + ignoreFocusOut: true, + }); vscodeWindowMock .expects("showQuickPick") - .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); - vscodeWindowMock - .expects("showQuickPick") - .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .withExactArgs([{}], { + placeHolder: messages.select_service_plan, + ignoreFocusOut: true, + }) .resolves({ label: "testPlanInfoLabel", guid: "plan-guid" }); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("showInputBox") @@ -355,7 +403,11 @@ describe("commands unit tests", () => { .expects("withProgress") .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, - title: messages.creating_service("instanceName", "testServiceInfoLabel", "testPlanInfoLabel"), + title: messages.creating_service( + "instanceName", + "testServiceInfoLabel", + "testPlanInfoLabel" + ), cancellable: true, }) .resolves("instanceName"); @@ -371,16 +423,24 @@ describe("commands unit tests", () => { }); it("fail:: withProgress is called, servicesInfo is not empty, planInfo is not empty, params canceled", async () => { + vscodeWindowMock.expects("showQuickPick").resolves({ + service_plans_url: "test_service_plans_url", + label: "testServiceInfoLabel", + ignoreFocusOut: true, + }); vscodeWindowMock .expects("showQuickPick") - .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); - vscodeWindowMock - .expects("showQuickPick") - .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .withExactArgs([{}], { + placeHolder: messages.select_service_plan, + ignoreFocusOut: true, + }) .resolves({ label: "testPlanInfoLabel" }); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("showInputBox") @@ -412,16 +472,24 @@ describe("commands unit tests", () => { }); it("fail:: withProgress is called, servicesInfo is not empty, select plan canceled", async () => { + vscodeWindowMock.expects("showQuickPick").resolves({ + service_plans_url: "test_service_plans_url", + label: "testServiceInfoLabel", + ignoreFocusOut: true, + }); vscodeWindowMock .expects("showQuickPick") - .resolves({ service_plans_url: "test_service_plans_url", label: "testServiceInfoLabel", ignoreFocusOut: true }); - vscodeWindowMock - .expects("showQuickPick") - .withExactArgs([{}], { placeHolder: messages.select_service_plan, ignoreFocusOut: true }) + .withExactArgs([{}], { + placeHolder: messages.select_service_plan, + ignoreFocusOut: true, + }) .resolves(undefined); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -455,7 +523,9 @@ describe("commands unit tests", () => { }; vscodeWindowMock .expects("showErrorMessage") - .withExactArgs(messages.no_services_instances_found_for_type(info.name)); + .withExactArgs( + messages.no_services_instances_found_for_type(info.name) + ); vscodeWindowMock .expects("showInputBox") .withExactArgs({ @@ -479,7 +549,10 @@ describe("commands unit tests", () => { vscodeWindowMock.expects("showQuickPick").never(); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -511,7 +584,11 @@ describe("commands unit tests", () => { .expects("withProgress") .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, - title: messages.creating_service("instanceName", "myService", "plan1"), + title: messages.creating_service( + "instanceName", + "myService", + "plan1" + ), cancellable: true, }) .resolves(); @@ -531,7 +608,10 @@ describe("commands unit tests", () => { const error = new Error("withProgress error"); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceName"); vscodeWindowMock .expects("withProgress") @@ -541,7 +621,10 @@ describe("commands unit tests", () => { cancellable: true, }) .rejects(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); const result = await commands.cmdCreateService(); expect(result).to.be.undefined; }); @@ -556,7 +639,10 @@ describe("commands unit tests", () => { vscodeWindowMock.expects("showQuickPick").never(); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(expectedInstanceName); vscodeWindowMock .expects("withProgress") @@ -587,7 +673,11 @@ describe("commands unit tests", () => { .expects("withProgress") .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, - title: messages.creating_service(expectedInstanceName, expectedServiceName, expectedPlanName), + title: messages.creating_service( + expectedInstanceName, + expectedServiceName, + expectedPlanName + ), cancellable: true, }) .resolves(expectedServiceName); @@ -610,7 +700,13 @@ describe("commands unit tests", () => { }; const plans = [{ label: expectedPlanName }]; - await testAllowCreateParams(expectedInstanceName, expectedServiceName, expectedPlanName, info, plans); + await testAllowCreateParams( + expectedInstanceName, + expectedServiceName, + expectedPlanName, + info, + plans + ); }); it("ok:: use allowCreate serviceName, plan and tag because they are defined in allowCreate ", async () => { @@ -631,7 +727,13 @@ describe("commands unit tests", () => { }; const plans = [{ label: expectedPlanName }]; - await testAllowCreateParams(expectedInstanceName, expectedServiceName, expectedPlanName, info, plans); + await testAllowCreateParams( + expectedInstanceName, + expectedServiceName, + expectedPlanName, + info, + plans + ); }); }); @@ -645,8 +747,11 @@ describe("commands unit tests", () => { "testInstance", undefined, undefined, - (undefined as unknown) as Progress<{ message?: string; increment?: number }>, - (undefined as unknown) as CancellationToken + undefined as unknown as Progress<{ + message?: string; + increment?: number; + }>, + undefined as unknown as CancellationToken ); fail("test should not reach here"); } catch (e) { @@ -682,8 +787,11 @@ describe("commands unit tests", () => { "testInstance", params, tags, - (undefined as unknown) as Progress<{ message?: string; increment?: number }>, - (undefined as unknown) as CancellationToken + undefined as unknown as Progress<{ + message?: string; + increment?: number; + }>, + undefined as unknown as CancellationToken ); }); }); @@ -828,7 +936,10 @@ describe("commands unit tests", () => { cancellable: true, }) .rejects(new Error(errorMessage)); - vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(OK); + vscodeCommandsMock + .expects("executeCommand") + .withExactArgs(commandId) + .resolves(OK); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -852,7 +963,10 @@ describe("commands unit tests", () => { }) .rejects(new Error(errorMessage)); cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023" }); - vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(undefined); // canceled by user + vscodeCommandsMock + .expects("executeCommand") + .withExactArgs(commandId) + .resolves(undefined); // canceled by user try { await commands.getAvailableServices(); fail("should fail"); @@ -871,7 +985,10 @@ describe("commands unit tests", () => { cancellable: true, }) .rejects(new Error(errorMessage)); - vscodeCommandsMock.expects("executeCommand").withExactArgs(commandId).resolves(OK); + vscodeCommandsMock + .expects("executeCommand") + .withExactArgs(commandId) + .resolves(OK); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -880,7 +997,9 @@ describe("commands unit tests", () => { cancellable: true, }) .resolves([{}]); - cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023", org: "org" }); + cfLocalMock + .expects("cfGetTarget") + .resolves({ user: "bag023", org: "org" }); const result = await commands.getAvailableServices(); expect(result).to.have.lengthOf(1); }); @@ -896,7 +1015,9 @@ describe("commands unit tests", () => { cancellable: true, }) .rejects(new Error(errorMessage)); - cfLocalMock.expects("cfGetTarget").resolves({ user: "bag023", org: "org", space: "space" }); + cfLocalMock + .expects("cfGetTarget") + .resolves({ user: "bag023", org: "org", space: "space" }); try { await commands.getAvailableServices(); fail("should fail"); @@ -975,8 +1096,16 @@ describe("commands unit tests", () => { describe("updateInstanceNameAndTags", () => { it("ok:: availableServices is empty", async () => { vscodeWindowMock.expects("showQuickPick").never(); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found).resolves(); - const result = await commands.updateInstanceNameAndTags([], undefined, [], []); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_services_instances_found) + .resolves(); + const result = await commands.updateInstanceNameAndTags( + [], + undefined, + [], + [] + ); expect(result).to.be.undefined; }); @@ -987,18 +1116,37 @@ describe("commands unit tests", () => { .expects("showErrorMessage") .withExactArgs(messages.no_services_instances_found_for_type(name)) .resolves(); - const result = await commands.updateInstanceNameAndTags([], { name, plan: "", tag: "", prompt: "" }, [], []); + const result = await commands.updateInstanceNameAndTags( + [], + { name, plan: "", tag: "", prompt: "" }, + [], + [] + ); expect(result).to.be.undefined; }); it("ok:: availableServices is not empty", async () => { - const plans = [{ guid: "plan_guid_1", label: "uuaa", description: "plan_description_1" }]; + const plans = [ + { + guid: "plan_guid_1", + label: "uuaa", + description: "plan_description_1", + }, + ]; const availableServices = [ - { serviceName: "testServiceName1", label: "testLabel1", plan_guid: "plan_guid_1", plan: plans[0].label }, + { + serviceName: "testServiceName1", + label: "testLabel1", + plan_guid: "plan_guid_1", + plan: plans[0].label, + }, { serviceName: "testServiceName2", label: "More results..." }, ]; const pickItems = [ - { description: `testServiceName1 (${plans[0].label})`, label: "testLabel1" }, + { + description: `testServiceName1 (${plans[0].label})`, + label: "testLabel1", + }, { description: "testServiceName2", label: "More results..." }, ]; vscodeWindowMock @@ -1012,7 +1160,12 @@ describe("commands unit tests", () => { .resolves(pickItems[0]); const result = await commands.updateInstanceNameAndTags( availableServices, - { tag: "testTag", name: "testServiceName1", plan: "testPlan", prompt: "testPrompt" }, + { + tag: "testTag", + name: "testServiceName1", + plan: "testPlan", + prompt: "testPrompt", + }, [], [] ); @@ -1020,8 +1173,12 @@ describe("commands unit tests", () => { }); it("ok:: availableServices have more services", async () => { - const availableServices = [{ serviceName: "testServiceName1", label: "More results..." }]; - const pickItems = [{ description: "testServiceName1", label: "More results..." }]; + const availableServices = [ + { serviceName: "testServiceName1", label: "More results..." }, + ]; + const pickItems = [ + { description: "testServiceName1", label: "More results..." }, + ]; vscodeWindowMock .expects("showQuickPick") @@ -1034,7 +1191,12 @@ describe("commands unit tests", () => { .resolves(pickItems[0]); const result = await commands.updateInstanceNameAndTags( availableServices, - { tag: "testTag", name: "testName", plan: "testPlan", prompt: "testPrompt" }, + { + tag: "testTag", + name: "testName", + plan: "testPlan", + prompt: "testPrompt", + }, [], [] ); @@ -1101,7 +1263,12 @@ describe("commands unit tests", () => { ignoreFocusOut: true, }) .resolves(pickItems[0]); - const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + const result = await commands.updateInstanceNameAndTags( + availableServices, + info, + [], + [] + ); expect(result).to.be.equal(infoName); }); @@ -1126,11 +1293,17 @@ describe("commands unit tests", () => { const infoName = info?.allowCreate?.name || ""; const availableServices = [ - { label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, serviceName: "" }, + { + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + serviceName: "", + }, { label: commands.CMD_CREATE_SERVICE, serviceName: "" }, ]; const pickItems = [ - { description: "", label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName }, + { + description: "", + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + }, { description: "", label: commands.CMD_CREATE_SERVICE }, ]; @@ -1165,12 +1338,21 @@ describe("commands unit tests", () => { .expects("withProgress") .withArgs({ location: nsVsMock.testVscode.ProgressLocation.Notification, - title: messages.creating_service(expectedInstanceName, expectedServiceName, expectedPlanName), + title: messages.creating_service( + expectedInstanceName, + expectedServiceName, + expectedPlanName + ), cancellable: true, }) .resolves(expectedInstanceName); - const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + const result = await commands.updateInstanceNameAndTags( + availableServices, + info, + [], + [] + ); expect(result).to.be.equal(infoName); }); @@ -1187,11 +1369,17 @@ describe("commands unit tests", () => { const infoName = info?.allowCreate?.name || ""; const availableServices = [ - { label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, serviceName: "" }, + { + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + serviceName: "", + }, { label: commands.CMD_CREATE_SERVICE, serviceName: "" }, ]; const pickItems = [ - { description: "", label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName }, + { + description: "", + label: commands.CMD_BIND_TO_DEFAULT_SERVICE + infoName, + }, { description: "", label: commands.CMD_CREATE_SERVICE }, ]; @@ -1205,7 +1393,10 @@ describe("commands unit tests", () => { }) .resolves(pickItems[0]); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(infoName)).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_created(infoName)) + .resolves(); cfLocalMock .expects("cfCreateUpsInstance") .withExactArgs({ @@ -1223,7 +1414,12 @@ describe("commands unit tests", () => { links: {}, }); - const result = await commands.updateInstanceNameAndTags(availableServices, info, [], []); + const result = await commands.updateInstanceNameAndTags( + availableServices, + info, + [], + [] + ); expect(result).to.be.equal(infoName); }); }); @@ -1233,7 +1429,11 @@ describe("commands unit tests", () => { label: "test", guid: "plan-guid", description: "", - service_offering: { guid: "service-guid", name: "service-name", description: "service-description" }, + service_offering: { + guid: "service-guid", + name: "service-name", + description: "service-description", + }, }; it("ok:: on success", async () => { @@ -1260,7 +1460,10 @@ describe("commands unit tests", () => { }) .rejects(new Error(errorMessage)); cfLocalMock.expects("cfGetTarget").rejects(); - vscodeCommandsMock.expects("executeCommand").withExactArgs("cf.login", true).resolves(OK); + vscodeCommandsMock + .expects("executeCommand") + .withExactArgs("cf.login", true) + .resolves(OK); vscodeWindowMock .expects("withProgress") .withArgs({ @@ -1283,7 +1486,10 @@ describe("commands unit tests", () => { }) .rejects(new Error(errorMessage)); cfLocalMock.expects("cfGetTarget").rejects(); - vscodeCommandsMock.expects("executeCommand").withExactArgs("cf.login", true).resolves(undefined); + vscodeCommandsMock + .expects("executeCommand") + .withExactArgs("cf.login", true) + .resolves(undefined); try { await commands.fetchServicePlanList(); fail("should fail"); @@ -1320,12 +1526,24 @@ describe("commands unit tests", () => { { label: MORE_RESULTS, serviceName: "more:2", alwaysShow: true }, ]; const items = [ - { label: availableServices[0].label, description: availableServices[0].serviceName }, - { label: availableServices[1].label, description: availableServices[1].serviceName }, + { + label: availableServices[0].label, + description: availableServices[0].serviceName, + }, + { + label: availableServices[1].label, + description: availableServices[1].serviceName, + }, ]; const allItems = [ - { label: availableServices[0].label, description: availableServices[0].serviceName }, - { label: servicesPage2[0].label, description: servicesPage2[0].serviceName }, + { + label: availableServices[0].label, + description: availableServices[0].serviceName, + }, + { + label: servicesPage2[0].label, + description: servicesPage2[0].serviceName, + }, ]; it("ok:: success", async () => { @@ -1360,8 +1578,13 @@ describe("commands unit tests", () => { }); it("fail:: no avialable services", async () => { - vscodeWindowMock.expects("showErrorMessage").withExactArgs(messages.no_services_instances_found).resolves(); - const result = await commands.getInstanceName((undefined as unknown) as ServiceInstanceInfo[]); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(messages.no_services_instances_found) + .resolves(); + const result = await commands.getInstanceName( + undefined as unknown as ServiceInstanceInfo[] + ); expect(result).to.undefined; }); }); @@ -1396,7 +1619,10 @@ describe("commands unit tests", () => { const instanceUpsName = "myUps"; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(instanceUpsName); vscodeWindowMock .expects("showInputBox") @@ -1413,13 +1639,21 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(instanceUpsName)); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_created(instanceUpsName)); cfLocalMock .expects("cfCreateUpsInstance") .withExactArgs({ @@ -1448,7 +1682,10 @@ describe("commands unit tests", () => { it("fail:: no name provided", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(undefined); cfLocalMock.expects("cfCreateUpsInstance").never(); expect(await commands.cmdCreateUps()).to.be.equal(undefined); @@ -1457,7 +1694,10 @@ describe("commands unit tests", () => { it("fail:: enter credentials canceled", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("name"); vscodeWindowMock .expects("showInputBox") @@ -1475,7 +1715,10 @@ describe("commands unit tests", () => { it("fail:: enter tags canceled", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("name"); vscodeWindowMock .expects("showInputBox") @@ -1497,7 +1740,10 @@ describe("commands unit tests", () => { it("fail:: entering enter_sys_log_url canceled", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("name"); vscodeWindowMock .expects("showInputBox") @@ -1514,7 +1760,10 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(undefined); cfLocalMock.expects("cfCreateUpsInstance").never(); expect(await commands.cmdCreateUps()).to.be.equal(undefined); @@ -1523,7 +1772,10 @@ describe("commands unit tests", () => { it("fail:: entering enter_route_service_url canceled", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("name"); vscodeWindowMock .expects("showInputBox") @@ -1540,11 +1792,17 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(undefined); cfLocalMock.expects("cfCreateUpsInstance").never(); expect(await commands.cmdCreateUps()).to.be.equal(undefined); @@ -1553,7 +1811,10 @@ describe("commands unit tests", () => { it("fail:: no info data - unexpected response format", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceUpsName"); vscodeWindowMock .expects("showInputBox") @@ -1570,14 +1831,23 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); const response = { label: "name", metadata: {} }; - vscodeWindowMock.expects("showInformationMessage").withExactArgs(_.toString(response)).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(_.toString(response)) + .resolves(); cfLocalMock.expects("cfCreateUpsInstance").resolves(response); expect(await commands.cmdCreateUps()).to.be.equal(undefined); }); @@ -1585,7 +1855,10 @@ describe("commands unit tests", () => { it("exception:: no info data, show error message", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceUpsName"); vscodeWindowMock .expects("showInputBox") @@ -1602,11 +1875,17 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock.expects("showInformationMessage").never(); const error = new Error("runtime error"); @@ -1618,7 +1897,10 @@ describe("commands unit tests", () => { it("exception:: no info data, exception no error thrown", async () => { vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves("instanceUpsName"); vscodeWindowMock .expects("showInputBox") @@ -1635,11 +1917,17 @@ describe("commands unit tests", () => { .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock.expects("showInformationMessage").never(); vscodeWindowMock.expects("showErrorMessage").once().resolves(); @@ -1660,9 +1948,16 @@ describe("commands unit tests", () => { const infoName = info?.allowCreate?.name || ""; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true, value: infoName }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + value: infoName, + }) .resolves(infoName); - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.service_created(infoName)).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.service_created(infoName)) + .resolves(); cfLocalMock .expects("cfCreateUpsInstance") .withExactArgs({ @@ -1692,7 +1987,10 @@ describe("commands unit tests", () => { const instanceUpsName = "myUps"; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(instanceUpsName); vscodeWindowMock .expects("showInputBox") @@ -1705,15 +2003,25 @@ describe("commands unit tests", () => { .resolves("{}"); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_tags, ignoreFocusOut: true, value: info.tag }) + .withExactArgs({ + prompt: messages.enter_tags, + ignoreFocusOut: true, + value: info.tag, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInformationMessage") @@ -1742,7 +2050,10 @@ describe("commands unit tests", () => { const instanceUpsName = "myUps"; vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_service_name, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_service_name, + ignoreFocusOut: true, + }) .resolves(instanceUpsName); vscodeWindowMock .expects("showInputBox") @@ -1759,14 +2070,23 @@ describe("commands unit tests", () => { .resolves([]); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_sys_log_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_sys_log_url, + ignoreFocusOut: true, + }) .resolves(""); vscodeWindowMock .expects("showInputBox") - .withExactArgs({ prompt: messages.enter_route_service_url, ignoreFocusOut: true }) + .withExactArgs({ + prompt: messages.enter_route_service_url, + ignoreFocusOut: true, + }) .resolves(""); const response: any = null; - vscodeWindowMock.expects("showInformationMessage").withExactArgs(_.toString(response)).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(_.toString(response)) + .resolves(); cfLocalMock .expects("cfCreateUpsInstance") .withExactArgs({ diff --git a/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts index 03998c876..ac8c0c5ab 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/ext/mockVscode.ts @@ -67,7 +67,13 @@ class Uri { public readonly fragment: string; public readonly fsPath: string; // eslint-disable-next-line @typescript-eslint/no-unused-vars - private constructor(scheme: string, authority: string, testpath: string, query: string, fragment: string) { + private constructor( + scheme: string, + authority: string, + testpath: string, + query: string, + fragment: string + ) { this.fsPath = testpath; this.scheme = scheme; this.path = ""; @@ -75,7 +81,13 @@ class Uri { this.query = ""; this.fragment = ""; } - public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): Uri { + public with(change: { + scheme?: string; + authority?: string; + path?: string; + query?: string; + fragment?: string; + }): Uri { return new Uri("", "", change.path || "", "", ""); } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -98,9 +110,16 @@ class RelativePattern { } function _path2Uri(extPath: string): { uri: Uri; name: string; index: number } { - return _.set({ uri: Uri.file(extPath), name: extPath, index: 0 }, ["uri", "fsPath"], extPath); + return _.set( + { uri: Uri.file(extPath), name: extPath, index: 0 }, + ["uri", "fsPath"], + extPath + ); } -const roots = [_path2Uri(path.resolve("workspace")), _path2Uri(path.resolve("project-test"))]; +const roots = [ + _path2Uri(path.resolve("workspace")), + _path2Uri(path.resolve("project-test")), +]; let selectedQuickPicks: any; export function getTestSelectedQuickPicks() { @@ -193,7 +212,11 @@ export const testVscode = { getWorkspaceFolder: (folderPath: vscode.Uri) => { const fsPath = _.get(folderPath, "fsPath"); return _.set( - { uri: testVscode.Uri.file(fsPath), name: _.last(_.split(fsPath, "\\")), index: 0 }, + { + uri: testVscode.Uri.file(fsPath), + name: _.last(_.split(fsPath, "\\")), + index: 0, + }, ["uri", "fsPath"], fsPath ); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts index 224a8c629..7e7698525 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/extension.spec.ts @@ -37,7 +37,9 @@ describe("extension unit test", () => { let isShowTarget: boolean; const config = { get: (key: string) => { - return key === "CloudFoundryTools.showTargetInformation" ? isShowTarget : undefined; + return key === "CloudFoundryTools.showTargetInformation" + ? isShowTarget + : undefined; }, }; @@ -90,8 +92,14 @@ describe("extension unit test", () => { beforeEach(() => { isShowTarget = true; windowMock.expects("createTreeView").withArgs("cfView"); - testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; - loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); + testContext = { + subscriptions: [], + logUri: { fsPath: path.resolve(__dirname) }, + }; + loggerWrapperMock + .expects("initLogger") + .withExactArgs(testContext) + .resolves(); mockStatusBarItem.expects("show"); windowMock .expects("createStatusBarItem") @@ -101,7 +109,10 @@ describe("extension unit test", () => { }); it("ok:: cfConfigFilePath does not exist", async () => { - extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns(undefined); mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); fsMock.expects("watchFile").never(); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ @@ -112,8 +123,13 @@ describe("extension unit test", () => { }); it("ok:: cfConfigFilePath exists", async () => { - extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); - mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns(undefined); + mockCfLocalUnits + .expects("cfGetConfigFilePath") + .returns("testCFConfigFilePath"); fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ await extension.activate(testContext).then(() => { @@ -143,7 +159,10 @@ describe("extension unit test", () => { .resolves({ Name: "testName2" }); mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); mockExt.expects("activate").resolves(); - extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(extRun); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns(extRun); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ await extension.activate(testContext).then(() => { // wait for the tree view to be created asynchonously @@ -173,7 +192,10 @@ describe("extension unit test", () => { .resolves({ Name: "testName2" }); mockCfLocalUnits.expects("cfGetConfigFilePath").returns(""); mockExt.expects("activate").rejects(new Error("my error")); - extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(extRun); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns(extRun); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ await extension.activate(testContext).then(() => { // wait for the tree view to be created asynchonously @@ -191,13 +213,21 @@ describe("extension unit test", () => { isShowTarget = false; workspaceMock.expects("getConfiguration").returns(config); mockCommands = sandbox.mock(commands); - testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; - loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); + testContext = { + subscriptions: [], + logUri: { fsPath: path.resolve(__dirname) }, + }; + loggerWrapperMock + .expects("initLogger") + .withExactArgs(testContext) + .resolves(); windowMock .expects("createStatusBarItem") .withExactArgs(nsVsMock.testVscode.StatusBarAlignment.Left, 100) .returns(statusBarItem); - mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + mockCfLocalUnits + .expects("cfGetConfigFilePath") + .returns("testCFConfigFilePath"); fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); mockStatusBarItem.expects("hide"); extensionsMock @@ -249,8 +279,16 @@ describe("extension unit test", () => { }); it("ok:: cmdLogin command triggered", async () => { - const targets = [new CFTargetTI({ label: "test-target", isCurrent: true, isDirty: false })]; - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + const targets = [ + new CFTargetTI({ + label: "test-target", + isCurrent: true, + isDirty: false, + }), + ]; + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); mockCommands.expects("cmdLogin").resolves(OK); viewCommandsMock .expects("cmdDeleteTarget") @@ -260,13 +298,24 @@ describe("extension unit test", () => { .expects("execSaveTarget") .withExactArgs(targets[0], { silent: true, "skip-reload": true }) .resolves(); - viewCommandsMock.expects("execSetTarget").withExactArgs(targets[0], { silent: true }).resolves(); + viewCommandsMock + .expects("execSetTarget") + .withExactArgs(targets[0], { silent: true }) + .resolves(); await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); }); it("ok:: cmdLogin command triggered and failed", async () => { - const targets = [new CFTargetTI({ label: "test-target", isCurrent: true, isDirty: false })]; - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + const targets = [ + new CFTargetTI({ + label: "test-target", + isCurrent: true, + isDirty: false, + }), + ]; + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); mockCommands.expects("cmdLogin").resolves("failed"); viewCommandsMock.expects("cmdDeleteTarget").never(); await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); @@ -282,21 +331,41 @@ describe("extension unit test", () => { }); it("ok:: cmdLogin command triggered, no active target", async () => { - sandbox.stub(nsVsMock.getTestTreeProvider(), "getCurrentTarget").returns(undefined); + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getCurrentTarget") + .returns(undefined); mockCommands.expects("cmdLogin").resolves(OK); await _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "login"])(); }); it("ok:: 'cf.target.set' command triggered", (done) => { const targets = [ - new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), - new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + new CFTargetTI({ + label: "test-target", + isCurrent: false, + isDirty: false, + }), + new CFTargetTI({ + label: "test-target-active", + isCurrent: true, + isDirty: false, + }), ]; const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); - cfViewMock.expects("reveal").withExactArgs(targets[1], { select: true, focus: true, expand: true }).resolves(); - viewCommandsMock.expects("cmdSetCurrentTarget").withExactArgs(targets[0]).resolves(); - _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])(targets[0]); + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); + cfViewMock + .expects("reveal") + .withExactArgs(targets[1], { select: true, focus: true, expand: true }) + .resolves(); + viewCommandsMock + .expects("cmdSetCurrentTarget") + .withExactArgs(targets[0]) + .resolves(); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])( + targets[0] + ); setTimeout(() => { cfViewMock.verify(); done(); @@ -305,14 +374,29 @@ describe("extension unit test", () => { it("ok:: 'cf.target.set' command triggered, reveal item not found", (done) => { const targets = [ - new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), - new CFTargetTI({ label: "test-target-active", isCurrent: false, isDirty: false }), + new CFTargetTI({ + label: "test-target", + isCurrent: false, + isDirty: false, + }), + new CFTargetTI({ + label: "test-target-active", + isCurrent: false, + isDirty: false, + }), ]; const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); cfViewMock.expects("reveal").never(); - viewCommandsMock.expects("cmdSetCurrentTarget").withExactArgs(targets[0]).resolves(); - _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])(targets[0]); + viewCommandsMock + .expects("cmdSetCurrentTarget") + .withExactArgs(targets[0]) + .resolves(); + _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "target", "set"])( + targets[0] + ); setTimeout(() => { cfViewMock.verify(); done(); @@ -321,14 +405,31 @@ describe("extension unit test", () => { it("ok:: 'cf.targets.create' command triggered", (done) => { const targets = [ - new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), - new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + new CFTargetTI({ + label: "test-target", + isCurrent: false, + isDirty: false, + }), + new CFTargetTI({ + label: "test-target-active", + isCurrent: true, + isDirty: false, + }), ]; const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); - cfViewMock.expects("reveal").withExactArgs(targets[1], { select: true, focus: true, expand: true }).resolves(); + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); + cfViewMock + .expects("reveal") + .withExactArgs(targets[1], { select: true, focus: true, expand: true }) + .resolves(); mockCommands.expects("cmdSelectAndSaveTarget").resolves(targets[1].label); - _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "targets", "create"])(); + _.get(nsVsMock.getTestRegisteredCommands(), [ + "cf", + "targets", + "create", + ])(); setTimeout(() => { cfViewMock.verify(); done(); @@ -337,14 +438,28 @@ describe("extension unit test", () => { it("ok:: 'cf.targets.create' command triggered, creation canceled", (done) => { const targets = [ - new CFTargetTI({ label: "test-target", isCurrent: false, isDirty: false }), - new CFTargetTI({ label: "test-target-active", isCurrent: true, isDirty: false }), + new CFTargetTI({ + label: "test-target", + isCurrent: false, + isDirty: false, + }), + new CFTargetTI({ + label: "test-target-active", + isCurrent: true, + isDirty: false, + }), ]; const cfViewMock = sandbox.mock(nsVsMock.getTestTreeView()); - sandbox.stub(nsVsMock.getTestTreeProvider(), "getTargets").returns(targets); + sandbox + .stub(nsVsMock.getTestTreeProvider(), "getTargets") + .returns(targets); cfViewMock.expects("reveal").never(); mockCommands.expects("cmdSelectAndSaveTarget").resolves(); - _.get(nsVsMock.getTestRegisteredCommands(), ["cf", "targets", "create"])(); + _.get(nsVsMock.getTestRegisteredCommands(), [ + "cf", + "targets", + "create", + ])(); setTimeout(() => { cfViewMock.verify(); done(); @@ -367,10 +482,21 @@ describe("extension unit test", () => { mockStatusBarItem.expects("hide"); workspaceMock.expects("getConfiguration").returns(config); windowMock.expects("createTreeView").withArgs("cfView"); - testContext = { subscriptions: [], logUri: { fsPath: path.resolve(__dirname) } }; - loggerWrapperMock.expects("initLogger").withExactArgs(testContext).resolves(); - extensionsMock.expects("getExtension").withExactArgs(runConfigExtName).returns(undefined); - mockCfLocalUnits.expects("cfGetConfigFilePath").returns("testCFConfigFilePath"); + testContext = { + subscriptions: [], + logUri: { fsPath: path.resolve(__dirname) }, + }; + loggerWrapperMock + .expects("initLogger") + .withExactArgs(testContext) + .resolves(); + extensionsMock + .expects("getExtension") + .withExactArgs(runConfigExtName) + .returns(undefined); + mockCfLocalUnits + .expects("cfGetConfigFilePath") + .returns("testCFConfigFilePath"); fsMock.expects("watchFile").withArgs("testCFConfigFilePath"); windowMock .expects("createStatusBarItem") @@ -388,7 +514,10 @@ describe("extension unit test", () => { .expects("cfGetConfigFileField") .withExactArgs("OrganizationFields") .resolves({ Name: "testName1" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "testName2" }); mockCfLocal.expects("cfGetTarget").resolves(target); setTimeout(() => { extension.onCFConfigFileChange(); @@ -408,7 +537,10 @@ describe("extension unit test", () => { .expects("cfGetConfigFileField") .withExactArgs("OrganizationFields") .resolves({ Name: "testName1" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "testName2" }); mockCfLocal.expects("cfGetTarget").resolves(target); extension.onCFConfigFileChange(); }, 100); @@ -418,7 +550,10 @@ describe("extension unit test", () => { .expects("cfGetConfigFileField") .withExactArgs("OrganizationFields") .resolves({ Name: "testName1" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName2" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "testName2" }); extension.onCFConfigFileChange(); }, 300); await new Promise((resolve) => setTimeout(resolve, 500)); @@ -432,8 +567,14 @@ describe("extension unit test", () => { }); setTimeout(() => { - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("OrganizationFields").resolves({ Name: "" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("OrganizationFields") + .resolves({ Name: "" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "" }); mockCfLocal.expects("cfGetTarget").never(); extension.onCFConfigFileChange(); }, 100); @@ -445,7 +586,10 @@ describe("extension unit test", () => { .expects("cfGetConfigFileField") .withExactArgs("OrganizationFields") .resolves({ Name: "testName1" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "testName3" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "testName3" }); /* eslint-disable-next-line @typescript-eslint/no-unsafe-argument */ await extension.activate(testContext).then(() => { // wait for the tree view to be created asynchonously @@ -457,7 +601,10 @@ describe("extension unit test", () => { .expects("cfGetConfigFileField") .withExactArgs("OrganizationFields") .resolves({ Name: "testName1" }); - mockCfLocalUnits.expects("cfGetConfigFileField").withExactArgs("SpaceFields").resolves({ Name: "" }); + mockCfLocalUnits + .expects("cfGetConfigFileField") + .withExactArgs("SpaceFields") + .resolves({ Name: "" }); mockCfLocal.expects("cfGetTarget").rejects(undefined); extension.onCFConfigFileChange(); }, 100); @@ -473,7 +620,10 @@ describe("extension unit test", () => { }, }; workspaceMock.expects("getConfiguration").never(); - extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + extension.callbackOnDidChangeConfiguration( + event, + undefined as unknown as ExtensionContext + ); }); it("ok:: callbackOnDidChangeConfiguration - triggered, hide", () => { @@ -484,7 +634,10 @@ describe("extension unit test", () => { }; workspaceMock.expects("getConfiguration").returns(config); mockStatusBarItem.expects("hide"); - extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + extension.callbackOnDidChangeConfiguration( + event, + undefined as unknown as ExtensionContext + ); }); it("ok:: callbackOnDidChangeConfiguration - triggered, show", () => { @@ -496,18 +649,26 @@ describe("extension unit test", () => { isShowTarget = true; workspaceMock.expects("getConfiguration").returns(config); mockStatusBarItem.expects("show"); - extension.callbackOnDidChangeConfiguration(event, (undefined as unknown) as ExtensionContext); + extension.callbackOnDidChangeConfiguration( + event, + undefined as unknown as ExtensionContext + ); }); }); describe("Common extension package definition", () => { const jsonPath = recognisePackageJsonPath(__dirname); const jsonPackage = JSON.parse( - fs.readFileSync(path.resolve(path.join(jsonPath ? jsonPath : "", "package.json")), { encoding: "utf8" }) + fs.readFileSync( + path.resolve(path.join(jsonPath ? jsonPath : "", "package.json")), + { encoding: "utf8" } + ) ); it("ok:: configuration title", () => { - expect(_.get(jsonPackage, ["contributes", "configuration", "title"])).to.be.equal("CloudFoundryTools"); + expect( + _.get(jsonPackage, ["contributes", "configuration", "title"]) + ).to.be.equal("CloudFoundryTools"); }); it("ok:: logging.loggingLevel desciption", () => { @@ -519,7 +680,9 @@ describe("extension unit test", () => { "CloudFoundryTools.logging.loggingLevel", "description", ]) - ).to.be.equal("The verbosity of logging. The Order is None < fatal < error < warn < info < debug < trace."); + ).to.be.equal( + "The verbosity of logging. The Order is None < fatal < error < warn < info < debug < trace." + ); }); it("ok:: logging.sourceLocationTracking desciption", () => { @@ -545,11 +708,17 @@ describe("extension unit test", () => { "CloudFoundryTools.showTargetInformation", "description", ]) - ).to.be.equal("Display the current Cloud Foundry target information in the status bar"); + ).to.be.equal( + "Display the current Cloud Foundry target information in the status bar" + ); }); it("ok:: commandPalette content", () => { - const menus: any[] = _.get(jsonPackage, ["contributes", "menus", "commandPalette"]); + const menus: any[] = _.get(jsonPackage, [ + "contributes", + "menus", + "commandPalette", + ]); for (const menu of menus) { expect( [ @@ -571,13 +740,21 @@ describe("extension unit test", () => { }); it("ok:: view/title content", () => { - const commands = _.get(jsonPackage, ["contributes", "menus", "view/title"]); - expect(_.find(commands, ["command", "cf.targets.create"])).to.be.deep.equal({ + const commands = _.get(jsonPackage, [ + "contributes", + "menus", + "view/title", + ]); + expect( + _.find(commands, ["command", "cf.targets.create"]) + ).to.be.deep.equal({ command: "cf.targets.create", when: "view == cfView", group: "navigation@1", }); - expect(_.find(commands, ["command", "cf.targets.reload"])).to.be.deep.equal({ + expect( + _.find(commands, ["command", "cf.targets.reload"]) + ).to.be.deep.equal({ command: "cf.targets.reload", when: "view == cfView", group: "navigation@2", @@ -585,8 +762,14 @@ describe("extension unit test", () => { }); it("ok:: view/item/context content", () => { - const commands = _.get(jsonPackage, ["contributes", "menus", "view/item/context"]); - expect(_.find(commands, ["command", "cf.services.create"])).to.be.deep.equal({ + const commands = _.get(jsonPackage, [ + "contributes", + "menus", + "view/item/context", + ]); + expect( + _.find(commands, ["command", "cf.services.create"]) + ).to.be.deep.equal({ command: "cf.services.create", when: "view == cfView && viewItem =~ /^services-active$/", group: "inline", @@ -596,7 +779,9 @@ describe("extension unit test", () => { when: "view == cfView && viewItem == cf-login-required", group: "inline", }); - expect(_.find(commands, ["command", "cf.services.bind.local"])).to.be.deep.equal({ + expect( + _.find(commands, ["command", "cf.services.bind.local"]) + ).to.be.deep.equal({ command: "cf.services.bind.local", when: "view == cfView && viewItem == cf-service", }); @@ -616,7 +801,9 @@ describe("extension unit test", () => { when: "view == cfView && viewItem =~ /^cf-target$/", group: "appearance@1", }); - expect(_.find(commands, ["command", "cf.target.delete"])).to.be.deep.equal({ + expect( + _.find(commands, ["command", "cf.target.delete"]) + ).to.be.deep.equal({ command: "cf.target.delete", when: "view == cfView && viewItem =~ /^cf-target(-active)?$/", group: "appearance@2", diff --git a/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts index 449c160ee..17766b0a9 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/loginTargetView/loginTargetView.spec.ts @@ -97,7 +97,8 @@ describe("loginTargetView tests", () => { sandbox.restore(); }); - const htmlContext = ""; + const htmlContext = + ""; describe("openLoginView", () => { it("should open login view and handle login", async () => { @@ -106,20 +107,34 @@ describe("loginTargetView tests", () => { sandbox.stub(panelObj, "onDidDispose").yields(); vscodeWindowMock .expects("createWebviewPanel") - .withExactArgs("cfLogin", "Cloud Foundry Sign In", nsVsMock.testVscode.ViewColumn.Beside, { - enableScripts: true, - localResourceRoots: [nsVsMock.testVscode.Uri.file(path.join(extensionPath, "dist", "media"))], - }) + .withExactArgs( + "cfLogin", + "Cloud Foundry Sign In", + nsVsMock.testVscode.ViewColumn.Beside, + { + enableScripts: true, + localResourceRoots: [ + nsVsMock.testVscode.Uri.file( + path.join(extensionPath, "dist", "media") + ), + ], + } + ) .returns(panelObj); cfFsMock .expects("readFileSync") - .withExactArgs(path.join(extensionPath, "dist", "media", "index.html"), "utf8") + .withExactArgs( + path.join(extensionPath, "dist", "media", "index.html"), + "utf8" + ) .returns(htmlContext); // Stub the rpcInvokeStub for invoking long function with progress form sandbox.stub(RpcExtension.prototype, "invoke").resolves(OK); - expect(await internalModule.openLoginView({ isSplit: true })).to.equal(undefined); + expect(await internalModule.openLoginView({ isSplit: true })).to.equal( + undefined + ); }); }); @@ -138,7 +153,9 @@ describe("loginTargetView tests", () => { it("should handle error when fetching orgs", async () => { loginTargetInstance = new internal.LoginTarget(panelObj); - cfApiMock.expects("cfGetAvailableOrgs").rejects(new Error("Failed to fetch orgs")); + cfApiMock + .expects("cfGetAvailableOrgs") + .rejects(new Error("Failed to fetch orgs")); const result = await loginTargetInstance.getOrgs(); @@ -155,7 +172,11 @@ describe("loginTargetView tests", () => { }; const cfLogoutStub = sandbox.stub().resolves(api); - mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLogoutStub, + "invokeLongFunctionWithProgressForm" + ); const result = await loginTargetInstance.getTarget(); @@ -166,7 +187,11 @@ describe("loginTargetView tests", () => { it("should handle error when fetching target", async () => { loginTargetInstance = new internal.LoginTarget(panelObj); const cfLogoutStub = sandbox.stub().rejects(new Error("Logout error")); - mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLogoutStub, + "invokeLongFunctionWithProgressForm" + ); const result = await loginTargetInstance.getTarget(); @@ -180,17 +205,27 @@ describe("loginTargetView tests", () => { const cfGetTargetStub = sandbox .stub(loginTargetInstance, "getTarget") - .resolves({ "api endpoint": extensionPath, org: "my-org", space: "my-space" }); + .resolves({ + "api endpoint": extensionPath, + org: "my-org", + space: "my-space", + }); mockPrivateMethod(loginTargetInstance, cfGetTargetStub, "getTarget"); const cfDefaultLandscape = sandbox.stub().resolves(extensionPath); - mockPrivateMethod(loginTargetInstance, cfDefaultLandscape, "getCFDefaultLandscape"); + mockPrivateMethod( + loginTargetInstance, + cfDefaultLandscape, + "getCFDefaultLandscape" + ); const result = await loginTargetInstance.init(); expect(result.defaultEndpoint).to.equal(extensionPath); expect(result.isLoggedIn).to.be.true; - expect(result.passcodeUrl).to.equal("login.sap.test.ondemand.com/passcode"); + expect(result.passcodeUrl).to.equal( + "login.sap.test.ondemand.com/passcode" + ); expect(result.currentOrg).to.equal("my-org"); expect(result.currentSpace).to.equal("my-space"); }); @@ -199,10 +234,15 @@ describe("loginTargetView tests", () => { describe("getSpaces", () => { it("should fetch spaces successfully", async () => { const org = "org-1"; - const spaces: Space[] = [{ label: "space", guid: "guid", orgGUID: "orgGUID" }]; + const spaces: Space[] = [ + { label: "space", guid: "guid", orgGUID: "orgGUID" }, + ]; loginTargetInstance = new internal.LoginTarget(panelObj); - cfApiMock.expects("cfGetAvailableSpaces").withExactArgs(org).resolves(spaces); + cfApiMock + .expects("cfGetAvailableSpaces") + .withExactArgs(org) + .resolves(spaces); const result = await loginTargetInstance.getSpaces(org); @@ -213,7 +253,10 @@ describe("loginTargetView tests", () => { const org = "org-1"; loginTargetInstance = new internal.LoginTarget(panelObj); - cfApiMock.expects("cfGetAvailableSpaces").withExactArgs(org).rejects(new Error("Failed to fetch spaces")); + cfApiMock + .expects("cfGetAvailableSpaces") + .withExactArgs(org) + .rejects(new Error("Failed to fetch spaces")); const result = await loginTargetInstance.getSpaces(org); @@ -256,7 +299,11 @@ describe("loginTargetView tests", () => { loginTargetInstance = new internal.LoginTarget(panelObj); const cfLogoutStub = sandbox.stub().rejects(new Error("Logout error")); - mockPrivateMethod(loginTargetInstance, cfLogoutStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLogoutStub, + "invokeLongFunctionWithProgressForm" + ); const result = await loginTargetInstance.logoutClick(); @@ -270,7 +317,10 @@ describe("loginTargetView tests", () => { // Mock the cfLogout function to resolve successfully const cfLogoutStub = sandbox.stub().resolves(); loginTargetInstance.invokeLongFunctionWithProgressForm = cfLogoutStub; - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.logout_success).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.logout_success) + .resolves(); const result = await loginTargetInstance.logoutClick(); @@ -286,13 +336,19 @@ describe("loginTargetView tests", () => { // Mock the long function (cfLogin) to return a success result const cfLoginStub = sandbox.stub().resolves(OK); - mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLoginStub, + "invokeLongFunctionWithProgressForm" + ); const showMessageStub = vscodeWindowMock .expects("showInformationMessage") .withExactArgs(messages.login_success) .resolves(); - const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + const result = await loginTargetInstance.loginClick( + {} as SSOLoginOptions + ); expect(result).to.be.true; expect(showMessageStub.calledOnceWith(messages.login_success)).to.be.true; @@ -310,9 +366,15 @@ describe("loginTargetView tests", () => { // Mock the long function (cfLogin) to return a failure result const cfLoginStub = sandbox.stub().resolves(loginResult); - mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLoginStub, + "invokeLongFunctionWithProgressForm" + ); - const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + const result = await loginTargetInstance.loginClick( + {} as SSOLoginOptions + ); expect(result).to.be.false; expect(showMessageStub.calledOnceWith(messages.login_failed)).to.be.true; @@ -324,9 +386,15 @@ describe("loginTargetView tests", () => { // Mock the long function (cfLogin) to throw an error const cfLoginStub = sandbox.stub().rejects(new Error("Login error")); - mockPrivateMethod(loginTargetInstance, cfLoginStub, "invokeLongFunctionWithProgressForm"); + mockPrivateMethod( + loginTargetInstance, + cfLoginStub, + "invokeLongFunctionWithProgressForm" + ); - const result = await loginTargetInstance.loginClick({} as SSOLoginOptions); + const result = await loginTargetInstance.loginClick( + {} as SSOLoginOptions + ); expect(result).to.be.false; expect(cfLoginStub.calledOnce).to.be.true; @@ -343,7 +411,10 @@ describe("loginTargetView tests", () => { const cfSetOrgSpaceStub = sandbox.stub().resolves(OK); cfApiProxy.cfSetOrgSpace = cfSetOrgSpaceStub; - vscodeWindowMock.expects("showInformationMessage").withExactArgs(messages.success_set_org_space).resolves(); + vscodeWindowMock + .expects("showInformationMessage") + .withExactArgs(messages.success_set_org_space) + .resolves(); const result = await loginTargetInstance.applyTarget(org, space); @@ -380,27 +451,42 @@ describe("loginTargetView tests", () => { const longFunctionStub = sandbox.stub().resolves("result"); const rpcInvokeStub = sandbox.stub(RpcExtension.prototype, "invoke"); - const result = await loginTargetInstance.invokeLongFunctionWithProgressForm(longFunctionStub, "arg1", "arg2"); + const result = + await loginTargetInstance.invokeLongFunctionWithProgressForm( + longFunctionStub, + "arg1", + "arg2" + ); expect(result).to.equal("result"); expect(rpcInvokeStub.calledTwice).to.be.true; - expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])).to.be.true; - expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])).to.be.true; + expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])).to + .be.true; + expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])) + .to.be.true; }); it("should handle error when invoking long function", async () => { loginTargetInstance = new internal.LoginTarget(panelObj); - const longFunctionStub = sandbox.stub().rejects(new Error("Function failed")); + const longFunctionStub = sandbox + .stub() + .rejects(new Error("Function failed")); const rpcInvokeStub = sandbox.stub(RpcExtension.prototype, "invoke"); try { - await loginTargetInstance.invokeLongFunctionWithProgressForm(longFunctionStub, "arg1", "arg2"); + await loginTargetInstance.invokeLongFunctionWithProgressForm( + longFunctionStub, + "arg1", + "arg2" + ); fail("Should have thrown an error"); } catch (error) { expect(error.message).to.equal("Function failed"); expect(rpcInvokeStub.calledTwice).to.be.true; - expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])).to.be.true; - expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])).to.be.true; + expect(rpcInvokeStub.firstCall.calledWith("setBusyIndicator", [true])) + .to.be.true; + expect(rpcInvokeStub.secondCall.calledWith("setBusyIndicator", [false])) + .to.be.true; } }); }); @@ -411,21 +497,34 @@ describe("loginTargetView tests", () => { it("ok:: getCFDefaultLandscape - 'Target' is defined in .cf config file - return it first", async () => { const loginTargetInstance = new internal.LoginTarget(panelObj); - cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(fileEndpoint); - expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal(fileEndpoint); + cfApiMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves(fileEndpoint); + expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal( + fileEndpoint + ); }); it("ok:: getCFDefaultLandscape - 'Target' is undefined in .cf config file - fetch from env", async () => { const loginTargetInstance = new internal.LoginTarget(panelObj); - cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").resolves(); + cfApiMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .resolves(); cfendpointMock.expects("getCFEndpoint").resolves(envEndpoint); - expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal(envEndpoint); + expect(await loginTargetInstance.getCFDefaultLandscape()).to.be.equal( + envEndpoint + ); }); it("fail:: getCFDefaultLandscape - cfGetConfigFileField is rejected", () => { const loginTargetInstance = new internal.LoginTarget(panelObj); const error = new Error("cfGetConfigFileField rejected"); - cfApiMock.expects("cfGetConfigFileField").withExactArgs("Target").rejects(error); + cfApiMock + .expects("cfGetConfigFileField") + .withExactArgs("Target") + .rejects(error); return loginTargetInstance .getCFDefaultLandscape() .then(() => { @@ -440,9 +539,9 @@ describe("loginTargetView tests", () => { describe("calculatePasscodeUrl", () => { it("ok:: calculatePasscodeUrl", () => { loginTargetInstance = new internal.LoginTarget(panelObj); - expect(loginTargetInstance.calculatePasscodeUrl(extensionPath)).to.be.equal( - "login.sap.test.ondemand.com/passcode" - ); + expect( + loginTargetInstance.calculatePasscodeUrl(extensionPath) + ).to.be.equal("login.sap.test.ondemand.com/passcode"); }); }); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts index 0972e15bb..f60669ba2 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/messages.spec.ts @@ -8,14 +8,20 @@ describe("messages unit test", () => { expect(messages.extension_active("test")).to.be.not.undefined; expect(messages.targeting("testOrg", "testSpace")).to.be.not.undefined; expect(messages.targeting("testOrg", "testSpace")).to.be.not.undefined; - expect(messages.no_service_plan_info_found("testPlan", "testService")).to.be.not.undefined; - expect(messages.no_services_instance_byname_found("testService")).to.be.not.undefined; + expect(messages.no_service_plan_info_found("testPlan", "testService")).to.be + .not.undefined; + expect(messages.no_services_instance_byname_found("testService")).to.be.not + .undefined; expect(messages.no_services_found("my-plan {1}")).to.be.ok; expect(messages.service_bound_successful("planName")).to.be.ok; - expect(messages.service_instances_list_incomplete(["s-1", "s-2"])).to.be.equal( + expect( + messages.service_instances_list_incomplete(["s-1", "s-2"]) + ).to.be.equal( "Could not get the details of the following service instances: 's-1,s-2'. Try to verify the specified service instances, then log in again to Cloud Foundry." ); - expect(messages.service_instances_list_incomplete(["s-1", "s-2", "s-3", "s-4"])).to.be.equal( + expect( + messages.service_instances_list_incomplete(["s-1", "s-2", "s-3", "s-4"]) + ).to.be.equal( "Could not get the details of some service instances. Try to verify the service instances, then log in again to Cloud Foundry." ); expect(messages.target_created("my-target")).to.be.ok; diff --git a/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts index b249b5f87..e188c1515 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/run-configuration.spec.ts @@ -9,7 +9,12 @@ import * as cfLocal from "@sap/cf-tools/out/src/cf-local"; import * as chisel from "../src/chisel"; import { DependencyHandler } from "../src/run-configuration"; import * as utils from "../src/utils"; -import { IBindContext, ConfigurationTarget, ConfigMetadata, BindState } from "@sap/wing-run-config-types"; +import { + IBindContext, + ConfigurationTarget, + ConfigMetadata, + BindState, +} from "@sap/wing-run-config-types"; import { messages } from "../src/messages"; import { createSandbox, SinonMock, SinonSandbox } from "sinon"; @@ -65,8 +70,13 @@ describe("run-configuration tests package", () => { }, isQuotedVcap: false, }; - sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).resolves(getEnvResourcesResult); - expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.cloud); + sandbox + .stub(utils, "getEnvResources") + .withArgs(bindContext.envPath.fsPath) + .resolves(getEnvResourcesResult); + expect( + await new DependencyHandler("test.Handler.id").getBindState(bindContext) + ).to.be.equal(BindState.cloud); }); it("ok:: 'getBindState' - not defined", async () => { @@ -76,42 +86,74 @@ describe("run-configuration tests package", () => { }, isQuotedVcap: false, }; - sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).resolves(getEnvResourcesResult); - expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.notbound); + sandbox + .stub(utils, "getEnvResources") + .withArgs(bindContext.envPath.fsPath) + .resolves(getEnvResourcesResult); + expect( + await new DependencyHandler("test.Handler.id").getBindState(bindContext) + ).to.be.equal(BindState.notbound); }); it("ok:: 'getBindState' - exception error, resolved to not bound", async () => { const error = new Error("some error"); - sandbox.stub(utils, "getEnvResources").withArgs(bindContext.envPath.fsPath).throws(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); - expect(await new DependencyHandler("test.Handler.id").getBindState(bindContext)).to.be.equal(BindState.notbound); + sandbox + .stub(utils, "getEnvResources") + .withArgs(bindContext.envPath.fsPath) + .throws(error); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); + expect( + await new DependencyHandler("test.Handler.id").getBindState(bindContext) + ).to.be.equal(BindState.notbound); }); it("ok:: 'bind' - no instance", async () => { sandbox.stub(cfViewCommands, "bindLocalService").resolves([]); - expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.equal(undefined); + expect( + await new DependencyHandler("test.Handler.id").bind(bindContext) + ).to.be.equal(undefined); }); it("ok:: 'bind' - exception", async () => { const error = new Error("my error"); sandbox.stub(cfViewCommands, "bindLocalService").rejects(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); - expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.equal(undefined); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); + expect( + await new DependencyHandler("test.Handler.id").bind(bindContext) + ).to.be.equal(undefined); }); it("ok:: 'bind' succeedded", async () => { - sandbox.stub(cfViewCommands, "bindLocalService").resolves(["testInstance"]); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfViewCommands, "bindLocalService") + .resolves(["testInstance"]); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); - expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(bindContext) + ).to.be.deep.equal({ configData: bindContext.configData, resource: { name: "testInstance", type: "resourceType" }, }); }); it("ok:: 'bind' - with tag", async () => { - const resourceTag: string = _.get(bindContext, "depContext.data.resourceTag"); - const resourceName: string = _.get(bindContext, "depContext.data.resourceName"); + const resourceTag: string = _.get( + bindContext, + "depContext.data.resourceTag" + ); + const resourceName: string = _.get( + bindContext, + "depContext.data.resourceName" + ); const serviceType = [ { name: bindContext.depContext.type, @@ -124,9 +166,13 @@ describe("run-configuration tests package", () => { .stub(cfViewCommands, "bindLocalService") .withArgs(serviceType, bindContext.envPath) .resolves(["testInstance"]); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); - expect(await new DependencyHandler("test.Handler.id").bind(bindContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(bindContext) + ).to.be.deep.equal({ configData: bindContext.configData, resource: { name: "testInstance", type: "resourceType" }, }); @@ -147,9 +193,13 @@ describe("run-configuration tests package", () => { .stub(cfViewCommands, "bindLocalService") .withArgs(serviceType, bindContext.envPath) .resolves(["testInstance"]); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); - expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext) + ).to.be.deep.equal({ configData: copyContext.configData, resource: { name: "testInstance", type: "resourceType" }, }); @@ -161,11 +211,15 @@ describe("run-configuration tests package", () => { .withArgs(bindContext.envPath.fsPath, instances.join("&")) .resolves(undefined); sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); const copyContext = _.cloneDeep(bindContext); copyContext.depContext.data = { isCreateChiselTask: true }; - expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext) + ).to.be.deep.equal({ configData: copyContext.configData, resource: { name: "testInstance", type: "resourceType" }, }); @@ -175,10 +229,15 @@ describe("run-configuration tests package", () => { const chiselTask = { label: "chiselLabel", data: { context: "some" } }; sandbox .stub(chisel, "checkAndCreateChiselTask") - .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .withArgs( + _.get(bindContext, "envPath.fsPath") as string, + instances.join("&") + ) .resolves(chiselTask); sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "ahana" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "ahana" }); const copyContext = _.cloneDeep(bindContext); copyContext.depContext.data = { isCreateChiselTask: true }; @@ -190,7 +249,9 @@ describe("run-configuration tests package", () => { ) .resolves(); expect( - await new DependencyHandler("test.Handler.id").bind(copyContext, { "skip-reload": false }) + await new DependencyHandler("test.Handler.id").bind(copyContext, { + "skip-reload": false, + }) ).to.be.deep.equal({ configData: copyContext.configData, resource: { @@ -205,10 +266,15 @@ describe("run-configuration tests package", () => { const chiselTask = { label: "chiselLabel", data: { context: "some" } }; sandbox .stub(chisel, "checkAndCreateChiselTask") - .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .withArgs( + _.get(bindContext, "envPath.fsPath") as string, + instances.join("&") + ) .resolves(chiselTask); sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "hanatrial" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "hanatrial" }); const copyContext = _.cloneDeep(bindContext); copyContext.configData.dependentTasks = undefined; @@ -218,7 +284,9 @@ describe("run-configuration tests package", () => { `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselTask.label}'` ) .resolves(); - expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext) + ).to.be.deep.equal({ configData: copyContext.configData, resource: { name: "testInstance", @@ -232,16 +300,25 @@ describe("run-configuration tests package", () => { const chiselTask = { label: "chiselLabel", data: { context: "some" } }; sandbox .stub(chisel, "checkAndCreateChiselTask") - .withArgs(_.get(bindContext, "envPath.fsPath") as string, instances.join("&")) + .withArgs( + _.get(bindContext, "envPath.fsPath") as string, + instances.join("&") + ) .resolves(chiselTask); sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); const copyContext = _.cloneDeep(bindContext); copyContext.depContext.data = { isCreateChiselTask: true }; copyContext.configData.dependentTasks = undefined; vscodeWindowMock.expects("showInformationMessage").never(); - expect(await new DependencyHandler("test.Handler.id").bind(copyContext, { silent: true })).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext, { + silent: true, + }) + ).to.be.deep.equal({ configData: copyContext.configData, resource: { name: "testInstance", @@ -260,7 +337,9 @@ describe("run-configuration tests package", () => { .withArgs(bindContext.envPath.fsPath, instances.join("&")) .resolves(chiselJson); sandbox.stub(cfViewCommands, "bindLocalService").resolves(instances); - sandbox.stub(cfLocal, "cfGetInstanceMetadata").resolves({ serviceName: "testInstance", service: "resourceType" }); + sandbox + .stub(cfLocal, "cfGetInstanceMetadata") + .resolves({ serviceName: "testInstance", service: "resourceType" }); const copyContext = _.cloneDeep(bindContext); copyContext.configData.config.data.envFile = undefined; copyContext.configData.dependentTasks = [tsk]; @@ -272,48 +351,86 @@ describe("run-configuration tests package", () => { `A task for opening the VPN tunnel to the Cloud Foundry space has been created. Name: '${chiselLabel}'` ) .resolves(); - expect(await new DependencyHandler("test.Handler.id").bind(copyContext)).to.be.deep.equal({ + expect( + await new DependencyHandler("test.Handler.id").bind(copyContext) + ).to.be.deep.equal({ configData: copyContext.configData, - resource: { name: "testInstance", type: "resourceType", data: { chiselTask: chiselJson } }, + resource: { + name: "testInstance", + type: "resourceType", + data: { chiselTask: chiselJson }, + }, }); - assert.deepEqual(copyContext.configData.dependentTasks, [tsk, chiselJson]); + assert.deepEqual(copyContext.configData.dependentTasks, [ + tsk, + chiselJson, + ]); }); it("ok:: 'unbind' succeddeed", async () => { - const configurationData = { config: { data: {}, type: ConfigurationTarget.launch }, dependentTasks: [{}] }; + const configurationData = { + config: { data: {}, type: ConfigurationTarget.launch }, + dependentTasks: [{}], + }; configurationData.dependentTasks = []; - const property = { resourceName: "propName", envPath: "env Path", resourceData: { label: "resource-type" } }; + const property = { + resourceName: "propName", + envPath: "env Path", + resourceData: { label: "resource-type" }, + }; sandbox.stub(utils, "removeResourceFromEnv").resolves(property); vscodeWindowMock .expects("showInformationMessage") - .withExactArgs(messages.service_unbound_successful(property.resourceName)) + .withExactArgs( + messages.service_unbound_successful(property.resourceName) + ) .resolves(); - assert.deepEqual(await new DependencyHandler("test.Handler.id").unbind(bindContext), { - configData: configurationData, - resource: { name: "propName", type: "resource-type", data: { label: "resource-type" } }, - }); + assert.deepEqual( + await new DependencyHandler("test.Handler.id").unbind(bindContext), + { + configData: configurationData, + resource: { + name: "propName", + type: "resource-type", + data: { label: "resource-type" }, + }, + } + ); }); it("ok:: 'unbind' - empty property", async () => { - const configurationData = { config: { data: {}, type: ConfigurationTarget.launch }, dependentTasks: [{}] }; + const configurationData = { + config: { data: {}, type: ConfigurationTarget.launch }, + dependentTasks: [{}], + }; configurationData.dependentTasks = []; const property = { resourceName: "", envPath: "", resourceData: {} }; sandbox.stub(utils, "removeResourceFromEnv").resolves(property); vscodeWindowMock .expects("showInformationMessage") - .withExactArgs(messages.service_unbound_successful(_.get(property, "resourceName"))) + .withExactArgs( + messages.service_unbound_successful(_.get(property, "resourceName")) + ) .resolves(); - assert.deepEqual(await new DependencyHandler("test.Handler.id").unbind(bindContext), { - configData: configurationData, - resource: { name: "", type: "", data: {} }, - }); + assert.deepEqual( + await new DependencyHandler("test.Handler.id").unbind(bindContext), + { + configData: configurationData, + resource: { name: "", type: "", data: {} }, + } + ); }); it("ok:: 'unbind' - exception", async () => { const error = new Error("my error"); sandbox.stub(utils, "removeResourceFromEnv").rejects(error); - vscodeWindowMock.expects("showErrorMessage").withExactArgs(error.message).resolves(); - expect(await new DependencyHandler("test.Handler.id").unbind(bindContext)).to.be.equal(undefined); + vscodeWindowMock + .expects("showErrorMessage") + .withExactArgs(error.message) + .resolves(); + expect( + await new DependencyHandler("test.Handler.id").unbind(bindContext) + ).to.be.equal(undefined); }); }); }); diff --git a/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts b/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts index 04c4aa6ea..fffdf90da 100644 --- a/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts +++ b/projects/cloud-foundry-tools/packages/backend/tests/utils.spec.ts @@ -60,7 +60,11 @@ describe("utils unit tests", () => { describe("getEnvResources", () => { it("ok:: sanity with valid VCAP_SERVICES value", async () => { sandbox.stub(fsSync, "existsSync").returns(true); - const envFilePath: string = path.join(__dirname, "resources", ".testValidEnv"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".testValidEnv" + ); const envResources = await utils.getEnvResources(envFilePath); const vcapServicesObj = envResources.vcapObject; expect(vcapServicesObj).to.not.be.empty; @@ -69,7 +73,11 @@ describe("utils unit tests", () => { it("ok:: sanity with valid VCAP_SERVICES value surrounded with single quotes", async () => { sandbox.stub(fsSync, "existsSync").returns(true); - const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotes"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".testValidEnvVCAPWithQuotes" + ); const envResources = await utils.getEnvResources(envFilePath); const vcapServicesObj = envResources.vcapObject; expect(vcapServicesObj).to.not.be.empty; @@ -78,7 +86,11 @@ describe("utils unit tests", () => { it("ok:: sanity with valid VCAP_SERVICES value surrounded with single quotes and spaces", async () => { sandbox.stub(fsSync, "existsSync").returns(true); - const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotesAndSpaces"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".testValidEnvVCAPWithQuotesAndSpaces" + ); const envResources = await utils.getEnvResources(envFilePath); const vcapServicesObj = envResources.vcapObject; expect(vcapServicesObj).to.not.be.empty; @@ -87,7 +99,11 @@ describe("utils unit tests", () => { it("fail:: invalid VCAP_SERVICES value in env file", async () => { sandbox.stub(fsSync, "existsSync").returns(true); - const envFilePath: string = path.join(__dirname, "resources", ".testInValidVCAP"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".testInValidVCAP" + ); let vcapServicesObj; try { const envResources = await utils.getEnvResources(envFilePath); @@ -100,19 +116,31 @@ describe("utils unit tests", () => { it("ok:: '.env' file does not exist", async () => { sandbox.stub(fsSync, "existsSync").returns(false); - const envFilePath: string = path.join(__dirname, "resources", ".notExists"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".notExists" + ); expect((await utils.getEnvResources(envFilePath)).vcapObject).to.be.null; }); it("ok:: '.env' file without VCAP_SERVICES key", async () => { sandbox.stub(fsSync, "existsSync").returns(true); - const envFilePath: string = path.join(__dirname, "resources", ".envNoVCAP"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".envNoVCAP" + ); expect((await utils.getEnvResources(envFilePath)).vcapObject).to.be.null; }); }); describe("Tests for removeResourceFromEnv when VCAP_SERVICES surrounded with quotes", () => { - const envFilePath: string = path.join(__dirname, "resources", ".testValidEnvVCAPWithQuotes"); + const envFilePath: string = path.join( + __dirname, + "resources", + ".testValidEnvVCAPWithQuotes" + ); const propReader = PropertiesReader(envFilePath); let vcapServicesObjBeforeChange: any; before(() => { @@ -140,14 +168,20 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); - const vcapServicesObjAfterChange: string | null = PropertiesReader(envFilePath).getRaw(utils.ENV_VCAP_RESOURCES); + const vcapServicesObjAfterChange: string | null = PropertiesReader( + envFilePath + ).getRaw(utils.ENV_VCAP_RESOURCES); if (vcapServicesObjAfterChange) { expect(vcapServicesObjAfterChange).to.not.include("hana"); expect(_.startsWith(vcapServicesObjAfterChange, "'")).to.be.true; expect(_.endsWith(vcapServicesObjAfterChange, "'")).to.be.true; } else { - expect(fail("VCAP_SERVICES must exists and be empty after removing the last dependency")); + expect( + fail( + "VCAP_SERVICES must exists and be empty after removing the last dependency" + ) + ); } }); }); @@ -206,7 +240,8 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); const envPropertiesAfterChange = PropertiesReader(envFilePath); - const vcapProperty: string = envPropertiesAfterChange.getRaw(utils.ENV_VCAP_RESOURCES) ?? ""; + const vcapProperty: string = + envPropertiesAfterChange.getRaw(utils.ENV_VCAP_RESOURCES) ?? ""; expect(vcapProperty).to.not.include("xsuaa"); expect(vcapProperty).to.include('"instance_name":"hdi-test-instance"'); expect(vcapProperty).to.not.include("xsuaa"); @@ -226,7 +261,10 @@ describe("utils unit tests", () => { }; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); - assert.deepEqual(PropertiesReader(envFilePath).get(utils.ENV_VCAP_RESOURCES), vcapServicesObjBeforeChange); + assert.deepEqual( + PropertiesReader(envFilePath).get(utils.ENV_VCAP_RESOURCES), + vcapServicesObjBeforeChange + ); }); it("ok:: remove resource when it has a tag", async () => { @@ -246,10 +284,12 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); const envPropertiesAfterChange = PropertiesReader(envFilePath); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include( - '"instance_name":"hdi-test-instance"' + expect( + envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES) + ).to.not.include('"instance_name":"hdi-test-instance"'); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include( + "xsuaa" ); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("xsuaa"); }); it("ok:: remove resource when it has a tag, resource type has more then 1 instance (e.g. two dependencies bound to different instances)", async () => { @@ -270,9 +310,15 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); envPropertiesAfterChange = PropertiesReader(envFilePath); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("hanatest"); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include('"instance_name":"hana-2"'); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-1"'); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include( + "hanatest" + ); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include( + '"instance_name":"hana-2"' + ); + expect( + envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES) + ).to.not.include('"instance_name":"hana-1"'); // Another remove should remove this key completely and not leave an empty array const bindContext2: any = { @@ -291,9 +337,15 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext2); envPropertiesAfterChange = PropertiesReader(envFilePath); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include("hanatest"); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-2"'); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.not.include('"instance_name":"hana-1"'); + expect( + envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES) + ).to.not.include("hanatest"); + expect( + envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES) + ).to.not.include('"instance_name":"hana-2"'); + expect( + envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES) + ).to.not.include('"instance_name":"hana-1"'); }); it("ok:: remove resource when it has a tag but it has not been found", async () => { @@ -312,8 +364,12 @@ describe("utils unit tests", () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await utils.removeResourceFromEnv(bindContext); const envPropertiesAfterChange = PropertiesReader(envFilePath); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include('"instance_name":"hdi-test-instance"'); - expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include("xsuaa"); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include( + '"instance_name":"hdi-test-instance"' + ); + expect(envPropertiesAfterChange.get(utils.ENV_VCAP_RESOURCES)).to.include( + "xsuaa" + ); }); }); @@ -324,7 +380,9 @@ describe("utils unit tests", () => { expect(_.startsWith(data.xsappname as string, "xsuaa_")).to.be.true; expect(data["tenant-mode"]).to.be.equal("dedicated"); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - expect(_.size(data["oauth2-configuration"]["redirect-uris"])).to.be.equal(1); + expect(_.size(data["oauth2-configuration"]["redirect-uris"])).to.be.equal( + 1 + ); }); it("generateParams4Service - xsuaa, plan - other", () => { @@ -332,81 +390,125 @@ describe("utils unit tests", () => { }); it("validateParams - other : valid", () => { - expect(utils.validateParams("other")('{ "key" : "value" }')).to.be.undefined; + expect(utils.validateParams("other")('{ "key" : "value" }')).to.be + .undefined; }); it("validateParams - other : incorrect", () => { - expect(_.size(utils.validateParams("other")('{ "key" :: "value" }'))).to.be.gt(1); + expect( + _.size(utils.validateParams("other")('{ "key" :: "value" }')) + ).to.be.gt(1); }); it("validateParams - xsuaa : valid", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); - expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be.undefined; + expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be + .undefined; }); it("validateParams - xsuaa : invalid xsappname", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); xsuaaData.xsappname = "xs.ass_345"; - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : padded xsappname", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); xsuaaData.xsappname = " "; - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : missing xsappname", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); delete xsuaaData.xsappname; - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : long xsappname", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); xsuaaData.xsappname = _.repeat("x", 103); - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : invalid tenant-mode", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); xsuaaData["tenant-mode"] = "lite"; - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : invalid redirect-uris", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); - xsuaaData["oauth2-configuration"]["redirect-uris"] = ["localhost://*", "localhos://*"]; - expect(_.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData)))).to.be.gt(1); + xsuaaData["oauth2-configuration"]["redirect-uris"] = [ + "localhost://*", + "localhos://*", + ]; + expect( + _.size(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))) + ).to.be.gt(1); }); it("validateParams - xsuaa : missing redirect-uris", () => { const xsuaaData = utils.generateParams4Service("xsuaa", plan); delete xsuaaData["oauth2-configuration"]["redirect-uris"]; - expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be.undefined; + expect(utils.validateParams("xsuaa", plan)(stringify(xsuaaData))).to.be + .undefined; }); }); describe("suite for getAllServiceInstances", () => { const services: ServiceInstanceInfo[] = [ - { label: "s1", serviceName: "hana", plan: "s-plan-1", plan_guid: "s-plan-guid-1" }, - { label: "s1", serviceName: "hana", plan: "s-plan-2", plan_guid: "s-plan-guid-2" }, + { + label: "s1", + serviceName: "hana", + plan: "s-plan-1", + plan_guid: "s-plan-guid-1", + }, + { + label: "s1", + serviceName: "hana", + plan: "s-plan-2", + plan_guid: "s-plan-guid-2", + }, ]; const ups = [ - { label: "ups1", serviceName: "user-provided-service", credentials: { tags: ["monodb", "hana"] } }, - { label: "ups2", serviceName: "user-provided-service", credentials: { tags: ["david"] } }, + { + label: "ups1", + serviceName: "user-provided-service", + credentials: { tags: ["monodb", "hana"] }, + }, + { + label: "ups2", + serviceName: "user-provided-service", + credentials: { tags: ["david"] }, + }, ]; it("ok:: there are both kind of services", async () => { sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); - assert.deepEqual(await utils.getAllServiceInstances({ ups: { isShow: true } }), _.concat(services, ups)); + assert.deepEqual( + await utils.getAllServiceInstances({ ups: { isShow: true } }), + _.concat(services, ups) + ); }); it("ok:: contain 'managed' and 'ups' filtered services", async () => { sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); - assert.deepEqual(await utils.getAllServiceInstances({ ups: { tag: "/.*dav.*/" } }), _.concat(services, ups[1])); + assert.deepEqual( + await utils.getAllServiceInstances({ ups: { tag: "/.*dav.*/" } }), + _.concat(services, ups[1]) + ); }); it("ok:: ups services are not required", async () => { @@ -426,19 +528,43 @@ describe("utils unit tests", () => { ups: { tag: "monodb" }, }; const expectedQuery = _.cloneDeep(opts.query); - expectedQuery.filters = [{ key: eFilters.space_guids, value: "testSpace" }]; + expectedQuery.filters = [ + { key: eFilters.space_guids, value: "testSpace" }, + ]; sandbox.stub(cfLocal, "cfGetManagedServiceInstances").resolves(services); - sandbox.stub(cfLocal, "cfGetUpsInstances").withArgs(expectedQuery).resolves(ups); - assert.deepEqual(await utils.getAllServiceInstances(opts), _.concat(services, ups[0])); + sandbox + .stub(cfLocal, "cfGetUpsInstances") + .withArgs(expectedQuery) + .resolves(ups); + assert.deepEqual( + await utils.getAllServiceInstances(opts), + _.concat(services, ups[0]) + ); }); }); describe("getUpsServiceInstances", () => { const ups = [ - { label: "ups1", serviceName: "user-provided-service", credentials: { tags: ["monodb", "hana"] } }, - { label: "ups2", serviceName: "user-provided-service", credentials: { tags: ["david"] } }, - { label: "ups3", serviceName: "user-provided-service", credentials: { tags: "hana" } }, - { label: "wrong-credential-tags", serviceName: "user-provided-service", credentials: { tags: 2 } }, + { + label: "ups1", + serviceName: "user-provided-service", + credentials: { tags: ["monodb", "hana"] }, + }, + { + label: "ups2", + serviceName: "user-provided-service", + credentials: { tags: ["david"] }, + }, + { + label: "ups3", + serviceName: "user-provided-service", + credentials: { tags: "hana" }, + }, + { + label: "wrong-credential-tags", + serviceName: "user-provided-service", + credentials: { tags: 2 }, + }, { label: "wrong-credential-tags2", serviceName: "user-provided-service", @@ -455,13 +581,19 @@ describe("utils unit tests", () => { it("ok:: get all user provides services filtered by credential tag", async () => { const options = { credentials: { tag: "hana" } }; sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); - assert.deepEqual(await utils.getUpsServiceInstances(options), [ups[0], ups[2]]); + assert.deepEqual(await utils.getUpsServiceInstances(options), [ + ups[0], + ups[2], + ]); }); it("ok:: get all user provides services filtered by credential tag as regex expression", async () => { const options = { credentials: { tag: "/.*mono|vid.*/" } }; sandbox.stub(cfLocal, "cfGetUpsInstances").resolves(ups); - assert.deepEqual(await utils.getUpsServiceInstances(options), [ups[0], ups[1]]); + assert.deepEqual(await utils.getUpsServiceInstances(options), [ + ups[0], + ups[1], + ]); }); }); @@ -470,33 +602,58 @@ describe("utils unit tests", () => { const GITIGNORE = ".gitignore"; describe("updateGitIgnoreList suite", () => { - const project = { uri: nsVsMock.testVscode.Uri.file(path.resolve("myProject")) }; - const ignorePattern = new nsVsMock.testVscode.RelativePattern(project, GITIGNORE); - const gitIgnoreFile = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/${GITIGNORE}`)); + const project = { + uri: nsVsMock.testVscode.Uri.file(path.resolve("myProject")), + }; + const ignorePattern = new nsVsMock.testVscode.RelativePattern( + project, + GITIGNORE + ); + const gitIgnoreFile = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/${GITIGNORE}`) + ); it("ok:: '.env' pattern added", async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const gitIgnoreContent = _.join([" # empty line"], EOL); - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); - vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([gitIgnoreFile]); + vscodeWorkspaceMock + .expects("findFiles") + .withExactArgs(ignorePattern) + .returns([gitIgnoreFile]); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + fsMock + .expects("readFile") + .withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }) + .resolves(gitIgnoreContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const patterns = _.split(gitIgnoreContent, EOL); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + const expectedPatterns = _.join( + _.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), + EOL + ); fsMock .expects("writeFile") - .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { + encoding: UTF8, + }) .resolves(undefined); await utils.updateGitIgnoreList(testEnv.fsPath); }); it("ok:: '.env' pattern not added - already exists", async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const gitIgnoreContent = _.join([" # empty line", "subFolder/.env"], EOL); - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const gitIgnoreContent = _.join( + [" # empty line", "subFolder/.env"], + EOL + ); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); vscodeWorkspaceMock.expects("findFiles").exactly(2).returns([testEnv]); fsMock.expects("readFile").resolves(gitIgnoreContent); @@ -505,51 +662,90 @@ describe("utils unit tests", () => { }); it("ok:: empty project", async () => { - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(undefined); await utils.updateGitIgnoreList(testEnv.fsPath); }); it("ok:: '.env' pattern added, .gitIgnore file not exist", async () => { const gitIgnoreContent = ""; - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); - vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([]); - fsMock.expects("open").withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w").resolves(undefined); - fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + vscodeWorkspaceMock + .expects("findFiles") + .withExactArgs(ignorePattern) + .returns([]); + fsMock + .expects("open") + .withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w") + .resolves(undefined); + fsMock + .expects("readFile") + .withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }) + .resolves(gitIgnoreContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const patterns = _.split(gitIgnoreContent, EOL); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + const expectedPatterns = _.join( + _.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), + EOL + ); fsMock .expects("writeFile") - .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { + encoding: UTF8, + }) .resolves(undefined); await utils.updateGitIgnoreList(testEnv.fsPath); }); it("fail:: '.env' pattern NOT added, .gitIgnore file not exist, creation failed", async () => { - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); - vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([]); - fsMock.expects("open").withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w").throws(new Error("access denied")); + vscodeWorkspaceMock + .expects("findFiles") + .withExactArgs(ignorePattern) + .returns([]); + fsMock + .expects("open") + .withExactArgs(path.resolve(gitIgnoreFile.fsPath), "w") + .throws(new Error("access denied")); await utils.updateGitIgnoreList(testEnv.fsPath); }); it("fail:: '.env' pattern NOT added, writeFile failed", async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const gitIgnoreContent = _.join([" # empty line"], EOL); - const testEnv = nsVsMock.testVscode.Uri.file(path.resolve(`${project.uri.fsPath}/subFolder/.env`)); + const testEnv = nsVsMock.testVscode.Uri.file( + path.resolve(`${project.uri.fsPath}/subFolder/.env`) + ); vscodeWorkspaceMock.expects("getWorkspaceFolder").returns(project); - vscodeWorkspaceMock.expects("findFiles").withExactArgs(ignorePattern).returns([gitIgnoreFile]); - fsMock.expects("readFile").withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }).resolves(gitIgnoreContent); + vscodeWorkspaceMock + .expects("findFiles") + .withExactArgs(ignorePattern) + .returns([gitIgnoreFile]); + fsMock + .expects("readFile") + .withExactArgs(gitIgnoreFile.fsPath, { encoding: UTF8 }) + .resolves(gitIgnoreContent); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const patterns = _.split(gitIgnoreContent, EOL); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const expectedPatterns = _.join(_.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), EOL); + const expectedPatterns = _.join( + _.concat(patterns, [`# auto generated wildcard`, "subFolder/.env"]), + EOL + ); fsMock .expects("writeFile") - .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { encoding: UTF8 }) + .withExactArgs(gitIgnoreFile.fsPath, expectedPatterns, { + encoding: UTF8, + }) .throws(new Error("access denied")); await utils.updateGitIgnoreList(testEnv.fsPath); }); @@ -578,13 +774,19 @@ describe("utils unit tests", () => { plan: "plan-2", }, ]; - expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + expect( + await utils.notifyWhenServicesInfoResultIncomplete( + Promise.resolve(infos) + ) + ).to.be.deep.equal(infos); }); it("ok:: serviceInstanceInfo list is empty", async () => { const list = Promise.resolve([]); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - expect(await utils.notifyWhenServicesInfoResultIncomplete(list)).to.be.deep.equal([]); + expect( + await utils.notifyWhenServicesInfoResultIncomplete(list) + ).to.be.deep.equal([]); }); it("ok:: serviceInstanceInfo list is incomplete", async () => { @@ -597,9 +799,17 @@ describe("utils unit tests", () => { ]; vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.messages.service_instances_list_incomplete(_.map(infos, "label"))) + .withExactArgs( + messages.messages.service_instances_list_incomplete( + _.map(infos, "label") + ) + ) .resolves(); - expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + expect( + await utils.notifyWhenServicesInfoResultIncomplete( + Promise.resolve(infos) + ) + ).to.be.deep.equal(infos); }); it("ok:: serviceInstanceInfo list is incomplete more than 3 instances", async () => { @@ -627,9 +837,17 @@ describe("utils unit tests", () => { ]; vscodeWindowMock .expects("showWarningMessage") - .withExactArgs(messages.messages.service_instances_list_incomplete(_.map(infos, "label"))) + .withExactArgs( + messages.messages.service_instances_list_incomplete( + _.map(infos, "label") + ) + ) .resolves(); - expect(await utils.notifyWhenServicesInfoResultIncomplete(Promise.resolve(infos))).to.be.deep.equal(infos); + expect( + await utils.notifyWhenServicesInfoResultIncomplete( + Promise.resolve(infos) + ) + ).to.be.deep.equal(infos); }); }); @@ -644,7 +862,9 @@ describe("utils unit tests", () => { it("ok:: validate", async () => { sandbox.stub(cfLocal, "cfGetTarget").resolves(target); - expect(await utils.examCFTarget("", ["user"], false)).to.be.deep.equal(target); + expect(await utils.examCFTarget("", ["user"], false)).to.be.deep.equal( + target + ); }); it("error:: invalid key requested", async () => { @@ -659,9 +879,15 @@ describe("utils unit tests", () => { }); it("ok:: weak mode", async () => { - const t: ITarget = { "api endpoint": "endPoint", user: "user", "api version": "3.100.0" }; + const t: ITarget = { + "api endpoint": "endPoint", + user: "user", + "api version": "3.100.0", + }; sandbox.stub(cfLocal, "cfGetTarget").resolves(t); - expect(await utils.examCFTarget("", ["api endpoint"], true)).to.be.deep.equal(t); + expect( + await utils.examCFTarget("", ["api endpoint"], true) + ).to.be.deep.equal(t); }); it("exception:: throws error", async () => { diff --git a/projects/cloud-foundry-tools/packages/frontend/src/App.vue b/projects/cloud-foundry-tools/packages/frontend/src/App.vue index a40c5c4d6..c44ff2386 100644 --- a/projects/cloud-foundry-tools/packages/frontend/src/App.vue +++ b/projects/cloud-foundry-tools/packages/frontend/src/App.vue @@ -3,7 +3,10 @@
- +
diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue index e8d42e442..38ef5a6d7 100644 --- a/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFHeader.vue @@ -2,8 +2,8 @@

Cloud Foundry Sign In and Targets

- Provide your credentials to sign in to the Cloud Foundry environment and then select the target org and - space.

diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue index 33a4f4582..6fe4bceb0 100644 --- a/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFSignin.vue @@ -6,7 +6,10 @@
- {{ "You are signed in to Cloud Foundry." + (orgAndSpaceSet ? "" : " You can now set the Org and Space.") }} + {{ + "You are signed in to Cloud Foundry." + + (orgAndSpaceSet ? "" : " You can now set the Org and Space.") + }}


@@ -24,7 +27,8 @@

- Enter Cloud Foundry Endpoint * + Enter Cloud Foundry Endpoint *
Select authentication method - + + Credentials - SSO Passcode + + SSO Passcode +
- Open a new browser page to generate your SSO passcode + + Open a new browser page to generate your SSO passcode + Enter your SSO Passcode * + Enter your SSO Passcode *
- Enter your username * + Enter your username *


- Enter your password * + Enter your password *

- +
- Authentication failed. Please try again. + + Authentication failed. Please try again.


- Sign in + + Sign in +


@@ -216,7 +243,11 @@ export default { methods: { btnStatus() { if (this.endpoint && this.isCFEndpointValid) { - if (this.ssoOrCredentials == "Credentials" && this.username != "" && this.password != "") { + if ( + this.ssoOrCredentials == "Credentials" && + this.username != "" && + this.password != "" + ) { this.disableButton = false; } else if (this.ssoOrCredentials == "SSO" && this.passcode != "") { this.disableButton = false; @@ -229,7 +260,8 @@ export default { }, setEndpoint(val) { this.endpoint = val.target.value.replace(/ /g, "").replace(/\/$/, ""); - this.isCFEndpointValid = this.endpoint !== "" && this.$refs?.cfendpointInput?.validity?.valid; + this.isCFEndpointValid = + this.endpoint !== "" && this.$refs?.cfendpointInput?.validity?.valid; }, paste() { navigator.clipboard.readText().then((clipText) => { @@ -247,7 +279,8 @@ export default { }, SigninClicked() { let payload = {}; - payload.endpoint = this.endpoint !== "" ? this.endpoint : this.target.defaultEndpoint; + payload.endpoint = + this.endpoint !== "" ? this.endpoint : this.target.defaultEndpoint; if (this.ssoOrCredentials === "SSO") { payload.ssoPasscode = `"${this.passcode}"`; } else { @@ -289,7 +322,10 @@ a { } a:hover { text-decoration: underline; /* Show underline on hover */ - color: var(--vscode-textLink-activeForeground, #1e50a2); /* Active link color */ + color: var( + --vscode-textLink-activeForeground, + #1e50a2 + ); /* Active link color */ } .signinerror-icon { padding-top: 1px; @@ -319,7 +355,10 @@ a:hover { display: flex; } .clippy-button:hover { - background-color: var(--vscode-toolbar-hoverBackground, rgba(184, 184, 184, 0.31)); + background-color: var( + --vscode-toolbar-hoverBackground, + rgba(184, 184, 184, 0.31) + ); } .tooltip { display: flex; diff --git a/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue b/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue index 693ecd228..e0a63cc87 100644 --- a/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue +++ b/projects/cloud-foundry-tools/packages/frontend/src/components/CFTarget.vue @@ -2,7 +2,10 @@

Cloud Foundry Target
-
+
Target is set to: {{ currentOrg }} org and {{ currentSpace }} space.
@@ -16,8 +19,9 @@


@@ -86,13 +105,17 @@ export default { return !this.isLoggedIn ? "none" : ""; }, orgAndSpaceSetVisibility() { - return this.areOrgAndSpaceSet || (this.target?.currentOrg && this.target?.currentSpace) ? "" : "none"; + return this.areOrgAndSpaceSet || + (this.target?.currentOrg && this.target?.currentSpace) + ? "" + : "none"; }, isApplyButtonDisabled() { return ( this.selectedOrg.label === undefined || this.selectedSpace.label === undefined || - (this.selectedOrg.label === this.currentOrg && this.selectedSpace.label === this.currentSpace) + (this.selectedOrg.label === this.currentOrg && + this.selectedSpace.label === this.currentSpace) ); }, optOrganizations() { @@ -126,7 +149,11 @@ export default { }, methods: { injectOptionStyle(ref, type) { - if (ref && ref.shadowRoot && !ref.shadowRoot.querySelector(`style[data-${type}-option-style]`)) { + if ( + ref && + ref.shadowRoot && + !ref.shadowRoot.querySelector(`style[data-${type}-option-style]`) + ) { const style = document.createElement("style"); style.setAttribute(`data-${type}-option-style`, "true"); style.textContent = ` @@ -138,11 +165,17 @@ export default { } }, updateSelectedOrg(newOrg) { - this.selectedOrg = _.find(this.orgs, (org) => org.label === newOrg.target.value); + this.selectedOrg = _.find( + this.orgs, + (org) => org.label === newOrg.target.value + ); this.selectSpace(undefined); }, updateSelectedSpace(newSpace) { - this.selectedSpace = _.find(this.spaces, (space) => space.label === newSpace.target.value); + this.selectedSpace = _.find( + this.spaces, + (space) => space.label === newSpace.target.value + ); }, getOrgAndSpace() { this.rpc.invoke("getSelectedTarget").then((target) => { @@ -161,7 +194,9 @@ export default { selected: org.label === target.org, }; }); - this.orgs = _.sortBy(orgsWithSelected, (item) => item.label.toLowerCase()); + this.orgs = _.sortBy(orgsWithSelected, (item) => + item.label.toLowerCase() + ); // If no org could be selected from the current target, set it to the first org if exists if (!this.selectedOrg?.label && this.orgs.length === 1) { @@ -188,10 +223,14 @@ export default { selected: targetSpace ? space.label === targetSpace : false, }; }); - this.spaces = _.sortBy(spacesWithSelected, (item) => item.label.toLowerCase()); + this.spaces = _.sortBy(spacesWithSelected, (item) => + item.label.toLowerCase() + ); // If a specific space should be selected - choose it, otherwise take the first if exists and is the only one, otherwise fallback to an empty selection object. this.selectedSpace = - this.spaces.length === 1 ? this.spaces[0] : _.find(this.spaces, (space) => space.selected === true) ?? {}; + this.spaces.length === 1 + ? this.spaces[0] + : _.find(this.spaces, (space) => space.selected === true) ?? {}; this.spaceMissing = this.spaces.length === 0; }); }, diff --git a/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js b/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js index 1f1f4754b..cd93b144e 100644 --- a/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js +++ b/projects/cloud-foundry-tools/packages/frontend/src/directives/tooltip.js @@ -29,7 +29,10 @@ export default { case "top": targetEl.style.setProperty("--v-tooltip-left", "50%"); targetEl.style.setProperty("--v-tooltip-top", "0%"); - targetEl.style.setProperty("--v-tooltip-translate", "translate(-50%, -110%)"); + targetEl.style.setProperty( + "--v-tooltip-translate", + "translate(-50%, -110%)" + ); if (value.displayArrow || modifiers.arrow) { targetEl.style.setProperty( "--v-tooltip-arrow-border-color", @@ -44,7 +47,10 @@ export default { case "bottom": targetEl.style.setProperty("--v-tooltip-left", "50%"); targetEl.style.setProperty("--v-tooltip-top", "100%"); - targetEl.style.setProperty("--v-tooltip-translate", "translate(-50%, 10%)"); + targetEl.style.setProperty( + "--v-tooltip-translate", + "translate(-50%, 10%)" + ); if (value.displayArrow || modifiers.arrow) { targetEl.style.setProperty( "--v-tooltip-arrow-border-color", @@ -59,13 +65,19 @@ export default { case "left": targetEl.style.setProperty("--v-tooltip-left", "0%"); targetEl.style.setProperty("--v-tooltip-top", "50%"); - targetEl.style.setProperty("--v-tooltip-translate", "translate(-110%, -50%)"); + targetEl.style.setProperty( + "--v-tooltip-translate", + "translate(-110%, -50%)" + ); if (value.displayArrow || modifiers.arrow) { targetEl.style.setProperty( "--v-tooltip-arrow-border-color", "transparent transparent transparent var(--v-tooltip-background-color)" ); - targetEl.style.setProperty("--v-tooltip-arrow-top", "calc(var(--v-tooltip-top)"); + targetEl.style.setProperty( + "--v-tooltip-arrow-top", + "calc(var(--v-tooltip-top)" + ); targetEl.style.setProperty( "--v-tooltip-arrow-left", "calc( var(--v-tooltip-left) - var(--v-tooltip-left-offset) + 1.5px)" @@ -75,13 +87,19 @@ export default { case "right": targetEl.style.setProperty("--v-tooltip-left", "100%"); targetEl.style.setProperty("--v-tooltip-top", "50%"); - targetEl.style.setProperty("--v-tooltip-translate", "translate(3%, -50%)"); + targetEl.style.setProperty( + "--v-tooltip-translate", + "translate(3%, -50%)" + ); if (value.displayArrow || modifiers.arrow) { targetEl.style.setProperty( "--v-tooltip-arrow-border-color", "transparent var(--v-tooltip-background-color) transparent transparent" ); - targetEl.style.setProperty("--v-tooltip-arrow-top", "calc(var(--v-tooltip-top)"); + targetEl.style.setProperty( + "--v-tooltip-arrow-top", + "calc(var(--v-tooltip-top)" + ); targetEl.style.setProperty( "--v-tooltip-arrow-left", "calc( var(--v-tooltip-left) - var(--v-tooltip-left-offset) - 2px)" diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js index 56cb956e9..1efd54f89 100644 --- a/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFHeader.spec.js @@ -46,7 +46,9 @@ describe("CFHeader.vue", () => { const computedStyle = window.getComputedStyle(subtitleField); // Assert the computed style properties - expect(computedStyle.getPropertyValue("--vscode-descriptionForeground")).to.equal("#717171"); + expect( + computedStyle.getPropertyValue("--vscode-descriptionForeground") + ).to.equal("#717171"); }); it("renders the correct HTML structure", () => { diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js index 381ac99db..e36aabd19 100644 --- a/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFSignin.spec.js @@ -201,7 +201,10 @@ describe("CFSignin.vue", () => { }); // Set invalid username and password or passcode - wrapper.setData({ username: "invalidUsername", password: "invalidPassword" }); + wrapper.setData({ + username: "invalidUsername", + password: "invalidPassword", + }); // Trigger the SigninClicked method await wrapper.vm.SigninClicked(); diff --git a/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js b/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js index 74630d797..9d7eb7ff0 100644 --- a/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js +++ b/projects/cloud-foundry-tools/packages/frontend/test/components/CFTarget.spec.js @@ -163,7 +163,9 @@ describe("CFTarget.vue", () => { await wrapper.vm.$nextTick(); // Simulate selecting an organization - await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + await wrapper.setData({ + selectedOrg: { guid: "org-guid", label: "Org Label" }, + }); // Wait for Vue to update the component await wrapper.vm.$nextTick(); @@ -259,7 +261,11 @@ describe("CFTarget.vue", () => { const orgsList = wrapper.vm.$data.orgs; expect(orgsList[0]?.label).equal("aaaaa"); expect(orgsList[1]?.label).equal("bbbb"); - expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423", selected: true }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + label: "aaaaa", + guid: "32432423", + selected: true, + }); }); it("does not select Organizaion when multiple Organizaions returned and unknown Organizaion from selectedTarget", async () => { @@ -351,7 +357,10 @@ describe("CFTarget.vue", () => { // Wait for Vue to update the component await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + label: "aaaaa", + guid: "32432423", + }); expect(wrapper.vm.selectedSpace).to.deep.equal({}); }); @@ -391,9 +400,16 @@ describe("CFTarget.vue", () => { // Wait for Vue to update the component await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + label: "aaaaa", + guid: "32432423", + }); await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "xxxxxxxx", label: "space1", selected: false }); + expect(wrapper.vm.selectedSpace).to.deep.equal({ + guid: "xxxxxxxx", + label: "space1", + selected: false, + }); }); it("does select Organisaion and Space when one Organisation and multiple Space and known org from selectedTarget", async () => { @@ -402,7 +418,11 @@ describe("CFTarget.vue", () => { if (funcName === "getOrgs") { return Promise.resolve([{ guid: "32432423", label: "aaaaa" }]); } else if (funcName === "getSelectedTarget") { - return Promise.resolve({ guid: "32432423", org: "aaaaa", space: "space1" }); + return Promise.resolve({ + guid: "32432423", + org: "aaaaa", + space: "space1", + }); } else if (funcName === "getSpaces") { return Promise.resolve([ { @@ -437,9 +457,17 @@ describe("CFTarget.vue", () => { // Wait for Vue to update the component await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423", selected: true }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + label: "aaaaa", + guid: "32432423", + selected: true, + }); await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "xxxxxxxx", label: "space1", selected: true }); + expect(wrapper.vm.selectedSpace).to.deep.equal({ + guid: "xxxxxxxx", + label: "space1", + selected: true, + }); }); it("does show error message when no Organisations are returned", async () => { @@ -479,7 +507,9 @@ describe("CFTarget.vue", () => { // Trigger watch by changing isLoggedIn prop expect(wrapper.vm.selectedOrg).to.deep.equal({}); expect(wrapper.vm.selectedSpace).to.deep.equal({}); - expect(wrapper.text()).contain("There is no Org defined for this landscape."); + expect(wrapper.text()).contain( + "There is no Org defined for this landscape." + ); }); it("does show error message when there is Organisaion but no Spaces are returned", async () => { @@ -516,10 +546,15 @@ describe("CFTarget.vue", () => { // Wait for Vue to update the component await wrapper.vm.$nextTick(); - expect(wrapper.vm.selectedOrg).to.deep.equal({ label: "aaaaa", guid: "32432423" }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + label: "aaaaa", + guid: "32432423", + }); await wrapper.vm.$nextTick(); expect(wrapper.vm.selectedSpace).to.deep.equal({}); - expect(wrapper.text()).contain("There is no Space defined for the org you selected."); + expect(wrapper.text()).contain( + "There is no Space defined for the org you selected." + ); }); it("updates selectedOrg when an organization is selected", async () => { @@ -539,10 +574,15 @@ describe("CFTarget.vue", () => { await wrapper.vm.$nextTick(); // Simulate selecting an organization - await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + await wrapper.setData({ + selectedOrg: { guid: "org-guid", label: "Org Label" }, + }); // Assert that selectedOrg is updated correctly - expect(wrapper.vm.selectedOrg).to.deep.equal({ guid: "org-guid", label: "Org Label" }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + guid: "org-guid", + label: "Org Label", + }); }); it("selects a space when an organization is selected", async () => { @@ -574,7 +614,9 @@ describe("CFTarget.vue", () => { await wrapper.vm.$nextTick(); // Simulate selecting an organization - await wrapper.setData({ selectedOrg: { guid: "org-guid", label: "Org Label" } }); + await wrapper.setData({ + selectedOrg: { guid: "org-guid", label: "Org Label" }, + }); // Wait for Vue to update the component await wrapper.vm.$nextTick(); @@ -584,10 +626,15 @@ describe("CFTarget.vue", () => { expect(spaceDropdown.exists()).to.be.true; // Simulate selecting a space - await wrapper.setData({ selectedSpace: { guid: "space-guid-1", label: "Space 1" } }); + await wrapper.setData({ + selectedSpace: { guid: "space-guid-1", label: "Space 1" }, + }); // Assert that selectedSpace is updated correctly - expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "space-guid-1", label: "Space 1" }); + expect(wrapper.vm.selectedSpace).to.deep.equal({ + guid: "space-guid-1", + label: "Space 1", + }); }); it("update selectedOrg and clears selected space when Organization is changed", async () => { @@ -631,7 +678,11 @@ describe("CFTarget.vue", () => { wrapper.vm.updateSelectedOrg({ target: { value: "bbbb" } }); - expect(wrapper.vm.selectedOrg).to.deep.equal({ guid: "47423742", label: "bbbb", selected: false }); + expect(wrapper.vm.selectedOrg).to.deep.equal({ + guid: "47423742", + label: "bbbb", + selected: false, + }); expect(wrapper.vm.selectedSpace).to.deep.equal({}); }); @@ -682,6 +733,10 @@ describe("CFTarget.vue", () => { wrapper.vm.updateSelectedSpace({ target: { value: "space2" } }); - expect(wrapper.vm.selectedSpace).to.deep.equal({ guid: "yyyyyyyy", label: "space2", orgGUID: "47423742" }); + expect(wrapper.vm.selectedSpace).to.deep.equal({ + guid: "yyyyyyyy", + label: "space2", + orgGUID: "47423742", + }); }); }); diff --git a/projects/cloud-foundry-tools/packages/frontend/vite.config.js b/projects/cloud-foundry-tools/packages/frontend/vite.config.js index d021f8772..c1ca7e97a 100644 --- a/projects/cloud-foundry-tools/packages/frontend/vite.config.js +++ b/projects/cloud-foundry-tools/packages/frontend/vite.config.js @@ -9,7 +9,8 @@ export default defineConfig({ template: { compilerOptions: { // Instruct the compiler to treat UI5 Web Components and VSCode Elements Web Components as custom elements. - isCustomElement: (tag) => tag.startsWith("ui5-") || tag.startsWith("vscode-"), + isCustomElement: (tag) => + tag.startsWith("ui5-") || tag.startsWith("vscode-"), }, }, }), @@ -17,8 +18,10 @@ export default defineConfig({ name: "html-transform", transformIndexHtml(html) { // Add id="vscode-codicon-stylesheet" to the existing link tag - return html.replace(//, (match) => - match.replace("/, + (match) => + match.replace(" Date: Mon, 21 Sep 2026 11:28:48 +0200 Subject: [PATCH 06/11] chore(cloud-foundry-tools): changeset ignore + coverage opt-out - add the 3 private packages (vscode-cf-tools, cloud-foundry-tools-frontend, vscode-wing-cf-tools) to changeset ignore; none are published to npm, so changeset must not bump them (prevents upload-vsix cascade). No fixed group needed: no internal workspace edges among them. - backend + wrapper: redirect nyc temp-dir/report-dir to ./reports so these VS Code extensions stay out of the root merged 100% gate (which targets library packages), keeping their own thresholds. Matches guided-development and yeoman-ui extension-backend precedent. coverage:copy exports lcov for coveralls; gitignore reports/. Signed-off-by: Jacob Kreyenbuehl --- .changeset/config.json | 25 +++++++++++++++---- projects/cloud-foundry-tools/.gitignore | 2 ++ .../packages/backend/nyc.config.js | 13 +++++----- .../packages/backend/package.json | 8 +++--- .../packages/wrapper/nyc.config.js | 6 +++++ .../packages/wrapper/package.json | 8 +++--- 6 files changed, 45 insertions(+), 17 deletions(-) create mode 100644 projects/cloud-foundry-tools/.gitignore diff --git a/.changeset/config.json b/.changeset/config.json index ed49f8a22..e052dbe5c 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,21 +3,33 @@ "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [ - ["vscode-deps-upgrade-tool", "vscode-using-upgrade-tool"], - ["@sap-devx/npm-dependencies-validation", "vscode-dependencies-validation"], + [ + "vscode-deps-upgrade-tool", + "vscode-using-upgrade-tool" + ], + [ + "@sap-devx/npm-dependencies-validation", + "vscode-dependencies-validation" + ], [ "app-studio-toolkit", "@sap-devx/app-studio-toolkit-types", "sample-action-client", "vscode-using-workspace-api" ], - ["yeoman-ui", "yeoman-ui-frontend"], + [ + "yeoman-ui", + "yeoman-ui-frontend" + ], [ "@vscode-logging/logger", "@vscode-logging/types", "@vscode-logging/wrapper" ], - ["guided-development", "guided-development-frontend"], + [ + "guided-development", + "guided-development-frontend" + ], [ "@sap-devx/inquirer-gui", "@sap-devx/inquirer-gui-auto-complete-plugin", @@ -59,7 +71,10 @@ "sample-navigator", "@sap-devx/inquirer-gui-date-plugin", "sample-inquirer-gui-vscode-extension", - "websocketserver" + "websocketserver", + "vscode-cf-tools", + "cloud-foundry-tools-frontend", + "vscode-wing-cf-tools" ], "privatePackages": { "version": true, diff --git a/projects/cloud-foundry-tools/.gitignore b/projects/cloud-foundry-tools/.gitignore new file mode 100644 index 000000000..68810db9c --- /dev/null +++ b/projects/cloud-foundry-tools/.gitignore @@ -0,0 +1,2 @@ +# nyc coverage reports (redirected here so extension packages stay out of the root merged gate) +**/reports diff --git a/projects/cloud-foundry-tools/packages/backend/nyc.config.js b/projects/cloud-foundry-tools/packages/backend/nyc.config.js index 7cd43022a..e9e947cff 100644 --- a/projects/cloud-foundry-tools/packages/backend/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/backend/nyc.config.js @@ -3,15 +3,16 @@ module.exports = { "check-coverage": true, all: true, extension: [".js", ".ts", ".vue"], + // Redirect coverage output away from the default .nyc_output so this VS Code + // extension is NOT pulled into the root merged 100% gate (which targets the + // library packages). It keeps its own realistic thresholds, matching the + // guided-development / yeoman-ui extension-backend precedent. + "temp-dir": "./reports/.nyc_output", + "report-dir": "./reports/coverage", branches: 90, lines: 95, functions: 91, statements: 95, include: ["**/src/**"], - exclude: [ - "**/src/run-configuration-*", - "**/src/sql-tools.ts", - "**/src/logger/logger-wrapper.ts", - "**/usage/*.ts", - ], + exclude: ["**/src/run-configuration-*", "**/src/sql-tools.ts", "**/src/logger/logger-wrapper.ts", "**/usage/*.ts"], }; diff --git a/projects/cloud-foundry-tools/packages/backend/package.json b/projects/cloud-foundry-tools/packages/backend/package.json index 8023eabcd..5c757567a 100644 --- a/projects/cloud-foundry-tools/packages/backend/package.json +++ b/projects/cloud-foundry-tools/packages/backend/package.json @@ -21,9 +21,9 @@ "scripts": { "build": "npm-run-all clean compile webpack-dev frontend:all", "bundle": "webpack --mode production", - "ci": "npm-run-all clean compile copy:test-resources coverage bundle frontend:all package", + "ci": "npm-run-all clean compile copy:test-resources coverage bundle frontend:all package coverage:copy", "ci:artifacts_only": "npm-run-all clean compile webpack-dev frontend:all package", - "clean": "npm-run-all clean:dist clean:out clean:vsix", + "clean": "npm-run-all clean:dist clean:out clean:vsix clean:reports", "clean:dist": "rimraf ./dist", "clean:frontend": "cd dist/ && shx rm -rf ./media", "clean:out": "rimraf ./out", @@ -37,7 +37,9 @@ "test": "mocha --colors", "watch": "tsc -watch -p ./", "webpack-dev": "webpack --mode development", - "webpack-dev:watch": "webpack --mode development --watch" + "webpack-dev:watch": "webpack --mode development --watch", + "clean:reports": "rimraf ./reports", + "coverage:copy": "shx mkdir -p ../../../../coverage && shx cp -u ./reports/coverage/lcov.info ../../../../coverage/lcov_cf_backend.info" }, "contributes": { "commands": [ diff --git a/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js index 7d4d99332..4925b9990 100644 --- a/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/wrapper/nyc.config.js @@ -3,6 +3,12 @@ module.exports = { "check-coverage": true, all: true, extension: [".js", ".ts", ".vue"], + // Redirect coverage output away from the default .nyc_output so this VS Code + // extension is NOT pulled into the root merged 100% gate (which targets the + // library packages). It keeps its own thresholds, matching the + // guided-development / yeoman-ui extension-backend precedent. + "temp-dir": "./reports/.nyc_output", + "report-dir": "./reports/coverage", branches: 100, lines: 100, functions: 100, diff --git a/projects/cloud-foundry-tools/packages/wrapper/package.json b/projects/cloud-foundry-tools/packages/wrapper/package.json index 8a8c53233..e65d1af14 100644 --- a/projects/cloud-foundry-tools/packages/wrapper/package.json +++ b/projects/cloud-foundry-tools/packages/wrapper/package.json @@ -21,9 +21,9 @@ "scripts": { "build": "npm-run-all clean compile webpack-dev", "bundle": "webpack --mode production", - "ci": "npm-run-all clean compile coverage bundle package", + "ci": "npm-run-all clean compile coverage bundle package coverage:copy", "ci:artifacts_only": "npm-run-all clean compile webpack-dev package", - "clean": "npm-run-all clean:dist clean:out clean:vsix", + "clean": "npm-run-all clean:dist clean:out clean:vsix clean:reports", "clean:dist": "rimraf ./dist", "clean:out": "rimraf ./out", "clean:vsix": "rimraf ./*.vsix", @@ -33,7 +33,9 @@ "test": "mocha --colors", "watch": "tsc -watch -p ./", "webpack-dev": "webpack --mode development", - "webpack-dev:watch": "webpack --mode development --watch" + "webpack-dev:watch": "webpack --mode development --watch", + "clean:reports": "rimraf ./reports", + "coverage:copy": "shx mkdir -p ../../../../coverage && shx cp -u ./reports/coverage/lcov.info ../../../../coverage/lcov_cf_wrapper.info" }, "activationEvents": [ "onStartupFinished" From 061cc0eb02ebddf0e1f23e2949321c4c54dd34a6 Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:29:25 +0200 Subject: [PATCH 07/11] ci(cloud-foundry-tools): collect backend + wrapper vsix build artifacts Signed-off-by: Jacob Kreyenbuehl --- .github/workflows/ci.yml | 2 ++ .../cloud-foundry-tools/packages/backend/nyc.config.js | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7b5eeeda..bcc64c8e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,8 @@ jobs: projects/task-explorer/packages/tasks_panel/*.vsix projects/task-explorer/examples/npm_task_contrib/*.vsix projects/task-explorer/examples/vscode_task_contrib/*.vsix + projects/cloud-foundry-tools/packages/backend/*.vsix + projects/cloud-foundry-tools/packages/wrapper/*.vsix # default is 90 days; however, we want to avoid excessive storage usage... retention-days: ${{ env.artifact-retention-days }} diff --git a/projects/cloud-foundry-tools/packages/backend/nyc.config.js b/projects/cloud-foundry-tools/packages/backend/nyc.config.js index e9e947cff..5c498b868 100644 --- a/projects/cloud-foundry-tools/packages/backend/nyc.config.js +++ b/projects/cloud-foundry-tools/packages/backend/nyc.config.js @@ -14,5 +14,10 @@ module.exports = { functions: 91, statements: 95, include: ["**/src/**"], - exclude: ["**/src/run-configuration-*", "**/src/sql-tools.ts", "**/src/logger/logger-wrapper.ts", "**/usage/*.ts"], + exclude: [ + "**/src/run-configuration-*", + "**/src/sql-tools.ts", + "**/src/logger/logger-wrapper.ts", + "**/usage/*.ts", + ], }; From 9892dd63f2617d4c0c8b4d4d8160d0c2d1cf3fb4 Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 11:36:52 +0200 Subject: [PATCH 08/11] chore(cloud-foundry-tools): reformat changeset config with prettier Signed-off-by: Jacob Kreyenbuehl --- .changeset/config.json | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index e052dbe5c..6770d96bd 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,33 +3,21 @@ "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [ - [ - "vscode-deps-upgrade-tool", - "vscode-using-upgrade-tool" - ], - [ - "@sap-devx/npm-dependencies-validation", - "vscode-dependencies-validation" - ], + ["vscode-deps-upgrade-tool", "vscode-using-upgrade-tool"], + ["@sap-devx/npm-dependencies-validation", "vscode-dependencies-validation"], [ "app-studio-toolkit", "@sap-devx/app-studio-toolkit-types", "sample-action-client", "vscode-using-workspace-api" ], - [ - "yeoman-ui", - "yeoman-ui-frontend" - ], + ["yeoman-ui", "yeoman-ui-frontend"], [ "@vscode-logging/logger", "@vscode-logging/types", "@vscode-logging/wrapper" ], - [ - "guided-development", - "guided-development-frontend" - ], + ["guided-development", "guided-development-frontend"], [ "@sap-devx/inquirer-gui", "@sap-devx/inquirer-gui-auto-complete-plugin", From 2e941808b7d0ce67b37aa25f829ca34fb165b19b Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 12:00:55 +0200 Subject: [PATCH 09/11] fix(cloud-foundry-tools): align frontend vue to caret range; gitignore out/ The exact 'vue: 3.3.8' pin forced a second Vue tree into the workspace, which polluted the shared @vue/test-utils resolution and broke inquirer-gui's tests (app.onUnmount is a Vue 3.5 API). Widen to '^3.3.4' so the frontend joins the existing vue@3.5.34 tree (no second tree); its 43 tests still pass. Other Vue toolchain pins left at source versions. Also gitignore the backend/wrapper out/ compile output at the project level. Signed-off-by: Jacob Kreyenbuehl --- pnpm-lock.yaml | 173 ++++++------------ projects/cloud-foundry-tools/.gitignore | 4 +- .../packages/frontend/package.json | 2 +- 3 files changed, 64 insertions(+), 115 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39c03beef..87fd7154c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -676,14 +676,14 @@ importers: specifier: 4.17.21 version: 4.17.21 vue: - specifier: 3.3.8 - version: 3.3.8(typescript@5.7.3) + specifier: ^3.3.4 + version: 3.4.3(typescript@5.7.3) vue-loader: specifier: 17.2.2 - version: 17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.3.8(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) + version: 17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) vue-loading-overlay: specifier: 6.0.3 - version: 6.0.3(vue@3.3.8(typescript@5.7.3)) + version: 6.0.3(vue@3.4.3(typescript@5.7.3)) devDependencies: '@babel/preset-env': specifier: ^7.22.20 @@ -696,16 +696,16 @@ importers: version: 6.10.0(@testing-library/dom@9.3.4) '@vitejs/plugin-vue': specifier: ^5.2.0 - version: 5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.3.8(typescript@5.7.3)) + version: 5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3)) '@vue/compiler-sfc': specifier: 3.3.8 version: 3.3.8 '@vue/test-utils': specifier: 2.4.1 - version: 2.4.1(@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3)))(vue@3.3.8(typescript@5.7.3)) + version: 2.4.1(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3)) '@vue/vue3-jest': specifier: 29.2.6 - version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.3.8(typescript@5.7.3)) + version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3)) eslint-plugin-vue: specifier: 9.18.1 version: 9.18.1(eslint@8.57.1) @@ -3149,6 +3149,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-jsx@7.29.7': resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} engines: {node: '>=6.9.0'} @@ -6747,38 +6753,24 @@ packages: '@vue/reactivity-transform@3.3.8': resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==} - '@vue/reactivity@3.3.8': - resolution: {integrity: sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==} - '@vue/reactivity@3.4.3': resolution: {integrity: sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==} '@vue/reactivity@3.5.34': resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} - '@vue/runtime-core@3.3.8': - resolution: {integrity: sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==} - '@vue/runtime-core@3.4.3': resolution: {integrity: sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==} '@vue/runtime-core@3.5.34': resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} - '@vue/runtime-dom@3.3.8': - resolution: {integrity: sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==} - '@vue/runtime-dom@3.4.3': resolution: {integrity: sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==} '@vue/runtime-dom@3.5.34': resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} - '@vue/server-renderer@3.3.8': - resolution: {integrity: sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==} - peerDependencies: - vue: 3.3.8 - '@vue/server-renderer@3.4.3': resolution: {integrity: sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==} peerDependencies: @@ -16526,14 +16518,6 @@ packages: vue-template-es2015-compiler@1.9.1: resolution: {integrity: sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==} - vue@3.3.8: - resolution: {integrity: sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - vue@3.4.3: resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==} peerDependencies: @@ -17447,7 +17431,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-annotate-as-pure@7.29.7': dependencies: @@ -17455,7 +17439,7 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.3 + '@babel/compat-data': 7.29.7 '@babel/helper-validator-option': 7.29.7 browserslist: 4.28.2 lru-cache: 5.1.1 @@ -17477,7 +17461,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -17522,7 +17506,7 @@ snapshots: '@babel/helper-environment-visitor@7.24.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-globals@7.28.0': {} @@ -17580,7 +17564,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color @@ -17598,14 +17582,14 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -17774,12 +17758,12 @@ snapshots: '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.29.3 + '@babel/compat-data': 7.29.7 '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.0) '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.29.0)': dependencies: @@ -17869,6 +17853,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -18007,7 +17996,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-globals': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) @@ -18130,7 +18119,7 @@ snapshots: '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/traverse': 7.29.7 transitivePeerDependencies: @@ -18647,7 +18636,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.0) '@babel/types': 7.29.7 esutils: 2.0.3 @@ -21426,16 +21415,16 @@ snapshots: '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.29.8 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/body-parser@1.19.5': dependencies: @@ -22082,10 +22071,10 @@ snapshots: vite: 5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0) vue: 3.4.3(typescript@5.7.3) - '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.3.8(typescript@5.7.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))': dependencies: vite: 6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) - vue: 3.3.8(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': dependencies: @@ -22283,12 +22272,12 @@ snapshots: '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.0)': dependencies: - '@babel/helper-module-imports': 7.28.6 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@vue/babel-helper-vue-transform-on': 1.5.0 '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0) '@vue/shared': 3.5.34 @@ -22312,7 +22301,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-helper-vue-jsx-merge-props': 1.4.0 html-tags: 2.0.0 lodash.kebabcase: 4.1.1 @@ -22323,12 +22312,12 @@ snapshots: '@vue/babel-preset-app@5.0.9(@babel/core@7.29.0)(core-js@3.49.0)(vue@3.5.34(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-imports': 7.28.6 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.0) '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.0) '@babel/preset-env': 7.29.7(@babel/core@7.29.0) '@babel/runtime': 7.26.0 @@ -22362,27 +22351,27 @@ snapshots: '@vue/babel-sugar-composition-api-inject-h@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-sugar-composition-api-render-instance@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-sugar-functional-vue@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-sugar-inject-h@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-sugar-v-model@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-helper-vue-jsx-merge-props': 1.4.0 '@vue/babel-plugin-transform-vue-jsx': 1.4.0(@babel/core@7.29.0) camelcase: 5.3.1 @@ -22394,7 +22383,7 @@ snapshots: '@vue/babel-sugar-v-on@1.4.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@vue/babel-plugin-transform-vue-jsx': 1.4.0(@babel/core@7.29.0) camelcase: 5.3.1 transitivePeerDependencies: @@ -22996,10 +22985,6 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.21 - '@vue/reactivity@3.3.8': - dependencies: - '@vue/shared': 3.3.8 - '@vue/reactivity@3.4.3': dependencies: '@vue/shared': 3.4.3 @@ -23008,11 +22993,6 @@ snapshots: dependencies: '@vue/shared': 3.5.34 - '@vue/runtime-core@3.3.8': - dependencies: - '@vue/reactivity': 3.3.8 - '@vue/shared': 3.3.8 - '@vue/runtime-core@3.4.3': dependencies: '@vue/reactivity': 3.4.3 @@ -23023,12 +23003,6 @@ snapshots: '@vue/reactivity': 3.5.34 '@vue/shared': 3.5.34 - '@vue/runtime-dom@3.3.8': - dependencies: - '@vue/runtime-core': 3.3.8 - '@vue/shared': 3.3.8 - csstype: 3.2.3 - '@vue/runtime-dom@3.4.3': dependencies: '@vue/runtime-core': 3.4.3 @@ -23042,25 +23016,12 @@ snapshots: '@vue/shared': 3.5.34 csstype: 3.2.3 - '@vue/server-renderer@3.3.8(vue@3.3.8(typescript@5.7.3))': - dependencies: - '@vue/compiler-ssr': 3.3.8 - '@vue/shared': 3.3.8 - vue: 3.3.8(typescript@5.7.3) - '@vue/server-renderer@3.4.3(vue@3.4.3(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.4.3 '@vue/shared': 3.4.3 vue: 3.4.3(typescript@5.7.3) - '@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3))': - dependencies: - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - vue: 3.3.8(typescript@5.7.3) - optional: true - '@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.5.34 @@ -23082,13 +23043,13 @@ snapshots: '@vue/shared@3.5.34': {} - '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.3.8(typescript@5.7.3)))(vue@3.3.8(typescript@5.7.3))': + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3))': dependencies: js-beautify: 1.14.9 - vue: 3.3.8(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) vue-component-type-helpers: 1.8.4 optionalDependencies: - '@vue/server-renderer': 3.5.34(vue@3.3.8(typescript@5.7.3)) + '@vue/server-renderer': 3.5.34(vue@3.4.3(typescript@5.7.3)) '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3))': dependencies: @@ -23129,7 +23090,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.3.8(typescript@5.7.3))': + '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) @@ -23140,7 +23101,7 @@ snapshots: jest: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) source-map: 0.5.6 tsconfig: 7.0.0 - vue: 3.3.8(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -23903,7 +23864,7 @@ snapshots: babel-plugin-jest-hoist@27.5.1: dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 @@ -24832,7 +24793,7 @@ snapshots: comment-json@4.1.0: dependencies: array-timsort: 1.0.3 - core-util-is: 1.0.3 + core-util-is: 1.0.2 esprima: 4.0.1 has-own-prop: 2.0.0 repeat-string: 1.6.1 @@ -25137,7 +25098,7 @@ snapshots: core-js-compat@3.49.0: dependencies: - browserslist: 4.28.2 + browserslist: 4.28.8 core-js@2.6.12: {} @@ -34874,7 +34835,7 @@ snapshots: - walrus - whiskers - vue-loader@17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.3.8(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): + vue-loader@17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 @@ -34882,7 +34843,7 @@ snapshots: webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) optionalDependencies: '@vue/compiler-sfc': 3.3.8 - vue: 3.3.8(typescript@5.7.3) + vue: 3.4.3(typescript@5.7.3) vue-loader@17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: @@ -34904,10 +34865,6 @@ snapshots: '@vue/compiler-sfc': 3.5.34 vue: 3.5.34(typescript@5.7.3) - vue-loading-overlay@6.0.3(vue@3.3.8(typescript@5.7.3)): - dependencies: - vue: 3.3.8(typescript@5.7.3) - vue-loading-overlay@6.0.3(vue@3.4.3(typescript@5.7.3)): dependencies: vue: 3.4.3(typescript@5.7.3) @@ -34928,16 +34885,6 @@ snapshots: vue-template-es2015-compiler@1.9.1: {} - vue@3.3.8(typescript@5.7.3): - dependencies: - '@vue/compiler-dom': 3.3.8 - '@vue/compiler-sfc': 3.3.8 - '@vue/runtime-dom': 3.3.8 - '@vue/server-renderer': 3.3.8(vue@3.3.8(typescript@5.7.3)) - '@vue/shared': 3.3.8 - optionalDependencies: - typescript: 5.7.3 - vue@3.4.3(typescript@5.7.3): dependencies: '@vue/compiler-dom': 3.4.3 diff --git a/projects/cloud-foundry-tools/.gitignore b/projects/cloud-foundry-tools/.gitignore index 68810db9c..b7fb87f98 100644 --- a/projects/cloud-foundry-tools/.gitignore +++ b/projects/cloud-foundry-tools/.gitignore @@ -1,2 +1,4 @@ -# nyc coverage reports (redirected here so extension packages stay out of the root merged gate) +# Build + coverage output (redirected here so extension packages stay out of the root merged gate) **/reports +# Compiled TypeScript output (backend/wrapper compile to out/ before webpack bundles to dist/) +**/out diff --git a/projects/cloud-foundry-tools/packages/frontend/package.json b/projects/cloud-foundry-tools/packages/frontend/package.json index b7b3627d5..2c1fc68c5 100644 --- a/projects/cloud-foundry-tools/packages/frontend/package.json +++ b/projects/cloud-foundry-tools/packages/frontend/package.json @@ -42,7 +42,7 @@ "@sap-devx/webview-rpc": "0.4.1", "@vscode-elements/elements": "1.16.1", "lodash": "4.17.21", - "vue": "3.3.8", + "vue": "^3.3.4", "vue-loader": "17.2.2", "vue-loading-overlay": "6.0.3" }, From 302237406c60744fac13cfc1ba5ec505ee885a7a Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Mon, 21 Sep 2026 12:22:26 +0200 Subject: [PATCH 10/11] fix(cloud-foundry-tools): pin workspace vue to 3.5.34 to prevent dual-instance Adding cloud-foundry-tools triggers a full pnpm re-resolution. With task-explorer's vue_frontend_rpc pinning vue@3.4.3, pnpm 11.1.1 then binds the published @sap-devx/inquirer-gui@3.x transitive vuetify to vue@3.4.3 instead of vue@3.5.34. vuetify@3.12.6 calls app.onUnmount (a Vue 3.5 API) in its install(), so the inquirer-gui plugin tests fail at runtime with 'app.onUnmount is not a function'. Pin vue to 3.5.34 workspace-wide (overrides). 3.5.34 was already the version every Vue consumer resolved to on main except task-explorer's rpc; 3.5.34 is backward compatible with its 3.4.3 pin (verified: vue_frontend_rpc ci + inquirer-gui plugin tests + task-explorer all pass). Eliminates vue@3.4.3 from the tree, so no dual Vue instance. Restores the auto-complete-plugin test to green. Signed-off-by: Jacob Kreyenbuehl --- pnpm-lock.yaml | 535 ++++++++++++++++++++------------------------ pnpm-workspace.yaml | 1 + 2 files changed, 241 insertions(+), 295 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87fd7154c..25831f0b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,7 @@ overrides: mem-fs-editor: 12.0.4 yeoman-environment@3>mem-fs: 2.3.0 yeoman-environment@3>mem-fs-editor: 9.7.0 + vue: 3.5.34 importers: @@ -676,14 +677,14 @@ importers: specifier: 4.17.21 version: 4.17.21 vue: - specifier: ^3.3.4 - version: 3.4.3(typescript@5.7.3) + specifier: 3.5.34 + version: 3.5.34(typescript@5.7.3) vue-loader: specifier: 17.2.2 - version: 17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) + version: 17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.5.34(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) vue-loading-overlay: specifier: 6.0.3 - version: 6.0.3(vue@3.4.3(typescript@5.7.3)) + version: 6.0.3(vue@3.5.34(typescript@5.7.3)) devDependencies: '@babel/preset-env': specifier: ^7.22.20 @@ -696,16 +697,16 @@ importers: version: 6.10.0(@testing-library/dom@9.3.4) '@vitejs/plugin-vue': specifier: ^5.2.0 - version: 5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3)) + version: 5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3)) '@vue/compiler-sfc': specifier: 3.3.8 version: 3.3.8 '@vue/test-utils': specifier: 2.4.1 - version: 2.4.1(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3)) + version: 2.4.1(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3)) '@vue/vue3-jest': specifier: 29.2.6 - version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3)) + version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.5.34(typescript@5.7.3)) eslint-plugin-vue: specifier: 9.18.1 version: 9.18.1(eslint@8.57.1) @@ -1067,7 +1068,7 @@ importers: version: 5.9.55 '@sap-devx/inquirer-gui': specifier: 3.0.0 - version: 3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(webpack-plugin-vuetify@2.0.1) + version: 3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) '@sap-devx/inquirer-gui-file-browser-plugin': specifier: 3.0.0 version: 3.0.0(typescript@5.7.3) @@ -1087,14 +1088,14 @@ importers: specifier: ^5.0.1 version: 5.0.1 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vue-loading-overlay: specifier: ^6.0.3 version: 6.0.6(vue@3.5.34(typescript@5.7.3)) vuetify: specifier: ^3.3.10 - version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) devDependencies: '@babel/preset-env': specifier: ^7.9.5 @@ -1197,7 +1198,7 @@ importers: specifier: ^5.0.1 version: 5.0.1 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.3.10 @@ -1234,7 +1235,7 @@ importers: specifier: ^5.0.1 version: 5.0.1 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.3.10 @@ -1265,7 +1266,7 @@ importers: specifier: '*' version: 0.10.0 vue: - specifier: ^3.3.8 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.3.10 @@ -1299,7 +1300,7 @@ importers: projects/inquirer-gui/examples/sample-plugin: dependencies: vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) devDependencies: '@vitejs/plugin-vue': @@ -1361,7 +1362,7 @@ importers: specifier: ^3.2.0 version: 3.5.2(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0)) vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1413,7 +1414,7 @@ importers: specifier: ^6.0.0 version: 6.0.0 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.4.0 @@ -1460,9 +1461,9 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1509,9 +1510,9 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1566,7 +1567,7 @@ importers: specifier: ^6.0.0 version: 6.0.0 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1636,7 +1637,7 @@ importers: projects/inquirer-gui/packages/label-plugin: dependencies: vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) devDependencies: '@babel/preset-env': @@ -1656,9 +1657,9 @@ importers: dependencies: '@sap-devx/inquirer-gui': specifier: ^3.4.13 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1704,7 +1705,7 @@ importers: projects/inquirer-gui/packages/radio-button-plugin: dependencies: vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1750,7 +1751,7 @@ importers: projects/inquirer-gui/packages/single-checkbox-plugin: dependencies: vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vuetify: specifier: ^3.5.7 @@ -1796,7 +1797,7 @@ importers: projects/inquirer-gui/packages/tiles-plugin: dependencies: vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) devDependencies: '@babel/preset-env': @@ -2009,17 +2010,17 @@ importers: specifier: 5.0.1 version: 5.0.1 vue: - specifier: 3.4.3 - version: 3.4.3(typescript@5.7.3) + specifier: 3.5.34 + version: 3.5.34(typescript@5.7.3) vue-loader: specifier: 17.4.2 - version: 17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) + version: 17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.5.34(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) vue-loading-overlay: specifier: 6.0.3 - version: 6.0.3(vue@3.4.3(typescript@5.7.3)) + version: 6.0.3(vue@3.5.34(typescript@5.7.3)) vuetify: specifier: 3.4.9 - version: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)) + version: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)) devDependencies: '@babel/core': specifier: ^7.23.0 @@ -2044,19 +2045,19 @@ importers: version: 6.1.6(@jest/globals@29.7.0)(@types/jest@29.5.14)(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3))) '@testing-library/vue': specifier: 8.0.1 - version: 8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3)) + version: 8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.5.34(typescript@5.7.3)) '@vitejs/plugin-vue': specifier: 5.0.2 - version: 5.0.2(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3)) + version: 5.0.2(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3)) '@vue/compiler-sfc': specifier: 3.4.3 version: 3.4.3 '@vue/test-utils': specifier: 2.4.3 - version: 2.4.3(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3)) + version: 2.4.3(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3)) '@vue/vue3-jest': specifier: 29.2.6 - version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3)) + version: 29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.5.34(typescript@5.7.3)) babel-jest: specifier: ^29.7.0 version: 29.7.0(@babel/core@7.29.0) @@ -2095,7 +2096,7 @@ importers: version: 5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0) vite-plugin-vuetify: specifier: 2.0.1 - version: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9) + version: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9) projects/vscode-logging/examples/extension: dependencies: @@ -2517,22 +2518,22 @@ importers: version: 5.9.55 '@sap-devx/inquirer-gui': specifier: 3.4.14 - version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) + version: 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) '@sap-devx/inquirer-gui-auto-complete-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3)(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0)) '@sap-devx/inquirer-gui-file-browser-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) '@sap-devx/inquirer-gui-folder-browser-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) '@sap-devx/inquirer-gui-label-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3) '@sap-devx/inquirer-gui-login-plugin': specifier: 3.4.14 - version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3) + version: 3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3) '@sap-devx/inquirer-gui-radio-plugin': specifier: 3.4.14 version: 3.4.14(typescript@5.7.3) @@ -2555,14 +2556,14 @@ importers: specifier: 6.1.0 version: 6.1.0 vue: - specifier: ^3.3.4 + specifier: 3.5.34 version: 3.5.34(typescript@5.7.3) vue-loading-overlay: specifier: ^6.0.0 version: 6.0.6(vue@3.5.34(typescript@5.7.3)) vuetify: specifier: ^3.5.7 - version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3)) + version: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) devDependencies: '@babel/core': specifier: ^7.22.0 @@ -2916,6 +2917,10 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} @@ -5853,7 +5858,7 @@ packages: engines: {node: '>=14'} peerDependencies: '@vue/compiler-sfc': '>= 3' - vue: '>= 3' + vue: 3.5.34 '@textlint/ast-node-types@14.8.4': resolution: {integrity: sha512-+fI7miec/r9VeniFV9ppL4jRCmHNsTxieulTUf/4tvGII3db5hGriKHC4p/diq1SkQ9Sgs7kg6UyydxZtpTz1Q==} @@ -6435,21 +6440,21 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.0.0 || ^5.0.0 - vue: ^3.2.25 + vue: 3.5.34 '@vitejs/plugin-vue@5.0.2': resolution: {integrity: sha512-kEjJHrLb5ePBvjD0SPZwJlw1QTRcjjCA9sB5VyfonoXVBxTS7TMnqL6EkLt1Eu61RDeiuZ/WN9Hf6PxXhPI2uA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 - vue: ^3.2.25 + vue: 3.5.34 '@vitejs/plugin-vue@5.2.4': resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 - vue: ^3.2.25 + vue: 3.5.34 '@vscode-elements/elements@1.11.0': resolution: {integrity: sha512-vC1QDaelqERypHinavJbe4Bl4/g66CVJWKFWFXZyp1seBvTxkgscayDTiE//H95V8b75BgLPDAS1y3pbSIKXag==} @@ -6576,7 +6581,7 @@ packages: peerDependencies: '@babel/core': '*' core-js: ^3 - vue: ^2 || ^3.2.13 + vue: 3.5.34 peerDependenciesMeta: core-js: optional: true @@ -6587,7 +6592,7 @@ packages: resolution: {integrity: sha512-QmfRpssBOPZWL5xw7fOuHNifCQcNQC1PrOo/4fu6xlhlKJJKSA3HqX92Nvgyx8fqHZTUGMPHmFA+IDqwXlqkSA==} peerDependencies: '@babel/core': ^7.0.0-0 - vue: '*' + vue: 3.5.34 peerDependenciesMeta: vue: optional: true @@ -6753,29 +6758,15 @@ packages: '@vue/reactivity-transform@3.3.8': resolution: {integrity: sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==} - '@vue/reactivity@3.4.3': - resolution: {integrity: sha512-q5f9HLDU+5aBKizXHAx0w4whkIANs1Muiq9R5YXm0HtorSlflqv9u/ohaMxuuhHWCji4xqpQ1eL04WvmAmGnFg==} - '@vue/reactivity@3.5.34': resolution: {integrity: sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==} - '@vue/runtime-core@3.4.3': - resolution: {integrity: sha512-C1r6QhB1qY7D591RCSFhMULyzL9CuyrGc+3PpB0h7dU4Qqw6GNyo4BNFjHZVvsWncrUlKX3DIKg0Y7rNNr06NQ==} - '@vue/runtime-core@3.5.34': resolution: {integrity: sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==} - '@vue/runtime-dom@3.4.3': - resolution: {integrity: sha512-wrsprg7An5Ec+EhPngWdPuzkp0BEUxAKaQtN9dPU/iZctPyD9aaXmVtehPJerdQxQale6gEnhpnfywNw3zOv2A==} - '@vue/runtime-dom@3.5.34': resolution: {integrity: sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==} - '@vue/server-renderer@3.4.3': - resolution: {integrity: sha512-BUxt8oVGMKKsqSkM1uU3d3Houyfy4WAc2SpSQRebNd+XJGATVkW/rO129jkyL+kpB/2VRKzE63zwf5RtJ3XuZw==} - peerDependencies: - vue: 3.4.3 - '@vue/server-renderer@3.5.34': resolution: {integrity: sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==} peerDependencies: @@ -6797,7 +6788,7 @@ packages: resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==} peerDependencies: '@vue/server-renderer': ^3.0.1 - vue: ^3.0.1 + vue: 3.5.34 peerDependenciesMeta: '@vue/server-renderer': optional: true @@ -6806,7 +6797,7 @@ packages: resolution: {integrity: sha512-F4K7mF+ad++VlTrxMJVRnenKSJmO6fkQt2wpRDiKDesQMkfpniGWsqEi/JevxGBo2qEkwwjvTUAoiGJLNx++CA==} peerDependencies: '@vue/server-renderer': ^3.0.1 - vue: ^3.0.1 + vue: 3.5.34 peerDependenciesMeta: '@vue/server-renderer': optional: true @@ -6822,7 +6813,7 @@ packages: jest: 27.x ts-jest: 27.x typescript: '>= 3.x' - vue: ^3.0.0-0 + vue: 3.5.34 peerDependenciesMeta: ts-jest: optional: true @@ -6837,7 +6828,7 @@ packages: babel-jest: 29.x jest: 29.x typescript: '>= 4.3' - vue: ^3.0.0-0 + vue: 3.5.34 peerDependenciesMeta: typescript: optional: true @@ -6848,13 +6839,13 @@ packages: '@vuetify/loader-shared@1.7.1': resolution: {integrity: sha512-kLUvuAed6RCvkeeTNJzuy14pqnkur8lTuner7v7pNE/kVhPR97TuyXwBSBMR1cJeiLiOfu6SF5XlCYbXByEx1g==} peerDependencies: - vue: ^3.0.0 + vue: 3.5.34 vuetify: ^3.0.0-beta.4 '@vuetify/loader-shared@2.1.2': resolution: {integrity: sha512-X+1jBLmXHkpQEnC0vyOb4rtX2QSkBiFhaFXz8yhQqN2A4vQ6k2nChxN4Ol7VAY5KoqMdFoRMnmNdp/1qYXDQig==} peerDependencies: - vue: ^3.0.0 + vue: 3.5.34 vuetify: '>=3' '@webassemblyjs/ast@1.14.1': @@ -16256,7 +16247,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: '>=5' - vue: ^3.0.0 + vue: 3.5.34 vuetify: ^3.0.0 vite-plugin-vuetify@2.1.3: @@ -16264,7 +16255,7 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: '>=5' - vue: ^3.0.0 + vue: 3.5.34 vuetify: '>=3' vite@4.5.11: @@ -16501,13 +16492,13 @@ packages: resolution: {integrity: sha512-Ab0hqnVKasVS2mNUo8Wkm95mSbk5fXp/karZmMou5yP0hTUOox/kZuBLNwgzJ4kFP829wFJl7O7FN1Dr0kRaVQ==} engines: {node: '>=12.13.0'} peerDependencies: - vue: ^3.2.0 + vue: 3.5.34 vue-loading-overlay@6.0.6: resolution: {integrity: sha512-ZPrWawjCoNKGbCG9z4nePgbs/K9KXPa1j1oAJXP6T8FQho3NO+/chhjx4MLYFzfpwr+xkiQ8SNrV1kUG1bZPAw==} engines: {node: '>=12.13.0'} peerDependencies: - vue: ^3.2.0 + vue: 3.5.34 vue-style-loader@4.1.3: resolution: {integrity: sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==} @@ -16518,14 +16509,6 @@ packages: vue-template-es2015-compiler@1.9.1: resolution: {integrity: sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==} - vue@3.4.3: - resolution: {integrity: sha512-GjN+culMAGv/mUbkIv8zMKItno8npcj5gWlXkSxf1SPTQf8eJ4A+YfHIvQFyL1IfuJcMl3soA7SmN1fRxbf/wA==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - vue@3.5.34: resolution: {integrity: sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==} peerDependencies: @@ -16539,7 +16522,7 @@ packages: peerDependencies: typescript: '>=4.7' vite-plugin-vuetify: '>=2.1.0' - vue: ^3.5.0 + vue: 3.5.34 webpack-plugin-vuetify: '>=3.1.0' peerDependenciesMeta: typescript: @@ -16555,7 +16538,7 @@ packages: peerDependencies: typescript: '>=4.7' vite-plugin-vuetify: '>=1.0.0-alpha.12' - vue: ^3.3.0 + vue: 3.5.34 vue-i18n: ^9.0.0 webpack-plugin-vuetify: '>=2.0.0-alpha.11' peerDependenciesMeta: @@ -16712,7 +16695,7 @@ packages: engines: {node: '>=12'} peerDependencies: '@vue/compiler-sfc': ^3.2.6 - vue: ^3.2.6 + vue: 3.5.34 vuetify: ^3.0.0-beta.4 webpack: ^5.0.0 @@ -17439,8 +17422,8 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 + '@babel/compat-data': 7.29.3 + '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 @@ -17461,7 +17444,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -17564,7 +17547,7 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -17582,13 +17565,13 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -17608,6 +17591,8 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-option@7.29.7': {} '@babel/helper-wrap-function@7.28.6': @@ -17697,7 +17682,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) transitivePeerDependencies: @@ -17707,7 +17692,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -17723,58 +17708,58 @@ snapshots: '@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.29.7 + '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) transitivePeerDependencies: @@ -17784,7 +17769,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -17796,12 +17781,12 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: @@ -17811,7 +17796,7 @@ snapshots: '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: @@ -17831,7 +17816,7 @@ snapshots: '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17851,7 +17836,7 @@ snapshots: '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: @@ -17866,32 +17851,32 @@ snapshots: '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: @@ -17901,7 +17886,7 @@ snapshots: '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: @@ -17922,7 +17907,7 @@ snapshots: '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17942,7 +17927,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -17959,7 +17944,7 @@ snapshots: '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17969,7 +17954,7 @@ snapshots: '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.0)': dependencies: @@ -17996,9 +17981,9 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) '@babel/traverse': 7.29.7 transitivePeerDependencies: @@ -18019,7 +18004,7 @@ snapshots: '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.29.7 '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.0)': @@ -18031,7 +18016,7 @@ snapshots: '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -18048,7 +18033,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18059,7 +18044,7 @@ snapshots: '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18088,7 +18073,7 @@ snapshots: '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18103,7 +18088,7 @@ snapshots: '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color @@ -18119,8 +18104,8 @@ snapshots: '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color @@ -18142,7 +18127,7 @@ snapshots: '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18157,7 +18142,7 @@ snapshots: '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18168,7 +18153,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -18184,7 +18169,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -18200,7 +18185,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.29.7 '@babel/traverse': 7.29.7 transitivePeerDependencies: @@ -18220,7 +18205,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -18236,7 +18221,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18247,7 +18232,7 @@ snapshots: '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18278,7 +18263,7 @@ snapshots: '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -18307,7 +18292,7 @@ snapshots: '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18334,7 +18319,7 @@ snapshots: '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18344,7 +18329,7 @@ snapshots: '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-regenerator@7.29.8(@babel/core@7.29.0)': dependencies: @@ -18360,7 +18345,7 @@ snapshots: '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18382,7 +18367,7 @@ snapshots: '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18392,7 +18377,7 @@ snapshots: '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color @@ -18408,7 +18393,7 @@ snapshots: '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18418,7 +18403,7 @@ snapshots: '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18428,7 +18413,7 @@ snapshots: '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18449,7 +18434,7 @@ snapshots: '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18466,7 +18451,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.0)': dependencies: @@ -18634,9 +18619,9 @@ snapshots: '@babel/preset-modules@0.1.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) '@babel/types': 7.29.7 esutils: 2.0.3 @@ -20765,98 +20750,98 @@ snapshots: dependencies: strip-ansi: 6.0.0 vite-plugin-css-injected-by-js: 3.5.2(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0)) - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript - vite '@sap-devx/inquirer-gui-file-browser-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-file-browser-plugin@3.0.1(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-file-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-file-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) - vue: 3.4.3(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-folder-browser-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-folder-browser-plugin@3.0.1(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-folder-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-folder-browser-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) - vue: 3.4.3(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-label-plugin@3.0.2(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-label-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-login-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui-login-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3))(typescript@5.7.3)': + '@sap-devx/inquirer-gui-login-plugin@3.4.14(@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)))(typescript@5.7.3)': dependencies: - '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3) - vue: 3.4.3(typescript@5.7.3) + '@sap-devx/inquirer-gui': 3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-radio-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-tiles-plugin@3.0.0(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript '@sap-devx/inquirer-gui-tiles-plugin@3.4.14(typescript@5.7.3)': dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) transitivePeerDependencies: - typescript - '@sap-devx/inquirer-gui@3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(webpack-plugin-vuetify@2.0.1)': + '@sap-devx/inquirer-gui@3.0.0(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))))': dependencies: lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.4.3(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) transitivePeerDependencies: - typescript - vite-plugin-vuetify @@ -20866,20 +20851,32 @@ snapshots: dependencies: lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.4.3(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)) transitivePeerDependencies: - typescript - vite-plugin-vuetify - webpack-plugin-vuetify - '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)': + '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))': dependencies: '@vscode-elements/elements': 1.11.0 lodash: 4.18.1 strip-ansi: 6.0.0 - vue: 3.4.3(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3)) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) + transitivePeerDependencies: + - typescript + - vite-plugin-vuetify + - webpack-plugin-vuetify + + '@sap-devx/inquirer-gui@3.4.14(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))': + dependencies: + '@vscode-elements/elements': 1.11.0 + lodash: 4.18.1 + strip-ansi: 6.0.0 + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) transitivePeerDependencies: - typescript - vite-plugin-vuetify @@ -21338,13 +21335,13 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 - '@testing-library/vue@8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))': + '@testing-library/vue@8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.5.34(typescript@5.7.3))': dependencies: '@babel/runtime': 7.26.0 '@testing-library/dom': 9.3.4 '@vue/compiler-sfc': 3.4.3 '@vue/test-utils': 2.4.6 - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) '@textlint/ast-node-types@14.8.4': {} @@ -22066,15 +22063,15 @@ snapshots: vite: 4.5.14(@types/node@22.20.1)(sass@1.104.0)(terser@5.48.0) vue: 3.5.34(typescript@5.7.3) - '@vitejs/plugin-vue@5.0.2(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))': + '@vitejs/plugin-vue@5.0.2(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': dependencies: vite: 5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0) - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) - '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))': + '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': dependencies: vite: 6.3.4(@types/node@14.18.63)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) '@vitejs/plugin-vue@5.2.4(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))': dependencies: @@ -22272,11 +22269,11 @@ snapshots: '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.0)': dependencies: - '@babel/helper-module-imports': 7.29.7 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8 + '@babel/traverse': 7.29.7 '@babel/types': 7.29.8 '@vue/babel-helper-vue-transform-on': 1.5.0 '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0) @@ -22312,7 +22309,7 @@ snapshots: '@vue/babel-preset-app@5.0.9(@babel/core@7.29.0)(core-js@3.49.0)(vue@3.5.34(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 - '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-imports': 7.28.6 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.0) '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.0) @@ -22985,30 +22982,15 @@ snapshots: estree-walker: 2.0.2 magic-string: 0.30.21 - '@vue/reactivity@3.4.3': - dependencies: - '@vue/shared': 3.4.3 - '@vue/reactivity@3.5.34': dependencies: '@vue/shared': 3.5.34 - '@vue/runtime-core@3.4.3': - dependencies: - '@vue/reactivity': 3.4.3 - '@vue/shared': 3.4.3 - '@vue/runtime-core@3.5.34': dependencies: '@vue/reactivity': 3.5.34 '@vue/shared': 3.5.34 - '@vue/runtime-dom@3.4.3': - dependencies: - '@vue/runtime-core': 3.4.3 - '@vue/shared': 3.4.3 - csstype: 3.2.3 - '@vue/runtime-dom@3.5.34': dependencies: '@vue/reactivity': 3.5.34 @@ -23016,19 +22998,6 @@ snapshots: '@vue/shared': 3.5.34 csstype: 3.2.3 - '@vue/server-renderer@3.4.3(vue@3.4.3(typescript@5.7.3))': - dependencies: - '@vue/compiler-ssr': 3.4.3 - '@vue/shared': 3.4.3 - vue: 3.4.3(typescript@5.7.3) - - '@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3))': - dependencies: - '@vue/compiler-ssr': 3.5.34 - '@vue/shared': 3.5.34 - vue: 3.4.3(typescript@5.7.3) - optional: true - '@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3))': dependencies: '@vue/compiler-ssr': 3.5.34 @@ -23043,14 +23012,6 @@ snapshots: '@vue/shared@3.5.34': {} - '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3))': - dependencies: - js-beautify: 1.14.9 - vue: 3.4.3(typescript@5.7.3) - vue-component-type-helpers: 1.8.4 - optionalDependencies: - '@vue/server-renderer': 3.5.34(vue@3.4.3(typescript@5.7.3)) - '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3))': dependencies: js-beautify: 1.14.9 @@ -23059,13 +23020,13 @@ snapshots: optionalDependencies: '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@5.7.3)) - '@vue/test-utils@2.4.3(@vue/server-renderer@3.5.34(vue@3.4.3(typescript@5.7.3)))(vue@3.4.3(typescript@5.7.3))': + '@vue/test-utils@2.4.3(@vue/server-renderer@3.5.34(vue@3.5.34(typescript@5.7.3)))(vue@3.5.34(typescript@5.7.3))': dependencies: js-beautify: 1.15.4 - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) vue-component-type-helpers: 1.8.27 optionalDependencies: - '@vue/server-renderer': 3.5.34(vue@3.4.3(typescript@5.7.3)) + '@vue/server-renderer': 3.5.34(vue@3.5.34(typescript@5.7.3)) '@vue/test-utils@2.4.6': dependencies: @@ -23090,7 +23051,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3))': + '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.5.34(typescript@5.7.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) @@ -23101,24 +23062,7 @@ snapshots: jest: 29.7.0(@types/node@14.18.63)(ts-node@10.9.2(@types/node@14.18.63)(typescript@5.7.3)) source-map: 0.5.6 tsconfig: 7.0.0 - vue: 3.4.3(typescript@5.7.3) - optionalDependencies: - typescript: 5.7.3 - transitivePeerDependencies: - - supports-color - - '@vue/vue3-jest@29.2.6(@babel/core@7.29.0)(babel-jest@29.7.0(@babel/core@7.29.0))(jest@29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)))(typescript@5.7.3)(vue@3.4.3(typescript@5.7.3))': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.0) - babel-jest: 29.7.0(@babel/core@7.29.0) - chalk: 2.4.2 - convert-source-map: 1.8.0 - css-tree: 2.3.1 - jest: 29.7.0(@types/node@22.20.1)(ts-node@10.9.2(@types/node@22.20.1)(typescript@5.7.3)) - source-map: 0.5.6 - tsconfig: 7.0.0 - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -23148,19 +23092,19 @@ snapshots: find-cache-dir: 3.3.2 upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) - '@vuetify/loader-shared@2.1.2(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9)': + '@vuetify/loader-shared@2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)': dependencies: upath: 2.0.1 - vue: 3.4.3(typescript@5.7.3) - vuetify: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) - '@vuetify/loader-shared@2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)': + '@vuetify/loader-shared@2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9)': dependencies: upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + vuetify: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)) '@webassemblyjs/ast@1.14.1': dependencies: @@ -23853,7 +23797,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -32309,7 +32253,7 @@ snapshots: readable-stream@2.3.8: dependencies: - core-util-is: 1.0.3 + core-util-is: 1.0.2 inherits: 2.0.4 isarray: 1.0.0 process-nextick-args: 2.0.1 @@ -34563,17 +34507,29 @@ snapshots: dependencies: vite: 6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0) - vite-plugin-vuetify@2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9): + vite-plugin-vuetify@2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9): dependencies: - '@vuetify/loader-shared': 2.1.2(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9) + '@vuetify/loader-shared': 2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9) debug: 4.4.3 upath: 2.0.1 vite: 5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0) - vue: 3.4.3(typescript@5.7.3) - vuetify: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)) transitivePeerDependencies: - supports-color + vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6): + dependencies: + '@vuetify/loader-shared': 2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) + debug: 4.4.3 + upath: 2.0.1 + vite: 4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0) + vue: 3.5.34(typescript@5.7.3) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)) + transitivePeerDependencies: + - supports-color + optional: true + vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6): dependencies: '@vuetify/loader-shared': 2.1.2(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) @@ -34592,7 +34548,7 @@ snapshots: upath: 2.0.1 vite: 6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0) vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) transitivePeerDependencies: - supports-color optional: true @@ -34835,7 +34791,7 @@ snapshots: - walrus - whiskers - vue-loader@17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): + vue-loader@17.2.2(@vue/compiler-sfc@3.3.8)(vue@3.5.34(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 @@ -34843,9 +34799,9 @@ snapshots: webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) optionalDependencies: '@vue/compiler-sfc': 3.3.8 - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) - vue-loader@17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.4.3(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): + vue-loader@17.4.2(@vue/compiler-sfc@3.4.3)(vue@3.5.34(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: chalk: 4.1.2 hash-sum: 2.0.0 @@ -34853,7 +34809,7 @@ snapshots: webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) optionalDependencies: '@vue/compiler-sfc': 3.4.3 - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) vue-loader@17.4.2(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))): dependencies: @@ -34865,9 +34821,9 @@ snapshots: '@vue/compiler-sfc': 3.5.34 vue: 3.5.34(typescript@5.7.3) - vue-loading-overlay@6.0.3(vue@3.4.3(typescript@5.7.3)): + vue-loading-overlay@6.0.3(vue@3.5.34(typescript@5.7.3)): dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) vue-loading-overlay@6.0.6(vue@3.5.34(typescript@5.7.3)): dependencies: @@ -34885,16 +34841,6 @@ snapshots: vue-template-es2015-compiler@1.9.1: {} - vue@3.4.3(typescript@5.7.3): - dependencies: - '@vue/compiler-dom': 3.4.3 - '@vue/compiler-sfc': 3.4.3 - '@vue/runtime-dom': 3.4.3 - '@vue/server-renderer': 3.4.3(vue@3.4.3(typescript@5.7.3)) - '@vue/shared': 3.4.3 - optionalDependencies: - typescript: 5.7.3 - vue@3.5.34(typescript@5.7.3): dependencies: '@vue/compiler-dom': 3.5.34 @@ -34905,23 +34851,30 @@ snapshots: optionalDependencies: typescript: 5.7.3 - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)): + dependencies: + vue: 3.5.34(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + vite-plugin-vuetify: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9) + + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)): dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 - vite-plugin-vuetify: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9) + vite-plugin-vuetify: 2.1.3(vite@4.5.11(@types/node@22.20.1)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3)): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3)): dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.4.3(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1): + vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))): dependencies: - vue: 3.4.3(typescript@5.7.3) + vue: 3.5.34(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) @@ -34934,20 +34887,12 @@ snapshots: typescript: 5.7.3 vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.104.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - vuetify@3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1): + vuetify@3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.5.34(typescript@5.7.3)): dependencies: vue: 3.5.34(typescript@5.7.3) optionalDependencies: typescript: 5.7.3 - vite-plugin-vuetify: 2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6) - webpack-plugin-vuetify: 2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) - - vuetify@3.4.9(typescript@5.7.3)(vite-plugin-vuetify@2.0.1)(vue@3.4.3(typescript@5.7.3)): - dependencies: - vue: 3.4.3(typescript@5.7.3) - optionalDependencies: - typescript: 5.7.3 - vite-plugin-vuetify: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.4.3(typescript@5.7.3))(vuetify@3.4.9) + vite-plugin-vuetify: 2.0.1(vite@5.0.10(@types/node@22.20.1)(sass@1.68.0)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.4.9) w3c-hr-time@1.0.2: dependencies: @@ -35158,7 +35103,7 @@ snapshots: null-loader: 4.0.1(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1))) upath: 2.0.1 vue: 3.5.34(typescript@5.7.3) - vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1) + vuetify: 3.12.6(typescript@5.7.3)(vite-plugin-vuetify@2.1.3(vite@6.3.4(@types/node@22.20.1)(jiti@1.21.6)(sass@1.56.1)(terser@5.48.0))(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6))(vue@3.5.34(typescript@5.7.3))(webpack-plugin-vuetify@2.0.1(@vue/compiler-sfc@3.5.34)(vue@3.5.34(typescript@5.7.3))(vuetify@3.12.6)(webpack@5.106.2(webpack-cli@6.0.1(webpack@5.104.1)))) webpack: 5.106.2(webpack-cli@6.0.1(webpack@5.104.1)) webpack-sources@3.4.1: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d237d6999..2818eb3f7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -9,6 +9,7 @@ overrides: 'mem-fs-editor': '12.0.4' 'yeoman-environment@3>mem-fs': '2.3.0' 'yeoman-environment@3>mem-fs-editor': '9.7.0' + vue: '3.5.34' allowBuilds: '@parcel/watcher': true '@vscode/vsce-sign': true From 19cc824a96c966f35eba396d171b923cc8eea4ec Mon Sep 17 00:00:00 2001 From: Jacob Kreyenbuehl Date: Wed, 23 Sep 2026 15:12:43 +0200 Subject: [PATCH 11/11] fix: address review comments on cloud-foundry-tools - changesets: move vscode-cf-tools + cloud-foundry-tools-frontend into a fixed group (bundled VSIX pair, must version together to get release tags); keep deprecated vscode-wing-cf-tools in ignore - frontend jest: scope transformIgnorePatterns to the pnpm layout with the ESM deps this project actually pulls (@vscode-elements, lit tree, lodash) - backend + wrapper: point bugs.url at the monorepo issue tracker - backend README: repoint media and CONTRIBUTING links to the monorepo Signed-off-by: Jacob Kreyenbuehl --- .changeset/config.json | 3 +-- projects/cloud-foundry-tools/packages/backend/README.md | 4 ++-- projects/cloud-foundry-tools/packages/backend/package.json | 2 +- projects/cloud-foundry-tools/packages/frontend/jest.config.js | 4 +++- projects/cloud-foundry-tools/packages/wrapper/package.json | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 6770d96bd..1c1c17346 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -18,6 +18,7 @@ "@vscode-logging/wrapper" ], ["guided-development", "guided-development-frontend"], + ["vscode-cf-tools", "cloud-foundry-tools-frontend"], [ "@sap-devx/inquirer-gui", "@sap-devx/inquirer-gui-auto-complete-plugin", @@ -60,8 +61,6 @@ "@sap-devx/inquirer-gui-date-plugin", "sample-inquirer-gui-vscode-extension", "websocketserver", - "vscode-cf-tools", - "cloud-foundry-tools-frontend", "vscode-wing-cf-tools" ], "privatePackages": { diff --git a/projects/cloud-foundry-tools/packages/backend/README.md b/projects/cloud-foundry-tools/packages/backend/README.md index 6257a2b43..8d6dedf21 100644 --- a/projects/cloud-foundry-tools/packages/backend/README.md +++ b/projects/cloud-foundry-tools/packages/backend/README.md @@ -48,7 +48,7 @@ This VS Code extension provides tools for easy application development with Clou You can configure logger preferences in the extension settings: -![Screenshot showing the settings of the Cloud Foundry Tools extension](https://raw.githubusercontent.com/SAP/cloud-foundry-tools/main/packages/backend/media/settings.png) +![Screenshot showing the settings of the Cloud Foundry Tools extension](https://raw.githubusercontent.com/SAP/app-studio-toolkit/main/projects/cloud-foundry-tools/packages/backend/media/settings.png) --- @@ -60,4 +60,4 @@ You can configure logger preferences in the extension settings: ## Contributing -See [CONTRIBUTING.md](https://github.com/SAP/cloud-foundry-tools/blob/main/CONTRIBUTING.md) for contribution guidelines. +See [CONTRIBUTING.md](https://github.com/SAP/app-studio-toolkit/blob/main/CONTRIBUTING.md) for contribution guidelines. diff --git a/projects/cloud-foundry-tools/packages/backend/package.json b/projects/cloud-foundry-tools/packages/backend/package.json index 5c757567a..d8699bf93 100644 --- a/projects/cloud-foundry-tools/packages/backend/package.json +++ b/projects/cloud-foundry-tools/packages/backend/package.json @@ -8,7 +8,7 @@ "Other" ], "bugs": { - "url": "https://github.com/SAP/cloud-foundry-tools.git/issues" + "url": "https://github.com/SAP/app-studio-toolkit/issues" }, "repository": { "type": "git", diff --git a/projects/cloud-foundry-tools/packages/frontend/jest.config.js b/projects/cloud-foundry-tools/packages/frontend/jest.config.js index dc8a0a69f..17bf31310 100644 --- a/projects/cloud-foundry-tools/packages/frontend/jest.config.js +++ b/projects/cloud-foundry-tools/packages/frontend/jest.config.js @@ -14,7 +14,9 @@ module.exports = { "^@/(.*)$": "/src/$1", }, moduleFileExtensions: ["js", "vue", "json"], - transformIgnorePatterns: ["/node_modules/(?!(.+)/)"], + transformIgnorePatterns: [ + "/node_modules/.pnpm/(?!(@vscode-elements\\+|@lit\\+|@lit-labs\\+|lit@|lit-html@|lit-element@|lodash@))", + ], modulePaths: ["/src", "/node_modules"], transform: { "^.+\\.vue$": "@vue/vue3-jest", diff --git a/projects/cloud-foundry-tools/packages/wrapper/package.json b/projects/cloud-foundry-tools/packages/wrapper/package.json index e65d1af14..123d417ec 100644 --- a/projects/cloud-foundry-tools/packages/wrapper/package.json +++ b/projects/cloud-foundry-tools/packages/wrapper/package.json @@ -8,7 +8,7 @@ "Other" ], "bugs": { - "url": "https://github.com/SAP/cloud-foundry-tools.git/issues" + "url": "https://github.com/SAP/app-studio-toolkit/issues" }, "repository": { "type": "git",