diff --git a/.changeset/blue-wombats-yell.md b/.changeset/blue-wombats-yell.md new file mode 100644 index 0000000000..830dc38554 --- /dev/null +++ b/.changeset/blue-wombats-yell.md @@ -0,0 +1,6 @@ +--- +'@sap-ux/repo-app-download-sub-generator': minor +'@sap-ux/axios-extension': minor +--- + +Added a new sub-generator: `@sap-ux/repo-app-download-sub-generator` to support downloading ABAP deployed Fiori apps from the repository. Enhanced `@sap-ux/axios-extension` to support Base64 download data. diff --git a/.vscode/launch.json b/.vscode/launch.json index 103482a9a5..5fc0f2ef94 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -429,6 +429,20 @@ "program": "${workspaceFolder}/node_modules/jest/bin/jest" }, "cwd": "${workspaceFolder}/packages/system-access" + }, + { + "type": "node", + "request": "launch", + "name": "repo-app-download-sub-generator: Launch Yeoman generators/app", + "program": "${workspaceFolder}/packages/repo-app-download-sub-generator/node_modules/yo/lib/cli.js", + "args": [ + "${workspaceFolder}/packages/repo-app-download-sub-generator/generators/app/index.js" + ], + "env": { + }, + "stopOnEntry": true, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" } ] } diff --git a/packages/axios-extension/src/abap/ui5-abap-repository-service.ts b/packages/axios-extension/src/abap/ui5-abap-repository-service.ts index 982920d1c8..c3cd0d832b 100644 --- a/packages/axios-extension/src/abap/ui5-abap-repository-service.ts +++ b/packages/axios-extension/src/abap/ui5-abap-repository-service.ts @@ -141,6 +141,21 @@ export class Ui5AbapRepositoryService extends ODataService { } } + /** + * + * @param str string to check + * @returns true if the string is base64 encoded, false otherwise + */ + private isBase64Encoded(str: string): boolean { + try { + // Decode the string and re-encode it to verify integrity + return Buffer.from(str, 'base64').toString('base64') === str.trim(); + } catch (e) { + // If decoding fails, it's not valid Base64 + return false; //NOSONAR + } + } + /** * Get the application files as zip archive. This will only work on ABAP systems 2308 or newer. * @@ -156,7 +171,12 @@ export class Ui5AbapRepositoryService extends ODataService { } }); const data = response.odata(); - return data.ZipArchive ? Buffer.from(data.ZipArchive) : undefined; + + if (!data.ZipArchive) { + return undefined; + } + const isBase64 = this.isBase64Encoded(data.ZipArchive); + return Buffer.from(data.ZipArchive, isBase64 ? 'base64' : undefined); } catch (error) { this.log.debug(`Retrieving application ${app}, ${error}`); if (isAxiosError(error) && error.response?.status === 404) { diff --git a/packages/axios-extension/test/abap/ui5-abap-repository-service.test.ts b/packages/axios-extension/test/abap/ui5-abap-repository-service.test.ts index d531d98c2d..64d10c8c77 100644 --- a/packages/axios-extension/test/abap/ui5-abap-repository-service.test.ts +++ b/packages/axios-extension/test/abap/ui5-abap-repository-service.test.ts @@ -26,7 +26,7 @@ describe('Ui5AbapRepositoryService', () => { const validAppInfo: AppInfo = { Name: validApp, Package: 'my_package', - ZipArchive: 'EncodeZippedDataHere' + ZipArchive: 'EncodeZippedDataHere@!#' }; const updateParams = `CodePage='UTF8'&CondenseMessagesInHttpResponseHeader=X&format=json`; const sapMessageHeader = JSON.stringify({ diff --git a/packages/repo-app-download-sub-generator/.eslintignore b/packages/repo-app-download-sub-generator/.eslintignore new file mode 100644 index 0000000000..89ff260405 --- /dev/null +++ b/packages/repo-app-download-sub-generator/.eslintignore @@ -0,0 +1,2 @@ +/test/ +generators diff --git a/packages/repo-app-download-sub-generator/.eslintrc.js b/packages/repo-app-download-sub-generator/.eslintrc.js new file mode 100644 index 0000000000..b717f83ae9 --- /dev/null +++ b/packages/repo-app-download-sub-generator/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: ['../../.eslintrc'], + parserOptions: { + project: './tsconfig.eslint.json', + tsconfigRootDir: __dirname + } +}; diff --git a/packages/repo-app-download-sub-generator/LICENSE b/packages/repo-app-download-sub-generator/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/packages/repo-app-download-sub-generator/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/repo-app-download-sub-generator/README.md b/packages/repo-app-download-sub-generator/README.md new file mode 100644 index 0000000000..1d41683cbb --- /dev/null +++ b/packages/repo-app-download-sub-generator/README.md @@ -0,0 +1,16 @@ +# @sap-ux/repo-app-download-sub-generator + +## Features + +The SAP App download sub-generator enables users to download a basic LROP App from an ABAP repository. The sub-generator will download the app from the repository and add it to the workspace. + +## Installation + +The SAP App download sub-generator is installed as part of the [@sap/generator-fiori](https://www.npmjs.com/package/@sap/generator-fiori) generator and cannot be used stand alone. + +## Launch the SAP Reuse Library sub-generator + +Open the Command Palette in MS Visual Studio Code ( CMD/CTRL + Shift + P ) and execute the Fiori: Download ADT deployed app from an UI5 ABAP repository. + +## Keywords +SAP Fiori Generator diff --git a/packages/repo-app-download-sub-generator/jest.config.js b/packages/repo-app-download-sub-generator/jest.config.js new file mode 100644 index 0000000000..2f0a4db758 --- /dev/null +++ b/packages/repo-app-download-sub-generator/jest.config.js @@ -0,0 +1,6 @@ +const config = require('../../jest.base'); +config.snapshotFormat = { + escapeString: false, + printBasicPrototype: false +}; +module.exports = config; diff --git a/packages/repo-app-download-sub-generator/package.json b/packages/repo-app-download-sub-generator/package.json new file mode 100644 index 0000000000..5680a36fce --- /dev/null +++ b/packages/repo-app-download-sub-generator/package.json @@ -0,0 +1,84 @@ +{ + "name": "@sap-ux/repo-app-download-sub-generator", + "description": "Generator to download LROP Fiori applications deployed from an ABAP repository.", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "https://github.com/SAP/open-ux-tools.git", + "directory": "packages/repo-app-download-sub-generator" + }, + "bugs": { + "url": "https://github.com/SAP/open-ux-tools/issues?q=is%3Aopen+is%3Aissue" + }, + "license": "Apache-2.0", + "main": "generators/app/index.js", + "scripts": { + "build": "tsc --build", + "clean": "rimraf --glob generators test/test-output coverage *.tsbuildinfo", + "watch": "tsc --watch", + "lint": "eslint . --ext .ts", + "lint:fix": "eslint . --ext .ts --fix", + "test": "jest --ci --forceExit --detectOpenHandles --colors --passWithNoTests", + "test-u": "jest --ci --forceExit --detectOpenHandles --colors -u", + "link": "pnpm link --global", + "unlink": "pnpm unlink --global" + }, + "files": [ + "LICENSE", + "generators", + "!generators/*.map", + "!generators/**/*.map" + ], + "dependencies": { + "@sap-devx/yeoman-ui-types": "1.14.4", + "@sap-ux/feature-toggle": "workspace:*", + "@sap-ux/fiori-generator-shared": "workspace:*", + "@sap-ux/inquirer-common": "workspace:*", + "@sap-ux/project-access": "workspace:*", + "@sap-ux/odata-service-inquirer": "workspace:*", + "@sap-ux/fiori-elements-writer": "workspace:*", + "@sap-ux/logger": "workspace:*", + "@sap-ux/project-input-validator": "workspace:*", + "@sap-ux/launch-config": "workspace:*", + "@sap-ux/fiori-tools-settings": "workspace:*", + "@sap-ux/abap-deploy-config-writer": "workspace:*", + "@sap-ux/btp-utils": "workspace:*", + "@sap-ux/ui5-info": "workspace:*", + "@sap-ux/axios-extension": "workspace:*", + "@sap-ux/store": "workspace:*", + "adm-zip": "0.5.10", + "i18next": "23.5.1", + "inquirer": "8.2.6", + "yeoman-generator": "5.10.0" + }, + "devDependencies": { + "@jest/types": "29.6.3", + "@types/inquirer": "8.2.6", + "@types/mem-fs": "1.1.2", + "@types/mem-fs-editor": "7.0.1", + "@types/yeoman-generator": "5.2.11", + "@types/yeoman-environment": "2.10.11", + "inquirer-autocomplete-prompt": "2.0.1", + "@types/inquirer-autocomplete-prompt": "2.0.1", + "@types/yeoman-test": "4.0.6", + "@sap-ux/nodejs-utils": "workspace:*", + "@types/fs-extra": "9.0.13", + "fs-extra": "10.0.0", + "@sap-ux/store": "workspace:*", + "@sap-ux/ui5-config": "workspace:*", + "@vscode-logging/logger": "2.0.0", + "@types/adm-zip": "0.5.5", + "memfs": "3.4.13", + "mem-fs-editor": "9.4.0", + "lodash": "4.17.21", + "@types/lodash": "4.14.202", + "rimraf": "5.0.5", + "typescript": "5.3.3", + "unionfs": "4.4.0", + "yeoman-test": "6.3.0", + "yo": "4" + }, + "engines": { + "node": ">=18.x" + } +} diff --git a/packages/repo-app-download-sub-generator/src/app/app-config.ts b/packages/repo-app-download-sub-generator/src/app/app-config.ts new file mode 100644 index 0000000000..947532480c --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/app/app-config.ts @@ -0,0 +1,129 @@ +import { TemplateType, type FioriElementsApp, type LROPSettings } from '@sap-ux/fiori-elements-writer'; +import { OdataVersion } from '@sap-ux/odata-service-inquirer'; +import type { AbapServiceProvider } from '@sap-ux/axios-extension'; +import type { Editor } from 'mem-fs-editor'; +import { t } from '../utils/i18n'; +import type { AppInfo, QfaJsonConfig } from '../app/types'; +import { readManifest } from '../utils/file-helpers'; +import { fioriAppSourcetemplateId } from '../utils/constants'; +import { PromptState } from '../prompts/prompt-state'; +import type { AbapDeployConfig } from '@sap-ux/ui5-config'; +import RepoAppDownloadLogger from '../utils/logger'; +import { FileName } from '@sap-ux/project-access'; +import { join } from 'path'; + +/** + * Generates the deployment configuration for an ABAP application. + * + * @param {AppInfo} app - Application info containing `url` and `repoName`. + * @param {QfaJsonConfig} qfaJson - The QFA JSON configuration containing app details. + * @returns {AbapDeployConfig} The deployment configuration containing `target` and `app` info. + */ +export const getAbapDeployConfig = (app: AppInfo, qfaJson: QfaJsonConfig): AbapDeployConfig => { + return { + target: { + url: PromptState.baseURL, + client: PromptState.sapClient, + destination: app.repoName + }, + app: { + name: qfaJson.deploymentDetails.repositoryName, + package: qfaJson.metadata.package, + description: qfaJson.deploymentDetails.repositoryDescription, + transport: 'REPLACE_WITH_TRANSPORT' + } + }; +}; + +/** + * Fetches the metadata of a given service from the provided ABAP service provider. + * + * @param {AbapServiceProvider} provider - The ABAP service provider instance. + * @param {string} serviceUrl - The URL of the service to retrieve metadata for. + * @returns {Promise} - A promise resolving to the service metadata. + */ +const fetchServiceMetadata = async (provider: AbapServiceProvider, serviceUrl: string): Promise => { + try { + return await provider.service(serviceUrl).metadata(); + } catch (err) { + RepoAppDownloadLogger.logger?.error(t('error.metadataFetchError', { error: err.message })); + } +}; + +/** + * Gets the application configuration based on the provided user answers and manifest data. + * This configuration will be used to initialize a new Fiori application. + * + * @param {AppInfo} app - Selected app information. + * @param {string} extractedProjectPath - Path where the app files are extracted. + * @param {QfaJsonConfig} qfaJson - The QFA JSON configuration containing app details. + * @param {Editor} fs - The file system editor to manipulate project files. + * @returns {Promise>} - A promise resolving to the generated app configuration. + * @throws {Error} - Throws an error if there are issues generating the configuration. + */ +export async function getAppConfig( + app: AppInfo, + extractedProjectPath: string, + qfaJson: QfaJsonConfig, + fs: Editor +): Promise> { + try { + const manifest = readManifest(join(extractedProjectPath, FileName.Manifest), fs); + const serviceProvider = PromptState.systemSelection?.connectedSystem?.serviceProvider as AbapServiceProvider; + if (!manifest?.['sap.app']?.dataSources) { + RepoAppDownloadLogger.logger?.error(t('error.dataSourcesNotFound')); + } + + const odataVersion = + manifest?.['sap.app']?.dataSources?.mainService?.settings?.odataVersion === '4.0' + ? OdataVersion.v4 + : OdataVersion.v2; + + // Fetch metadata for the service + const metadata = await fetchServiceMetadata( + serviceProvider, + manifest?.['sap.app']?.dataSources?.mainService.uri ?? '' + ); + const appConfig: FioriElementsApp = { + app: { + id: app.appId, + title: app.title, + description: app.description, + sourceTemplate: { + id: fioriAppSourcetemplateId + }, + projectType: 'EDMXBackend', + flpAppId: `${app.appId.replace(/[-_.#]/g, '')}-tile` + }, + package: { + name: app.appId, + description: app.description, + devDependencies: {}, + scripts: {}, + version: manifest?.['sap.app']?.applicationVersion?.version ?? '0.0.1' + }, + template: { + type: TemplateType.ListReportObjectPage, + settings: { + entityConfig: { + mainEntityName: qfaJson.serviceBindingDetails.mainEntityName + } + } + }, + service: { + path: manifest?.['sap.app']?.dataSources?.mainService.uri, + version: odataVersion, + metadata: metadata, + url: serviceProvider.defaults.baseURL + }, + appOptions: { + addAnnotations: odataVersion === OdataVersion.v4, + addTests: true + } + }; + return appConfig; + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.appConfigGenError', { error: error.message })); + throw error; + } +} diff --git a/packages/repo-app-download-sub-generator/src/app/index.ts b/packages/repo-app-download-sub-generator/src/app/index.ts new file mode 100644 index 0000000000..282eb721a1 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/app/index.ts @@ -0,0 +1,335 @@ +import Generator from 'yeoman-generator'; +import RepoAppDownloadLogger from '../utils/logger'; +import { AppWizard, Prompts, MessageType } from '@sap-devx/yeoman-ui-types'; +import { isInternalFeaturesSettingEnabled } from '@sap-ux/feature-toggle'; +import type { Logger } from '@sap-ux/logger'; +import { generatorTitle, extractedFilePath, generatorName, defaultAnswers, qfaJsonFileName } from '../utils/constants'; +import { t } from '../utils/i18n'; +import { extractZip } from '../utils/download-utils'; +import { EventName } from '../telemetryEvents'; +import { + getDefaultTargetFolder, + generateReadMe, + type ReadMe, + type YeomanEnvironment, + sendTelemetry, + TelemetryHelper +} from '@sap-ux/fiori-generator-shared'; +import type { RepoAppDownloadOptions, RepoAppDownloadAnswers, RepoAppDownloadQuestions, QfaJsonConfig } from './types'; +import { getPrompts } from '../prompts/prompts'; +import { generate, TemplateType, type FioriElementsApp, type LROPSettings } from '@sap-ux/fiori-elements-writer'; +import { join, basename } from 'path'; +import { platform } from 'os'; +import { runPostAppGenHook } from '../utils/event-hook'; +import { getDefaultUI5Theme } from '@sap-ux/ui5-info'; +import type { DebugOptions, FioriOptions } from '@sap-ux/launch-config'; +import { createLaunchConfig, updateWorkspaceFoldersIfNeeded } from '@sap-ux/launch-config'; +import { isAppStudio } from '@sap-ux/btp-utils'; +import { OdataVersion } from '@sap-ux/odata-service-inquirer'; +import { writeApplicationInfoSettings } from '@sap-ux/fiori-tools-settings'; +import { generate as generateDeployConfig } from '@sap-ux/abap-deploy-config-writer'; +import { PromptState } from '../prompts/prompt-state'; +import { PromptNames } from './types'; +import { getAbapDeployConfig, getAppConfig } from './app-config'; +import type { AbapDeployConfig } from '@sap-ux/ui5-config'; +import { makeValidJson } from '../utils/file-helpers'; +import { replaceWebappFiles, validateAndUpdateManifestUI5Version } from '../utils/updates'; +import { getYUIDetails } from '../prompts/prompt-helpers'; +import { isValidPromptState, validateQfaJsonFile } from '../utils/validators'; +import { FileName, DirName } from '@sap-ux/project-access'; + +/** + * Generator class for downloading a basic app from a repository. + * This class handles the process of app selection, downloading the app and generating a fiori app from the downloaded app + */ +export default class extends Generator { + private readonly appWizard: AppWizard; + private readonly vscode?: any; + private readonly appRootPath: string; + private readonly prompts: Prompts; + private readonly answers: RepoAppDownloadAnswers = defaultAnswers; + public options: RepoAppDownloadOptions; + private projectPath: string; + private extractedProjectPath: string; + setPromptsCallback: (fn: object) => void; + + /** + * Constructor for Downloading App. + * + * @param args - arguments passed to the generator + * @param opts - options passed to the generator + */ + constructor(args: string | string[], opts: RepoAppDownloadOptions) { + super(args, opts); + + // Initialise properties from options + this.appWizard = opts.appWizard ?? AppWizard.create(opts); + this.vscode = opts.vscode; + this.appRootPath = opts?.appRootPath ?? getDefaultTargetFolder(this.vscode) ?? this.destinationRoot(); + this.options = opts; + + // Configure logging + RepoAppDownloadLogger.configureLogging( + this.rootGeneratorName(), + this.log, + this.options.logWrapper, + this.options.logLevel, + this.options.logger, + this.vscode + ); + + // Initialise prompts and callbacks if not launched as a subgenerator + this.appWizard.setHeaderTitle(generatorTitle); + this.prompts = new Prompts(getYUIDetails()); + this.setPromptsCallback = (fn): void => { + if (this.prompts) { + this.prompts.setCallback(fn); + } + }; + } + + /** + * Initialises necessary settings and telemetry for the generator. + */ + public async initializing(): Promise { + if ((this.env as unknown as YeomanEnvironment).conflicter) { + (this.env as unknown as YeomanEnvironment).conflicter.force = this.options.force ?? true; + } + // Initialise telemetry settings + await TelemetryHelper.initTelemetrySettings({ + consumerModule: { + name: generatorName, + version: this.rootGeneratorVersion() + }, + internalFeature: isInternalFeaturesSettingEnabled(), + watchTelemetrySettingStore: false + }); + } + + /** + * Prompts the user for application details and downloads the app. + */ + public async prompting(): Promise { + const quickDeployedAppConfig = this.options?.data?.quickDeployedAppConfig; + const questions: RepoAppDownloadQuestions[] = await getPrompts(this.appRootPath, quickDeployedAppConfig); + const answers: RepoAppDownloadAnswers = await this.prompt(questions); + const { targetFolder } = answers; + if (quickDeployedAppConfig?.appId) { + // Handle quick deployed app download where prompts for system selection and app selection are not displayed + // Only target folder prompt is shown + this.answers.targetFolder = targetFolder; + this.answers.systemSelection = PromptState.systemSelection; + this.answers.selectedApp = answers.selectedApp; + } else { + // Handle app download where prompts for system selection and app selection are shown + Object.assign(this.answers, answers); + } + if (isValidPromptState(this.answers.targetFolder, this.answers.selectedApp.appId)) { + this.projectPath = join(this.answers.targetFolder, this.answers.selectedApp.appId); + this.extractedProjectPath = join(this.projectPath, extractedFilePath); + } + } + + /** + * Writes the configuration files for the project, including deployment config, and README. + */ + public async writing(): Promise { + // Extract downloaded app + const archive = PromptState.downloadedAppPackage; + await extractZip(this.extractedProjectPath, archive, this.fs); + + // Check if the qfa.json file + const qfaJsonFilePath = join(this.extractedProjectPath, qfaJsonFileName); + if (this.fs.exists(qfaJsonFilePath)) { + const qfaJson: QfaJsonConfig = makeValidJson(qfaJsonFilePath, this.fs); + // Generate project files + validateQfaJsonFile(qfaJson); + + // Generate app config + const config = await getAppConfig(this.answers.selectedApp, this.extractedProjectPath, qfaJson, this.fs); + await generate(this.projectPath, config, this.fs); + + // Generate deploy config + const deployConfig: AbapDeployConfig = getAbapDeployConfig(this.answers.selectedApp, qfaJson); + await generateDeployConfig(this.projectPath, deployConfig, undefined, this.fs); + + if (this.vscode) { + // Generate Fiori launch config + const fioriOptions = this._getLaunchConfig(config); + // Create launch configuration + await createLaunchConfig( + this.projectPath, + fioriOptions, + this.fs, + RepoAppDownloadLogger.logger as unknown as Logger + ); + writeApplicationInfoSettings(this.projectPath); + } + + // Generate README + const readMeConfig = this._getReadMeConfig(config); + generateReadMe(this.projectPath, readMeConfig, this.fs); + + // Replace webapp files with downloaded app files + await replaceWebappFiles(this.projectPath, this.extractedProjectPath, this.fs); + + await validateAndUpdateManifestUI5Version( + join(this.projectPath, DirName.Webapp, FileName.Manifest), + this.fs + ); + + // Clean up extracted project files + this.fs.delete(this.extractedProjectPath); + } else { + RepoAppDownloadLogger.logger?.error(t('error.qfaJsonNotFound', { jsonFileName: qfaJsonFileName })); + } + } + + /** + * Returns the configuration for the README file. + * + * @param config - The app configuration object. + * @returns {ReadMe} The configuration for generating the README. + */ + private _getReadMeConfig(config: FioriElementsApp): ReadMe { + const readMeConfig: ReadMe = { + appName: config.app.id, + appTitle: config.app.title ?? '', + appNamespace: config.app.id.substring(0, config.app.id.lastIndexOf('.')), + appDescription: t('readMe.appDescription'), + ui5Theme: getDefaultUI5Theme(config.ui5?.version), + generatorName: generatorName, + generatorVersion: this.rootGeneratorVersion(), + ui5Version: config.ui5?.version ?? '', + template: TemplateType.ListReportObjectPage, + serviceUrl: config.service.url, + launchText: t('readMe.launchText') + }; + return readMeConfig; + } + + /** + * Returns the configuration for launching the app with Fiori options. + * + * @param config - The app configuration object. + * @returns {FioriOptions} The launch configuration options. + */ + private _getLaunchConfig(config: FioriElementsApp): FioriOptions { + const debugOptions: DebugOptions = { + vscode: this.vscode, + addStartCmd: true, + sapClientParam: PromptState.sapClient, + flpAppId: config.app.flpAppId ?? config.app.id, + flpSandboxAvailable: true, + isAppStudio: isAppStudio(), + odataVersion: config.service.version === OdataVersion.v2 ? '2.0' : '4.0' + }; + const fioriOptions: FioriOptions = { + name: config.app.id, + projectRoot: this.projectPath, + /** + * The `enableVSCodeReload` property is set to `false` to prevent automatic reloading of the VS Code workspace + * after the app generation process. This is necessary to ensure that the `.vscode/launch-config.json` file is + * written to disk before the workspace reload occurs. See {@link _handlePostAppGeneration} for details. + */ + enableVSCodeReload: false, + debugOptions + }; + return fioriOptions; + } + + /** + * Installs npm dependencies for the project. + */ + public async install(): Promise { + if (!this.options.skipInstall) { + try { + await this._runNpmInstall(this.projectPath); + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.installationErrors.npmInstall', { error })); + } + } else { + RepoAppDownloadLogger.logger?.info(t('info.installationErrors.skippedInstallation')); + } + } + + /** + * Runs npm install in the specified path. + * + * @param path - The path to run npm install. + */ + private async _runNpmInstall(path: string): Promise { + const npm = platform() === 'win32' ? 'npm.cmd' : 'npm'; + // install dependencies + await this.spawnCommand( + npm, + ['install', '--no-audit', '--no-fund', '--silent', '--prefer-offline', '--no-progress'], + { + cwd: path + } + ); + } + + /** + * Responsible for updating workspace folders and running post-generation commands if defined. + */ + private async _handlePostAppGeneration(): Promise { + /** + * `enableVSCodeReload` is set to false when generating launch config here {@link _getLaunchConfig}. + * This prevents issues where the `.vscode/launch-config.json` file may not be written to disk due to the timing of mem-fs commits. + * + * In Yeoman, a commit occurs between the writing phase and the end phase. If no workspace is open in VS Code and the generated + * app is added to the workspace, VS Code automatically reloads the window. However, by this point in the end phase, the in-memory file system + * (mem-fs) has written all files except for `.vscode/launch-config.json`, because the commit happens before the end phase + * causing it to be missed when the workspace reload occurs. + * + * Workflow: + * 1. **Workspace URI**: The `updateWorkspaceFolders` object is created with the project folder's path, the project name, + * and the VS Code instance to handle workspace folder updates. + * 2. **Update Workspace Folders**: The `updateWorkspaceFoldersIfNeeded` function is called to update the workspace folders, + * if necessary. See {@link updateWorkspaceFoldersIfNeeded} for details. + * 3. **Run Post-Generation Commands**: If defined, post-generation commands from `options.data?.postGenCommands` are executed + * using the `runPostAppGenHook` function. This allows for additional setup or configuration tasks to be performed after + * the app generation process. + */ + if (this.vscode) { + const updateWorkspaceFolders = { + uri: this.vscode?.Uri?.file(join(this.projectPath)), + projectName: basename(this.projectPath), + vscode: this.vscode + }; + updateWorkspaceFoldersIfNeeded(updateWorkspaceFolders); + } + if (this.options.data?.postGenCommand) { + await runPostAppGenHook({ + path: this.projectPath, + vscodeInstance: this.vscode, + postGenCommand: this.options.data?.postGenCommand + }); + } + } + + /** + * Finalises the generator process by creating launch configurations and running post-generation hooks. + */ + async end(): Promise { + try { + this.appWizard.showInformation(t('info.repoAppDownloadCompleteMsg'), MessageType.notification); + await sendTelemetry( + EventName.GENERATION_SUCCESS, + TelemetryHelper.createTelemetryData({ + appType: 'repo-app-download-sub-generator', + ...this.options.telemetryData + }) ?? {} + ).catch((error) => { + RepoAppDownloadLogger.logger?.error(t('error.telemetry', { error: error.message })); + }); + await this._handlePostAppGeneration(); + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.endPhase', { error: error.message })); + } + } +} + +export { PromptNames }; +export type { RepoAppDownloadOptions }; diff --git a/packages/repo-app-download-sub-generator/src/app/types.ts b/packages/repo-app-download-sub-generator/src/app/types.ts new file mode 100644 index 0000000000..dc642f0b43 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/app/types.ts @@ -0,0 +1,151 @@ +import type Generator from 'yeoman-generator'; +import type { AppWizard } from '@sap-devx/yeoman-ui-types'; +import type { VSCodeInstance, TelemetryData, LogWrapper } from '@sap-ux/fiori-generator-shared'; +import type { Destination } from '@sap-ux/btp-utils'; +import type { BackendSystem } from '@sap-ux/store'; +import type { AbapServiceProvider, AppIndex } from '@sap-ux/axios-extension'; +import type { YUIQuestion } from '@sap-ux/inquirer-common'; +import type { AutocompleteQuestionOptions } from 'inquirer-autocomplete-prompt'; + +/** + * Quick deploy app config are applicable only in scenarios where an application + * deployed via ADT Quick Deploy is being downloaded from a repository. + */ +export interface QuickDeployedAppConfig { + /** application Id to be downloaded. */ + appId: string; + /** appUrl is the URL pointing to the application */ + appUrl?: string; + /** + * Information about the system from which the application is to be downloaded. + */ + serviceProviderInfo?: { + /** + * The base URL of the system providing the application. + */ + serviceUrl?: string; + /** + * The name of the system providing the application. + */ + name: string; + }; +} + +/** + * Options for downloading an application from repository. + */ +export interface RepoAppDownloadOptions extends Generator.GeneratorOptions { + /** VSCode instance for interacting with the VSCode environment. */ + vscode?: VSCodeInstance; + + /** The quick deploy config is provided only when an ADT quick deployed app is being downloaded */ + quickDeployedAppConfig?: QuickDeployedAppConfig; + + /** AppWizard instance for managing the application download flow. */ + appWizard?: AppWizard; + + /** Path to the application root where the Fiori launchpad configuration will be added. */ + appRootPath?: string; + + /** Telemetry data for tracking events post deployment configuration. */ + telemetryData?: TelemetryData; + + /** Logger instance for logging operations. */ + logWrapper?: LogWrapper; +} + +/** + * Answers related to system selection in the application download process. + */ +export interface SystemSelectionAnswers { + /** + * Details of the connected system allowing downstream consumers to access it without creating new connections. + */ + connectedSystem?: { + /** Service provider for the connected ABAP system. */ + serviceProvider: AbapServiceProvider; + + /** + * Persistable backend system representation of the connected service provider. + * `newOrUpdated` is true if the system was newly created or updated during connection validation. + */ + backendSystem?: BackendSystem & { newOrUpdated?: boolean }; + + /** Destination details of the connected system. */ + destination?: Destination; + }; +} + +/** + * Represents a question in the app download process. + * Extends `YUIQuestion` with optional autocomplete functionality. + */ +export type RepoAppDownloadQuestions = YUIQuestion & + Partial>; + +// Extract the type of a single element in the AppIndex array +export type AppItem = AppIndex extends (infer U)[] ? U : never; + +export interface AppInfo { + appId: string; + title: string; + description: string; + repoName: string; + url: string; +} + +/** + * Enum representing the names of prompts used in the application download process. + */ +export enum PromptNames { + selectedApp = 'selectedApp', + systemSelection = 'systemSelection', + targetFolder = 'targetFolder' +} + +/** + * Structure of answers provided by the user for application download prompts. + */ +export interface RepoAppDownloadAnswers { + /** Selected backend system connection details. */ + [PromptNames.systemSelection]: SystemSelectionAnswers; + /** Information about the selected application for download. */ + [PromptNames.selectedApp]: AppInfo; + /** Target folder where the application will be generated. */ + [PromptNames.targetFolder]: string; +} + +/** + * Interface representing the configuration of a QFA JSON file. + * This QFA JSON file is used for configuring the application download process + * and contains user inputs. + */ +export interface QfaJsonConfig { + metadata: { + package: string; + masterLanguage?: string; + }; + serviceBindingDetails: { + name?: string; + serviceName: string; + serviceVersion: string; + mainEntityName: string; + navigationEntity?: string; + }; + projectAttribute: { + moduleName: string; + applicationTitle?: string; + minimumUi5Version?: string; + template?: string; + }; + deploymentDetails: { + repositoryName: string; + repositoryDescription?: string; + }; + fioriLaunchpadConfiguration: { + semanticObject: string; + action: string; + title: string; + subtitle?: string; + }; +} diff --git a/packages/repo-app-download-sub-generator/src/prompts/prompt-helpers.ts b/packages/repo-app-download-sub-generator/src/prompts/prompt-helpers.ts new file mode 100644 index 0000000000..5096b062a7 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/prompts/prompt-helpers.ts @@ -0,0 +1,107 @@ +import { appListSearchParams, appListResultFields, generatorTitle, generatorDescription } from '../utils/constants'; +import type { AbapServiceProvider, AppIndex } from '@sap-ux/axios-extension'; +import type { AppInfo, AppItem } from '../app/types'; +import { PromptState } from './prompt-state'; +import { t } from '../utils/i18n'; +import RepoAppDownloadLogger from '../utils/logger'; + +/** + * Returns the details for the YUI prompt. + * + * @returns step details + */ +export function getYUIDetails(): { name: string; description: string }[] { + return [ + { + name: generatorTitle, + description: generatorDescription + } + ]; +} + +/** + * Returns the prompt details for the selected application. + * + * @param {AppItem} app - The application item to extract details from. + * @returns {{ name: string; value: AppInfo }} The extracted details including name and value. + */ +export const extractAppData = (app: AppItem): { name: string; value: AppInfo } => { + // cast to string because TypeScript doesn't automatically know at the point that these fields are defined + // after filtering out invalid apps. + const id = app['sap.app/id'] as string; + const title = app['sap.app/title'] as string; + const description = (app['sap.app/description'] ?? '') as string; + const repoName = app.repoName as string; + const url = app.url as string; + + return { + name: id, + value: { + appId: id, + title, + description, + repoName, + url + } + }; +}; + +/** + * Formats the application list into selectable choices. + * + * @param {AppIndex} appList - List of applications retrieved from the system. + * @returns {Array<{ name: string; value: AppInfo }>} The formatted choices for selection. + */ +export const formatAppChoices = (appList: AppIndex): Array<{ name: string; value: AppInfo }> => { + return appList + .filter((app: AppItem) => { + const hasRequiredFields = app['sap.app/id'] && app['sap.app/title'] && app['repoName'] && app['url']; + if (!hasRequiredFields) { + RepoAppDownloadLogger.logger?.error(t('error.requiredFieldsMissing', { app: JSON.stringify(app) })); + } + return hasRequiredFields; + }) + .map((app) => extractAppData(app)); +}; + +/** + * Fetches a list of deployed applications from the ABAP repository. + * + * @param {AbapServiceProvider} provider - The ABAP service provider. + * @param {string} appId - Application ID to filter the list. + * @returns {Promise} A list of applications filtered by source template. + */ +async function getAppList(provider: AbapServiceProvider, appId?: string): Promise { + try { + const searchParams = appId + ? { + ...appListSearchParams, + 'sap.app/id': appId + } + : appListSearchParams; + return await provider.getAppIndex().search(searchParams, appListResultFields); + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.applicationListFetchError', { error: error.message })); + return []; + } +} + +/** + * Fetches the application list for the selected system. + * + * @param {AbapServiceProvider} serviceProvider - The ABAP service provider. + * @param {string} appId - Application ID to be downloaded. + * @returns {Promise} A list of applications filtered by source template. + */ +export async function fetchAppListForSelectedSystem( + serviceProvider: AbapServiceProvider, + appId?: string +): Promise { + if (serviceProvider) { + PromptState.systemSelection = { + connectedSystem: { serviceProvider } + }; + return await getAppList(serviceProvider, appId); + } + return []; +} diff --git a/packages/repo-app-download-sub-generator/src/prompts/prompt-state.ts b/packages/repo-app-download-sub-generator/src/prompts/prompt-state.ts new file mode 100644 index 0000000000..575d359d9f --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/prompts/prompt-state.ts @@ -0,0 +1,69 @@ +import type { SystemSelectionAnswers } from '../app/types'; + +/** + * Much of the values returned by the app downloader prompting are derived from prompt answers and are not direct answer values. + * Since inquirer does not provide a way to return values that are not direct answers from prompts, this class will maintain the derived values + * across prompts statically for the lifespan of the prompting session. + * + */ +export class PromptState { + private static _systemSelection: SystemSelectionAnswers = {}; + private static _downloadedAppPackage?: Buffer; + + /** + * Returns the current state of the service config. + * + * @returns {SystemSelectionAnswers} service config + */ + public static get systemSelection(): SystemSelectionAnswers { + return this._systemSelection; + } + + /** + * Set the state of the system selection. + * + * @param {SystemSelectionAnswers} value - system selection value + */ + public static set systemSelection(value: Partial) { + this._systemSelection = value; + } + + /** + * Set the downloaded app package. + */ + public static set downloadedAppPackage(archive: Buffer) { + this._downloadedAppPackage = archive; + } + + /** + * Returns the downloaded app package. + * + * @returns {Buffer} downloaded app package + */ + public static get downloadedAppPackage(): Buffer { + return this._downloadedAppPackage ?? Buffer.alloc(0); + } + + /** + * Get the baseURL from the connected system's service provider defaults. + * + * @returns {string | undefined} baseURL + */ + public static get baseURL(): string | undefined { + return this._systemSelection.connectedSystem?.serviceProvider?.defaults?.baseURL; + } + + /** + * Get the sap-client parameter from the connected system's service provider defaults. + * + * @returns {string | undefined} sap-client + */ + public static get sapClient(): string | undefined { + return this._systemSelection.connectedSystem?.serviceProvider?.defaults?.params?.['sap-client']; + } + + static reset(): void { + PromptState.systemSelection = {}; + PromptState._downloadedAppPackage = undefined; + } +} diff --git a/packages/repo-app-download-sub-generator/src/prompts/prompts.ts b/packages/repo-app-download-sub-generator/src/prompts/prompts.ts new file mode 100644 index 0000000000..0862ace4fa --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/prompts/prompts.ts @@ -0,0 +1,123 @@ +import type { AppIndex, AbapServiceProvider } from '@sap-ux/axios-extension'; +import { getSystemSelectionQuestions } from '@sap-ux/odata-service-inquirer'; +import type { RepoAppDownloadAnswers, RepoAppDownloadQuestions, QuickDeployedAppConfig, AppInfo } from '../app/types'; +import { PromptNames } from '../app/types'; +import { t } from '../utils/i18n'; +import type { FileBrowserQuestion } from '@sap-ux/inquirer-common'; +import { validateFioriAppTargetFolder } from '@sap-ux/project-input-validator'; +import { PromptState } from './prompt-state'; +import { fetchAppListForSelectedSystem, formatAppChoices } from './prompt-helpers'; +import { downloadApp } from '../utils/download-utils'; + +/** + * Gets the target folder selection prompt. + * + * @param {string} [appRootPath] - The application root path. + * @param {string} appId - The application ID. + * @returns {FileBrowserQuestion} The target folder prompt configuration. + */ +const getTargetFolderPrompt = (appRootPath?: string, appId?: string): FileBrowserQuestion => { + return { + type: 'input', + name: PromptNames.targetFolder, + message: t('prompts.targetPath.message'), + guiType: 'folder-browser', + when: (answers: RepoAppDownloadAnswers) => { + // Display the prompt if appId is provided. This occurs when the generator is invoked + // as part of the quick deployment process from ADT. + if (appId) { + return true; + } + // If appId is not provided, check if the user has selected an app. + // If an app is selected, display the prompt accordingly. + return Boolean(answers?.selectedApp?.appId); + }, + guiOptions: { + applyDefaultWhenDirty: true, + mandatory: true, + breadcrumb: t('prompts.targetPath.breadcrumb') + }, + validate: async (target, answers: RepoAppDownloadAnswers): Promise => { + const selectedAppId = answers.selectedApp?.appId ?? appId; + return await validateFioriAppTargetFolder(target, selectedAppId, true); + }, + default: () => appRootPath + } as FileBrowserQuestion; +}; + +/** + * Retrieves prompts for selecting a system, app list, and target folder where the app will be generated. + * + * @param {string} [appRootPath] - The root path of the application. + * @param {QuickDeployedAppConfig} [quickDeployedAppConfig] - The quick deployed app configuration. + * @returns {Promise} A list of prompts for user interaction. + */ +export async function getPrompts( + appRootPath?: string, + quickDeployedAppConfig?: QuickDeployedAppConfig +): Promise { + try { + PromptState.reset(); + + const systemQuestions = await getSystemSelectionQuestions( + { + serviceSelection: { hide: true }, + systemSelection: { defaultChoice: quickDeployedAppConfig?.serviceProviderInfo?.name } + }, + false + ); + + let appList: AppIndex = []; + const appSelectionPrompt = [ + { + when: async (answers: RepoAppDownloadAnswers): Promise => { + if (answers[PromptNames.systemSelection]) { + appList = await fetchAppListForSelectedSystem( + systemQuestions.answers.connectedSystem?.serviceProvider as AbapServiceProvider, + quickDeployedAppConfig?.appId + ); + } + return !!systemQuestions.answers.connectedSystem?.serviceProvider; + }, + type: 'list', + name: PromptNames.selectedApp, + default: () => (quickDeployedAppConfig?.appId ? 0 : undefined), + guiOptions: { + mandatory: !!appList.length, + breadcrumb: t('prompts.appSelection.breadcrumb') + }, + message: t('prompts.appSelection.message'), + choices: (): { name: string; value: AppInfo }[] => (appList.length ? formatAppChoices(appList) : []), + validate: async (answers: AppInfo): Promise => { + // Quick deploy config exists but no apps found + if (quickDeployedAppConfig?.appId && appList.length === 0) { + return t('error.quickDeployedAppDownloadErrors.noAppsFound', { + appId: quickDeployedAppConfig.appId + }); + } + + // No apps available at all + if (appList.length === 0) { + return t('prompts.appSelection.noAppsDeployed'); + } + + // Valid app selected, try to download + if (answers?.appId) { + try { + await downloadApp(answers.repoName); + return true; + } catch (error) { + return t('error.appDownloadErrors.appDownloadFailure', { error: error.message }); + } + } + return false; + } + } + ]; + + const targetFolderPrompts = getTargetFolderPrompt(appRootPath, quickDeployedAppConfig?.appId); + return [...systemQuestions.prompts, ...appSelectionPrompt, targetFolderPrompts] as RepoAppDownloadQuestions[]; + } catch (error) { + throw new Error(`Failed to generate prompts: ${error.message}`); + } +} diff --git a/packages/repo-app-download-sub-generator/src/telemetryEvents/index.ts b/packages/repo-app-download-sub-generator/src/telemetryEvents/index.ts new file mode 100644 index 0000000000..76148247df --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/telemetryEvents/index.ts @@ -0,0 +1,6 @@ +/** + * Event names for telemetry for the generator when downloading an app from repository + */ +export enum EventName { + GENERATION_SUCCESS = 'GENERATION_SUCCESS' +} diff --git a/packages/repo-app-download-sub-generator/src/translations/repo-app-download-sub-generator.i18n.json b/packages/repo-app-download-sub-generator/src/translations/repo-app-download-sub-generator.i18n.json new file mode 100644 index 0000000000..5a7e7c68c6 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/translations/repo-app-download-sub-generator.i18n.json @@ -0,0 +1,67 @@ +{ + "error": { + "telemetry": "Failed to send telemetry data after downloading app from UI5 ABAP Repository. {{- error}}", + "qfaJsonNotFound": "{{- jsonFileName }} not found in the downloaded app", + "replaceWebappFilesError": "Error replacing files in the downloaded app: {{- error}}", + "requiredFieldsMissing": "Required fields are missing for app: {{- app }}. Check if the app is deployed correctly", + "applicationListFetchError": "Error fetching application list: {{- error}}", + "metadataFetchError": "Error fetching metadata: {{- error}}", + "appConfigGenError": "Error generating application configuration: {{- error}}", + "endPhase": "Error in end phase: {{- error}}", + "errorProcessingJsonFile": "Error processing JSON file: {{- error}}", + "validationErrors": { + "invalidMetadataPackage": "Invalid or missing package in metadata", + "invalidServiceName": "Invalid or missing serviceName in serviceBindingDetails", + "invalidServiceVersion": "Invalid or missing serviceVersion in serviceBindingDetails", + "invalidMainEntityName": "Invalid or missing mainEntityName in serviceBindingDetails", + "invalidModuleName": "Invalid or missing moduleName in serviceBindingDetails", + "invalidRepositoryName": "Invalid or missing repositoryName in serviceBindingDetails" + }, + "installationErrors": { + "npmInstall": "Error in install phase: {{- error}}", + "skippedInstallation": "Option `--skipInstall` was specified. Installation of dependencies will be skipped." + }, + "appDownloadErrors": { + "downloadedFileNotBufferError": "Error: The downloaded file is not a Buffer.", + "appDownloadFailure": "Error downloading app: {{- error}}", + "zipExtractionError": "Error extracting zip: {{- error}}" + }, + "eventHookErrors": { + "vscodeInstanceMissing": "Error: Missing VSCode instance in event hook", + "postGenCommandMissing": "Error: Missing postGenCommand in event hook", + "commandExecutionFailed": "Error executing postGenCommand in event hook: {{- error}}" + }, + "readManifestErrors": { + "manifestFileNotFound": "Error: Manifest file not found in the downloaded app", + "readManifestFailed": "Error: Failed to read manifest file", + "sapAppNotDefined": "Error: sap.app not defined in the manifest file", + "sourceTemplateNotSupported": "Error: Source template not supported", + "invalidManifestStructureError": "Invalid manifest structure: 'sap.ui5' or 'sap.app' are missing." + }, + "quickDeployedAppDownloadErrors": { + "noAppsFound": "No application with id {{ appId }} found in the system. Please check if the application is deployed correctly or select another app" + } + }, + "warn": { + "extractedFileNotFound": "Extracted file not found - {{- extractedFilePath}}" + }, + "prompts": { + "appSelection": { + "message": "App", + "hint": "Select the app to download", + "breadcrumb": "App", + "noAppsDeployed": "No applications deployed to this system can be downloaded. {{- help}}" + }, + "targetPath": { + "message": "Project folder path", + "breadcrumb": "Project Path" + } + }, + "readMe": { + "appDescription" : "This application was converted from a SAP Fiori app that was deployed from ADT using the ADT Quick Fiori Application generator", + "launchText": "In order to launch the generated app, simply run the following from the generated app root folder:\n\n```\n npm start\n```" + }, + "info": { + "repoAppDownloadCompleteMsg": "The selected application has been downloaded and updated to support SAP Fiori tools." + } +} diff --git a/packages/repo-app-download-sub-generator/src/utils/constants.ts b/packages/repo-app-download-sub-generator/src/utils/constants.ts new file mode 100644 index 0000000000..fea6cceb8c --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/constants.ts @@ -0,0 +1,46 @@ +import { PromptNames, type RepoAppDownloadAnswers } from '../app/types'; + +// Title and description for the generator +export const generatorTitle = 'Download ADT deployed app from UI5 ABAP repository'; +export const generatorDescription = + 'Download an application that was generated with the ADT Quick Fiori Application generator'; + +// Name of the generator used for Fiori app download +export const generatorName = '@sap-ux/repo-app-download-sub-generator'; +// The source template ID used for filtering the apps in the repository +export const adtSourceTemplateId = '@sap.adt.sevicebinding.deploy:lrop'; +// The source template ID used for Fiori app generation +export const fioriAppSourcetemplateId = '@sap/generator-fiori:lrop'; +// The name of the QFA JSON file provided with the downloaded app, containing all user inputs. +export const qfaJsonFileName = 'qfa.json'; + +// Default initial answers to use as a fallback. +export const defaultAnswers: RepoAppDownloadAnswers = { + [PromptNames.systemSelection]: {}, + [PromptNames.selectedApp]: { + appId: '', + title: '', + description: '', + repoName: '', + url: '' + }, + [PromptNames.targetFolder]: '' +}; + +// Path for storing the extracted files from repository +export const extractedFilePath = 'extractedFiles'; + +// Search parameters to filter applications by the source template ID +export const appListSearchParams = { + 'sap.app/sourceTemplate/id': adtSourceTemplateId +}; +// Fields to retrieve from the app list, useful for displaying app metadata +export const appListResultFields = [ + 'sap.app/id', // ID of the application + 'sap.app/title', // Title of the application + 'sap.app/description', // Description of the application + 'sap.app/sourceTemplate/id', // ID of the source template + 'repoName', // Repository name where the app is located + 'fileType', // Type of file (.zip etc) + 'url' // URL for accessing the app +]; diff --git a/packages/repo-app-download-sub-generator/src/utils/download-utils.ts b/packages/repo-app-download-sub-generator/src/utils/download-utils.ts new file mode 100644 index 0000000000..f7790c952f --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/download-utils.ts @@ -0,0 +1,42 @@ +import type { AbapServiceProvider } from '@sap-ux/axios-extension'; +import AdmZip from 'adm-zip'; +import { join } from 'path'; +import type { Editor } from 'mem-fs-editor'; +import { PromptState } from '../prompts/prompt-state'; +import { t } from './i18n'; +import RepoAppDownloadLogger from '../utils/logger'; + +/** + * Extracts a ZIP archive to a temporary directory. + * + * @param {string} extractedProjectPath - The path where the archive should be extracted. + * @param {Buffer} archive - The ZIP archive buffer. + * @param {Editor} fs - The file system editor. + */ +export async function extractZip(extractedProjectPath: string, archive: Buffer, fs: Editor): Promise { + try { + const zip = new AdmZip(archive); + zip.getEntries().forEach(function (zipEntry) { + if (!zipEntry.isDirectory) { + // Extract the file content + const fileContent = zipEntry.getData().toString('utf8'); + // Load the file content into mem-fs for use in the temporary extracted project directory + fs.write(join(extractedProjectPath, zipEntry.entryName), fileContent); + } + }); + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.appDownloadErrors.zipExtractionError', { error: error.message })); + } +} + +/** + * Downloads application files from the ABAP repository. + * + * @param {string} repoName - The repository name of the application. + */ +export async function downloadApp(repoName: string): Promise { + const serviceProvider = PromptState.systemSelection?.connectedSystem?.serviceProvider as AbapServiceProvider; + const downloadedAppPackage = await serviceProvider.getUi5AbapRepository().downloadFiles(repoName); + // store downloaded package in prompt state + PromptState.downloadedAppPackage = downloadedAppPackage; +} diff --git a/packages/repo-app-download-sub-generator/src/utils/event-hook.ts b/packages/repo-app-download-sub-generator/src/utils/event-hook.ts new file mode 100644 index 0000000000..e9db291c14 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/event-hook.ts @@ -0,0 +1,40 @@ +import RepoAppDownloadLogger from './logger'; +import { t } from './i18n'; +import type { VSCodeInstance } from '@sap-ux/fiori-generator-shared'; + +/** + * Context object for the App generation process. + * Contains the path of the project and the post-generation command. + */ +export interface RepoAppGenContext { + // The file path for the generated project + path: string; + // The post-generation command to be executed + postGenCommand: string; + // The VSCode instance to execute the command + vscodeInstance?: VSCodeInstance; +} + +/** + * Executes the post-generation command for the app. + * Runs the specified command in the context of the generated project, typically for tasks like refreshing or reloading the project in the editor. + * + * @param {RepoAppGenContext} context - The context containing the project path, post-generation command, and optional VSCode instance. + */ +export async function runPostAppGenHook(context: RepoAppGenContext): Promise { + try { + // Ensure that context has necessary values before proceeding + if (!context.vscodeInstance) { + RepoAppDownloadLogger.logger?.error(t('error.eventHookErrors.vscodeInstanceMissing')); + } + if (!context.postGenCommand || context.postGenCommand.trim() === '') { + RepoAppDownloadLogger.logger?.error(t('error.eventHookErrors.postGenCommandMissing')); + } + // Execute the post-generation command + await context.vscodeInstance?.commands?.executeCommand?.(context.postGenCommand, { + fsPath: context.path + }); + } catch (e) { + RepoAppDownloadLogger.logger?.error(t('error.eventHookErrors.commandExecutionFailed', e.message)); + } +} diff --git a/packages/repo-app-download-sub-generator/src/utils/file-helpers.ts b/packages/repo-app-download-sub-generator/src/utils/file-helpers.ts new file mode 100644 index 0000000000..e0f41627bb --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/file-helpers.ts @@ -0,0 +1,47 @@ +import { adtSourceTemplateId } from './constants'; +import type { Editor } from 'mem-fs-editor'; +import { type Manifest } from '@sap-ux/project-access'; +import { t } from './i18n'; +import RepoAppDownloadLogger from './logger'; +import type { QfaJsonConfig } from '../app/types'; + +/** + * + * @param filePath - Path to the JSON file + * @param fs - File system editor instance + * @returns - Parsed JSON object + */ +export function makeValidJson(filePath: string, fs: Editor): QfaJsonConfig { + try { + // Read the file contents + const fileContents = fs.read(filePath); + const validJson: QfaJsonConfig = JSON.parse(fileContents); + return validJson; + } catch (error) { + throw new Error(t('error.errorProcessingJsonFile', { error })); + } +} + +/** + * Reads and validates the `manifest.json` file. + * + * @param {string} manifesFilePath - Manifest file path. + * @param {Editor} fs - The file system editor. + * @returns {Manifest} The validated manifest object. + */ +export function readManifest(manifesFilePath: string, fs: Editor): Manifest { + if (!fs.exists(manifesFilePath)) { + RepoAppDownloadLogger.logger?.error(t('error.readManifestErrors.manifestFileNotFound')); + } + const manifest = fs.readJSON(manifesFilePath) as unknown as Manifest; + if (!manifest) { + RepoAppDownloadLogger.logger?.error(t('error.readManifestErrors.readManifestFailed')); + } + if (!manifest?.['sap.app']) { + RepoAppDownloadLogger.logger?.error(t('error.readManifestErrors.sapAppNotDefined')); + } + if (manifest?.['sap.app']?.sourceTemplate?.id !== adtSourceTemplateId) { + RepoAppDownloadLogger.logger?.error(t('error.readManifestErrors.sourceTemplateNotSupported')); + } + return manifest; +} diff --git a/packages/repo-app-download-sub-generator/src/utils/i18n.ts b/packages/repo-app-download-sub-generator/src/utils/i18n.ts new file mode 100644 index 0000000000..ad6d44baa4 --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/i18n.ts @@ -0,0 +1,32 @@ +import type { TOptions } from 'i18next'; +import i18next from 'i18next'; +import translations from '../translations/repo-app-download-sub-generator.i18n.json'; + +const repoAppDownloadGeneratorNs = 'repo-app-download-sub-generator'; + +/** + * Initialize i18next with the translations for this module. + */ +export async function initI18n(): Promise { + await i18next.init({ lng: 'en', fallbackLng: 'en' }, () => + i18next.addResourceBundle('en', repoAppDownloadGeneratorNs, translations) + ); +} + +/** + * Helper function facading the call to i18next. Unless a namespace option is provided the local namespace will be used. + * + * @param key i18n key + * @param options additional options + * @returns {string} localized string stored for the given key + */ +export function t(key: string, options?: TOptions): string { + if (!options?.ns) { + options = Object.assign(options ?? {}, { ns: repoAppDownloadGeneratorNs }); + } + return i18next.t(key, options); +} + +initI18n().catch(() => { + // Needed for lint +}); diff --git a/packages/repo-app-download-sub-generator/src/utils/logger.ts b/packages/repo-app-download-sub-generator/src/utils/logger.ts new file mode 100644 index 0000000000..046f20590e --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/logger.ts @@ -0,0 +1,50 @@ +import { DefaultLogger, LogWrapper, type ILogWrapper } from '@sap-ux/fiori-generator-shared'; +import type { Logger } from 'yeoman-environment'; +import type { IVSCodeExtLogger, LogLevel } from '@vscode-logging/logger'; + +/** + * Static logger prevents passing of logger references through all functions, as this is a cross-cutting concern. + */ +export default class RepoAppDownloadLogger { + private static _logger: ILogWrapper = DefaultLogger; + + /** + * Get the logger. + * + * @returns the logger + */ + public static get logger(): ILogWrapper { + return RepoAppDownloadLogger._logger; + } + + /** + * Set the logger. + * + * @param value the logger to set + */ + public static set logger(value: ILogWrapper) { + RepoAppDownloadLogger._logger = value; + } + + /** + * Configures the logger. + * + * @param loggerName - the logger name + * @param yoLogger - the yeoman logger + * @param logWrapper - log wrapper instance + * @param logLevel - the log level + * @param vscLogger - the vscode logger + * @param vscode - the vscode instance + */ + static configureLogging( + loggerName: string, + yoLogger: Logger, + logWrapper?: LogWrapper, + logLevel?: LogLevel, + vscLogger?: IVSCodeExtLogger, + vscode?: unknown + ): void { + const logger = logWrapper ?? new LogWrapper(loggerName, yoLogger, logLevel, vscLogger, vscode); + RepoAppDownloadLogger.logger = logger; + } +} diff --git a/packages/repo-app-download-sub-generator/src/utils/updates.ts b/packages/repo-app-download-sub-generator/src/utils/updates.ts new file mode 100644 index 0000000000..df3e7b616f --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/updates.ts @@ -0,0 +1,86 @@ +import { join } from 'path'; +import type { Editor } from 'mem-fs-editor'; +import { FileName, DirName } from '@sap-ux/project-access'; +import { t } from './i18n'; +import RepoAppDownloadLogger from './logger'; +import { isInternalFeaturesSettingEnabled } from '@sap-ux/feature-toggle'; +import { getUI5Versions } from '@sap-ux/ui5-info'; +import { readManifest } from './file-helpers'; +import { fioriAppSourcetemplateId } from './constants'; + +/** + * Validates and updates the UI5 version in the manifest. + * + * - If the minUI5Version in the manifest is not found in the list of released UI5 versions, + * it updates the manifest with the closest released version. + * - If internal features are enabled, it sets the minUI5Version to '${sap.ui5.dist.version}'. + * + * @param {string} manifestFilePath - The manifest file path. + * @param {Editor} fs - The file system editor instance. + * @returns {Promise} - The updated manifest object. + * @throws {Error} - Throws an error if the manifest structure is invalid or no fallback version is available. + */ +export async function validateAndUpdateManifestUI5Version(manifestFilePath: string, fs: Editor): Promise { + const manifestJson = readManifest(manifestFilePath, fs); + if (!manifestJson?.['sap.ui5']?.dependencies || !manifestJson?.['sap.app']?.sourceTemplate) { + // Check if the manifest structure is valid) { + throw new Error(t('error.readManifestErrors.invalidManifestStructureError')); + } + + const manifestUi5Version = manifestJson['sap.ui5']?.dependencies?.minUI5Version; + const availableUI5Versions = await getUI5Versions({ includeMaintained: true }); + + // Check if the manifest version exists in the list of released versions + const ui5VersionAvailable = availableUI5Versions.find( + (ui5Version: { version: string }) => ui5Version.version === manifestUi5Version + ); + + if (ui5VersionAvailable) { + // Return the manifest as it is if the version is valid + // No changes needed + } else if (isInternalFeaturesSettingEnabled()) { + // Handle internal features setting + manifestJson['sap.ui5'].dependencies.minUI5Version = '${sap.ui5.dist.version}'; + } else { + // Handle fallback to the closest released version + const closestAvailableUi5Version = availableUI5Versions[0]?.version; + manifestJson['sap.ui5'].dependencies.minUI5Version = closestAvailableUi5Version; + } + manifestJson['sap.app'].sourceTemplate.id = fioriAppSourcetemplateId; + // update manifest at extracted path + fs.writeJSON(manifestFilePath, manifestJson, undefined, 2); +} + +/** + * Replaces the specified files in the `webapp` directory with the corresponding files from the `extractedPath`. + * + * @param {string} projectPath - The path to the downloaded App. + * @param {string} extractedPath - The path from which files will be copied. + * @param {Editor} fs - The file system editor instance to modify files in memory. + */ +export async function replaceWebappFiles(projectPath: string, extractedPath: string, fs: Editor): Promise { + try { + const webappPath = join(projectPath, DirName.Webapp); + // Define the paths of the files to be replaced + const filesToReplace = [ + { webappFile: FileName.Manifest, extractedFile: FileName.Manifest }, + { webappFile: join('i18n', 'i18n.properties'), extractedFile: join('i18n', 'i18n.properties') }, + { webappFile: 'index.html', extractedFile: 'index.html' }, + { webappFile: 'Component.js', extractedFile: 'component.js' } + ]; + // Loop through each file and perform the replacement + for (const { webappFile, extractedFile } of filesToReplace) { + const webappFilePath = join(webappPath, webappFile); + const extractedFilePath = join(extractedPath, extractedFile); + + // Check if the extracted file exists before replacing + if (fs.exists(extractedFilePath)) { + fs.copy(extractedFilePath, webappFilePath); + } else { + RepoAppDownloadLogger.logger?.warn(t('warn.extractedFileNotFound', { extractedFilePath })); + } + } + } catch (error) { + RepoAppDownloadLogger.logger?.error(t('error.replaceWebappFilesError', { error })); + } +} diff --git a/packages/repo-app-download-sub-generator/src/utils/validators.ts b/packages/repo-app-download-sub-generator/src/utils/validators.ts new file mode 100644 index 0000000000..363499f45b --- /dev/null +++ b/packages/repo-app-download-sub-generator/src/utils/validators.ts @@ -0,0 +1,94 @@ +import { t } from '../utils/i18n'; +import type { QfaJsonConfig } from '../app/types'; +import RepoAppDownloadLogger from '../utils/logger'; +import { PromptState } from '../prompts/prompt-state'; + +/** + * Validates the metadata section of the app configuration. + * + * @param {QfaJsonConfig['metadata']} metadata - The metadata object. + * @returns {boolean} - Returns true if valid, false otherwise. + */ +const validateMetadata = (metadata: QfaJsonConfig['metadata']): boolean => { + if (!metadata.package || typeof metadata.package !== 'string') { + RepoAppDownloadLogger.logger?.error(t('error.invalidMetadataPackage')); + return false; + } + return true; +}; + +/** + * Validates the service binding details section of the app configuration. + * + * @param {QfaJsonConfig['serviceBindingDetails']} serviceBinding - The service binding details object. + * @returns {boolean} - Returns true if valid, false otherwise. + */ +const validateServiceBindingDetails = (serviceBinding: QfaJsonConfig['serviceBindingDetails']): boolean => { + if (!serviceBinding.serviceName || typeof serviceBinding.serviceName !== 'string') { + RepoAppDownloadLogger.logger?.error(t('error.invalidServiceName')); + return false; + } + if (!serviceBinding.serviceVersion || typeof serviceBinding.serviceVersion !== 'string') { + RepoAppDownloadLogger.logger?.error(t('error.invalidServiceVersion')); + return false; + } + if (!serviceBinding.mainEntityName || typeof serviceBinding.mainEntityName !== 'string') { + RepoAppDownloadLogger.logger?.error(t('error.invalidMainEntityName')); + return false; + } + return true; +}; + +/** + * Validates the project attribute section of the app configuration. + * + * @param {QfaJsonConfig['projectAttribute']} projectAttribute - The project attribute object. + * @returns {boolean} - Returns true if valid, false otherwise. + */ +const validateProjectAttribute = (projectAttribute: QfaJsonConfig['projectAttribute']): boolean => { + if (!projectAttribute.moduleName || typeof projectAttribute.moduleName !== 'string') { + RepoAppDownloadLogger.logger?.error(t('error.invalidModuleName')); + return false; + } + return true; +}; + +/** + * Validates the deployment details section of the app configuration. + * + * @param {QfaJsonConfig['deploymentDetails']} deploymentDetails - The deployment details object. + * @returns {boolean} - Returns true if valid, false otherwise. + */ +const validateDeploymentDetails = (deploymentDetails: QfaJsonConfig['deploymentDetails']): boolean => { + if (!deploymentDetails.repositoryName) { + RepoAppDownloadLogger.logger?.error(t('error.invalidRepositoryName')); + return false; + } + return true; +}; + +/** + * Validates the entire app configuration. + * + * @param {QfaJsonConfig} config - The QFA JSON configuration containing app details. + * @returns {boolean} - Returns true if the configuration is valid, false otherwise. + */ +export const validateQfaJsonFile = (config: QfaJsonConfig): boolean => { + return ( + validateMetadata(config.metadata) && + validateServiceBindingDetails(config.serviceBindingDetails) && + validateProjectAttribute(config.projectAttribute) && + validateDeploymentDetails(config.deploymentDetails) + ); +}; + +/** + * Validates the prompt state for the app download process. + * + * @param {string} targetFolder - The target folder for the app download. + * @param {string} [appId] - The selected app id. + * @returns {boolean} - Returns true if the prompt state is valid, false otherwise. + */ +export const isValidPromptState = (targetFolder: string, appId?: string): boolean => { + return !!(PromptState.systemSelection.connectedSystem?.serviceProvider && appId && targetFolder); +}; diff --git a/packages/repo-app-download-sub-generator/test/app-config.test.ts b/packages/repo-app-download-sub-generator/test/app-config.test.ts new file mode 100644 index 0000000000..f6071ab856 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/app-config.test.ts @@ -0,0 +1,293 @@ +import { getAppConfig, getAbapDeployConfig } from '../src/app/app-config'; +import type { AbapServiceProvider } from '@sap-ux/axios-extension'; +import type { Editor } from 'mem-fs-editor'; +import { getUI5Versions } from '@sap-ux/ui5-info'; +import { getMinimumUI5Version } from '@sap-ux/project-access'; +import { PromptState } from '../src/prompts/prompt-state'; +import type { AppInfo, QfaJsonConfig } from '../src/app/types'; +import { readManifest } from '../src/utils/file-helpers'; +import { t } from '../src/utils/i18n'; +import { fioriAppSourcetemplateId } from '../src/utils/constants'; +import RepoAppDownloadLogger from '../src/utils/logger'; +import { TestFixture } from './fixtures'; +import { join } from 'path'; +import { qfaJsonFileName } from '../src/utils/constants'; + +jest.mock('../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +jest.mock('../src/utils/file-helpers', () => ({ + ...jest.requireActual('../src/utils/file-helpers'), + readManifest: jest.fn() +})); + +jest.mock('@sap-ux/ui5-info', () => ({ + ...jest.requireActual('@sap-ux/ui5-info'), + getUI5Versions: jest.fn() +})); + +jest.mock('@sap-ux/project-access', () => ({ + ...jest.requireActual('@sap-ux/project-access'), + getMinimumUI5Version: jest.fn() +})); + + +jest.mock('@sap-ux/feature-toggle', () => ({ + isInternalFeaturesSettingEnabled: jest.fn(), +})); + +const testFixture = new TestFixture(); +const mockQfaJson: QfaJsonConfig = JSON.parse(testFixture.getContents(join('downloaded-app', qfaJsonFileName))); + +describe('getAppConfig', () => { + const mockApp: AppInfo = { + appId: 'testAppId', + title: 'Test App', + description: 'Test Description', + repoName: 'testRepoName', + url: 'https://example.com/testApp' + }; + const mockFs = {} as Editor; + const expectedAppConfig = { + app: { + id: mockApp.appId, + title: mockApp.title, + description: mockApp.description, + flpAppId: `${mockApp.appId}-tile`, + sourceTemplate: { id: fioriAppSourcetemplateId }, + projectType: 'EDMXBackend' + }, + package: { + name: mockApp.appId, + description: mockApp.description, + devDependencies: {}, + scripts: {}, + version: '1.0.0' + }, + template: { + type: expect.any(String), + settings: { + entityConfig: { + mainEntityName: mockQfaJson.serviceBindingDetails.mainEntityName + } + } + }, + service: { + path: '/odata/service', + version: expect.any(String), + metadata: undefined, + url: 'https://test-url.com' + }, + appOptions: { + addAnnotations: true, + addTests: true + } + }; + + const mockManifest = { + 'sap.app': { + dataSources: { + mainService: { + uri: '/odata/service', + settings: { odataVersion: '4.0' } + } + }, + applicationVersion: { version: '1.0.0' } + } + }; + + const availableUI5Versions = [{ version: '1.88.0' }]; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + (getUI5Versions as jest.Mock).mockResolvedValue(availableUI5Versions); + }); + + it('should generate app configuration successfully', async () => { + const mockServiceProvider = { + defaults: { + baseURL: 'https://test-url.com', + params: { 'sap-client': '100' } + } + } as unknown as AbapServiceProvider; + + PromptState.systemSelection = { + connectedSystem: { serviceProvider: mockServiceProvider } + }; + + (readManifest as jest.Mock).mockReturnValue(mockManifest); + (getMinimumUI5Version as jest.Mock).mockReturnValue('1.90.0'); + const mockQfaJsonWithoutNavEntity = { + ...mockQfaJson, + serviceBindingDetails: { + name: mockQfaJson.serviceBindingDetails.name, + serviceName: mockQfaJson.serviceBindingDetails.serviceName, + serviceVersion: mockQfaJson.serviceBindingDetails.serviceVersion, + mainEntityName: mockQfaJson.serviceBindingDetails.mainEntityName, + } + } + const result = await getAppConfig(mockApp, '/path/to/project', mockQfaJsonWithoutNavEntity, mockFs); + expect(result).toEqual(expectedAppConfig); + }); + + it('should generate app configuration successfully when navigation entity is provided', async () => { + const mockManifest = { + 'sap.app': { + dataSources: { + mainService: { + uri: '/odata/service', + settings: { odataVersion: '4.0' } + } + }, + applicationVersion: { version: '1.0.0' } + } + }; + + const mockServiceProvider = { + defaults: { + baseURL: 'https://test-url.com', + params: { 'sap-client': '100' } + } + } as unknown as AbapServiceProvider; + + PromptState.systemSelection = { + connectedSystem: { serviceProvider: mockServiceProvider } + }; + + (readManifest as jest.Mock).mockReturnValue(mockManifest); + (getMinimumUI5Version as jest.Mock).mockReturnValue('1.90.0'); + + const mockQfaJsonJsonWithNavEntity = { + ...mockQfaJson, + service_binding_details: { + ...mockQfaJson.serviceBindingDetails, + main_entity_name: mockQfaJson.serviceBindingDetails.mainEntityName, + navigation_entity: mockQfaJson.serviceBindingDetails.navigationEntity + } + } + const result = await getAppConfig(mockApp, '/path/to/project', mockQfaJsonJsonWithNavEntity, mockFs); + expect(result).toEqual(expectedAppConfig); + }); + + it('should throw an error if manifest data sources are missing', async () => { + const mockManifest = { + 'sap.app': {} + }; + + (readManifest as jest.Mock).mockReturnValue(mockManifest); + const result = await getAppConfig(mockApp, '/path/to/project', mockQfaJson, mockFs); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.dataSourcesNotFound')); + }); + + it('should log an error if fetchServiceMetadata throws an error', async () => { + const mockManifest = { + 'sap.app': { + dataSources: { + mainService: { + uri: '/odata/service', + settings: { odataVersion: '4.0' } + } + } + } + }; + + const errorMsg = 'Metadata fetch failed'; + const mockServiceProvider = { + defaults: { + baseURL: 'https://test-url.com', + params: { 'sap-client': '100' } + }, + service: jest.fn().mockReturnValue({ + metadata: jest.fn().mockRejectedValue(new Error(errorMsg)) + }) + } as unknown as AbapServiceProvider; + + PromptState.systemSelection = { + connectedSystem: { serviceProvider: mockServiceProvider } + }; + + (readManifest as jest.Mock).mockReturnValue(mockManifest); + + await getAppConfig(mockApp, '/path/to/project', mockQfaJson, mockFs); + expect(RepoAppDownloadLogger.logger?.error).toHaveBeenCalledWith(t('error.metadataFetchError', { error: errorMsg })); + }); + + it('should generate app config when minUi5Version is not provided in manifest', async () => { + const mockManifest = { + 'sap.app': { + dataSources: { + mainService: { + uri: '/odata/service', + settings: { odataVersion: '4.0' } + } + }, + applicationVersion: { version: '1.0.0' } + } + }; + + const mockServiceProvider = { + defaults: { + baseURL: 'https://test-url.com', + params: { 'sap-client': '100' } + }, + service: jest.fn().mockReturnValue({ + metadata: jest.fn().mockResolvedValue({ + dataServices: { + schema: [] + } + }) + }) + } as unknown as AbapServiceProvider; + + PromptState.systemSelection = { + connectedSystem: { serviceProvider: mockServiceProvider } + }; + (readManifest as jest.Mock).mockReturnValue(mockManifest); + (getMinimumUI5Version as jest.Mock).mockReturnValue('1.90.0'); + + const mockQfaJsonJsonWithoutUi5Version = { + ...mockQfaJson, + projectAttribute: { + ...mockQfaJson.projectAttribute, + minimum_ui5_version: null + } + } as unknown as QfaJsonConfig; + await getAppConfig(mockApp, '/path/to/project', mockQfaJsonJsonWithoutUi5Version, mockFs); + expect(RepoAppDownloadLogger.logger?.error).not.toHaveBeenCalled(); + }); + +}); + +describe('getAbapDeployConfig', () => { + it('should generate the correct deployment configuration', () => { + const app: AppInfo = { + url: 'https://target-url.com', + repoName: 'TEST_REPO', + appId: 'TEST_APP_ID', + title: 'Test App', + description: 'Test Description' + }; + + const expectedConfig = { + target: { + url: 'https://test-url.com', + client: '100', + destination: 'TEST_REPO' + }, + app: { + name: 'TEST_REPOSITORY_NAME', + package: 'TEST_PACKAGE', + description: 'TEST_REPOSITORY_DESCRIPTION', + transport: 'REPLACE_WITH_TRANSPORT' + } + }; + const result = getAbapDeployConfig(app, mockQfaJson); + expect(result).toEqual(expectedConfig); + }); +}); + + \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/app.test.ts b/packages/repo-app-download-sub-generator/test/app.test.ts new file mode 100644 index 0000000000..cf290e3b04 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/app.test.ts @@ -0,0 +1,402 @@ +import yeomanTest from 'yeoman-test'; +import { AppWizard, MessageType } from '@sap-devx/yeoman-ui-types'; +import { join } from 'path'; +import RepoAppDownloadGenerator from '../src/app'; +import * as prompts from '../src/prompts/prompts'; +import { PromptNames } from '../src/app/types'; +import fs from 'fs'; +import { TestFixture } from './fixtures'; +import { getAppConfig } from '../src/app/app-config'; +import { OdataVersion } from '@sap-ux/odata-service-inquirer'; +import { TemplateType, type FioriElementsApp, type LROPSettings } from '@sap-ux/fiori-elements-writer'; +import { adtSourceTemplateId, fioriAppSourcetemplateId, extractedFilePath } from '../src/utils/constants'; +import { removeSync } from 'fs-extra'; +import { isValidPromptState } from '../src/utils/validators'; +import { hostEnvironment, sendTelemetry } from '@sap-ux/fiori-generator-shared'; +import { FileName, DirName, type Manifest } from '@sap-ux/project-access'; +import RepoAppDownloadLogger from '../src/utils/logger'; +import { t } from '../src/utils/i18n'; +import { type AbapServiceProvider } from '@sap-ux/axios-extension'; +import { fetchAppListForSelectedSystem } from '../src/prompts/prompt-helpers'; + +jest.setTimeout(60000); +jest.mock('../src/prompts/prompt-helpers', () => ({ + ...jest.requireActual('../src/prompts/prompt-helpers'), + fetchAppListForSelectedSystem: jest.fn() +})); + +jest.mock('../src/utils/logger', () => ({ + ...jest.requireActual('../src/utils/logger'), + logger: { + error: jest.fn(), + info: jest.fn(), + warn: jest.fn() + }, + configureLogging: jest.fn() +})); + +jest.mock('../src/utils/download-utils'); +jest.mock('../src/app/app-config', () => ({ + ...jest.requireActual('../src/app/app-config'), + getAppConfig: jest.fn() +})); +jest.mock('../src/utils/validators'); +jest.mock('@sap-ux/fiori-generator-shared', () => { + return { + ...(jest.requireActual('@sap-ux/fiori-generator-shared') as {}), + TelemetryHelper: { + initTelemetrySettings: jest.fn(), + createTelemetryData: jest.fn().mockReturnValue({ + OperatingSystem: 'CLI', + Platform: 'darwin' + }) + }, + sendTelemetry: jest.fn(), + isExtensionInstalled: jest.fn(), + getHostEnvironment: () => { + return hostEnvironment.cli; + } + }; +}); +const mockSendTelemetry = sendTelemetry as jest.Mock; + +function createAppConfig(appId: string, metadata: string): FioriElementsApp { + return { + app: { + id: appId, + title: 'App 1', + description: 'App 1 description', + sourceTemplate: { id: adtSourceTemplateId }, + projectType: 'EDMXBackend', + flpAppId: `app-1-tile` + }, + package: { + name: appId, + description: 'App 1 description', + devDependencies: {}, + scripts: {}, + version: '0.0.1' + }, + template: { + type: TemplateType.ListReportObjectPage, + settings: { + entityConfig: { mainEntityName: 'Booking' } + } + }, + service: { + path: '/sap/opu/odata4/sap/zsb_travel_draft/srvd/dmo/ui_travel_d_d/0001/', + version: OdataVersion.v4, + metadata: metadata, + url: 'url-1' + }, + appOptions: { + addAnnotations: true, + addTests: true + }, + ui5: { + version: '1.100.0' + } + }; +} + +function mockPrompts(testOutputDir: string): void { + jest.spyOn(prompts, 'getPrompts').mockResolvedValue([ + { + type: 'list', + name: PromptNames.systemSelection, + message: 'Select a system', + choices: [ + { name: 'system1', value: 'system1' }, + { name: 'system2', value: 'system2' }, + { name: 'system3', value: 'system3' } + ] + }, + { + type: 'list', + name: PromptNames.selectedApp, + message: 'Select an app', + choices: [ + { name: 'App 1', value: { appId: 'app-1', title: 'App 1', description: 'App 1 description', repoName: 'app-1-repo', url: 'url-1' } }, + { name: 'App 2', value: { appId: 'app-2', title: 'App 2', description: 'App 2 description', repoName: 'app-2-repo', url: 'url-2' } } + ] + }, + { + type: 'input', + name: PromptNames.targetFolder, + message: 'Enter the target folder', + default: testOutputDir + } + ] as any); +} + +function copyFilesToExtractedProjectPath( + testFixtureDir: string, + extractedProjectPath: string +): void { + if (!fs.existsSync(extractedProjectPath)) { + fs.mkdirSync(extractedProjectPath, { recursive: true }); + } + // List all files in the test fixture directory + const files = fs.readdirSync(testFixtureDir); + // Copy each file to the extracted project path + files.forEach((file) => { + const sourceFilePath = join(testFixtureDir, file); + const destinationFilePath = join(extractedProjectPath, file); + if(file === 'i18n') { + // Create the directory if it doesn't exist + if (!fs.existsSync(destinationFilePath)) { + fs.mkdirSync(destinationFilePath, { recursive: true }); + } + // Copy the i18n.properties file + fs.copyFileSync(join(sourceFilePath, 'i18n.properties'), join(destinationFilePath, 'i18n.properties')); + } else { + // Copy the file + fs.copyFileSync(sourceFilePath, destinationFilePath); + } + }); +} + + +function verifyGeneratedFiles(testOutputDir: string, appId: string, testFixtureDir: string): void { + const projectPath = join(`${testOutputDir}/${appId}`); + expect(fs.existsSync(projectPath)).toBe(true); + + const expectedFiles = [ + FileName.Ui5Yaml, + FileName.Ui5LocalYaml, + FileName.Ui5MockYaml, + FileName.UI5DeployYaml, + FileName.Package, + 'README.md', + DirName.Webapp, + join(DirName.Webapp, FileName.Manifest), + join(DirName.Webapp, 'index.html'), + join(DirName.Webapp, 'Component.js'), + join(DirName.Webapp, 'test'), + join(DirName.Webapp, DirName.LocalService, 'mainService', 'metadata.xml'), + join(DirName.Webapp, 'i18n', 'i18n.properties'), + join(DirName.Webapp, DirName.Annotations, 'annotation.xml') + ]; + + expectedFiles.forEach((file) => { + const filePath = join(projectPath, file); + expect(fs.existsSync(filePath)).toBe(true); + }); + // after converting to fiori app, manifest will be updated with fiori app source template id + const extractedManifest = JSON.parse( + fs.readFileSync(join(testFixtureDir, FileName.Manifest), 'utf-8') + ) as Manifest; + if (extractedManifest && extractedManifest['sap.app'] && extractedManifest['sap.app'].sourceTemplate) { + extractedManifest['sap.app'].sourceTemplate.id = fioriAppSourcetemplateId; + } + const projectManifest = JSON.parse( + fs.readFileSync(join(projectPath, DirName.Webapp, FileName.Manifest), 'utf-8') + ); + expect(projectManifest).toEqual(extractedManifest); + expect(fs.readFileSync(join(projectPath, DirName.Webapp, 'i18n', 'i18n.properties'), 'utf-8')).toBe( + fs.readFileSync(join(testFixtureDir, 'i18n', 'i18n.properties'), 'utf-8') + ); + expect(fs.readFileSync(join(projectPath, DirName.Webapp, 'index.html'), 'utf-8')).toBe( + fs.readFileSync(join(testFixtureDir, 'index.html'), 'utf-8') + ); +} + +describe('Repo App Download', () => { + const testFixture = new TestFixture(); + const repoAppDownloadGenPath = join(__dirname, '../src/app/index.ts'); + const testOutputDir = join(__dirname, 'test-output'); + const metadata = testFixture.getContents('metadata.xml'); + let appConfig: FioriElementsApp; + let mockVSCode: any; + const mockAppWizard: Partial = { + setHeaderTitle: jest.fn(), + showWarning: jest.fn(), + showError: jest.fn(), + showInformation: jest.fn() + }; + const appId = 'test-app-id', repoName = 'app-1-repo'; + const extractedProjectPath = join(testOutputDir, appId, extractedFilePath); + const testFixtureDir = join(__dirname, 'fixtures', 'downloaded-app'); + copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath); + + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetModules(); + removeSync(testOutputDir); + }) + + beforeEach(() => { + copyFilesToExtractedProjectPath(testFixtureDir, extractedProjectPath); + appConfig = createAppConfig(appId, metadata); + mockPrompts(testOutputDir); + mockVSCode = { + workspace: { + workspaceFolders: [], + updateWorkspaceFolders: jest.fn() + } + }; + }); + + it('Should successfully run app download from repository', async () => { + (isValidPromptState as jest.Mock).mockReturnValue(true); + (getAppConfig as jest.Mock).mockResolvedValue(appConfig); + await expect( + yeomanTest + .run(RepoAppDownloadGenerator, { + resolved: repoAppDownloadGenPath + }) + .cd('.') + .withOptions({ + appRootPath: testOutputDir, + appWizard: mockAppWizard, + vscode: mockVSCode, + skipInstall: true + }) + .withPrompts({ + systemSelection: 'system3', + selectedApp: { + appId: appConfig.app.id, + title: appConfig.app.title, + description: appConfig.app.description, + repoName: 'app-1-repo', + url: 'url-1' + }, + targetFolder: testOutputDir + }) + ) + .resolves.not.toThrow(); + verifyGeneratedFiles(testOutputDir, appId, testFixtureDir); + expect(mockAppWizard.showInformation).toHaveBeenCalledWith(t('info.repoAppDownloadCompleteMsg'), MessageType.notification); + expect(RepoAppDownloadLogger.logger.info).toHaveBeenCalledWith(t('info.installationErrors.skippedInstallation')); + }); + + it('Should not throw error in end phase if telemetry fails', async () => { + const errorMsg = 'Telemetry error'; + mockSendTelemetry.mockRejectedValue(new Error(errorMsg)); + (isValidPromptState as jest.Mock).mockReturnValue(true); + (getAppConfig as jest.Mock).mockResolvedValue(appConfig); + + await expect( + yeomanTest + .run(RepoAppDownloadGenerator, { + resolved: repoAppDownloadGenPath + }) + .cd('.') + .withOptions({ + appRootPath: testOutputDir, + appWizard: mockAppWizard, + vscode: mockVSCode + }) + .withPrompts({ + systemSelection: 'system3', + selectedApp: { + appId: appConfig.app.id, + title: appConfig.app.title, + description: appConfig.app.description, + repoName: 'app-1-repo', + url: 'url-1' + }, + targetFolder: testOutputDir + }) + ) + .resolves.not.toThrow(); + expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith(t('error.telemetry', { error: errorMsg })); + verifyGeneratedFiles(testOutputDir, appId, testFixtureDir); + }); + + it('Should execute post app gen hook event when postGenCommand is provided', async () => { + (isValidPromptState as jest.Mock).mockReturnValue(true); + (getAppConfig as jest.Mock).mockResolvedValue(appConfig); + + await expect( + yeomanTest + .run(RepoAppDownloadGenerator, { + resolved: repoAppDownloadGenPath + }) + .cd('.') + .withOptions({ + appRootPath: testOutputDir, + appWizard: mockAppWizard, + vscode: mockVSCode, + data: { + postGenCommand: 'test-post-gen-command' + } + }) + .withPrompts({ + systemSelection: 'system3', + selectedApp: { + appId: appConfig.app.id, + title: appConfig.app.title, + description: appConfig.app.description, + repoName: 'app-1-repo', + url: 'url-1' + }, + targetFolder: testOutputDir + }) + ) + .resolves.not.toThrow(); + verifyGeneratedFiles(testOutputDir, appId, testFixtureDir); + }); + + it('Should successfully download a quick deployed app from repostory', async () => { + (isValidPromptState as jest.Mock).mockReturnValue(true); + (getAppConfig as jest.Mock).mockResolvedValue(appConfig); + (fetchAppListForSelectedSystem as jest.Mock).mockResolvedValue([ + { + 'sap.app/id': appConfig.app.id, + 'sap.app/title': appConfig.app.title, + repoName: repoName, + url: 'url-1' + } + ]); + const mockServiceProvider = { + defaults: { baseURL: 'https://test-url.com' }, + service: jest.fn().mockReturnValue({ + metadata: jest.fn().mockResolvedValue({ + dataServices: { + schema: [] + } + }) + }) + } as unknown as AbapServiceProvider; + + await expect( + yeomanTest + .run(RepoAppDownloadGenerator, { + resolved: repoAppDownloadGenPath + }) + .cd('.') + .withOptions({ + appRootPath: testOutputDir, + appWizard: mockAppWizard, + vscode: mockVSCode, + skipInstall: false, + data: { + postGenCommand: 'test-post-gen-command', + quickDeployedAppConfig: { + appId: appConfig.app.id, + appUrl: 'https://app-url.com/app', + serviceProviderInfo: { + serviceUrl: 'https://test-url.com', + name: 'system3' + } + } + } + }) + .withPrompts({ + systemSelection: 'system3', + selectedApp: { + appId: appConfig.app.id, + title: appConfig.app.title, + description: appConfig.app.description, + repoName: repoName, + url: 'url-1' + }, + targetFolder: testOutputDir + }) + ) + .resolves.not.toThrow(); + verifyGeneratedFiles(testOutputDir, appId, testFixtureDir); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/component.js b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/component.js new file mode 100644 index 0000000000..a0f7626475 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/component.js @@ -0,0 +1,11 @@ +sap.ui.define( + ["sap/fe/core/AppComponent"], + function (Component){ + "use strict"; + return Component.extend("test-app-id.Component",{ + metadata:{ + manifest: "json" + } + }); + } +); diff --git a/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/i18n/i18n.properties b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/i18n/i18n.properties new file mode 100644 index 0000000000..24e8a22c51 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/i18n/i18n.properties @@ -0,0 +1,5 @@ +#XTIT: Application title +appTitle=Travel Approver 2.0 + +#XTIT: Application description +appDescription=Travel Approver 2.0 diff --git a/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/index.html b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/index.html new file mode 100644 index 0000000000..c970d9a6f5 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/index.html @@ -0,0 +1,31 @@ + + + + + + + Fiori Application Preview + + + + +
+
+ + diff --git a/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/manifest.json b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/manifest.json new file mode 100644 index 0000000000..3eef953991 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/manifest.json @@ -0,0 +1,189 @@ +{ + "_version": "1.65.0", + "sap.app": { + "id": "test-app-id", + "type": "application", + "i18n": "i18n/i18n.properties", + "applicationVersion": { + "version": "0.0.1" + }, + "title": "{{appTitle}}", + "description": "{{appDescription}}", + "resources": "resources.json", + "sourceTemplate": { + "id": "@sap.adt.sevicebinding.deploy:lrop", + "version": "1.0.0", + "toolsId": "15AB9F96A8DF1FE085AC7E6BBC288DEE" + }, + "dataSources": { + "mainService": { + "uri": "/sap/opu/odata4/sap/zsb_travel_draft_2/srvd/dmo/ui_travel_d_d/0001/", + "type": "OData", + "settings": { + "odataVersion": "4.0" + } + } + } + }, + "sap.ui": { + "technology": "UI5" + }, + "sap.ui5": { + "flexEnabled": true, + "resources": { + "js": [], + "css": [] + }, + "dependencies": { + "minUI5Version": "1.134.1", + "libs": { + "sap.fe.templates": {} + }, + "components": {} + }, + "models": { + "i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "settings": { + "bundleName": "test-app-id.i18n.i18n" + } + }, + "@i18n": { + "type": "sap.ui.model.resource.ResourceModel", + "uri": "i18n/i18n.properties" + }, + "": { + "dataSource": "mainService", + "preload": true, + "settings": { + "operationMode": "Server", + "autoExpandSelect": true, + "earlyRequests": true + } + } + }, + "routing": { + "config": {}, + "routes": [ + { + "pattern": ":?query:", + "name": "TravelList", + "target": "TravelList" + }, + { + "pattern": "Travel({TravelKey}):?query:", + "name": "TravelObjectPage", + "target": "TravelObjectPage" + }, + { + "pattern": "Travel({TravelKey})/_Booking({BookingKey}):?query:", + "name": "BookingObjectPage", + "target": "BookingObjectPage" + }, + { + "pattern": "Travel({TravelKey})/_Booking({BookingKey})/_BookingStatus({BookingStatusKey}):?query:", + "name": "BookingStatusObjectPage", + "target": "BookingStatusObjectPage" + }, + { + "pattern": "Travel({TravelKey})/_Booking({BookingKey})/_BookingSupplement({BookingSupplementKey}):?query:", + "name": "BookingSupplementObjectPage", + "target": "BookingSupplementObjectPage" + } + ], + "targets": { + "TravelList": { + "type": "Component", + "id": "TravelList", + "name": "sap.fe.templates.ListReport", + "options": { + "settings": { + "contextPath": "/Travel", + "variantManagement": "Page", + "navigation": { + "Travel": { + "detail": { + "route": "TravelObjectPage" + } + } + }, + "controlConfiguration": {} + } + } + }, + "TravelObjectPage": { + "type": "Component", + "id": "TravelObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Travel", + "editableHeaderContent": false, + "navigation": { + "_Booking": { + "detail": { + "route": "BookingObjectPage" + } + } + }, + "controlConfiguration": {} + } + } + }, + "BookingObjectPage": { + "type": "Component", + "id": "BookingObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Travel/_Booking", + "editableHeaderContent": false, + "navigation": { + "_BookingStatus": { + "detail": { + "route": "BookingStatusObjectPage" + } + }, + "_BookingSupplement": { + "detail": { + "route": "BookingSupplementObjectPage" + } + } + }, + "controlConfiguration": {} + } + } + }, + "BookingStatusObjectPage": { + "type": "Component", + "id": "BookingStatusObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Travel/_Booking/_BookingStatus", + "editableHeaderContent": false, + "navigation": {}, + "controlConfiguration": {} + } + } + }, + "BookingSupplementObjectPage": { + "type": "Component", + "id": "BookingSupplementObjectPage", + "name": "sap.fe.templates.ObjectPage", + "options": { + "settings": { + "contextPath": "/Travel/_Booking/_BookingSupplement", + "editableHeaderContent": false, + "navigation": {}, + "controlConfiguration": {} + } + } + } + } + } + }, + "sap.fiori": { + "archeType": "transactional" + } +} \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/qfa.json b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/qfa.json new file mode 100644 index 0000000000..dcccc91b5b --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/downloaded-app/qfa.json @@ -0,0 +1,27 @@ +{ + "metadata": { + "package": "TEST_PACKAGE", + "masterLanguage": "TEST_LANGUAGE" + }, + "serviceBindingDetails": { + "name": "TEST_SERVICE_NAME", + "serviceName": "/TEST/SERVICE/NAME", + "serviceVersion": "TEST_VERSION", + "mainEntityName": "TEST_ENTITY", + "navigationEntity": "TEST_NAVIGATION" + }, + "projectAttribute": { + "moduleName": "TEST_MODULE_NAME", + "applicationTitle": "TEST_APPLICATION_TITLE" + }, + "deploymentDetails": { + "repositoryName": "TEST_REPOSITORY_NAME", + "repositoryDescription": "TEST_REPOSITORY_DESCRIPTION" + }, + "fioriLaunchpadConfiguration": { + "semanticObject": "TEST_SEMANTIC_OBJECT", + "action": "TEST_ACTION", + "title": "TEST_TITLE", + "subtitle": "TEST_SUBTITLE" + } +} diff --git a/packages/repo-app-download-sub-generator/test/fixtures/index.ts b/packages/repo-app-download-sub-generator/test/fixtures/index.ts new file mode 100644 index 0000000000..9c92c9f32f --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/index.ts @@ -0,0 +1,19 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +/** + * A simple caching store for test fixtures + * + * @export + * @class TestFixture + */ +export class TestFixture { + private fileContents: { [filename: string]: string } = {}; + + getContents(relativePath: string): string { + if (!this.fileContents[relativePath]) { + this.fileContents[relativePath] = fs.readFileSync(path.join(__dirname, relativePath)).toString(); + } + return this.fileContents[relativePath]; + } +} diff --git a/packages/repo-app-download-sub-generator/test/fixtures/metadata.xml b/packages/repo-app-download-sub-generator/test/fixtures/metadata.xml new file mode 100644 index 0000000000..29fb8b020e --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/fixtures/metadata.xml @@ -0,0 +1,553 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CpWfHandle + RetentionTime + CpWfDefId + PaWfDefId + Consumer + LastChangeOn + Context + CallbackClass + + + + + + + + + + + + + + + __OperationControl + + + + + + + + + __OperationControl + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eq + ne + gt + ge + lt + le + and + or + contains + startswith + endswith + any + all + + + + + application/json + application/pdf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/repo-app-download-sub-generator/test/prompts/prompt-helpers.test.ts b/packages/repo-app-download-sub-generator/test/prompts/prompt-helpers.test.ts new file mode 100644 index 0000000000..caa0e80cfa --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/prompts/prompt-helpers.test.ts @@ -0,0 +1,128 @@ +import { fetchAppListForSelectedSystem, formatAppChoices, getYUIDetails } from '../../src/prompts/prompt-helpers'; +import { PromptNames, RepoAppDownloadAnswers, AppItem } from '../../src/app/types'; +import { PromptState } from '../../src/prompts/prompt-state'; +import type { AbapServiceProvider, AppIndex } from '@sap-ux/axios-extension'; +import { generatorTitle, generatorDescription } from '../../src/utils/constants'; +import { t } from '../../src/utils/i18n'; +import RepoAppDownloadLogger from '../../src/utils/logger'; + +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +describe('fetchAppListForSelectedSystem', () => { + const mockServiceProvider = { + getAppIndex: jest.fn().mockReturnValue({ + search: jest.fn().mockResolvedValue([{ id: 'app1' }, { id: 'app2' }]) + }) + } as unknown as AbapServiceProvider; + + const mockAnswers: RepoAppDownloadAnswers = { + [PromptNames.systemSelection]: { + connectedSystem: { + serviceProvider: mockServiceProvider + } + }, + [PromptNames.selectedApp]: { + appId: 'mockAppId', + title: 'mockTitle', + description: 'mockDescription', + repoName: 'mockRepoName', + url: 'mockUrl' + }, + [PromptNames.targetFolder]: 'mockTargetFolder' + }; + + it('should fetch the application list when systemSelection and serviceProvider are provided', async () => { + const result = await fetchAppListForSelectedSystem(mockServiceProvider, mockAnswers[PromptNames.selectedApp].appId); + + expect(mockServiceProvider.getAppIndex().search).toHaveBeenCalledWith( + expect.anything(), + expect.anything() + ); + expect(result).toEqual([{ id: 'app1' }, { id: 'app2' }]); + expect(PromptState.systemSelection).toEqual({ + connectedSystem: { serviceProvider: mockServiceProvider } + }); + }); + + it('should return an empty array when serviceProvider is not provided', async () => { + const result = await fetchAppListForSelectedSystem(undefined as unknown as AbapServiceProvider); + expect(result).toEqual([]); + }); + + it('should log an error if getAppList throws an error', async () => { + const error = new Error('Mock error'); + mockServiceProvider.getAppIndex().search = jest.fn().mockRejectedValue(error); + const result = await fetchAppListForSelectedSystem(mockServiceProvider, mockAnswers[PromptNames.selectedApp].appId); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.applicationListFetchError', { error: error.message })); + expect(result).toEqual([]); + }); +}); + +describe('formatAppChoices', () => { + const validApp: AppItem = { + 'sap.app/id': 'app1', + 'sap.app/title': 'App 1', + 'sap.app/description': 'Description for App 1', + 'repoName': 'repo1', + 'url': 'http://mock-url.com/app1' + }; + + const invalidApp: AppItem = { + 'sap.app/id': 'app2', + 'sap.app/title': 'App 2', + 'repoName': '', // no repo name + 'url': 'http://mock-url.com/app2' + }; + + it('should format valid app list correctly', () => { + const appList: AppIndex = [validApp]; + const result = formatAppChoices(appList); + + expect(result).toEqual([ + { + name: 'app1', + value: { + appId: 'app1', + title: 'App 1', + description: 'Description for App 1', + repoName: 'repo1', + url: 'http://mock-url.com/app1' + } + } + ]); + }); + + it('should log error if required fields are missing', () => { + const appList: AppIndex = [invalidApp]; + const result = formatAppChoices(appList); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith( t('error.requiredFieldsMissing', { app: JSON.stringify(appList) }) ); + }); + + it('should handle a mix of valid and invalid apps by throwing an error', () => { + const appList: AppIndex = [validApp, invalidApp]; + const result = formatAppChoices(appList); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith( t('error.requiredFieldsMissing', { app: JSON.stringify(appList) }) ); + }); + + it('should return an empty array if the app list is empty', () => { + const appList: AppIndex = []; + const result = formatAppChoices(appList); + expect(result).toEqual([]); + }); +}); + +describe('getYUIDetails', () => { + it('should return an array with the correct name and description', () => { + const result = getYUIDetails(); + expect(result).toEqual([ + { + name: generatorTitle, + description: generatorDescription + } + ]); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/prompts/prompt-state.test.ts b/packages/repo-app-download-sub-generator/test/prompts/prompt-state.test.ts new file mode 100644 index 0000000000..999beb9322 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/prompts/prompt-state.test.ts @@ -0,0 +1,59 @@ +import { PromptState } from '../../src/prompts/prompt-state'; +import type { SystemSelectionAnswers } from '../../src/app/types'; +import type { AbapServiceProvider } from '@sap-ux/axios-extension'; + +describe('PromptState', () => { + const mockServiceProvider = { + defaults: { + baseURL: 'https://mock.sap-system.com', + params: { + 'sap-client': '100' + } + } + } as unknown as AbapServiceProvider; + let mockSystemSelection: SystemSelectionAnswers; + beforeEach(() => { + mockSystemSelection = { + connectedSystem: { + serviceProvider: mockServiceProvider + } + } + }); + + afterEach(() => { + PromptState.reset(); + }); + + it('should set the state of systemSelection', () => { + PromptState.systemSelection = mockSystemSelection; + expect(PromptState.systemSelection).toEqual(mockSystemSelection); + }); + + it('should reset systemSelection to an empty object', () => { + PromptState.systemSelection = mockSystemSelection; + expect(PromptState.systemSelection).toEqual(mockSystemSelection); + + PromptState.reset(); + expect(PromptState.systemSelection).toEqual({}); + }); + + it('should set and get downloadedAppPackage correctly', () => { + const mockBuffer = Buffer.from('mock zip content'); + PromptState.downloadedAppPackage = mockBuffer; + expect(PromptState.downloadedAppPackage).toBe(mockBuffer); + }); + + it('should return undefined for downloadedAppPackage if not set', () => { + expect(PromptState.downloadedAppPackage.length).toBe(0); + }); + + it('should return baseURL from connected system', () => { + PromptState.systemSelection = mockSystemSelection; + expect(PromptState.baseURL).toBe('https://mock.sap-system.com'); + }); + + it('should return sapClient from connected system', () => { + PromptState.systemSelection = mockSystemSelection; + expect(PromptState.sapClient).toBe('100'); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/prompts/prompts.test.ts b/packages/repo-app-download-sub-generator/test/prompts/prompts.test.ts new file mode 100644 index 0000000000..5795690631 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/prompts/prompts.test.ts @@ -0,0 +1,172 @@ +import { t } from '../../src/utils/i18n'; +import { validateFioriAppTargetFolder } from '@sap-ux/project-input-validator'; +import { getPrompts } from '../../src/prompts/prompts'; +import { PromptNames } from '../../src/app/types'; +import { PromptState } from '../../src/prompts/prompt-state'; +import * as helpers from '../../src/prompts/prompt-helpers'; +import * as downloadUtils from '../../src/utils/download-utils'; +import RepoAppDownloadLogger from '../../src/utils/logger'; + +jest.mock('@sap-ux/odata-service-inquirer', () => ({ + getSystemSelectionQuestions: jest.fn().mockResolvedValue({ + prompts: [{ + name: 'systemSelection', + type: 'list', + choices: [{ name: 'Sys', value: { system: { name: 'Sys' } } }] + }], + answers: { + connectedSystem: { serviceProvider: {} } + } + }) +})); + +jest.mock('../../src/prompts/prompt-helpers', () => ({ + fetchAppListForSelectedSystem: jest.fn().mockResolvedValue([ + { appId: 'app1', repoName: 'repo1' }, + { appId: 'app2', repoName: 'repo2' } + ]), + formatAppChoices: jest.fn().mockReturnValue([ + { name: 'App 1', value: { appId: 'app1', repoName: 'repo1' } }, + { name: 'App 2', value: { appId: 'app2', repoName: 'repo2' } } + ]) +})); + +jest.mock('../../src/utils/download-utils', () => ({ + downloadApp: jest.fn() +})); + +jest.mock('@sap-ux/project-input-validator', () => ({ + validateFioriAppTargetFolder: jest.fn().mockResolvedValue(true) +})); + +jest.mock('@sap-ux/project-input-validator', () => ({ + validateFioriAppTargetFolder: jest.fn().mockResolvedValue(true) +})); + +describe('getPrompts', () => { + const mockGetSystemSelectionQuestions = require('@sap-ux/odata-service-inquirer').getSystemSelectionQuestions; + const mockFetchAppList = helpers.fetchAppListForSelectedSystem as jest.Mock; + const mockDownloadApp = downloadUtils.downloadApp as jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetAllMocks(); + PromptState.reset(); + }); + + it('should return prompts including system, app, and target folder', async () => { + mockGetSystemSelectionQuestions.mockResolvedValue({ + prompts: [{ + name: PromptNames.systemSelection, + type: 'list', + choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }] + }], + answers: { + connectedSystem: { serviceProvider: {} } + } + }); + + mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]); + mockDownloadApp.mockResolvedValue(undefined); + + const prompts = await getPrompts('/app/path'); + expect(prompts).toBeInstanceOf(Array); + expect(prompts.find(p => p.name === PromptNames.systemSelection)).toBeTruthy(); + expect(prompts.find(p => p.name === PromptNames.selectedApp)).toBeTruthy(); + expect(prompts.find(p => p.name === PromptNames.targetFolder)).toBeTruthy(); + }); + + it('should preselect default system if quickDeployedAppConfig is provided', async () => { + const quickDeployedAppConfig = { + appId: 'app1', + serviceProviderInfo: { + name: 'DefaultSystem' + } + }; + + mockGetSystemSelectionQuestions.mockResolvedValue({ + prompts: [{ + name: PromptNames.systemSelection, + type: 'list', + choices: [ + { name: 'System A', value: { system: { name: 'SystemA' } } }, + { name: 'Default System', value: { system: { name: 'DefaultSystem' } } } + ], + default: 'DefaultSystem' + }], + answers: { + connectedSystem: { serviceProvider: {} } + } + }); + + mockFetchAppList.mockResolvedValue([{ appId: 'app1', repoName: 'repo1' }]); + const prompts = await getPrompts(undefined, quickDeployedAppConfig); + + const systemPrompt = prompts.find(p => p.name === PromptNames.systemSelection) as any; + expect(systemPrompt.default).toBe('DefaultSystem'); + }); + + it('should use validateFioriAppTargetFolder in folder prompt', async () => { + mockGetSystemSelectionQuestions.mockResolvedValue({ + prompts: [], + answers: {} + }); + const prompts = await getPrompts('/some/path'); + const projectPathPrompt = prompts.find(p => p.name === PromptNames.targetFolder) as any; + await projectPathPrompt.validate('/some/path', { + selectedApp: { appId: 'id1' } + }); + expect(validateFioriAppTargetFolder).toHaveBeenCalledWith('/some/path', 'id1', true); + }); + + it('should handle quickDeployedAppConfig and return the correct prompts', async () => { + mockGetSystemSelectionQuestions.mockResolvedValue({ + prompts: [{ + name: PromptNames.systemSelection, + type: 'list', + choices: [{ name: 'System 1', value: { system: { name: 'MockSystem' } } }] + }], + answers: { + connectedSystem: { serviceProvider: {} } + } + }); + const quickDeployedAppConfig = { + appId: 'app1', + serviceProviderInfo: { name: 'System 1' } + }; + // Call getPrompts with quickDeployedAppConfig + const prompts = await getPrompts('/some/path', quickDeployedAppConfig); + + // Ensure prompts are returned correctly + expect(prompts).toBeDefined(); + + // Check if the system selection prompt exists + const systemSelectionPrompt = prompts.find(p => p.name === PromptNames.systemSelection); + expect(systemSelectionPrompt).toBeDefined(); + + // Check if the system selection prompt is filtered correctly + if (systemSelectionPrompt) { + const listPrompt = systemSelectionPrompt as unknown as { choices: () => { name: string; value: {} }[] }; + expect(listPrompt.choices).toEqual([ + { name: 'System 1', value: { system: { name: 'MockSystem' } } } + ]); + } + + // Check if the app selection prompt exists and is populated correctly + const appSelectionPrompt = prompts.find(p => p.name === PromptNames.selectedApp); + expect(appSelectionPrompt).toBeDefined(); + if (appSelectionPrompt) { + const listPrompt = appSelectionPrompt as unknown as { choices: () => { name: string; value: {} }[] }; + expect(appSelectionPrompt.when).toBeTruthy(); + } + + // Check if the target folder prompt exists and is included + const targetFolderPrompt = prompts.find(p => p.name === PromptNames.targetFolder); + expect(targetFolderPrompt).toBeDefined(); + if (targetFolderPrompt) { + expect(targetFolderPrompt.when).toBeTruthy(); + } + }); +}); + + diff --git a/packages/repo-app-download-sub-generator/test/utils/download-utils.test.ts b/packages/repo-app-download-sub-generator/test/utils/download-utils.test.ts new file mode 100644 index 0000000000..ff20036d85 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/download-utils.test.ts @@ -0,0 +1,109 @@ +import { downloadApp, extractZip } from '../../src/utils/download-utils'; +import AdmZip from 'adm-zip'; +import { PromptState } from '../../src/prompts/prompt-state'; +import { t } from '../../src/utils/i18n'; +import RepoAppDownloadLogger from '../../src/utils/logger'; +import type { SystemSelectionAnswers } from '../../src/app/types'; + +jest.mock('adm-zip'); +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +describe('extractZip', () => { + let mockZip: any; + let mockEntry1: any; + let mockEntry2: any; + let mockFs: any; + + beforeEach(() => { + mockEntry1 = { + isDirectory: false, + entryName: 'file1.txt', + getData: jest.fn(() => Buffer.from('File 1 content')) + }; + mockEntry2 = { + isDirectory: false, + entryName: 'folder/file2.txt', + getData: jest.fn(() => Buffer.from('File 2 content')) + }; + mockZip = { + getEntries: jest.fn(() => [mockEntry1, mockEntry2]) + }; + + (AdmZip as jest.Mock).mockImplementation(() => mockZip); + mockFs = { + write: jest.fn() + }; + }); + + it('should extract files from zip and write them using fs', async () => { + const extractedPath = '/tmp/project'; + const dummyBuffer = Buffer.from('fake zip content'); + + await extractZip(extractedPath, dummyBuffer, mockFs); + + expect(mockZip.getEntries).toHaveBeenCalled(); + + expect(mockFs.write).toHaveBeenCalledWith( + '/tmp/project/file1.txt', + 'File 1 content' + ); + + expect(mockFs.write).toHaveBeenCalledWith( + '/tmp/project/folder/file2.txt', + 'File 2 content' + ); + }); + + it('should log an error if zip extraction fails', async () => { + const errorMessage = 'Zip corrupted!'; + (AdmZip as jest.Mock).mockImplementation(() => { + throw new Error(errorMessage); + }); + + const dummyBuffer = Buffer.from('broken zip'); + await extractZip('/tmp/fail', dummyBuffer, mockFs); + + expect(RepoAppDownloadLogger.logger.error).toHaveBeenCalledWith( + t('error.appDownloadErrors.zipExtractionError', { error: errorMessage }) + ); + }); +}); + +describe('downloadApp', () => { + const mockDownloadFiles = jest.fn(); + const mockGetUi5AbapRepository = jest.fn(() => ({ + downloadFiles: mockDownloadFiles + })); + const mockServiceProvider = { + getUi5AbapRepository: mockGetUi5AbapRepository + }; + + beforeEach(() => { + mockDownloadFiles.mockReset(); + mockGetUi5AbapRepository.mockClear(); + PromptState.systemSelection = { + connectedSystem: { + serviceProvider: mockServiceProvider + } + } as any; + }); + + it('should download app and store it in PromptState', async () => { + const mockPackage = { name: 'app-1', files: ['files.js'] }; + mockDownloadFiles.mockResolvedValue(mockPackage); + + await downloadApp('repo-1'); + + expect(mockDownloadFiles).toHaveBeenCalledWith('repo-1'); + expect(PromptState.downloadedAppPackage).toEqual(mockPackage); + }); + + it('should throw if serviceProvider is undefined', async () => { + PromptState.systemSelection = undefined as unknown as Partial; + await expect(downloadApp('repo-1')).rejects.toThrow(); + }); +}); diff --git a/packages/repo-app-download-sub-generator/test/utils/event-hook.test.ts b/packages/repo-app-download-sub-generator/test/utils/event-hook.test.ts new file mode 100644 index 0000000000..88d12e97da --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/event-hook.test.ts @@ -0,0 +1,58 @@ +import { runPostAppGenHook, type RepoAppGenContext } from '../../src/utils/event-hook'; +import type { VSCodeInstance } from '@sap-ux/fiori-generator-shared'; +import { t } from '../../src/utils/i18n'; +import RepoAppDownloadLogger from '../../src/utils/logger'; + +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +describe('runPostAppGenHook', () => { + let mockContext: RepoAppGenContext; + + beforeEach(() => { + mockContext = { + vscodeInstance: { + commands: { + executeCommand: jest.fn() + } + } as unknown as VSCodeInstance, + postGenCommand: 'mockCommand', + path: '/mock/path' + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should log an error if vscodeInstance is missing', async () => { + mockContext.vscodeInstance = undefined; + await runPostAppGenHook(mockContext); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.eventHookErrors.vscodeInstanceMissing')); + }); + + it('should log an error if postGenCommand is missing', async () => { + mockContext.postGenCommand = ''; + await runPostAppGenHook(mockContext); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.eventHookErrors.postGenCommandMissing')); + }); + + it('should execute the post-generation command successfully', async () => { + await runPostAppGenHook(mockContext); + expect(mockContext.vscodeInstance?.commands?.executeCommand).toHaveBeenCalledWith('mockCommand', { + fsPath: '/mock/path' + }); + }); + + it('should log an error if executeCommand throws an error', async () => { + const mockError = new Error('Command execution failed'); + if (mockContext.vscodeInstance) { + mockContext.vscodeInstance.commands.executeCommand = jest.fn().mockRejectedValue(mockError); + } + await runPostAppGenHook(mockContext); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.eventHookErrors.commandExecutionFailed')); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/utils/file-helpers.test.ts b/packages/repo-app-download-sub-generator/test/utils/file-helpers.test.ts new file mode 100644 index 0000000000..72ede10be6 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/file-helpers.test.ts @@ -0,0 +1,70 @@ +import { readManifest } from '../../src/utils/file-helpers'; +import type { Editor } from 'mem-fs-editor'; +import { t } from '../../src/utils/i18n'; +import { adtSourceTemplateId } from '../../src/utils/constants'; +import RepoAppDownloadLogger from '../../src/utils/logger'; +import { join } from 'path'; + +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +describe('readManifest', () => { + const mockReadJSON = jest.fn(); + const mockFs = { readJSON: mockReadJSON, exists: jest.fn() } as unknown as Editor; + const extractedProjectPath = 'project-path'; + + afterEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + }); + + it('should return manifest when valid manifest is read', async () => { + const validManifest = { + 'sap.app': { + id: 'test-app', + sourceTemplate: { + id: adtSourceTemplateId + } + } + }; + mockReadJSON.mockReturnValue(validManifest); + const manifestFilePath = join(extractedProjectPath, 'manifest.json'); + const result = readManifest(manifestFilePath, mockFs); + expect(result).toBe(validManifest); + expect(mockFs.readJSON).toHaveBeenCalledWith(manifestFilePath); + }); + + it('should throw an error if manifest is not found', async () => { + mockReadJSON.mockReturnValue(null); + readManifest(extractedProjectPath, mockFs) + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.readManifestErrors.readManifestFailed')); + }); + + it('should throw an error if "sap.app" is not defined in the manifest', async () => { + const invalidManifestNoSapApp = { + // No 'sap.app' field + }; + // Mock fs readJSON function to return a manifest without 'sap.app' + mockReadJSON.mockReturnValue(invalidManifestNoSapApp); + readManifest(extractedProjectPath, mockFs) + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.readManifestErrors.sapAppNotDefined')); + }); + + it('should throw an error if the sourceTemplate.id is not supported', async () => { + const invalidManifestWrongTemplate = { + 'sap.app': { + id: 'test-app', + sourceTemplate: { + id: 'wrong-template-id' + } + } + }; + // Mock fs readJSON function to return a manifest with an unsupported sourceTemplate.id + mockReadJSON.mockReturnValue(invalidManifestWrongTemplate); + readManifest(extractedProjectPath, mockFs) + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.readManifestErrors.sourceTemplateNotSupported')); + }); +}); diff --git a/packages/repo-app-download-sub-generator/test/utils/logger.test.ts b/packages/repo-app-download-sub-generator/test/utils/logger.test.ts new file mode 100644 index 0000000000..19214ba03b --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/logger.test.ts @@ -0,0 +1,47 @@ +import RepoAppDownloadLogger from '../../src/utils/logger'; +import { DefaultLogger, LogWrapper } from '@sap-ux/fiori-generator-shared'; +import type { Logger } from 'yeoman-environment'; +import type { IVSCodeExtLogger, LogLevel } from '@vscode-logging/logger'; + +describe('RepoAppDownloadLogger', () => { + const testLoggerName = 'testLogger'; + afterEach(() => { + // Reset the logger to the default after each test + RepoAppDownloadLogger.logger = DefaultLogger; + }); + + it('should return the default logger initially', () => { + expect(RepoAppDownloadLogger.logger).toBe(DefaultLogger); + }); + + it('should allow setting a custom logger', () => { + const mockLogger = { log: jest.fn() } as unknown as LogWrapper; + RepoAppDownloadLogger.logger = mockLogger; + expect(RepoAppDownloadLogger.logger).toBe(mockLogger); + }); + + it('should configure the logger with provided parameters', () => { + const mockYoLogger = { log: jest.fn() } as unknown as Logger; + const mockLogWrapper = { log: jest.fn() } as unknown as LogWrapper; + + RepoAppDownloadLogger.configureLogging(testLoggerName, mockYoLogger, mockLogWrapper); + + expect(RepoAppDownloadLogger.logger).toBe(mockLogWrapper); + }); + + it('should create a new LogWrapper if none is provided', () => { + const mockYoLogger = { log: jest.fn() } as unknown as Logger; + const mockLogLevel: LogLevel = 'info'; + const mockVscLogger = { + log: jest.fn(), + debug: jest.fn(), + getChildLogger: jest.fn().mockReturnValue({ + log: jest.fn(), + debug: jest.fn() + }) + } as unknown as IVSCodeExtLogger; + + RepoAppDownloadLogger.configureLogging(testLoggerName, mockYoLogger, undefined, mockLogLevel, mockVscLogger); + expect(RepoAppDownloadLogger.logger).toBeInstanceOf(LogWrapper); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/utils/updates.test.ts b/packages/repo-app-download-sub-generator/test/utils/updates.test.ts new file mode 100644 index 0000000000..1d5a609c66 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/updates.test.ts @@ -0,0 +1,215 @@ +import { validateAndUpdateManifestUI5Version, replaceWebappFiles } from '../../src/utils/updates'; +import type { Editor } from 'mem-fs-editor'; +import { t } from '../../src/utils/i18n'; +import { getUI5Versions } from '@sap-ux/ui5-info'; +import { isInternalFeaturesSettingEnabled } from '@sap-ux/feature-toggle'; +import type { Manifest } from '@sap-ux/project-access'; +import { readManifest } from '../../src/utils/file-helpers'; +import { join } from 'path'; +import { FileName, DirName } from '@sap-ux/project-access'; +import RepoAppDownloadLogger from '../../src/utils/logger'; +import { fioriAppSourcetemplateId } from '../../src/utils/constants'; + +jest.mock('@sap-ux/ui5-info', () => ({ + ...jest.requireActual('@sap-ux/ui5-info'), + getUI5Versions: jest.fn() +})); + +jest.mock('@sap-ux/feature-toggle', () => ({ + ...jest.requireActual('@sap-ux/feature-toggle'), + isInternalFeaturesSettingEnabled: jest.fn() +})); + +jest.mock('../../src/utils/file-helpers', () => ({ + ...jest.requireActual('../../src/utils/file-helpers'), + readManifest: jest.fn() +})); + +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn(), + warn: jest.fn() + } +})); + +describe('validateAndUpdateManifestUI5Version', () => { + let fs: jest.Mocked; + + beforeEach(() => { + fs = { + writeJSON: jest.fn(), + exists: jest.fn(), + readJSON: jest.fn(), + } as unknown as jest.Mocked; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should throw an error if manifest structure is invalid', async () => { + (readManifest as jest.Mock).mockReturnValue({ + 'sap.ui5': {} + } as unknown as Manifest); + (getUI5Versions as jest.Mock).mockResolvedValue([{ version: '1.90.0' }]); + + await expect(validateAndUpdateManifestUI5Version('path/to/manifest.json', fs)).rejects.toThrow( + t('error.readManifestErrors.invalidManifestStructureError') + ); + }); + + it('should not modify the manifest if minUI5Version is valid', async () => { + const manifest = { + 'sap.ui5': { + dependencies: { + minUI5Version: '1.90.0', + } + }, + 'sap.app': { + sourceTemplate: { + id: fioriAppSourcetemplateId + } + } + }; + (readManifest as jest.Mock).mockReturnValue(manifest); + (getUI5Versions as jest.Mock).mockResolvedValue([{ version: '1.90.0' }]); + + await validateAndUpdateManifestUI5Version('path/to/manifest.json', fs); + expect(fs.writeJSON).toHaveBeenCalledWith('path/to/manifest.json', manifest, undefined, 2); + }); + + it('should update minUI5Version to internal version if internal features are enabled', async () => { + const manifest = { + 'sap.ui5': { + dependencies: { + minUI5Version: '1.80.0', + }, + }, + 'sap.app': { + sourceTemplate: { + id: fioriAppSourcetemplateId + } + } + }; + (readManifest as jest.Mock).mockReturnValue(manifest); + (getUI5Versions as jest.Mock).mockResolvedValue([{ version: '1.90.0' }]); + (isInternalFeaturesSettingEnabled as jest.Mock).mockReturnValue(true); + + await validateAndUpdateManifestUI5Version('path/to/manifest.json', fs); + + expect(fs.writeJSON).toHaveBeenCalledWith( + 'path/to/manifest.json', + { + 'sap.ui5': { + dependencies: { + minUI5Version: '${sap.ui5.dist.version}', + }, + }, + 'sap.app': { + sourceTemplate: { + id: fioriAppSourcetemplateId + } + } + }, + undefined, + 2 + ); + }); + + it('should update minUI5Version to the closest available version if invalid', async () => { + const manifest = { + 'sap.ui5': { + dependencies: { + minUI5Version: '1.70.0', + } + }, + 'sap.app': { + sourceTemplate: { + id: fioriAppSourcetemplateId + } + } + }; + (readManifest as jest.Mock).mockReturnValue(manifest); + (getUI5Versions as jest.Mock).mockResolvedValue([{ version: '1.90.0' }]); + (isInternalFeaturesSettingEnabled as jest.Mock).mockReturnValue(false); + + await validateAndUpdateManifestUI5Version('path/to/manifest.json', fs); + + expect(fs.writeJSON).toHaveBeenCalledWith( + 'path/to/manifest.json', + { + 'sap.ui5': { + dependencies: { + minUI5Version: '1.90.0', + }, + }, + 'sap.app': { + sourceTemplate: { + id: fioriAppSourcetemplateId + } + } + }, + undefined, + 2 + ); + }); +}); + +describe('replaceWebappFiles', () => { + let fs: jest.Mocked; + + beforeEach(() => { + fs = { + exists: jest.fn(), + copy: jest.fn() + } as unknown as jest.Mocked; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should copy files from extractedPath to webappPath if they exist', async () => { + const projectPath = '/project'; + const extractedPath = '/extracted'; + const webappPath = join(`${projectPath}/${DirName.Webapp}`); + + // Mock fs.exists to return true for all files + fs.exists.mockReturnValue(true); + await replaceWebappFiles(projectPath, extractedPath, fs); + + // Verify that fs.copy is called for each file + expect(fs.copy).toHaveBeenCalledWith(join(`${extractedPath}/${FileName.Manifest}`), join(`${webappPath}/${FileName.Manifest}`)); + expect(fs.copy).toHaveBeenCalledWith(join(`${extractedPath}/i18n/i18n.properties`), join(`${webappPath}/i18n/i18n.properties`)); + expect(fs.copy).toHaveBeenCalledWith(join(`${extractedPath}/index.html`), join(`${webappPath}/index.html`)); + expect(fs.copy).toHaveBeenCalledWith(join(`${extractedPath}/component.js`), join(`${webappPath}/Component.js`)); + }); + + it('should log a warning if a file does not exist in extractedPath', async () => { + const projectPath = '/project'; + const extractedPath = '/extracted'; + const webappPath = join(`${projectPath}/${DirName.Webapp}`); + + // Mock fs.exists to return false for one file + fs.exists.mockImplementation((filePath) => filePath !== join(`${extractedPath}/${FileName.Manifest}`)); + await replaceWebappFiles(projectPath, extractedPath, fs); + + // Verify that fs.copy is not called for the missing file + expect(fs.copy).not.toHaveBeenCalledWith(join(`${extractedPath}/${FileName.Manifest}`), join(`${webappPath}/${FileName.Manifest}`)); + expect(RepoAppDownloadLogger.logger?.warn).toHaveBeenCalledWith( + t('warn.extractedFileNotFound', { extractedFilePath: join(`${extractedPath}/${FileName.Manifest}`) }) + ); + }); + + it('should log an error if an exception occurs', async () => { + const projectPath = '/project'; + const extractedPath = '/extracted'; + fs.exists.mockImplementation(() => { + throw new Error('Test error'); + }); + await replaceWebappFiles(projectPath, extractedPath, fs); + expect(RepoAppDownloadLogger.logger?.error).toHaveBeenCalledWith( + t('error.replaceWebappFilesError', { error: new Error('Test error') }) + ); + }); +}); \ No newline at end of file diff --git a/packages/repo-app-download-sub-generator/test/utils/validators.test.ts b/packages/repo-app-download-sub-generator/test/utils/validators.test.ts new file mode 100644 index 0000000000..5322f5bb80 --- /dev/null +++ b/packages/repo-app-download-sub-generator/test/utils/validators.test.ts @@ -0,0 +1,112 @@ +import { validateQfaJsonFile } from '../../src/utils/validators'; +import { QfaJsonConfig } from '../../src/app/types'; +import { t } from '../../src/utils/i18n'; +import RepoAppDownloadLogger from '../../src/utils/logger'; + +jest.mock('../../src/utils/logger', () => ({ + logger: { + error: jest.fn() + } +})); + +describe('validateQfaJsonFile', () => { + const validConfig: QfaJsonConfig = { + metadata: { package: 'valid-package' }, + serviceBindingDetails: { + serviceName: 'validService', + serviceVersion: '1.0.0', + mainEntityName: 'validEntity', + }, + projectAttribute: { moduleName: 'validModule' }, + deploymentDetails: { repositoryName: 'validRepository' }, + fioriLaunchpadConfiguration: { + semanticObject: 'semanticObject', + action: 'action', + title: 'title' + } + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return true when all validation functions pass', () => { + const result = validateQfaJsonFile(validConfig); + expect(result).toBe(true); + }); + + it('should return false and log an error when metadata validation fails', () => { + const invalidMetadataConfig = { + ...validConfig, + metadata: { package: '' } // Invalid package + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidMetadataConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidMetadataPackage')); + }); + + it('should return false and log an error when service binding details validation fails', () => { + const invalidServiceBindingConfig = { + ...validConfig, + serviceBindingDetails: { + ...validConfig.serviceBindingDetails, + serviceName: '', // Invalid service name + } + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidServiceBindingConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidServiceName')); + }); + + it('should return false and log an error when service binding version is not provided', () => { + const invalidServiceBindingConfig = { + ...validConfig, + serviceBindingDetails: { + ...validConfig.serviceBindingDetails, + serviceVersion: '' // Invalid service version + } + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidServiceBindingConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidServiceVersion')); + }); + + it('should return false and log an error when main entity name is missing', () => { + const invalidServiceBindingConfig = { + ...validConfig, + serviceBindingDetails: { + ...validConfig.serviceBindingDetails, + mainEntityName: '' // Invalid main entity name + } + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidServiceBindingConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidMainEntityName')); + }); + + it('should return false and log an error when project attribute validation fails', () => { + const invalidProjectAttributeConfig = { + ...validConfig, + projectAttribute: { moduleName: '' } // Invalid module name + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidProjectAttributeConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidModuleName')); + }); + + it('should return false and log an error when deployment details validation fails', () => { + const invalidDeploymentDetailsConfig = { + ...validConfig, + deploymentDetails: { repositoryName: '' } // Invalid repository name + } as unknown as QfaJsonConfig; + + const result = validateQfaJsonFile(invalidDeploymentDetailsConfig); + expect(result).toBe(false); + expect(RepoAppDownloadLogger.logger.error).toBeCalledWith(t('error.invalidRepositoryName')); + }); +}); diff --git a/packages/repo-app-download-sub-generator/tsconfig.eslint.json b/packages/repo-app-download-sub-generator/tsconfig.eslint.json new file mode 100644 index 0000000000..d5f1aa3474 --- /dev/null +++ b/packages/repo-app-download-sub-generator/tsconfig.eslint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "include": ["src", "test", ".eslintrc.js"] +} diff --git a/packages/repo-app-download-sub-generator/tsconfig.json b/packages/repo-app-download-sub-generator/tsconfig.json new file mode 100644 index 0000000000..3034b18067 --- /dev/null +++ b/packages/repo-app-download-sub-generator/tsconfig.json @@ -0,0 +1,64 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src", + "src/**/*.json" + ], + "compilerOptions": { + "rootDir": "src", + "outDir": "generators" + }, + "references": [ + { + "path": "../abap-deploy-config-writer" + }, + { + "path": "../axios-extension" + }, + { + "path": "../btp-utils" + }, + { + "path": "../feature-toggle" + }, + { + "path": "../fiori-elements-writer" + }, + { + "path": "../fiori-generator-shared" + }, + { + "path": "../fiori-tools-settings" + }, + { + "path": "../inquirer-common" + }, + { + "path": "../launch-config" + }, + { + "path": "../logger" + }, + { + "path": "../nodejs-utils" + }, + { + "path": "../odata-service-inquirer" + }, + { + "path": "../project-access" + }, + { + "path": "../project-input-validator" + }, + { + "path": "../store" + }, + { + "path": "../ui5-config" + }, + { + "path": "../ui5-info" + } + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc6ae5ed26..fe6fdd6f0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3284,6 +3284,142 @@ importers: specifier: 6.3.3 version: 6.3.3 + packages/repo-app-download-sub-generator: + dependencies: + '@sap-devx/yeoman-ui-types': + specifier: 1.14.4 + version: 1.14.4 + '@sap-ux/abap-deploy-config-writer': + specifier: workspace:* + version: link:../abap-deploy-config-writer + '@sap-ux/axios-extension': + specifier: workspace:* + version: link:../axios-extension + '@sap-ux/btp-utils': + specifier: workspace:* + version: link:../btp-utils + '@sap-ux/feature-toggle': + specifier: workspace:* + version: link:../feature-toggle + '@sap-ux/fiori-elements-writer': + specifier: workspace:* + version: link:../fiori-elements-writer + '@sap-ux/fiori-generator-shared': + specifier: workspace:* + version: link:../fiori-generator-shared + '@sap-ux/fiori-tools-settings': + specifier: workspace:* + version: link:../fiori-tools-settings + '@sap-ux/inquirer-common': + specifier: workspace:* + version: link:../inquirer-common + '@sap-ux/launch-config': + specifier: workspace:* + version: link:../launch-config + '@sap-ux/logger': + specifier: workspace:* + version: link:../logger + '@sap-ux/odata-service-inquirer': + specifier: workspace:* + version: link:../odata-service-inquirer + '@sap-ux/project-access': + specifier: workspace:* + version: link:../project-access + '@sap-ux/project-input-validator': + specifier: workspace:* + version: link:../project-input-validator + '@sap-ux/store': + specifier: workspace:* + version: link:../store + '@sap-ux/ui5-info': + specifier: workspace:* + version: link:../ui5-info + adm-zip: + specifier: 0.5.10 + version: 0.5.10 + i18next: + specifier: 23.5.1 + version: 23.5.1 + inquirer: + specifier: 8.2.6 + version: 8.2.6 + yeoman-generator: + specifier: 5.10.0 + version: 5.10.0(mem-fs@2.1.0)(yeoman-environment@3.19.3) + devDependencies: + '@jest/types': + specifier: 29.6.3 + version: 29.6.3 + '@sap-ux/nodejs-utils': + specifier: workspace:* + version: link:../nodejs-utils + '@sap-ux/ui5-config': + specifier: workspace:* + version: link:../ui5-config + '@types/adm-zip': + specifier: 0.5.5 + version: 0.5.5 + '@types/fs-extra': + specifier: 9.0.13 + version: 9.0.13 + '@types/inquirer': + specifier: 8.2.6 + version: 8.2.6 + '@types/inquirer-autocomplete-prompt': + specifier: 2.0.1 + version: 2.0.1 + '@types/lodash': + specifier: 4.14.202 + version: 4.14.202 + '@types/mem-fs': + specifier: 1.1.2 + version: 1.1.2 + '@types/mem-fs-editor': + specifier: 7.0.1 + version: 7.0.1 + '@types/yeoman-environment': + specifier: 2.10.11 + version: 2.10.11 + '@types/yeoman-generator': + specifier: 5.2.11 + version: 5.2.11 + '@types/yeoman-test': + specifier: 4.0.6 + version: 4.0.6 + '@vscode-logging/logger': + specifier: 2.0.0 + version: 2.0.0 + fs-extra: + specifier: 10.0.0 + version: 10.0.0 + inquirer-autocomplete-prompt: + specifier: 2.0.1 + version: 2.0.1(inquirer@8.2.6) + lodash: + specifier: 4.17.21 + version: 4.17.21 + mem-fs-editor: + specifier: 9.4.0 + version: 9.4.0(mem-fs@2.1.0) + memfs: + specifier: 3.4.13 + version: 3.4.13 + rimraf: + specifier: 5.0.5 + version: 5.0.5 + typescript: + specifier: 5.3.3 + version: 5.3.3 + unionfs: + specifier: 4.4.0 + version: 4.4.0 + yeoman-test: + specifier: 6.3.0 + version: 6.3.0(mem-fs@2.1.0)(yeoman-environment@3.19.3)(yeoman-generator@5.10.0) + yo: + specifier: '4' + version: 4.3.1(mem-fs@2.1.0) + packages/serve-static-middleware: dependencies: '@sap-ux/logger': @@ -9098,6 +9234,21 @@ packages: /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + /@sindresorhus/is@0.14.0: + resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} + engines: {node: '>=6'} + dev: true + + /@sindresorhus/is@0.7.0: + resolution: {integrity: sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==} + engines: {node: '>=4'} + dev: true + + /@sindresorhus/is@4.6.0: + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + dev: true + /@sindresorhus/is@5.6.0: resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} engines: {node: '>=14.16'} @@ -9671,6 +9822,20 @@ packages: dependencies: tslib: 2.6.3 + /@szmarczak/http-timer@1.1.2: + resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} + engines: {node: '>=6'} + dependencies: + defer-to-connect: 1.1.3 + dev: true + + /@szmarczak/http-timer@4.0.6: + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + dependencies: + defer-to-connect: 2.0.1 + dev: true + /@szmarczak/http-timer@5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -9862,6 +10027,15 @@ packages: '@types/connect': 3.4.38 '@types/node': 18.11.9 + /@types/cacheable-request@6.0.3: + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + dependencies: + '@types/http-cache-semantics': 4.0.1 + '@types/keyv': 3.1.4 + '@types/node': 18.11.9 + '@types/responselike': 1.0.3 + dev: true + /@types/cheerio@0.22.31: resolution: {integrity: sha512-Kt7Cdjjdi2XWSfrZ53v4Of0wG3ZcmaegFXjMmz9tfNrZSkzzo36G0AL1YqSdcIA78Etjt6E609pt5h1xnQkPUw==} dependencies: @@ -10107,6 +10281,12 @@ packages: '@types/node': 18.11.9 dev: false + /@types/keyv@3.1.4: + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + dependencies: + '@types/node': 18.11.9 + dev: true + /@types/linkify-it@3.0.3: resolution: {integrity: sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==} dev: true @@ -10308,6 +10488,12 @@ packages: resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true + /@types/responselike@1.0.3: + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + dependencies: + '@types/node': 18.11.9 + dev: true + /@types/sanitize-html@2.11.0: resolution: {integrity: sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==} dependencies: @@ -10506,7 +10692,7 @@ packages: '@types/mem-fs': 1.1.2 '@types/mem-fs-editor': 7.0.1 '@types/yeoman-environment': 2.10.11 - '@types/yeoman-generator': 5.2.11 + '@types/yeoman-generator': 5.2.14 dev: true /@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0)(eslint@8.57.0)(typescript@5.8.2): @@ -11494,6 +11680,11 @@ packages: engines: {node: '>=6'} dev: true + /ansi-escapes@1.4.0: + resolution: {integrity: sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==} + engines: {node: '>=0.10.0'} + dev: true + /ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -11510,6 +11701,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + dev: true + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -11518,6 +11714,11 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} + /ansi-styles@2.2.1: + resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} + engines: {node: '>=0.10.0'} + dev: true + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -11538,6 +11739,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + /ansi@0.3.1: + resolution: {integrity: sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A==} + dev: true + /antlr4@4.9.3: resolution: {integrity: sha512-qNy2odgsa0skmNMCuxzXhM4M8J1YDaPv3TI+vCdnOAanu0N982wBrSqziDKRDctEZLZy9VffqIZXc0UGjjSP/g==} engines: {node: '>=14'} @@ -11804,6 +12009,11 @@ packages: is-shared-array-buffer: 1.0.3 dev: true + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + /arrify@2.0.1: resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} engines: {node: '>=8'} @@ -12228,6 +12438,23 @@ packages: rimraf: 3.0.2 write-file-atomic: 4.0.2 + /bin-version-check@4.0.0: + resolution: {integrity: sha512-sR631OrhC+1f8Cvs8WyVWOA33Y8tgwjETNPyyD/myRBXLkfS/vl74FmH/lFcRl9KY3zwGh7jFhvyk9vV3/3ilQ==} + engines: {node: '>=6'} + dependencies: + bin-version: 3.1.0 + semver: 5.7.2 + semver-truncate: 1.1.2 + dev: true + + /bin-version@3.1.0: + resolution: {integrity: sha512-Mkfm4iE1VFt4xd4vH+gx+0/71esbfus2LsnCGe8Pi4mndSPyT+NGES/Eg99jx8/lUGWfu3z2yuB/bt5UB+iVbQ==} + engines: {node: '>=6'} + dependencies: + execa: 1.0.0 + find-versions: 3.2.0 + dev: true + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -12278,6 +12505,25 @@ packages: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + dev: true + + /boxen@5.1.2: + resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} + engines: {node: '>=10'} + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 2.2.1 + string-width: 4.2.3 + type-fest: 0.20.2 + widest-line: 3.1.0 + wrap-ansi: 7.0.0 + dev: true + /boxen@7.1.1: resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} engines: {node: '>=14.16'} @@ -12522,6 +12768,11 @@ packages: unique-filename: 3.0.0 dev: true + /cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + dev: true + /cacheable-lookup@7.0.0: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} @@ -12540,6 +12791,44 @@ packages: responselike: 3.0.0 dev: true + /cacheable-request@2.1.4: + resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==} + dependencies: + clone-response: 1.0.2 + get-stream: 3.0.0 + http-cache-semantics: 3.8.1 + keyv: 3.0.0 + lowercase-keys: 1.0.0 + normalize-url: 2.0.1 + responselike: 1.0.2 + dev: true + + /cacheable-request@6.1.0: + resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} + engines: {node: '>=8'} + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.1.1 + keyv: 3.1.0 + lowercase-keys: 2.0.0 + normalize-url: 4.5.1 + responselike: 1.0.2 + dev: true + + /cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.1.1 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + dev: true + /call-bind@1.0.7: resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} engines: {node: '>= 0.4'} @@ -12561,6 +12850,20 @@ packages: tslib: 2.6.3 dev: true + /camelcase-keys@4.2.0: + resolution: {integrity: sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==} + engines: {node: '>=4'} + dependencies: + camelcase: 4.1.0 + map-obj: 2.0.0 + quick-lru: 1.1.0 + dev: true + + /camelcase@4.1.0: + resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} + engines: {node: '>=4'} + dev: true + /camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -12587,6 +12890,11 @@ packages: /caniuse-lite@1.0.30001677: resolution: {integrity: sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==} + /capture-stack-trace@1.0.2: + resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} + engines: {node: '>=0.10.0'} + dev: true + /case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} engines: {node: '>=4'} @@ -12605,6 +12913,17 @@ packages: traverse: 0.3.9 dev: false + /chalk@1.1.3: + resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -12751,6 +13070,10 @@ packages: zod: 3.23.8 dev: true + /ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + dev: true + /ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -12774,17 +13097,38 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + /cli-boxes@1.0.0: + resolution: {integrity: sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==} + engines: {node: '>=0.10.0'} + dev: true + + /cli-boxes@2.2.1: + resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} + engines: {node: '>=6'} + dev: true + /cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} dev: true + /cli-cursor@1.0.2: + resolution: {integrity: sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==} + engines: {node: '>=0.10.0'} + dependencies: + restore-cursor: 1.0.1 + dev: true + /cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + /cli-list@0.2.0: + resolution: {integrity: sha512-+3MlQHdTSiT7e3Uxco/FL1MjuIYLmvDEhCAekRLCrGimHGfAR1LbJwCrKGceVp95a4oDFVB9CtLWiw2MT8NDXw==} + dev: true + /cli-progress@3.12.0: resolution: {integrity: sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==} engines: {node: '>=4'} @@ -12802,6 +13146,10 @@ packages: dependencies: colors: 1.0.3 + /cli-width@2.2.1: + resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} + dev: true + /cli-width@3.0.0: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} @@ -12827,8 +13175,28 @@ packages: resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} engines: {node: '>= 0.10'} - /clone-stats@1.0.0: - resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==} + /clone-regexp@1.0.1: + resolution: {integrity: sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==} + engines: {node: '>=0.10.0'} + dependencies: + is-regexp: 1.0.0 + is-supported-regexp-flag: 1.0.1 + dev: true + + /clone-response@1.0.2: + resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==} + dependencies: + mimic-response: 1.0.1 + dev: true + + /clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + dependencies: + mimic-response: 1.0.1 + dev: true + + /clone-stats@1.0.0: + resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==} /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} @@ -13044,6 +13412,16 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.7 + typedarray: 0.0.6 + dev: true + /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -13051,6 +13429,18 @@ packages: proto-list: 1.2.4 dev: true + /configstore@5.0.1: + resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} + engines: {node: '>=8'} + dependencies: + dot-prop: 5.3.0 + graceful-fs: 4.2.11 + make-dir: 3.1.0 + unique-string: 2.0.0 + write-file-atomic: 3.0.3 + xdg-basedir: 4.0.0 + dev: true + /configstore@6.0.0: resolution: {integrity: sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==} engines: {node: '>=12'} @@ -13144,6 +13534,11 @@ packages: browserslist: 4.23.3 dev: true + /core-js@3.41.0: + resolution: {integrity: sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==} + requiresBuild: true + dev: true + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -13178,6 +13573,13 @@ packages: readable-stream: 4.5.2 dev: false + /create-error-class@3.0.2: + resolution: {integrity: sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==} + engines: {node: '>=0.10.0'} + dependencies: + capture-stack-trace: 1.0.2 + dev: true + /create-jest@29.7.0(@types/node@18.11.9)(ts-node@10.9.2): resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -13234,6 +13636,11 @@ packages: shebang-command: 2.0.0 which: 2.0.2 + /crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + dev: true + /crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} @@ -13454,9 +13861,41 @@ packages: resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /decamelize@2.0.0: + resolution: {integrity: sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==} + engines: {node: '>=4'} + dependencies: + xregexp: 4.0.0 + dev: true + /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + /decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + dev: true + + /decompress-response@3.3.0: + resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} + engines: {node: '>=4'} + dependencies: + mimic-response: 1.0.1 + dev: true + /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -13530,11 +13969,20 @@ packages: titleize: 3.0.0 dev: true + /default-uid@1.0.0: + resolution: {integrity: sha512-KqOPKqX9VLrCfdKK/zMll+xb9kZOP4QyguB6jyN4pKaPoedk1bMFIfyTCFhVdrHb3GU7aJvKjd8myKxFRRDwCg==} + engines: {node: '>=0.10.0'} + dev: true + /defaults@1.0.3: resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} dependencies: clone: 1.0.4 + /defer-to-connect@1.1.3: + resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} + dev: true + /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -13781,6 +14229,13 @@ packages: tslib: 2.6.3 dev: true + /dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + dependencies: + is-obj: 2.0.0 + dev: true + /dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -13804,6 +14259,18 @@ packages: engines: {node: '>=12'} dev: true + /downgrade-root@1.2.2: + resolution: {integrity: sha512-K/QnPfqybcxP6rriuM17fnaQ/zDnG0hh8ISbm9szzIqZSI4wtfaj4D5oL6WscT2xVFQ3kDISZrrgeUtd+rW8pQ==} + engines: {node: '>=0.10.0'} + dependencies: + default-uid: 1.0.0 + is-root: 1.0.0 + dev: true + + /duplexer3@0.1.5: + resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} + dev: true + /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true @@ -14226,6 +14693,10 @@ packages: is-symbol: 1.0.4 dev: true + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + dev: true + /esbuild-android-64@0.14.49: resolution: {integrity: sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==} engines: {node: '>=12'} @@ -14518,6 +14989,11 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + /escape-goat@2.1.1: + resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} + engines: {node: '>=8'} + dev: true + /escape-goat@4.0.0: resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} engines: {node: '>=12'} @@ -14982,6 +15458,19 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + /execa@1.0.0: + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} + dependencies: + cross-spawn: 6.0.6 + get-stream: 4.1.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + dev: true + /execa@4.1.0: resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} engines: {node: '>=10'} @@ -15025,6 +15514,18 @@ packages: strip-final-newline: 3.0.0 dev: true + /execall@1.0.0: + resolution: {integrity: sha512-/J0Q8CvOvlAdpvhfkD/WnTQ4H1eU0exze2nFGPj/RSC7jpQ0NkKe2r28T5eMkhEEs+fzepMZNy1kVRKNlC04nQ==} + engines: {node: '>=0.10.0'} + dependencies: + clone-regexp: 1.0.1 + dev: true + + /exit-hook@1.1.1: + resolution: {integrity: sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg==} + engines: {node: '>=0.10.0'} + dev: true + /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -15097,12 +15598,19 @@ packages: /extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: false /extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} dev: true + /external-editor@1.1.1: + resolution: {integrity: sha512-0XYlP43jzxMgJjugDJ85Z0UDPnowkUbfFztNvsSGC9sJVIk97MZbGEb9WAhIVH0UgNxoLj/9ZQgB4CHJyz2GGQ==} + dependencies: + extend: 3.0.2 + spawn-sync: 1.0.15 + tmp: 0.0.29 + dev: true + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -15203,6 +15711,14 @@ packages: /fecha@4.2.1: resolution: {integrity: sha512-MMMQ0ludy/nBs1/o0zVOiKTpG7qMbonKUzjJgQFEuvq6INZ1OraKPRAWkBq5vlKLOUMpmNYG1JoN3oDPUQ9m3Q==} + /figures@1.7.0: + resolution: {integrity: sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==} + engines: {node: '>=0.10.0'} + dependencies: + escape-string-regexp: 1.0.5 + object-assign: 4.1.1 + dev: true + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -15255,6 +15771,11 @@ packages: dependencies: to-regex-range: 5.0.1 + /filter-obj@2.0.2: + resolution: {integrity: sha512-lO3ttPjHZRfjMcxWKb1j1eDhTFsu4meeR3lnMcnBFhk6RuLhvEiuALu2TlfL310ph4lCYYwgF/ElIjdP739tdg==} + engines: {node: '>=8'} + dev: true + /finalhandler@1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} @@ -15331,6 +15852,13 @@ packages: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} dev: false + /find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -15357,6 +15885,13 @@ packages: resolution: {integrity: sha512-epNL4mnl3HUYrwVQtZ8s0nxkE4ogAoSqO1V1fa670Ww1fXp8Yr74zNS9Aib/vLNf0rq0AF/4mboo7ev5XkikXQ==} dev: true + /find-versions@3.2.0: + resolution: {integrity: sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==} + engines: {node: '>=6'} + dependencies: + semver-regex: 2.0.0 + dev: true + /find-yarn-workspace-root2@1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: @@ -15417,6 +15952,10 @@ packages: is-callable: 1.2.7 dev: true + /foreachasync@3.0.0: + resolution: {integrity: sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==} + dev: true + /foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -15506,6 +16045,13 @@ packages: engines: {node: '>= 0.6'} dev: true + /from2@2.3.0: + resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.7 + dev: true + /front-matter@4.0.2: resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==} dependencies: @@ -15619,6 +16165,18 @@ packages: requiresBuild: true optional: true + /fullname@4.0.1: + resolution: {integrity: sha512-jVT8q9Ah9JwqfIGKwKzTdbRRthdPpIjEe9kgvxM104Tv+q6SgOAQqJMVP90R0DBRAqejGMHDRWJtl3Ats6BjfQ==} + engines: {node: '>=8'} + dependencies: + execa: 1.0.0 + filter-obj: 2.0.2 + mem: 5.1.1 + p-any: 2.1.0 + passwd-user: 3.0.0 + rc: 1.2.8 + dev: true + /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -15651,6 +16209,17 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /gauge@1.2.7: + resolution: {integrity: sha512-fVbU2wRE91yDvKUnrIaQlHKAWKY5e08PmztCrwuH5YVQ+Z/p3d0ny2T48o6uvAAXHIUnfaQdHkmxYbQft1eHVA==} + deprecated: This package is no longer supported. + dependencies: + ansi: 0.3.1 + has-unicode: 2.0.1 + lodash.pad: 4.5.1 + lodash.padend: 4.6.1 + lodash.padstart: 4.6.1 + dev: true + /gauge@2.7.4: resolution: {integrity: sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==} deprecated: This package is no longer supported. @@ -15729,6 +16298,23 @@ packages: engines: {node: '>=4'} dev: true + /get-stdin@4.0.1: + resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} + engines: {node: '>=0.10.0'} + dev: true + + /get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + dev: true + + /get-stream@4.1.0: + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} + dependencies: + pump: 3.0.0 + dev: true + /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -15856,6 +16442,31 @@ packages: minimatch: 5.1.6 once: 1.4.0 + /global-agent@2.2.0: + resolution: {integrity: sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==} + engines: {node: '>=10.0'} + dependencies: + boolean: 3.2.0 + core-js: 3.41.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.6.3 + serialize-error: 7.0.1 + dev: true + + /global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.6.3 + serialize-error: 7.0.1 + dev: true + /global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} engines: {node: '>=10'} @@ -15881,6 +16492,16 @@ packages: which: 1.3.1 dev: false + /global-tunnel-ng@2.7.1: + resolution: {integrity: sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==} + engines: {node: '>=0.10'} + dependencies: + encodeurl: 1.0.2 + lodash: 4.17.21 + npm-conf: 1.1.3 + tunnel: 0.0.6 + dev: true + /globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -15943,6 +16564,23 @@ packages: dependencies: get-intrinsic: 1.2.4 + /got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + dev: true + /got@12.6.1: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} @@ -15960,6 +16598,69 @@ packages: responselike: 3.0.0 dev: true + /got@6.7.1: + resolution: {integrity: sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==} + engines: {node: '>=4'} + dependencies: + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + create-error-class: 3.0.2 + duplexer3: 0.1.5 + get-stream: 3.0.0 + is-redirect: 1.0.0 + is-retry-allowed: 1.2.0 + is-stream: 1.1.0 + lowercase-keys: 1.0.1 + safe-buffer: 5.2.1 + timed-out: 4.0.1 + unzip-response: 2.0.1 + url-parse-lax: 1.0.0 + dev: true + + /got@8.3.2: + resolution: {integrity: sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==} + engines: {node: '>=4'} + dependencies: + '@sindresorhus/is': 0.7.0 + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + cacheable-request: 2.1.4 + decompress-response: 3.3.0 + duplexer3: 0.1.5 + get-stream: 3.0.0 + into-stream: 3.1.0 + is-retry-allowed: 1.2.0 + isurl: 1.0.0 + lowercase-keys: 1.0.1 + mimic-response: 1.0.1 + p-cancelable: 0.4.1 + p-timeout: 2.0.1 + pify: 3.0.0 + safe-buffer: 5.2.1 + timed-out: 4.0.1 + url-parse-lax: 3.0.0 + url-to-options: 1.0.1 + dev: true + + /got@9.6.0: + resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} + engines: {node: '>=8.6'} + dependencies: + '@sindresorhus/is': 0.14.0 + '@szmarczak/http-timer': 1.1.2 + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.3 + cacheable-request: 6.1.0 + decompress-response: 3.3.0 + duplexer3: 0.1.5 + get-stream: 4.1.0 + lowercase-keys: 1.0.1 + mimic-response: 1.0.1 + p-cancelable: 1.1.0 + to-readable-stream: 1.0.0 + url-parse-lax: 3.0.0 + dev: true + /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -15978,10 +16679,22 @@ packages: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: true + /has-ansi@2.0.0: + resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} + engines: {node: '>=0.10.0'} + dependencies: + ansi-regex: 2.1.1 + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true + /has-flag@1.0.0: + resolution: {integrity: sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==} + engines: {node: '>=0.10.0'} + dev: true + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -16003,10 +16716,20 @@ packages: resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} engines: {node: '>= 0.4'} + /has-symbol-support-x@1.4.2: + resolution: {integrity: sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==} + dev: true + /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + /has-to-string-tag-x@1.4.1: + resolution: {integrity: sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==} + dependencies: + has-symbol-support-x: 1.4.2 + dev: true + /has-tostringtag@1.0.2: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} @@ -16017,6 +16740,11 @@ packages: /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + /has-yarn@2.1.0: + resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} + engines: {node: '>=8'} + dev: true + /has-yarn@3.0.0: resolution: {integrity: sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -16185,6 +16913,10 @@ packages: entities: 4.5.0 dev: false + /http-cache-semantics@3.8.1: + resolution: {integrity: sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==} + dev: true + /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -16260,6 +16992,14 @@ packages: transitivePeerDependencies: - debug + /http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + dev: true + /http2-wrapper@2.2.0: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} engines: {node: '>=10.19.0'} @@ -16308,6 +17048,13 @@ packages: dependencies: ms: 2.1.3 + /humanize-string@2.1.0: + resolution: {integrity: sha512-sQ+hqmxyXW8Cj7iqxcQxD7oSy3+AXnIZXdUF9lQMkzaG8dtbKAB8U7lCtViMnwQ+MpdCKsO2Kiij3G6UUXq/Xg==} + engines: {node: '>=6'} + dependencies: + decamelize: 2.0.0 + dev: true + /husky@8.0.3: resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} engines: {node: '>=14'} @@ -16441,6 +17188,11 @@ packages: module-details-from-path: 1.0.3 dev: false + /import-lazy@2.1.0: + resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} + engines: {node: '>=4'} + dev: true + /import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} @@ -16458,6 +17210,11 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + /indent-string@3.2.0: + resolution: {integrity: sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==} + engines: {node: '>=4'} + dev: true + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -16500,7 +17257,25 @@ packages: picocolors: 1.1.1 run-async: 2.4.1 rxjs: 7.8.1 - dev: false + + /inquirer@1.2.3: + resolution: {integrity: sha512-diSnpgfv/Ozq6QKuV2mUcwZ+D24b03J3W6EVxzvtkCWJTPrH2gKLsqgSW0vzRMZZFhFdhnvzka0RUJxIm7AOxQ==} + dependencies: + ansi-escapes: 1.4.0 + chalk: 1.1.3 + cli-cursor: 1.0.2 + cli-width: 2.2.1 + external-editor: 1.1.1 + figures: 1.7.0 + lodash: 4.17.21 + mute-stream: 0.0.6 + pinkie-promise: 2.0.1 + run-async: 2.4.1 + rx: 4.1.0 + string-width: 1.0.2 + strip-ansi: 3.0.1 + through: 2.3.8 + dev: true /inquirer@8.2.6: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} @@ -16551,6 +17326,14 @@ packages: tslib: 2.6.3 dev: true + /into-stream@3.1.0: + resolution: {integrity: sha512-TcdjPibTksa1NQximqep2r17ISRiNE9fwlfbg3F8ANdvP5/yrFTew86VcO//jk4QTaMlbjypPBq76HN2zaKfZQ==} + engines: {node: '>=4'} + dependencies: + from2: 2.3.0 + p-is-promise: 1.1.0 + dev: true + /ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -16653,6 +17436,13 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-ci@2.0.0: + resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} + hasBin: true + dependencies: + ci-info: 2.0.0 + dev: true + /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} dependencies: @@ -16683,6 +17473,11 @@ packages: resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} dev: false + /is-docker@1.1.0: + resolution: {integrity: sha512-ZEpopPu+bLIb/x3IF9wXxRdAW74e/ity1XGRxpznAaABKhc8mmtRamRB2l71CSs1YMS8FQxDK/vPK10XlhzG2A==} + engines: {node: '>=0.10.0'} + dev: true + /is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -16690,6 +17485,7 @@ packages: /is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true dev: true /is-extglob@2.1.1: @@ -16709,6 +17505,11 @@ packages: number-is-nan: 1.0.1 dev: true + /is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + dev: true + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -16775,6 +17576,11 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-npm@5.0.0: + resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} + engines: {node: '>=10'} + dev: true + /is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -16802,10 +17608,19 @@ packages: engines: {node: '>=8'} dev: true + /is-object@1.0.2: + resolution: {integrity: sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==} + dev: true + /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + /is-plain-obj@2.1.0: resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} engines: {node: '>=8'} @@ -16833,6 +17648,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-redirect@1.0.0: + resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==} + engines: {node: '>=0.10.0'} + dev: true + /is-reference@1.2.1: resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} dependencies: @@ -16847,6 +17667,21 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-regexp@1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-retry-allowed@1.2.0: + resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-root@1.0.0: + resolution: {integrity: sha512-1d50EJ7ipFxb9bIx213o6KPaJmHN8f+nR48UZWxWVzDx+NA3kpscxi02oQX3rGkEaLBi9m3ZayHngQc3+bBX9w==} + engines: {node: '>=0.10.0'} + dev: true + /is-scoped@2.1.0: resolution: {integrity: sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==} engines: {node: '>=8'} @@ -16870,6 +17705,11 @@ packages: call-bind: 1.0.7 dev: true + /is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -16897,6 +17737,11 @@ packages: resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} dev: true + /is-supported-regexp-flag@1.0.1: + resolution: {integrity: sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==} + engines: {node: '>=0.10.0'} + dev: true + /is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -16971,6 +17816,10 @@ packages: dependencies: is-docker: 2.2.1 + /is-yarn-global@0.3.0: + resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} + dev: true + /is-yarn-global@0.4.1: resolution: {integrity: sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==} engines: {node: '>=12'} @@ -17063,7 +17912,15 @@ packages: istanbul-lib-report: 3.0.1 dev: true - /iterate-object@1.3.5: + /isurl@1.0.0: + resolution: {integrity: sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==} + engines: {node: '>= 4'} + dependencies: + has-to-string-tag-x: 1.4.1 + is-object: 1.0.2 + dev: true + + /iterate-object@1.3.5: resolution: {integrity: sha512-eL23u8oFooYTq6TtJKjp2RYjZnCkUYQvC0T/6fJfWykXJ3quvdDdzKZ3CEjy8b3JGOvLTjDYMEMIp5243R906A==} dev: true @@ -17704,6 +18561,10 @@ packages: resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} engines: {node: '>=4'} + /json-buffer@3.0.0: + resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + dev: true + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -17803,6 +18664,18 @@ packages: engines: {node: '>=18'} dev: false + /keyv@3.0.0: + resolution: {integrity: sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==} + dependencies: + json-buffer: 3.0.0 + dev: true + + /keyv@3.1.0: + resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} + dependencies: + json-buffer: 3.0.0 + dev: true + /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -17821,6 +18694,20 @@ packages: /kuler@2.0.0: resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + /latest-version@3.1.0: + resolution: {integrity: sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==} + engines: {node: '>=4'} + dependencies: + package-json: 4.0.1 + dev: true + + /latest-version@5.1.0: + resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} + engines: {node: '>=8'} + dependencies: + package-json: 6.5.0 + dev: true + /latest-version@7.0.0: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} @@ -17938,6 +18825,14 @@ packages: engines: {node: '>= 12.13.0'} dev: true + /locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -17963,6 +18858,11 @@ packages: signal-exit: 3.0.7 dev: true + /locutus@2.0.32: + resolution: {integrity: sha512-fr7OCpbE4xeefhHqfh6hM2/l9ZB3XvClHgtgFnQNImrM/nqL950o6FO98vmUH8GysfQRCcyBYtZ4C8GcY52Edw==} + engines: {node: '>= 10', yarn: '>= 1'} + dev: true + /lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: true @@ -18006,6 +18906,18 @@ packages: /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + /lodash.pad@4.5.1: + resolution: {integrity: sha512-mvUHifnLqM+03YNzeTBS1/Gr6JRFjd3rRx88FHWUvamVaT9k2O/kXha3yBSOwB9/DTQrSTLJNHvLBBt2FdX7Mg==} + dev: true + + /lodash.padend@4.6.1: + resolution: {integrity: sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==} + dev: true + + /lodash.padstart@4.6.1: + resolution: {integrity: sha512-sW73O6S8+Tg66eY56DBk85aQzzUJDtpoXFBgELMd5P/SotAguo+1kYO6RuYgXxA4HJH3LFTFPASX6ET6bjfriw==} + dev: true + /lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true @@ -18021,6 +18933,13 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + /log-symbols@2.2.0: + resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + engines: {node: '>=4'} + dependencies: + chalk: 2.4.2 + dev: true + /log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -18054,6 +18973,14 @@ packages: dependencies: js-tokens: 4.0.0 + /loud-rejection@1.6.0: + resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} + engines: {node: '>=0.10.0'} + dependencies: + currently-unhandled: 0.4.1 + signal-exit: 3.0.7 + dev: true + /loud-rejection@2.2.0: resolution: {integrity: sha512-S0FayMXku80toa5sZ6Ro4C+s+EtFDCsyJNG/AzFMfX3AxD5Si4dZsgzm/kKnbOxHl5Cv8jBlno8+3XYIh2pNjQ==} engines: {node: '>=8'} @@ -18068,6 +18995,21 @@ packages: tslib: 2.6.3 dev: true + /lowercase-keys@1.0.0: + resolution: {integrity: sha512-RPlX0+PHuvxVDZ7xX+EBVAp4RsVxP/TdDSN2mJYdiq1Lc4Hz7EUSjUI7RZrKKlmrIzVhf6Jo2stj7++gVarS0A==} + engines: {node: '>=0.10.0'} + dev: true + + /lowercase-keys@1.0.1: + resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} + engines: {node: '>=0.10.0'} + dev: true + + /lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + dev: true + /lowercase-keys@3.0.0: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -18229,6 +19171,23 @@ packages: tmpl: 1.0.5 dev: true + /map-age-cleaner@0.1.3: + resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==} + engines: {node: '>=6'} + dependencies: + p-defer: 1.0.0 + dev: true + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj@2.0.0: + resolution: {integrity: sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==} + engines: {node: '>=4'} + dev: true + /map-or-similar@1.5.0: resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true @@ -18258,6 +19217,13 @@ packages: engines: {node: '>= 12'} dev: true + /matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + dependencies: + escape-string-regexp: 4.0.0 + dev: true + /mdast-add-list-metadata@1.0.1: resolution: {integrity: sha512-fB/VP4MJ0LaRsog7hGPxgOrSL3gE/2uEdZyDuSEnKCv/8IkYHiDkIQSbChiJoHyxZZXZ9bzckyRk+vNxFzh8rA==} dependencies: @@ -18317,6 +19283,15 @@ packages: vinyl: 2.2.1 vinyl-file: 3.0.0 + /mem@5.1.1: + resolution: {integrity: sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==} + engines: {node: '>=8'} + dependencies: + map-age-cleaner: 0.1.3 + mimic-fn: 2.1.0 + p-is-promise: 2.1.0 + dev: true + /memfs@3.3.0: resolution: {integrity: sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg==} engines: {node: '>= 4.0.0'} @@ -18346,6 +19321,21 @@ packages: engines: {node: '>= 0.10.0'} dev: true + /meow@5.0.0: + resolution: {integrity: sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==} + engines: {node: '>=6'} + dependencies: + camelcase-keys: 4.2.0 + decamelize-keys: 1.1.1 + loud-rejection: 1.6.0 + minimist-options: 3.0.2 + normalize-package-data: 2.5.0 + read-pkg-up: 3.0.0 + redent: 2.0.0 + trim-newlines: 2.0.0 + yargs-parser: 10.1.0 + dev: true + /merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} dev: true @@ -18419,6 +19409,11 @@ packages: engines: {node: '>=12'} dev: true + /mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + dev: true + /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -18473,6 +19468,14 @@ packages: dependencies: brace-expansion: 2.0.1 + /minimist-options@3.0.2: + resolution: {integrity: sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==} + engines: {node: '>= 4'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + dev: true + /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -18654,6 +19657,10 @@ packages: arrify: 2.0.1 minimatch: 3.0.5 + /mute-stream@0.0.6: + resolution: {integrity: sha512-m0kBTDLF/0lgzCsPVmJSKM5xkLNX7ZAB0Q+n2DP37JMIRPVC2R4c3BdO6x++bXFKftbhvSfKgwxAexME+BRDRw==} + dev: true + /mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} @@ -18918,6 +19925,25 @@ packages: engines: {node: '>=0.10.0'} dev: true + /normalize-url@2.0.1: + resolution: {integrity: sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==} + engines: {node: '>=4'} + dependencies: + prepend-http: 2.0.0 + query-string: 5.1.1 + sort-keys: 2.0.0 + dev: true + + /normalize-url@4.5.1: + resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} + engines: {node: '>=8'} + dev: true + + /normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + dev: true + /normalize-url@8.0.0: resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==} engines: {node: '>=14.16'} @@ -18934,6 +19960,14 @@ packages: dependencies: npm-normalize-package-bin: 3.0.1 + /npm-conf@1.1.3: + resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} + engines: {node: '>=4'} + dependencies: + config-chain: 1.1.13 + pify: 3.0.0 + dev: true + /npm-install-checks@4.0.0: resolution: {integrity: sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==} engines: {node: '>=10'} @@ -18946,6 +19980,14 @@ packages: dependencies: semver: 7.6.3 + /npm-keyword@6.1.0: + resolution: {integrity: sha512-ghcShMAA28IPhJAP4d3T+tndUPzHmvqEfaYwLG1whi4WJ06pdhA3vqL8gXF+Jn8wiqbaRuGVfjE5VXjOgVpW4Q==} + engines: {node: '>=8'} + dependencies: + got: 9.6.0 + registry-url: 5.1.0 + dev: true + /npm-normalize-package-bin@1.0.1: resolution: {integrity: sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==} @@ -19125,6 +20167,13 @@ packages: string.prototype.padend: 3.1.6 dev: true + /npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + dependencies: + path-key: 2.0.1 + dev: true + /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -19138,6 +20187,15 @@ packages: path-key: 4.0.0 dev: true + /npmlog@2.0.4: + resolution: {integrity: sha512-DaL6RTb8Qh4tMe2ttPT1qWccETy2Vi5/8p+htMpLBeXJTr2CAqnF5WQtSP2eFpvaNbhLZ5uilDb98mRm4Q+lZQ==} + deprecated: This package is no longer supported. + dependencies: + ansi: 0.3.1 + are-we-there-yet: 1.1.7 + gauge: 1.2.7 + dev: true + /npmlog@4.1.2: resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} dependencies: @@ -19410,6 +20468,11 @@ packages: dependencies: fn.name: 1.1.0 + /onetime@1.1.0: + resolution: {integrity: sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A==} + engines: {node: '>=0.10.0'} + dev: true + /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -19520,7 +20583,6 @@ packages: /os-homedir@1.0.2: resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} engines: {node: '>=0.10.0'} - dev: false /os-name@4.0.1: resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} @@ -19530,6 +20592,11 @@ packages: windows-release: 4.0.0 dev: false + /os-shim@0.1.3: + resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} + engines: {node: '>= 0.4.0'} + dev: true + /os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -19538,11 +20605,40 @@ packages: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} dev: true + /p-any@2.1.0: + resolution: {integrity: sha512-JAERcaMBLYKMq+voYw36+x5Dgh47+/o7yuv2oQYuSSUml4YeqJEFznBrY2UeEkoSHqBua6hz518n/PsowTYLLg==} + engines: {node: '>=8'} + dependencies: + p-cancelable: 2.1.1 + p-some: 4.1.0 + type-fest: 0.3.1 + dev: true + + /p-cancelable@0.4.1: + resolution: {integrity: sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==} + engines: {node: '>=4'} + dev: true + + /p-cancelable@1.1.0: + resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} + engines: {node: '>=6'} + dev: true + + /p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + dev: true + /p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} dev: true + /p-defer@1.0.0: + resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} + engines: {node: '>=4'} + dev: true + /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -19554,6 +20650,23 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} + /p-is-promise@1.1.0: + resolution: {integrity: sha512-zL7VE4JVS2IFSkR2GQKDSPEVxkoH43/p7oEnwpdCndKYJO0HVeRB7fA8TJwuLOTBREtK0ea8eHaxdwcpob5dmg==} + engines: {node: '>=4'} + dev: true + + /p-is-promise@2.1.0: + resolution: {integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==} + engines: {node: '>=6'} + dev: true + + /p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -19573,6 +20686,13 @@ packages: yocto-queue: 1.0.0 dev: true + /p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -19610,6 +20730,21 @@ packages: eventemitter3: 4.0.7 p-timeout: 3.2.0 + /p-some@4.1.0: + resolution: {integrity: sha512-MF/HIbq6GeBqTrTIl5OJubzkGU+qfFhAFi0gnTAK6rgEIJIknEiABHOTtQu4e6JiXjIwuMPMUFQzyHh5QjCl1g==} + engines: {node: '>=8'} + dependencies: + aggregate-error: 3.1.0 + p-cancelable: 2.1.1 + dev: true + + /p-timeout@2.0.1: + resolution: {integrity: sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==} + engines: {node: '>=4'} + dependencies: + p-finally: 1.0.0 + dev: true + /p-timeout@3.2.0: resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} @@ -19625,6 +20760,11 @@ packages: transitivePeerDependencies: - supports-color + /p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + dev: true + /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -19656,6 +20796,36 @@ packages: /package-json-from-dist@1.0.0: resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + /package-json@4.0.1: + resolution: {integrity: sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==} + engines: {node: '>=4'} + dependencies: + got: 6.7.1 + registry-auth-token: 3.4.0 + registry-url: 3.1.0 + semver: 5.7.2 + dev: true + + /package-json@6.5.0: + resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} + engines: {node: '>=8'} + dependencies: + got: 9.6.0 + registry-auth-token: 4.2.2 + registry-url: 5.1.0 + semver: 6.3.1 + dev: true + + /package-json@7.0.0: + resolution: {integrity: sha512-CHJqc94AA8YfSLHGQT3DbvSIuE12NLFekpM4n7LRrAd3dOJtA911+4xe9q6nC3/jcKraq7nNS9VxgtT0KC+diA==} + engines: {node: '>=12'} + dependencies: + got: 11.8.6 + registry-auth-token: 4.2.2 + registry-url: 5.1.0 + semver: 7.6.3 + dev: true + /package-json@8.1.1: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} @@ -19751,6 +20921,10 @@ packages: - supports-color dev: true + /pad-component@0.0.1: + resolution: {integrity: sha512-8EKVBxCRSvLnsX1p2LlSFSH3c2/wuhY9/BXXWu8boL78FbVKqn2L5SpURt1x5iw6Gq8PTqJ7MdPoe5nCtX3I+g==} + dev: true + /param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} dependencies: @@ -19783,6 +20957,13 @@ packages: is-hexadecimal: 1.0.4 dev: false + /parse-help@1.0.0: + resolution: {integrity: sha512-dlOrbBba6Rrw/nrJ+V7/vkGZdiimWJQzMHZZrYsUq03JE8AV3fAv6kOYX7dP/w2h67lIdmRf8ES8mU44xAgE/Q==} + engines: {node: '>=4'} + dependencies: + execall: 1.0.0 + dev: true + /parse-json@4.0.0: resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} engines: {node: '>=4'} @@ -19844,10 +21025,22 @@ packages: tslib: 2.6.3 dev: true + /passwd-user@3.0.0: + resolution: {integrity: sha512-Iu90rROks+uDK00ppSewoZyqeCwjGR6W8PcY0Phl8YFWju/lRmIogQb98+vSb5RUeYkONL3IC4ZLBFg4FiE0Hg==} + engines: {node: '>=8'} + dependencies: + execa: 1.0.0 + dev: true + /path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} dev: true + /path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -19958,6 +21151,18 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + /pinkie-promise@2.0.1: + resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} + engines: {node: '>=0.10.0'} + dependencies: + pinkie: 2.0.4 + dev: true + + /pinkie@2.0.4: + resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} + engines: {node: '>=0.10.0'} + dev: true + /pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -20191,6 +21396,16 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + /prepend-http@1.0.4: + resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} + engines: {node: '>=0.10.0'} + dev: true + + /prepend-http@2.0.0: + resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} + engines: {node: '>=4'} + dev: true + /prettier-linter-helpers@1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -20391,6 +21606,13 @@ packages: resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} engines: {node: '>=6'} + /pupa@2.1.1: + resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} + engines: {node: '>=8'} + dependencies: + escape-goat: 2.1.1 + dev: true + /pupa@3.1.0: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} engines: {node: '>=12.20'} @@ -20442,6 +21664,15 @@ packages: dependencies: side-channel: 1.0.6 + /query-string@5.1.1: + resolution: {integrity: sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==} + engines: {node: '>=0.10.0'} + dependencies: + decode-uri-component: 0.2.2 + object-assign: 4.1.1 + strict-uri-encode: 1.1.0 + dev: true + /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} @@ -20451,6 +21682,11 @@ packages: /queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + /quick-lru@1.1.0: + resolution: {integrity: sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==} + engines: {node: '>=4'} + dev: true + /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -20783,6 +22019,14 @@ packages: type-fest: 4.25.0 dev: true + /read-pkg-up@3.0.0: + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} + dependencies: + find-up: 2.1.0 + read-pkg: 3.0.0 + dev: true + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -20913,6 +22157,14 @@ packages: dependencies: resolve: 1.22.8 + /redent@2.0.0: + resolution: {integrity: sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==} + engines: {node: '>=4'} + dependencies: + indent-string: 3.2.0 + strip-indent: 2.0.0 + dev: true + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -21022,6 +22274,20 @@ packages: unicode-match-property-value-ecmascript: 2.1.0 dev: true + /registry-auth-token@3.4.0: + resolution: {integrity: sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==} + dependencies: + rc: 1.2.8 + safe-buffer: 5.2.1 + dev: true + + /registry-auth-token@4.2.2: + resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} + engines: {node: '>=6.0.0'} + dependencies: + rc: 1.2.8 + dev: true + /registry-auth-token@5.0.2: resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} engines: {node: '>=14'} @@ -21029,6 +22295,20 @@ packages: '@pnpm/npm-conf': 2.2.2 dev: true + /registry-url@3.1.0: + resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} + engines: {node: '>=0.10.0'} + dependencies: + rc: 1.2.8 + dev: true + + /registry-url@5.1.0: + resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} + engines: {node: '>=8'} + dependencies: + rc: 1.2.8 + dev: true + /registry-url@6.0.1: resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} engines: {node: '>=12'} @@ -21174,6 +22454,18 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /responselike@1.0.2: + resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} + dependencies: + lowercase-keys: 1.0.1 + dev: true + + /responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + dependencies: + lowercase-keys: 2.0.0 + dev: true + /responselike@3.0.0: resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==} engines: {node: '>=14.16'} @@ -21181,6 +22473,14 @@ packages: lowercase-keys: 3.0.0 dev: true + /restore-cursor@1.0.1: + resolution: {integrity: sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==} + engines: {node: '>=0.10.0'} + dependencies: + exit-hook: 1.1.1 + onetime: 1.1.0 + dev: true + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -21224,6 +22524,18 @@ packages: glob: 10.4.5 dev: true + /roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.3 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + dev: true + /rollup-plugin-inject@3.0.2: resolution: {integrity: sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==} dependencies: @@ -21260,6 +22572,14 @@ packages: fsevents: 2.3.3 dev: true + /root-check@1.0.0: + resolution: {integrity: sha512-lt1ts72QmU7jh1DlOJqFN/le/aiRGAbchSSMhNpLQubDWPEOe0YKCcrhprkgyMxxFAcrEhyfTTUfc+Dj/bo4JA==} + engines: {node: '>=0.10.0'} + dependencies: + downgrade-root: 1.2.2 + sudo-block: 1.2.0 + dev: true + /router@1.3.8: resolution: {integrity: sha512-461UFH44NtSfIlS83PUg2N7OZo86BC/kB3dY77gJdsODsBhhw7+2uE0tzTINxrY9CahCUVk1VhpWCA5i1yoIEg==} engines: {node: '>= 0.8'} @@ -21298,6 +22618,10 @@ packages: dependencies: queue-microtask: 1.2.3 + /rx@4.1.0: + resolution: {integrity: sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==} + dev: true + /rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -21452,6 +22776,17 @@ packages: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} dev: true + /semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + dev: true + + /semver-diff@3.1.1: + resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.1 + dev: true + /semver-diff@4.0.0: resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==} engines: {node: '>=12'} @@ -21459,6 +22794,18 @@ packages: semver: 7.6.3 dev: true + /semver-regex@2.0.0: + resolution: {integrity: sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==} + engines: {node: '>=6'} + dev: true + + /semver-truncate@1.1.2: + resolution: {integrity: sha512-V1fGg9i4CL3qesB6U0L6XAm4xOJiHmt4QAacazumuasc03BvtFGIMCduv01JWQ69Nv+JST9TqhSCiJoxoY031w==} + engines: {node: '>=0.10.0'} + dependencies: + semver: 5.7.2 + dev: true + /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -21505,6 +22852,13 @@ packages: - supports-color dev: true + /serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + dependencies: + type-fest: 0.13.1 + dev: true + /serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: @@ -21742,12 +23096,27 @@ packages: ip-address: 9.0.5 smart-buffer: 4.2.0 + /sort-keys@2.0.0: + resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} + engines: {node: '>=4'} + dependencies: + is-plain-obj: 1.1.0 + dev: true + /sort-keys@4.2.0: resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==} engines: {node: '>=8'} dependencies: is-plain-obj: 2.1.0 + /sort-on@4.1.1: + resolution: {integrity: sha512-nj8myvTCEErLMMWnye61z1pV5osa7njoosoQNdylD8WyPYHoHCBQx/xn7mGJL6h4oThvGpYSIAxfm8VUr75qTQ==} + engines: {node: '>=8'} + dependencies: + arrify: 2.0.1 + dot-prop: 5.3.0 + dev: true + /source-list-map@2.0.1: resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} dev: true @@ -21804,6 +23173,14 @@ packages: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} dev: true + /spawn-sync@1.0.15: + resolution: {integrity: sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==} + requiresBuild: true + dependencies: + concat-stream: 1.6.2 + os-shim: 0.1.3 + dev: true + /spawnd@10.0.0: resolution: {integrity: sha512-6GKcakMTryb5b1SWCvdubCDHEsR2k+5VZUD5G19umZRarkvj1RyCGyizcqhjewI7cqZo8fTVD8HpnDZbVOLMtg==} engines: {node: '>=16'} @@ -22003,6 +23380,11 @@ packages: optionalDependencies: bare-events: 2.5.0 + /strict-uri-encode@1.1.0: + resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} + engines: {node: '>=0.10.0'} + dev: true + /string-hash@1.1.3: resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} dev: true @@ -22024,6 +23406,14 @@ packages: strip-ansi: 3.0.1 dev: true + /string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + dev: true + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -22145,6 +23535,13 @@ packages: ansi-regex: 2.1.1 dev: true + /strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + dependencies: + ansi-regex: 3.0.1 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -22185,6 +23582,11 @@ packages: engines: {node: '>=8'} dev: true + /strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + dev: true + /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -22194,6 +23596,11 @@ packages: engines: {node: '>=12'} dev: true + /strip-indent@2.0.0: + resolution: {integrity: sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==} + engines: {node: '>=4'} + dev: true + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -22249,6 +23656,15 @@ packages: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: false + /sudo-block@1.2.0: + resolution: {integrity: sha512-RE3gka+wcmkvAMt7Ht/TORJ6uxIo+MBPCCibLLygj6xec817CtEYDG6IyICFyWwHZwO3c6d61XdWRrgffq7WJQ==} + engines: {node: '>=0.10.0'} + dependencies: + chalk: 1.1.3 + is-docker: 1.1.0 + is-root: 1.0.0 + dev: true + /superagent@8.0.5: resolution: {integrity: sha512-lQVE0Praz7nHiSaJLKBM/cZyi7J0E4io8tWnGSBdBrqAzhzrjQ/F5iGP9Zr29CJC8N5zYdhG2kKaNcB6dKxp7g==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -22277,6 +23693,18 @@ packages: - supports-color dev: true + /supports-color@2.0.0: + resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} + engines: {node: '>=0.8.0'} + dev: true + + /supports-color@3.2.3: + resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==} + engines: {node: '>=0.8.0'} + dependencies: + has-flag: 1.0.0 + dev: true + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -22323,6 +23751,27 @@ packages: strip-ansi: 6.0.1 dev: true + /tabtab@1.3.2: + resolution: {integrity: sha512-qHWOJ5g7lrpftZMyPv3ZaYZs7PuUTKWEP/TakZHfpq66bSwH25SQXn5616CCh6Hf/1iPcgQJQHGcJkzQuATabQ==} + hasBin: true + dependencies: + debug: 2.6.9 + inquirer: 1.2.3 + minimist: 1.2.8 + mkdirp: 0.5.6 + npmlog: 2.0.4 + object-assign: 4.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /taketalk@1.0.0: + resolution: {integrity: sha512-kS7E53It6HA8S1FVFBWP7HDwgTiJtkmYk7TsowGlizzVrivR1Mf9mgjXHY1k7rOfozRVMZSfwjB3bevO4QEqpg==} + dependencies: + get-stdin: 4.0.1 + minimist: 1.2.8 + dev: true + /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -22460,15 +23909,32 @@ packages: /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + /timed-out@4.0.1: + resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} + engines: {node: '>=0.10.0'} + dev: true + /tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} dev: true + /titleize@2.1.0: + resolution: {integrity: sha512-m+apkYlfiQTKLW+sI4vqUkwMEzfgEUEYSqljx1voUE3Wz/z1ZsxyzSxvH2X8uKVrOp7QkByWt0rA6+gvhCKy6g==} + engines: {node: '>=6'} + dev: true + /titleize@3.0.0: resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} engines: {node: '>=12'} dev: true + /tmp@0.0.29: + resolution: {integrity: sha512-89PTqMWGDva+GqClOqBV9s3SMh7MA3Mq0pJUdAoHuF65YoE7O0LermaZkVfT5/Ngfo18H4eYiyG7zKOtnEbxsw==} + engines: {node: '>=0.4.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -22488,6 +23954,11 @@ packages: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} + /to-readable-stream@1.0.0: + resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} + engines: {node: '>=6'} + dev: true + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -22528,6 +23999,11 @@ packages: /treeverse@1.0.4: resolution: {integrity: sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==} + /trim-newlines@2.0.0: + resolution: {integrity: sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==} + engines: {node: '>=4'} + dev: true + /trim-repeated@1.0.0: resolution: {integrity: sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==} engines: {node: '>=0.10.0'} @@ -22779,6 +24255,22 @@ packages: safe-buffer: 5.2.1 dev: true + /tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + dev: true + + /twig@1.17.1: + resolution: {integrity: sha512-atxccyr/BHtb1gPMA7Lvki0OuU17XBqHsNH9lzDHt9Rr1293EVZOosSZabEXz/DPVikIW8ZDqSkEddwyJnQN2w==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@babel/runtime': 7.25.0 + locutus: 2.0.32 + minimatch: 3.0.5 + walk: 2.3.15 + dev: true + /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} engines: {node: '>= 0.8.0'} @@ -22796,6 +24288,11 @@ packages: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} + /type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -22804,6 +24301,11 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + /type-fest@0.3.1: + resolution: {integrity: sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==} + engines: {node: '>=6'} + dev: true + /type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -22933,6 +24435,10 @@ packages: is-typedarray: 1.0.0 dev: true + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: true + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -23103,6 +24609,13 @@ packages: dependencies: imurmurhash: 0.1.4 + /unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + dependencies: + crypto-random-string: 2.0.0 + dev: true + /unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} @@ -23167,6 +24680,11 @@ packages: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} + /unzip-response@2.0.1: + resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==} + engines: {node: '>=4'} + dev: true + /unzip-stream@0.3.4: resolution: {integrity: sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw==} dependencies: @@ -23217,6 +24735,26 @@ packages: picocolors: 1.1.1 dev: true + /update-notifier@5.1.0: + resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==} + engines: {node: '>=10'} + dependencies: + boxen: 5.1.2 + chalk: 4.1.2 + configstore: 5.0.1 + has-yarn: 2.1.0 + import-lazy: 2.1.0 + is-ci: 2.0.0 + is-installed-globally: 0.4.0 + is-npm: 5.0.0 + is-yarn-global: 0.3.0 + latest-version: 5.1.0 + pupa: 2.1.1 + semver: 7.6.3 + semver-diff: 3.1.1 + xdg-basedir: 4.0.0 + dev: true + /update-notifier@6.0.2: resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} engines: {node: '>=14.16'} @@ -23260,12 +24798,31 @@ packages: /url-join@4.0.1: resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + /url-parse-lax@1.0.0: + resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} + engines: {node: '>=0.10.0'} + dependencies: + prepend-http: 1.0.4 + dev: true + + /url-parse-lax@3.0.0: + resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} + engines: {node: '>=4'} + dependencies: + prepend-http: 2.0.0 + dev: true + /url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + /url-to-options@1.0.1: + resolution: {integrity: sha512-0kQLIzG4fdk/G5NONku64rSH/x32NOA39LVQqlK8Le6lvTF6GGRJpqaQFGgU+CLwySIqBSMdwYM0sYcW9f6P4A==} + engines: {node: '>= 4'} + dev: true + /url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -23290,6 +24847,13 @@ packages: react: 16.14.0 dev: false + /user-home@2.0.0: + resolution: {integrity: sha512-KMWqdlOcjCYdtIJpicDSFBQ8nFwS2i9sslAd6f4+CBGcU4gist2REnr2fxj2YocvJFxSF3ZOHLYLVZnUxv4BZQ==} + engines: {node: '>=0.10.0'} + dependencies: + os-homedir: 1.0.2 + dev: true + /utf8-byte-length@1.0.4: resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==} dev: false @@ -23466,6 +25030,12 @@ packages: resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} dev: true + /walk@2.3.15: + resolution: {integrity: sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==} + dependencies: + foreachasync: 3.0.0 + dev: true + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -23696,6 +25266,13 @@ packages: dependencies: string-width: 4.2.3 + /widest-line@3.1.0: + resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} + engines: {node: '>=8'} + dependencies: + string-width: 4.2.3 + dev: true + /widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} engines: {node: '>=12'} @@ -23751,6 +25328,14 @@ packages: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} dev: true + /wrap-ansi@2.1.0: + resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==} + engines: {node: '>=0.10.0'} + dependencies: + string-width: 1.0.2 + strip-ansi: 3.0.1 + dev: true + /wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -23831,6 +25416,11 @@ packages: utf-8-validate: optional: true + /xdg-basedir@4.0.0: + resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} + engines: {node: '>=8'} + dev: true + /xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} engines: {node: '>=12'} @@ -23881,6 +25471,10 @@ packages: engines: {node: '>=0.6.0'} dev: false + /xregexp@4.0.0: + resolution: {integrity: sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==} + dev: true + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -23920,6 +25514,12 @@ packages: glob: 7.2.0 dev: false + /yargs-parser@10.1.0: + resolution: {integrity: sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==} + dependencies: + camelcase: 4.1.0 + dev: true + /yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -23962,6 +25562,30 @@ packages: fd-slicer: 1.1.0 dev: true + /yeoman-character@1.1.0: + resolution: {integrity: sha512-oxzeZugaEkVJC+IHwcb+DZDb8IdbZ3f4rHax4+wtJstCx+9BAaMX+Inmp3wmGmTWftJ7n5cPqQRbo1FaV/vNXQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + supports-color: 3.2.3 + dev: true + + /yeoman-doctor@5.0.0: + resolution: {integrity: sha512-9Ni+uXWeFix9+1t7s1q40zZdbcpdi/OwgD4N4cVaqI+bppPciOOXQ/RSggannwZu8m8zrSWELn6/93G7308jgg==} + engines: {node: '>=12.10.0'} + hasBin: true + dependencies: + ansi-styles: 3.2.1 + bin-version-check: 4.0.0 + chalk: 2.4.2 + global-agent: 2.2.0 + latest-version: 3.1.0 + log-symbols: 2.2.0 + semver: 5.7.2 + twig: 1.17.1 + user-home: 2.0.0 + dev: true + /yeoman-environment@3.19.3: resolution: {integrity: sha512-/+ODrTUHtlDPRH9qIC0JREH8+7nsRcjDl3Bxn2Xo/rvAaVvixH5275jHwg0C85g4QsF4P6M2ojfScPPAl+pLAg==} engines: {node: '>=12.10.0'} @@ -24066,6 +25690,49 @@ packages: engines: {node: '>=6'} dev: true + /yo@4.3.1(mem-fs@2.1.0): + resolution: {integrity: sha512-KKp5WNPq0KdqfJY4W6HSiDG4DcgvmL4InWfkg5SVG9oYp+DTUUuc5ZmDw9VAvK0Z2J6XeEumDHcWh8NDhzrtOw==} + engines: {node: '>=12.10.0'} + hasBin: true + requiresBuild: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + cli-list: 0.2.0 + configstore: 5.0.1 + cross-spawn: 7.0.6 + figures: 3.2.0 + fullname: 4.0.1 + global-agent: 3.0.0 + global-tunnel-ng: 2.7.1 + got: 8.3.2 + humanize-string: 2.1.0 + inquirer: 8.2.6 + lodash: 4.17.21 + mem-fs-editor: 9.4.0(mem-fs@2.1.0) + meow: 5.0.0 + npm-keyword: 6.1.0 + open: 8.4.2 + package-json: 7.0.0 + parse-help: 1.0.0 + read-pkg-up: 7.0.1 + root-check: 1.0.0 + sort-on: 4.1.1 + string-length: 4.0.2 + tabtab: 1.3.2 + titleize: 2.1.0 + update-notifier: 5.1.0 + user-home: 2.0.0 + yeoman-character: 1.1.0 + yeoman-doctor: 5.0.0 + yeoman-environment: 3.19.3 + yosay: 2.0.2 + transitivePeerDependencies: + - bluebird + - mem-fs + - supports-color + dev: true + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -24075,6 +25742,22 @@ packages: engines: {node: '>=12.20'} dev: true + /yosay@2.0.2: + resolution: {integrity: sha512-avX6nz2esp7IMXGag4gu6OyQBsMh/SEn+ZybGu3yKPlOTE6z9qJrzG/0X5vCq/e0rPFy0CUYCze0G5hL310ibA==} + engines: {node: '>=4'} + hasBin: true + dependencies: + ansi-regex: 2.1.1 + ansi-styles: 3.2.1 + chalk: 1.1.3 + cli-boxes: 1.0.0 + pad-component: 0.0.1 + string-width: 2.1.1 + strip-ansi: 3.0.1 + taketalk: 1.0.0 + wrap-ansi: 2.1.0 + dev: true + /zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} diff --git a/sonar-project.properties b/sonar-project.properties index ed837c2fef..0d12c97311 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -42,6 +42,7 @@ sonar.javascript.lcov.reportPaths=packages/abap-deploy-config-inquirer/coverage/ packages/i18n/coverage/lcov.info, \ packages/environment-check/coverage/lcov.info, \ packages/eslint-plugin-fiori-tools/coverage/lcov.info, \ + packages/repo-app-download-sub-generator/coverage/lcov.info, \ packages/generator-adp/coverage/lcov.info, \ packages/logger/coverage/lcov.info, \ packages/mockserver-config-writer/coverage/lcov.info, \ @@ -99,6 +100,7 @@ sonar.testExecutionReportPaths=packages/abap-deploy-config-inquirer/coverage/son packages/axios-extension/coverage/sonar-report.xml, \ packages/backend-proxy-middleware/coverage/sonar-report.xml, \ packages/btp-utils/coverage/sonar-report.xml, \ + packages/repo-app-download-sub-generator/coverage/sonar-report.xml, \ packages/cap-config-writer/coverage/sonar-report.xml, \ packages/cards-editor-config-writer/coverage/sonar-report.xml, \ packages/cards-editor-middleware/coverage/sonar-report.xml, \ diff --git a/tsconfig.json b/tsconfig.json index 2bb78af3a3..58272a6d63 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -201,6 +201,9 @@ { "path": "packages/reload-middleware" }, + { + "path": "packages/repo-app-download-sub-generator" + }, { "path": "packages/serve-static-middleware" },