diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index 37cb13ffbe..5ebf67bc9a 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -33,19 +33,11 @@ jobs: with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - - name: cache all node_modules - id: cache-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} - name: find location of global yarn cache id: yarn-cache - if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache uses: actions/cache@v2 - if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} diff --git a/.github/workflows/master-win.yml b/.github/workflows/master-win.yml index b5a042a236..296a7511bc 100644 --- a/.github/workflows/master-win.yml +++ b/.github/workflows/master-win.yml @@ -25,19 +25,11 @@ jobs: with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - - name: cache all node_modules - id: cache-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} - name: find location of global yarn cache id: yarn-cache - if: steps.cache-modules.outputs.cache-hit != 'true' run: echo "::set-output name=dir::$(yarn cache dir)" - name: cache global yarn cache uses: actions/cache@v2 - if: steps.cache-modules.outputs.cache-hit != 'true' with: path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} diff --git a/app-config.yaml b/app-config.yaml index 38f16e1ac3..0a85198630 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -61,6 +61,11 @@ catalog: - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-lib-component.yaml - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/www-artist-component.yaml - https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/shuffle-api-component.yaml + - https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml + - https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml + - https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml + - https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml + - https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml auth: providers: diff --git a/docs/assets/techdocs/techdocs_big_picture.png b/docs/assets/techdocs/techdocs_big_picture.png new file mode 100644 index 0000000000..ffe437180f Binary files /dev/null and b/docs/assets/techdocs/techdocs_big_picture.png differ diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index c0e6ccfda9..8bdbd27d3c 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -101,3 +101,7 @@ more to come... https://github.com/spotify/backstage/blob/master/packages/techdocs-container [techdocs/cli]: https://github.com/spotify/backstage/blob/master/packages/techdocs-cli + +## TechDocs Big Picture + +![TechDocs Big Picture](../..//assets/techdocs/techdocs_big_picture.png) diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 1d32b6e16e..d0a0171207 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -39,6 +39,7 @@ import { SidebarUserSettings, SidebarThemeToggle, SidebarPinButton, + DefaultProviderSettings, } from '@backstage/core'; import { NavLink } from 'react-router-dom'; import { graphiQLRouteRef } from '@backstage/plugin-graphiql'; @@ -107,7 +108,7 @@ const Root: FC<{}> = ({ children }) => ( - + } /> {children} diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 1a254d891f..92d301d7e3 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -37,11 +37,13 @@ "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", + "express-prom-bundle": "^6.1.0", "express-promise-router": "^3.0.3", "helmet": "^4.0.0", "knex": "^0.21.1", "lodash": "^4.17.15", "morgan": "^1.10.0", + "prom-client": "^12.0.0", "selfsigned": "^1.10.7", "stoppable": "^1.1.0", "winston": "^3.2.1" diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 4669c00e68..df69387e40 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -37,6 +37,7 @@ import { HttpsSettings, } from './config'; import { createHttpServer, createHttpsServer } from './hostFactory'; +import { metricsHandler } from './metrics'; const DEFAULT_PORT = 7000; // '' is express default, which listens to all interfaces @@ -48,6 +49,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; private httpsSettings: HttpsSettings | undefined; + private enableMetrics: boolean = true; private routers: [string, Router][]; // Reference to the module where builder is created - needed for hot module // reloading @@ -82,6 +84,9 @@ export class ServiceBuilderImpl implements ServiceBuilder { this.httpsSettings = httpsSettings; } + // For now, configuration of metrics is a simple boolean and active by default + this.enableMetrics = backendConfig.getOptionalBoolean('metrics') !== false; + return this; } @@ -131,6 +136,9 @@ export class ServiceBuilderImpl implements ServiceBuilder { } app.use(compression()); app.use(express.json()); + if (this.enableMetrics) { + app.use(metricsHandler()); + } app.use(requestLoggingHandler()); for (const [root, route] of this.routers) { app.use(root, route); diff --git a/packages/backend-common/src/service/lib/metrics.ts b/packages/backend-common/src/service/lib/metrics.ts new file mode 100644 index 0000000000..d7b544765b --- /dev/null +++ b/packages/backend-common/src/service/lib/metrics.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +import prom from 'prom-client'; +import promBundle from 'express-prom-bundle'; +import { RequestHandler } from 'express'; + +/** + * Adds a /metrics endpoint, register default runtime metrics and instrument the router. + */ +export function metricsHandler(): RequestHandler { + // We can only initialize the metrics once and have to clean them up between hot reloads + prom.register.clear(); + + return promBundle({ + includeMethod: true, + includePath: true, + // Using includePath alone is problematic, as it will include path labels with high + // cardinality (e.g. path params). Instead we would have to template them. However, this + // is difficult, as every backend plugin might use different routes. Instead we only take + // the first directory of the path, to have at least an idea how each plugin performs: + normalizePath: [['^/([^/]*)/.*', '/$1']], + promClient: { collectDefaultMetrics: {} }, + }); +} diff --git a/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx b/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx new file mode 100644 index 0000000000..293cefb19a --- /dev/null +++ b/packages/core/src/layout/Sidebar/DefaultProviderSettings.tsx @@ -0,0 +1,81 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +import { + configApiRef, + githubAuthApiRef, + gitlabAuthApiRef, + googleAuthApiRef, + oauth2ApiRef, + oktaAuthApiRef, + microsoftAuthApiRef, + useApi, +} from '@backstage/core-api'; +import Star from '@material-ui/icons/Star'; +import React from 'react'; +import { OAuthProviderSettings, OIDCProviderSettings } from './Settings'; + +export const DefaultProviderSettings = () => { + const configApi = useApi(configApiRef); + const providersConfig = configApi.getOptionalConfig('auth.providers'); + const providers = providersConfig?.keys() ?? []; + + return ( + <> + {providers.includes('google') && ( + + )} + {providers.includes('microsoft') && ( + + )} + {providers.includes('github') && ( + + )} + {providers.includes('gitlab') && ( + + )} + {providers.includes('okta') && ( + + )} + {providers.includes('oauth2') && ( + + )} + + ); +}; diff --git a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx index 906317fe9c..b286071426 100644 --- a/packages/core/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core/src/layout/Sidebar/Sidebar.stories.tsx @@ -23,10 +23,15 @@ import { SidebarSearchField, SidebarSpace, SidebarUserSettings, + OAuthProviderSettings, } from '.'; import HomeOutlinedIcon from '@material-ui/icons/HomeOutlined'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; +import Star from '@material-ui/icons/Star'; import { MemoryRouter } from 'react-router-dom'; +import { + githubAuthApiRef, +} from '@backstage/core-api'; export default { title: 'Sidebar', @@ -55,6 +60,12 @@ export const SampleSidebar = () => ( - + + } /> ); diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index 5ea7d2b359..f586f6d077 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -14,36 +14,22 @@ * limitations under the License. */ -import { - githubAuthApiRef, - gitlabAuthApiRef, - googleAuthApiRef, - identityApiRef, - oauth2ApiRef, - oktaAuthApiRef, - microsoftAuthApiRef, - useApi, - configApiRef, -} from '@backstage/core-api'; +import { identityApiRef, useApi } from '@backstage/core-api'; import Collapse from '@material-ui/core/Collapse'; import SignOutIcon from '@material-ui/icons/MeetingRoom'; -import Star from '@material-ui/icons/Star'; import React, { useContext, useEffect } from 'react'; import { SidebarContext } from './config'; import { SidebarItem } from './Items'; -import { - OAuthProviderSettings, - OIDCProviderSettings, - UserProfile as SidebarUserProfile, -} from './Settings'; +import { UserProfile as SidebarUserProfile } from './Settings'; -export function SidebarUserSettings() { +type SidebarUserSettingsProps = { providerSettings?: React.ReactNode }; + +export function SidebarUserSettings({ + providerSettings, +}: SidebarUserSettingsProps) { const { isOpen: sidebarOpen } = useContext(SidebarContext); const [open, setOpen] = React.useState(false); const identityApi = useApi(identityApiRef); - const configApi = useApi(configApiRef); - const providersConfig = configApi.getOptionalConfig('auth.providers'); - const providers = providersConfig?.keys() ?? []; // Close the provider list when sidebar collapse useEffect(() => { @@ -54,48 +40,8 @@ export function SidebarUserSettings() { <> - {providers.includes('google') && ( - - )} - {providers.includes('microsoft') && ( - - )} - {providers.includes('github') && ( - - )} - {providers.includes('gitlab') && ( - - )} - {providers.includes('okta') && ( - - )} - {providers.includes('oauth2') && ( - - )} + {providerSettings} + ( @@ -22,7 +23,7 @@ export const AppSidebar = () => ( - + } /> ); diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts.hbs b/packages/create-app/templates/default-app/packages/backend/src/index.ts similarity index 73% rename from packages/create-app/templates/default-app/packages/backend/src/index.ts.hbs rename to packages/create-app/templates/default-app/packages/backend/src/index.ts index 98f40ba4a4..0e1a85f0a3 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts.hbs +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -7,18 +7,13 @@ */ import { + createDatabase, createServiceBuilder, loadBackendConfig, getRootLogger, useHotMemoize, } from '@backstage/backend-common'; import { ConfigReader, AppConfig } from '@backstage/config'; -{{#if dbTypePG}} -import knex, { PgConnectionConfig } from 'knex'; -{{/if}} -{{#if dbTypeSqlite}} -import knex from 'knex'; -{{/if}} import auth from './plugins/auth'; import catalog from './plugins/catalog'; import identity from './plugins/identity'; @@ -32,30 +27,10 @@ function makeCreateEnv(loadedConfigs: AppConfig[]) { return (plugin: string): PluginEnvironment => { const logger = getRootLogger().child({ type: 'plugin', plugin }); - - {{#if dbTypePG}} - const knexConfig = { - client: 'pg', - useNullAsDefault: true, + const database = createDatabase(config.getConfig('backend.database'), { connection: { - port: process.env.POSTGRES_PORT, - host: process.env.POSTGRES_HOST, - user: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD, database: `backstage_plugin_${plugin}`, - } as PgConnectionConfig, - }; - {{/if}} - {{#if dbTypeSqlite}} - const knexConfig = { - client: 'sqlite3', - connection: ':memory:', - useNullAsDefault: true, - }; - {{/if}} - const database = knex(knexConfig); - database.client.pool.on('createSuccess', (_eventId: any, resource: any) => { - resource.run('PRAGMA foreign_keys = ON', () => {}); + }, }); return { logger, database, config }; }; diff --git a/plugins/github-actions/src/components/Widget/Widget.tsx b/plugins/github-actions/src/components/Widget/Widget.tsx index 031364c8ce..59e1cc5289 100644 --- a/plugins/github-actions/src/components/Widget/Widget.tsx +++ b/plugins/github-actions/src/components/Widget/Widget.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect } from 'react'; import { useWorkflowRuns } from '../useWorkflowRuns'; -import { WorkflowRun } from '../WorkflowRunsTable'; +import { WorkflowRun, WorkflowRunsTable } from '../WorkflowRunsTable'; import { Entity } from '@backstage/catalog-model'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { @@ -108,3 +108,51 @@ export const Widget = ({ ); }; + +const RecentWorkflowRunsCardContent = ({ + error, + loading, + branch, +}: { + error?: Error; + loading?: boolean; + branch: string; +}) => { + if (error) return Couldn't fetch {branch} runs; + if (loading) return ; + return ; +}; + +export const RecentWorkflowRunsCard = ({ + entity, + branch = 'master', +}: { + entity: Entity; + branch: string; +}) => { + const errorApi = useApi(errorApiRef); + const [owner, repo] = ( + entity?.metadata.annotations?.['backstage.io/github-actions-id'] ?? '/' + ).split('/'); + const [{ loading, error }] = useWorkflowRuns({ + owner, + repo, + branch, + }); + + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + + return ( + + + + ); +}; diff --git a/plugins/github-actions/src/components/Widget/index.ts b/plugins/github-actions/src/components/Widget/index.ts index 2b34671ab5..0bd5dad98a 100644 --- a/plugins/github-actions/src/components/Widget/index.ts +++ b/plugins/github-actions/src/components/Widget/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { Widget } from './Widget'; +export { Widget, RecentWorkflowRunsCard } from './Widget'; diff --git a/yarn.lock b/yarn.lock index d56a2cbd2c..6ecd9aa3da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6511,6 +6511,11 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" +bintrees@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" + integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= + bl@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" @@ -7389,6 +7394,11 @@ cli-width@^2.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + clipboard@^2.0.0: version "2.0.6" resolved "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" @@ -10135,6 +10145,14 @@ expect@^26.0.1: jest-message-util "^26.0.1" jest-regex-util "^26.0.0" +express-prom-bundle@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/express-prom-bundle/-/express-prom-bundle-6.1.0.tgz#8fd72e5bedbbd686b8e7c49e0aecbc1b14b28743" + integrity sha512-krlvp5r6sgJ1IwL6M6/coMrNbAlwtpk+uyivfeyRMCupTK4HzIEQHH0gwrNhLiKyPmSbtZcSmtO6s+PRumRp5g== + dependencies: + on-finished "^2.3.0" + url-value-parser "^2.0.0" + express-promise-router@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/express-promise-router/-/express-promise-router-3.0.3.tgz#5e6d22a5a3f013d71833172fe8d7ab780c3f6b70" @@ -12505,20 +12523,20 @@ inquirer@^6.2.0: through "^2.3.6" inquirer@^7.0.0, inquirer@^7.0.4: - version "7.2.0" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" - integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== + version "7.3.3" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== dependencies: ansi-escapes "^4.2.1" - chalk "^3.0.0" + chalk "^4.1.0" cli-cursor "^3.1.0" - cli-width "^2.0.0" + cli-width "^3.0.0" external-editor "^3.0.3" figures "^3.0.0" - lodash "^4.17.15" + lodash "^4.17.19" mute-stream "0.0.8" run-async "^2.4.0" - rxjs "^6.5.3" + rxjs "^6.6.0" string-width "^4.1.0" strip-ansi "^6.0.0" through "^2.3.6" @@ -16227,7 +16245,7 @@ omggif@1.0.7: resolved "https://registry.npmjs.org/omggif/-/omggif-1.0.7.tgz#59d2eecb0263de84635b3feb887c0c9973f1e49d" integrity sha1-WdLuywJj3oRjWz/riHwMmXPx5J0= -on-finished@~2.3.0: +on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= @@ -17809,6 +17827,13 @@ progress@^2.0.0: resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +prom-client@^12.0.0: + version "12.0.0" + resolved "https://registry.npmjs.org/prom-client/-/prom-client-12.0.0.tgz#9689379b19bd3f6ab88a9866124db9da3d76c6ed" + integrity sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ== + dependencies: + tdigest "^0.1.1" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -19538,10 +19563,10 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.3, rxjs@^6.5.5: - version "6.6.0" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" - integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== +rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.3, rxjs@^6.5.5, rxjs@^6.6.0: + version "6.6.2" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" + integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== dependencies: tslib "^1.9.0" @@ -21142,6 +21167,13 @@ tcp-port-used@^1.0.1: debug "4.1.0" is2 "2.0.1" +tdigest@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" + integrity sha1-Ljyyw56kSeVdHmzZEReszKRYgCE= + dependencies: + bintrees "1.0.1" + telejson@^3.2.0: version "3.3.0" resolved "https://registry.npmjs.org/telejson/-/telejson-3.3.0.tgz#6d814f3c0d254d5c4770085aad063e266b56ad03" @@ -22131,6 +22163,11 @@ url-to-options@^1.0.1: resolved "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= +url-value-parser@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/url-value-parser/-/url-value-parser-2.0.1.tgz#c8179a095ab9ec1f5aa17ca36af5af396b4e95ed" + integrity sha512-bexECeREBIueboLGM3Y1WaAzQkIn+Tca/Xjmjmfd0S/hFHSCEoFkNh0/D0l9G4K74MkEP/lLFRlYnxX3d68Qgw== + url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.npmjs.org/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"