From bb728e3f109db84e3135a14beb4e8fe816b59639 Mon Sep 17 00:00:00 2001 From: matthieu-crouzet Date: Mon, 22 Jan 2024 18:45:19 +0100 Subject: [PATCH] feat(metrics): question to activate telemetry + documentation --- docs/telemetry/README.md | 25 ++++++++ packages/@ama-sdk/create/src/index.it.spec.ts | 4 +- packages/@ama-sdk/create/src/index.ts | 3 +- .../schematics/typescript/core/index.ts | 1 - packages/@o3r/extractors/package.json | 5 +- packages/@o3r/extractors/src/core/wrapper.ts | 62 +++++++++++++++++- .../@o3r/schematics/src/utility/wrapper.ts | 63 ++++++++++++++++++- .../@o3r/telemetry/schematics/ng-add/index.ts | 17 ++++- packages/@o3r/telemetry/src/builders/index.ts | 30 +++++++-- .../@o3r/telemetry/src/environment/index.ts | 3 +- .../telemetry/src/schematics/index.spec.ts | 10 ++- .../@o3r/telemetry/src/schematics/index.ts | 27 ++++++-- yarn.lock | 27 ++++++-- 13 files changed, 246 insertions(+), 31 deletions(-) create mode 100644 docs/telemetry/README.md diff --git a/docs/telemetry/README.md b/docs/telemetry/README.md new file mode 100644 index 0000000000..bc5dfb9a64 --- /dev/null +++ b/docs/telemetry/README.md @@ -0,0 +1,25 @@ +# Telemetry + +You can help the Otter Team to prioritize features and improvements by permitting the Otter team to send command-line command usage statistics to Amadeus. The Otter Team does not collect usage statistics unless you explicitly opt in. + +## What is collected? + +Usage analytics may include the following information: +- Your operating system (macOS, Linux distribution, Windows) and its version. +- Package manager name and version (local version only). +- Node.js version (local version only). +- Otter version (local version only). +- Command name that was run. +- The time it took to run. +- Project name. +- The schematic/builder options. + +> [!WARNING] +> We don't use it, but your IP address will also be stored for one month for security reasons. + +## How to disable telemetry? + +To disable it for: +- your project, set `config.o3rMetrics` to false in your `package.json`. +- your machine, set `O3R_METRICS` to false in your environment variables. +- a builder/schematic run, run it with `--no-o3r-metrics` diff --git a/packages/@ama-sdk/create/src/index.it.spec.ts b/packages/@ama-sdk/create/src/index.it.spec.ts index 575d712a5d..730b79de24 100644 --- a/packages/@ama-sdk/create/src/index.it.spec.ts +++ b/packages/@ama-sdk/create/src/index.it.spec.ts @@ -62,8 +62,8 @@ describe('Create new sdk command', () => { packageManagerExec({ script: 'schematics', args: ['@ama-sdk/schematics:typescript-core', '--spec-path', path.join(path.relative(sdkPackagePath, sdkFolderPath), 'swagger-spec.yml')] - }, { ...execAppOptions, cwd: sdkPackagePath } - )).not.toThrow(); + }, { ...execAppOptions, cwd: sdkPackagePath }) + ).not.toThrow(); expect(() => packageManagerRun({script: 'build'}, { ...execAppOptions, cwd: sdkPackagePath })).not.toThrow(); expect(() => packageManagerRun({ script: 'doc:generate'}, { ...execAppOptions, cwd: sdkPackagePath })).not.toThrow(); }); diff --git a/packages/@ama-sdk/create/src/index.ts b/packages/@ama-sdk/create/src/index.ts index f94f28a3c2..f02ce93d68 100644 --- a/packages/@ama-sdk/create/src/index.ts +++ b/packages/@ama-sdk/create/src/index.ts @@ -70,7 +70,8 @@ const schematicArgs = [ '--package', pck, '--package-manager', packageManager, '--directory', targetDirectory, - ...(argv['spec-path'] ? ['--spec-path', argv['spec-path']] : []) + ...(argv['spec-path'] ? ['--spec-path', argv['spec-path']] : []), + ...(typeof argv['o3r-metrics'] !== 'undefined' ? [`--${!argv['o3r-metrics'] ? 'no-' : ''}o3r-metrics`] : []) ]; const getSchematicStepInfo = (schematic: string) => ({ diff --git a/packages/@ama-sdk/schematics/schematics/typescript/core/index.ts b/packages/@ama-sdk/schematics/schematics/typescript/core/index.ts index dfdcbc9ede..b50b58d418 100644 --- a/packages/@ama-sdk/schematics/schematics/typescript/core/index.ts +++ b/packages/@ama-sdk/schematics/schematics/typescript/core/index.ts @@ -71,7 +71,6 @@ function ngGenerateTypescriptSDKFn(options: NgGenerateTypescriptSDKCoreSchematic * @param _context */ const clearGeneratedCode = (tree: Tree, _context: SchematicContext) => { - treeGlob(tree, path.posix.join(targetPath, 'src', 'api', '**', '*.ts')).forEach((file) => tree.delete(file)); treeGlob(tree, path.posix.join(targetPath, 'src', 'api', '**', '*.ts')).forEach((file) => tree.delete(file)); treeGlob(tree, path.posix.join(targetPath, 'src', 'models', 'base', '**', '!(index).ts')).forEach((file) => tree.delete(file)); treeGlob(tree, path.posix.join(targetPath, 'src', 'spec', '!(operation-adapter|index).ts')).forEach((file) => tree.delete(file)); diff --git a/packages/@o3r/extractors/package.json b/packages/@o3r/extractors/package.json index abb55b3c25..918372be57 100644 --- a/packages/@o3r/extractors/package.json +++ b/packages/@o3r/extractors/package.json @@ -28,15 +28,13 @@ "typescript": "~5.2.2" }, "peerDependenciesMeta": { - "@o3r/schematics": { - "optional": true - }, "@o3r/telemetry": { "optional": true } }, "dependencies": { "@microsoft/tsdoc": "~0.14.1", + "inquirer": "~8.2.6", "jsonschema": "~1.4.1", "tslib": "^2.5.3", "typedoc": "~0.25.0" @@ -69,6 +67,7 @@ "@o3r/telemetry": "workspace:^", "@o3r/test-helpers": "workspace:^", "@stylistic/eslint-plugin-ts": "^1.5.4", + "@types/inquirer": "~8.2.10", "@types/jest": "~29.5.2", "@types/node": "^20.0.0", "@typescript-eslint/eslint-plugin": "^7.0.1", diff --git a/packages/@o3r/extractors/src/core/wrapper.ts b/packages/@o3r/extractors/src/core/wrapper.ts index aa2835ca55..6fe0f83375 100644 --- a/packages/@o3r/extractors/src/core/wrapper.ts +++ b/packages/@o3r/extractors/src/core/wrapper.ts @@ -1,4 +1,9 @@ +import { getPackageManagerRunner } from '@o3r/schematics'; import type { BuilderWrapper } from '@o3r/telemetry'; +import { prompt, Question } from 'inquirer'; +import { execFileSync } from 'node:child_process'; +import { existsSync, promises } from 'node:fs'; +import * as path from 'node:path'; const noopBuilderWrapper: BuilderWrapper = (fn) => fn; @@ -8,10 +13,63 @@ const noopBuilderWrapper: BuilderWrapper = (fn) => fn; * @param builderFn */ export const createBuilderWithMetricsIfInstalled: BuilderWrapper = (builderFn) => async (opts, ctx) => { + const packageJsonPath = path.join(ctx.workspaceRoot, 'package.json'); + const packageJson = existsSync(packageJsonPath) + ? JSON.parse(await promises.readFile(packageJsonPath, {encoding: 'utf8'})) + : {}; let wrapper: BuilderWrapper = noopBuilderWrapper; try { const { createBuilderWithMetrics } = await import('@o3r/telemetry'); - wrapper = createBuilderWithMetrics; - } catch {} + if (packageJson.config?.o3rMetrics) { + wrapper = createBuilderWithMetrics; + } + } catch (e: any) { + // Do not throw if `@o3r/telemetry is not installed + if (packageJson.config?.o3rMetrics === true) { + ctx.logger.info('`config.o3rMetrics` is set to true in your package.json, please install the telemetry package with `ng add @o3r/telemetry` to enable the collection of metrics.'); + } else if ((!process.env.CI || process.env.CI === 'false') && typeof packageJson.config?.o3rMetrics === 'undefined') { + ctx.logger.debug('`@o3r/telemetry` is not available.\nAsking to add the dependency\n' + e.toString()); + + const question: Question = { + type: 'confirm', + name: 'isReplyPositive', + message: ` +Would you like to share anonymous data about the usage of Otter builders and schematics with the Otter Team at Amadeus ? +It will help us to improve our tools. +For more details and instructions on how to change these settings, see https://github.com/AmadeusITGroup/otter/blob/main/docs/telemetry/README.md. + ` + }; + const { isReplyPositive } = await prompt([question]); + + if (isReplyPositive) { + const pmr = getPackageManagerRunner(packageJson); + + try { + const version = JSON.parse(await promises.readFile(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version; + execFileSync(`${pmr} ng add @o3r/telemetry@${version}`); + } catch { + ctx.logger.warn('Failed to install `@o3r/telemetry`.'); + } + + try { + const { createBuilderWithMetrics } = await import('@o3r/telemetry'); + wrapper = createBuilderWithMetrics; + } catch { + // If pnp context package installed in the same process will not be available + } + } else { + ctx.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.'); + + packageJson.config ||= {}; + packageJson.config.o3rMetrics = false; + + if (existsSync(packageJsonPath)) { + await promises.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); + } else { + ctx.logger.warn(`No package.json found in ${ctx.workspaceRoot}.`); + } + } + } + } return wrapper(builderFn)(opts, ctx); }; diff --git a/packages/@o3r/schematics/src/utility/wrapper.ts b/packages/@o3r/schematics/src/utility/wrapper.ts index a90943bf93..e9fce22124 100644 --- a/packages/@o3r/schematics/src/utility/wrapper.ts +++ b/packages/@o3r/schematics/src/utility/wrapper.ts @@ -1,4 +1,11 @@ +import type { JsonObject } from '@angular-devkit/core'; +import { askConfirmation } from '@angular/cli/src/utilities/prompt'; import type { SchematicWrapper } from '@o3r/telemetry'; +import { NodeDependencyType } from '@schematics/angular/utility/dependencies'; +import { readFileSync } from 'node:fs'; +import * as path from 'node:path'; +import { lastValueFrom } from 'rxjs'; +import { NodePackageNgAddTask } from '../tasks/index'; const noopSchematicWrapper: SchematicWrapper = (fn) => fn; @@ -7,14 +14,64 @@ const noopSchematicWrapper: SchematicWrapper = (fn) => fn; * if @o3r/telemetry is installed * @param schematicFn */ -export const createSchematicWithMetricsIfInstalled: SchematicWrapper = (schematicFn) => (opts) => async (_, context) => { +export const createSchematicWithMetricsIfInstalled: SchematicWrapper = (schematicFn) => (opts) => async (tree, context) => { let wrapper: SchematicWrapper = noopSchematicWrapper; + const packageJsonPath = 'package.json'; + const packageJson = tree.exists(packageJsonPath) ? tree.readJson(packageJsonPath) as JsonObject : {}; try { const { createSchematicWithMetrics } = await import('@o3r/telemetry'); - wrapper = createSchematicWithMetrics; + if ((packageJson.config as JsonObject)?.o3rMetrics) { + wrapper = createSchematicWithMetrics; + } } catch (e: any) { // Do not throw if `@o3r/telemetry is not installed - context.logger.debug('`@o3r/telemetry` is not available\n' + e.toString()); + if ((packageJson.config as JsonObject)?.o3rMetrics) { + context.logger.warn('`config.o3rMetrics` is set to true in your package.json, please install the telemetry package with `ng add @o3r/telemetry` to enable the collection of metrics.'); + } else if (context.interactive && (packageJson.config as JsonObject)?.o3rMetrics !== false) { + context.logger.debug('`@o3r/telemetry` is not available.\nAsking to add the dependency\n' + e.toString()); + + const isReplyPositive = await askConfirmation( + ` +Would you like to share anonymous data about the usage of Otter builders and schematics with the Otter Team at Amadeus ? +It will help us to improve our tools. +For more details and instructions on how to change these settings, see https://github.com/AmadeusITGroup/otter/blob/main/docs/telemetry/README.md. + `, + false + ); + + if (isReplyPositive) { + const version = JSON.parse(readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8')).version; + context.addTask( + new NodePackageNgAddTask( + '@o3r/telemetry', + { + dependencyType: NodeDependencyType.Dev, + version + } + ) + ); + await lastValueFrom(context.engine.executePostTasks()); + + try { + const { createSchematicWithMetrics } = await import('@o3r/telemetry'); + wrapper = createSchematicWithMetrics; + } catch { + // If pnp context package installed in the same process will not be available + } + } else { + context.logger.info('You can activate it at any time by running `ng add @o3r/telemetry`.'); + + packageJson.config ||= {}; + (packageJson.config as JsonObject).o3rMetrics = false; + + if (tree.exists(packageJsonPath)) { + tree.overwrite( + packageJsonPath, + JSON.stringify(packageJson, null, 2) + ); + } + } + } } return wrapper(schematicFn)(opts); }; diff --git a/packages/@o3r/telemetry/schematics/ng-add/index.ts b/packages/@o3r/telemetry/schematics/ng-add/index.ts index d26ce60c21..fed76a38ab 100644 --- a/packages/@o3r/telemetry/schematics/ng-add/index.ts +++ b/packages/@o3r/telemetry/schematics/ng-add/index.ts @@ -1,4 +1,5 @@ -import { noop, Rule } from '@angular-devkit/schematics'; +import type { JsonObject } from '@angular-devkit/core'; +import type { Rule } from '@angular-devkit/schematics'; import type { NgAddSchematicsSchema } from './schema'; /** @@ -6,5 +7,17 @@ import type { NgAddSchematicsSchema } from './schema'; * @param options */ export function ngAdd(_options: NgAddSchematicsSchema): Rule { - return noop(); + return (tree, context) => { + if (tree.exists('/package.json')) { + const packageJson = tree.readJson('/package.json') as JsonObject; + packageJson.config ||= {}; + (packageJson.config as JsonObject).o3rMetrics = true; + tree.overwrite('/package.json', JSON.stringify(packageJson, null, 2)); + } + context.logger.info(` +By installing '@o3r/telemetry', you have activated the collection of anonymous data for Otter builders and schematics usage. +You can deactivate it at any time by changing 'config.o3rMetrics' in 'package.json' or by setting 'O3R_METRICS' to false as environment variable. +You can also temporarily deactivate it by running your builder or schematic with '--no-o3r-metrics'. + `); + }; } diff --git a/packages/@o3r/telemetry/src/builders/index.ts b/packages/@o3r/telemetry/src/builders/index.ts index 6818ae2e8e..64c1127e9e 100644 --- a/packages/@o3r/telemetry/src/builders/index.ts +++ b/packages/@o3r/telemetry/src/builders/index.ts @@ -2,8 +2,10 @@ import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; -import { getEnvironmentInfo } from '../environment/index'; +import { existsSync, readFileSync } from 'node:fs'; +import * as path from 'node:path'; import { performance } from 'node:perf_hooks'; +import { getEnvironmentInfo } from '../environment/index'; import { BuilderMetricData, sendData as defaultSendData, type SendDataFn } from '../sender'; type BuilderWrapperFn = @@ -58,11 +60,27 @@ export const createBuilderWithMetrics: BuilderWrapper = (builderFn, sendData = d error }; context.logger.debug(JSON.stringify(data, null, 2)); - void sendData(data, context.logger).catch((e) => { - // Do not throw error if we don't manage to collect data - const err = (e instanceof Error ? e : new Error(error)); - context.logger.error(err.stack || err.toString()); - }); + const packageJsonPath = path.join(context.currentDirectory, 'package.json'); + const packageJson = existsSync(packageJsonPath) ? JSON.parse(readFileSync(packageJsonPath, 'utf-8')) : {}; + const shouldSendData = !!( + (options as any).o3rMetrics + ?? ((process.env.O3R_METRICS || '').length > 0 ? process.env.O3R_METRICS !== 'false' : undefined) + ?? packageJson.config?.o3rMetrics + ); + if (shouldSendData) { + if (typeof ((options as any).o3rMetrics ?? process.env.O3R_METRICS) === 'undefined') { + context.logger.info( + 'Telemetry is globally activated for the project (`config.o3rMetrics` in package.json). ' + + 'If you personally don\'t want to send telemetry, you can deactivate it by setting `O3R_METRICS` to false in your environment variables, ' + + 'or by calling the builder with `--no-o3r-metrics`.' + ); + } + void sendData(data, context.logger).catch((e) => { + // Do not throw error if we don't manage to collect data + const err = (e instanceof Error ? e : new Error(error)); + context.logger.error(err.stack || err.toString()); + }); + } } }; diff --git a/packages/@o3r/telemetry/src/environment/index.ts b/packages/@o3r/telemetry/src/environment/index.ts index 244d856ec0..56ca1b96e3 100644 --- a/packages/@o3r/telemetry/src/environment/index.ts +++ b/packages/@o3r/telemetry/src/environment/index.ts @@ -153,7 +153,8 @@ export const getEnvironmentInfo = async (): Promise => { os: osInfo, node: nodeInfo, packageManager: packageManagerInfo, - otter: otterInfo, ci, + otter: otterInfo, + ci, ...(projectName ? { project: { name: projectName } } : {}) }; }; diff --git a/packages/@o3r/telemetry/src/schematics/index.spec.ts b/packages/@o3r/telemetry/src/schematics/index.spec.ts index 100d2658ee..b7f29fb5ea 100644 --- a/packages/@o3r/telemetry/src/schematics/index.spec.ts +++ b/packages/@o3r/telemetry/src/schematics/index.spec.ts @@ -18,15 +18,17 @@ jest.mock('node:perf_hooks', () => { }); import { callRule, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; -import { lastValueFrom } from 'rxjs'; +import { lastValueFrom, of } from 'rxjs'; import { createSchematicWithMetrics, SchematicWrapper } from './index'; let context: SchematicContext; let debug: jest.Mock; +let executePostTasks: jest.Mock; describe('createSchematicWithMetricsIfInstalled', () => { beforeEach(() => { debug = jest.fn(); + executePostTasks = jest.fn().mockReturnValue(of('')); context = { schematic: { description: { @@ -36,6 +38,9 @@ describe('createSchematicWithMetricsIfInstalled', () => { name: 'MySchematic' } }, + engine: { + executePostTasks + }, interactive: false, logger: { debug @@ -55,6 +60,7 @@ describe('createSchematicWithMetricsIfInstalled', () => { expect(originalSchematic).toHaveBeenCalled(); expect(originalSchematic).toHaveBeenCalledWith(options); expect(rule).toHaveBeenCalled(); + expect(executePostTasks).toHaveBeenCalled(); expect(debug).toHaveBeenCalled(); expect(debug).toHaveBeenCalledWith(JSON.stringify({ environment: { env: 'env' }, @@ -76,6 +82,7 @@ describe('createSchematicWithMetricsIfInstalled', () => { expect(originalSchematic).toHaveBeenCalled(); expect(originalSchematic).toHaveBeenCalledWith(options); expect(rule).toHaveBeenCalled(); + expect(executePostTasks).toHaveBeenCalled(); expect(debug).toHaveBeenCalled(); expect(debug).toHaveBeenCalledWith(JSON.stringify({ environment: { env: 'env' }, @@ -97,6 +104,7 @@ describe('createSchematicWithMetricsIfInstalled', () => { expect(originalSchematic).toHaveBeenCalled(); expect(originalSchematic).toHaveBeenCalledWith(options); expect(rule).toHaveBeenCalled(); + expect(executePostTasks).not.toHaveBeenCalled(); expect(debug).toHaveBeenCalled(); expect(debug).toHaveBeenCalledWith(expect.stringContaining('error example')); }); diff --git a/packages/@o3r/telemetry/src/schematics/index.ts b/packages/@o3r/telemetry/src/schematics/index.ts index a7a9a5b7ee..dd3aaa6e66 100644 --- a/packages/@o3r/telemetry/src/schematics/index.ts +++ b/packages/@o3r/telemetry/src/schematics/index.ts @@ -1,3 +1,4 @@ +import type { JsonObject } from '@angular-devkit/core'; import { callRule, Rule } from '@angular-devkit/schematics'; import { performance } from 'node:perf_hooks'; import { lastValueFrom } from 'rxjs'; @@ -26,6 +27,7 @@ export const createSchematicWithMetrics: SchematicWrapper = try { const rule = schematicFn(options); await lastValueFrom(callRule(rule, tree, context)); + await lastValueFrom(context.engine.executePostTasks()); } catch (e: any) { const err = e instanceof Error ? e : new Error(error); @@ -48,11 +50,26 @@ export const createSchematicWithMetrics: SchematicWrapper = error }; context.logger.debug(JSON.stringify(data, null, 2)); - void sendData(data, context.logger).catch((e) => { - // Do not throw error if we don't manage to collect data - const err = (e instanceof Error ? e : new Error(error)); - context.logger.error(err.stack || err.toString()); - }); + const packageJson = (tree.exists('/package.json') ? tree.readJson('/package.json') : {}) as JsonObject; + const shouldSendData = !!( + (options as any).o3rMetrics + ?? ((process.env.O3R_METRICS || '').length > 0 ? process.env.O3R_METRICS !== 'false' : undefined) + ?? (packageJson.config as JsonObject)?.o3rMetrics + ); + if (shouldSendData) { + if (typeof ((options as any).o3rMetrics ?? typeof process.env.O3R_METRICS) === 'undefined') { + context.logger.info( + 'Telemetry is globally activated for the project (`config.o3rMetrics` in package.json). ' + + 'If you personally don\'t want to send telemetry, you can deactivate it by setting `O3R_METRICS` to false in your environment variables, ' + + 'or by calling the schematic with `--no-o3r-metrics`.' + ); + } + void sendData(data, context.logger).catch((e) => { + // Do not throw error if we don't manage to collect data + const err = (e instanceof Error ? e : new Error(error)); + context.logger.error(err.stack || err.toString()); + }); + } } }; diff --git a/yarn.lock b/yarn.lock index ab607c62ba..07f9243c60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7252,6 +7252,7 @@ __metadata: "@o3r/telemetry": "workspace:^" "@o3r/test-helpers": "workspace:^" "@stylistic/eslint-plugin-ts": "npm:^1.5.4" + "@types/inquirer": "npm:~8.2.10" "@types/jest": "npm:~29.5.2" "@types/node": "npm:^20.0.0" "@typescript-eslint/eslint-plugin": "npm:^7.0.1" @@ -7263,6 +7264,7 @@ __metadata: eslint-plugin-jsdoc: "npm:~48.1.0" eslint-plugin-prefer-arrow: "npm:~1.2.3" eslint-plugin-unicorn: "npm:^51.0.0" + inquirer: "npm:~8.2.6" intl-messageformat: "npm:~10.5.1" jest: "npm:~29.7.0" jest-environment-jsdom: "npm:~29.7.0" @@ -7290,8 +7292,6 @@ __metadata: "@o3r/telemetry": "workspace:^" typescript: ~5.2.2 peerDependenciesMeta: - "@o3r/schematics": - optional: true "@o3r/telemetry": optional: true languageName: unknown @@ -11447,6 +11447,16 @@ __metadata: languageName: node linkType: hard +"@types/inquirer@npm:~8.2.10": + version: 8.2.10 + resolution: "@types/inquirer@npm:8.2.10" + dependencies: + "@types/through": "npm:*" + rxjs: "npm:^7.2.0" + checksum: 10/d7c0c5ec95af583191942ac33f8af2eb1fe839da6b4560277a8c251fa289f2dd3a5d14850baf910343700646200258ecff89dc9e1d57df29c16a1082d91a5ae3 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.6 resolution: "@types/istanbul-lib-coverage@npm:2.0.6" @@ -11872,6 +11882,15 @@ __metadata: languageName: node linkType: hard +"@types/through@npm:*": + version: 0.0.33 + resolution: "@types/through@npm:0.0.33" + dependencies: + "@types/node": "npm:*" + checksum: 10/fd0b73f873a64ed5366d1d757c42e5dbbb2201002667c8958eda7ca02fff09d73de91360572db465ee00240c32d50c6039ea736d8eca374300f9664f93e8da39 + languageName: node + linkType: hard + "@types/tough-cookie@npm:*": version: 4.0.5 resolution: "@types/tough-cookie@npm:4.0.5" @@ -20120,7 +20139,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:8.2.6": +"inquirer@npm:8.2.6, inquirer@npm:~8.2.6": version: 8.2.6 resolution: "inquirer@npm:8.2.6" dependencies: @@ -26972,7 +26991,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.0, rxjs@npm:^7.8.1": +"rxjs@npm:7.8.1, rxjs@npm:^7.2.0, rxjs@npm:^7.5.5, rxjs@npm:^7.8.0, rxjs@npm:^7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: