From 8bdf613b31ab7aab8acd3ddc998e0058cc5ab758 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 4 Jan 2022 18:27:04 +0100 Subject: [PATCH 01/52] api-extractor: add option to generate API reports for select packages Signed-off-by: Patrik Oldsberg --- package.json | 2 +- scripts/api-extractor.ts | 135 ++++++++++++++++++++++++++++++++++----- 2 files changed, 120 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 57cf603090..eda5d16ba9 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "start": "yarn workspace example-app start", "start-backend": "yarn workspace example-backend start", "build": "lerna run build", - "build:api-reports": "yarn tsc:full && yarn build:api-reports:only", + "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts", "build:api-docs": "yarn build:api-reports --docs", "tsc": "tsc", diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index 22eae267ee..c7aaede9b0 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -15,14 +15,15 @@ */ /* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-restricted-imports */ -// eslint-disable-next-line no-restricted-imports import { resolve as resolvePath, relative as relativePath, dirname, join, } from 'path'; +import { spawnSync } from 'child_process'; import prettier from 'prettier'; import fs from 'fs-extra'; import { @@ -197,6 +198,48 @@ const SKIPPED_PACKAGES = [ join('packages', 'techdocs-cli'), ]; +async function resolvePackagePath( + packagePath: string, +): Promise { + const projectRoot = resolvePath(__dirname, '..'); + const fullPackageDir = resolvePath(projectRoot, packagePath); + + const stat = await fs.stat(fullPackageDir); + if (!stat.isDirectory()) { + return undefined; + } + + try { + const packageJsonPath = join(fullPackageDir, 'package.json'); + await fs.access(packageJsonPath); + } catch (_) { + return undefined; + } + + return relativePath(projectRoot, fullPackageDir); +} + +async function findSpecificPackageDirs(unresolvedPackageDirs: string[]) { + const packageDirs = new Array(); + + for (const unresolvedPackageDir of unresolvedPackageDirs) { + const packageDir = await resolvePackagePath(unresolvedPackageDir); + if (!packageDir) { + throw new Error(`'${unresolvedPackageDir}' is not a valid package path`); + } + if (SKIPPED_PACKAGES.includes(packageDir)) { + throw new Error(`'${packageDir}' does not have an API report`); + } + packageDirs.push(packageDir); + } + + if (packageDirs.length === 0) { + return undefined; + } + + return packageDirs; +} + async function findPackageDirs() { const packageDirs = new Array(); const projectRoot = resolvePath(__dirname, '..'); @@ -204,21 +247,11 @@ async function findPackageDirs() { for (const packageRoot of PACKAGE_ROOTS) { const dirs = await fs.readdir(resolvePath(projectRoot, packageRoot)); for (const dir of dirs) { - const fullPackageDir = resolvePath(projectRoot, packageRoot, dir); - - const stat = await fs.stat(fullPackageDir); - if (!stat.isDirectory()) { + const packageDir = await resolvePackagePath(join(packageRoot, dir)); + if (!packageDir) { continue; } - try { - const packageJsonPath = join(fullPackageDir, 'package.json'); - await fs.access(packageJsonPath); - } catch (_) { - continue; - } - - const packageDir = relativePath(projectRoot, fullPackageDir); if (!SKIPPED_PACKAGES.includes(packageDir)) { packageDirs.push(packageDir); } @@ -228,6 +261,26 @@ async function findPackageDirs() { return packageDirs; } +async function createTemporaryTsConfig(includedPackageDirs: string[]) { + const path = resolvePath(__dirname, '..', 'tsconfig.tmp.json'); + + process.once('exit', () => { + fs.removeSync(path); + }); + + await fs.writeJson(path, { + extends: './tsconfig.json', + include: [ + // These two contain global definitions that are needed for stable API report generation + 'packages/cli/asset-types/asset-types.d.ts', + 'node_modules/handlebars/types/index.d.ts', + ...includedPackageDirs.map(dir => join(dir, 'src')), + ], + }); + + return path; +} + async function getTsDocConfig() { const tsdocConfigFile = await TSDocConfigFile.loadFile( require.resolve('@microsoft/api-extractor/extends/tsdoc-base.json'), @@ -261,12 +314,14 @@ interface ApiExtractionOptions { packageDirs: string[]; outputDir: string; isLocalBuild: boolean; + tsconfigFilePath: string; } async function runApiExtraction({ packageDirs, outputDir, isLocalBuild, + tsconfigFilePath, }: ApiExtractionOptions) { await fs.remove(outputDir); @@ -287,7 +342,7 @@ async function runApiExtraction({ bundledPackages: [], compiler: { - tsconfigFilePath: resolvePath(__dirname, '../tsconfig.json'), + tsconfigFilePath, }, apiReport: { @@ -663,23 +718,71 @@ async function buildDocs({ } async function main() { + const projectRoot = resolvePath(__dirname, '..'); const isCiBuild = process.argv.includes('--ci'); const isDocsBuild = process.argv.includes('--docs'); + const runTsc = process.argv.includes('--tsc'); - const packageDirs = await findPackageDirs(); + const selectedPackageDirs = await findSpecificPackageDirs( + process.argv.slice(2).filter(arg => !arg.startsWith('--')), + ); + if (selectedPackageDirs && (isCiBuild || isDocsBuild)) { + throw new Error( + 'Package path arguments are not supported for the --ci and --docs flags', + ); + } + if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) { + console.log(''); + console.log( + 'TIP: You can generate changesets for select packages by passing package paths:', + ); + console.log(''); + console.log( + ' yarn build:api-reports packages/config packages/core-plugin-api', + ); + console.log(''); + } + + let temporaryTsConfigPath: string | undefined; + if (selectedPackageDirs) { + temporaryTsConfigPath = await createTemporaryTsConfig(selectedPackageDirs); + } + const tsconfigFilePath = + temporaryTsConfigPath ?? resolvePath(projectRoot, 'tsconfig.json'); + + if (runTsc) { + await fs.remove(resolvePath(projectRoot, 'dist-types')); + spawnSync( + 'yarn', + [ + 'tsc', + ['--project', tsconfigFilePath], + ['--skipLibCheck', 'false'], + ['--incremental', 'false'], + ].flat(), + { + stdio: 'inherit', + shell: true, + cwd: projectRoot, + }, + ); + } + + const packageDirs = selectedPackageDirs ?? (await findPackageDirs()); console.log('# Generating package API reports'); await runApiExtraction({ packageDirs, outputDir: tmpDir, isLocalBuild: !isCiBuild, + tsconfigFilePath, }); if (isDocsBuild) { console.log('# Generating package documentation'); await buildDocs({ inputDir: tmpDir, - outputDir: resolvePath(__dirname, '..', 'docs/reference'), + outputDir: resolvePath(projectRoot, 'docs/reference'), }); } } From 2d3fd91e33567bc45b4f33657ac11366e833d614 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 4 Jan 2022 11:46:49 +0100 Subject: [PATCH 02/52] test-utils: add MockConfigApi Signed-off-by: Patrik Oldsberg --- .changeset/brave-pugs-fold.md | 5 + packages/test-utils/api-report.md | 40 +++++++ packages/test-utils/package.json | 1 + .../apis/ConfigApi/MockConfigApi.test.ts | 42 +++++++ .../testUtils/apis/ConfigApi/MockConfigApi.ts | 111 ++++++++++++++++++ .../src/testUtils/apis/ConfigApi/index.ts | 17 +++ .../test-utils/src/testUtils/apis/index.ts | 1 + 7 files changed, 217 insertions(+) create mode 100644 .changeset/brave-pugs-fold.md create mode 100644 packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.test.ts create mode 100644 packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts create mode 100644 packages/test-utils/src/testUtils/apis/ConfigApi/index.ts diff --git a/.changeset/brave-pugs-fold.md b/.changeset/brave-pugs-fold.md new file mode 100644 index 0000000000..f79867a719 --- /dev/null +++ b/.changeset/brave-pugs-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Add new `MockConfigApi` as a more discoverable and leaner method for mocking configuration. diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index a75a6d958e..6a3813e49f 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -8,10 +8,13 @@ import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; +import { Config } from '@backstage/config'; +import { ConfigApi } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; import { ErrorApiError } from '@backstage/core-plugin-api'; import { ErrorApiErrorContext } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; +import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { ReactElement } from 'react'; @@ -52,6 +55,43 @@ export class MockAnalyticsApi implements AnalyticsApi { // @public export function mockBreakpoint(options: { matches: boolean }): void; +// @public +export class MockConfigApi implements ConfigApi { + constructor(data: JsonObject); + // (undocumented) + get(key?: string): T; + // (undocumented) + getBoolean(key: string): boolean; + // (undocumented) + getConfig(key: string): Config; + // (undocumented) + getConfigArray(key: string): Config[]; + // (undocumented) + getNumber(key: string): number; + // (undocumented) + getOptional(key?: string): T | undefined; + // (undocumented) + getOptionalBoolean(key: string): boolean | undefined; + // (undocumented) + getOptionalConfig(key: string): Config | undefined; + // (undocumented) + getOptionalConfigArray(key: string): Config[] | undefined; + // (undocumented) + getOptionalNumber(key: string): number | undefined; + // (undocumented) + getOptionalString(key: string): string | undefined; + // (undocumented) + getOptionalStringArray(key: string): string[] | undefined; + // (undocumented) + getString(key: string): string; + // (undocumented) + getStringArray(key: string): string[]; + // (undocumented) + has(key: string): boolean; + // (undocumented) + keys(): string[]; +} + // @public export class MockErrorApi implements ErrorApi { constructor(options?: MockErrorApiOptions); diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index acfe03bf99..48745f7068 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -29,6 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/config": "^0.1.11", "@backstage/core-app-api": "^0.3.0", "@backstage/core-plugin-api": "^0.4.0", "@backstage/theme": "^0.2.14", diff --git a/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.test.ts b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.test.ts new file mode 100644 index 0000000000..972733d44d --- /dev/null +++ b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { MockConfigApi } from './MockConfigApi'; + +describe('MockConfigApi', () => { + it('is able to read some basic config', () => { + const mock = new MockConfigApi({ + app: { + title: 'Hello', + }, + x: 1, + y: false, + z: [{ a: 3 }], + }); + + expect(mock.getString('app.title')).toEqual('Hello'); + expect(mock.getNumber('x')).toEqual(1); + expect(mock.getBoolean('y')).toEqual(false); + expect(mock.getConfigArray('z')[0].getOptionalNumber('a')).toEqual(3); + + expect(() => mock.getString('x')).toThrow( + "Invalid type in config for key 'x' in 'mock-config', got number, wanted string", + ); + expect(() => mock.getString('missing')).toThrow( + "Missing required config value at 'missing'", + ); + }); +}); diff --git a/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts new file mode 100644 index 0000000000..93d975c18a --- /dev/null +++ b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts @@ -0,0 +1,111 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { Config, ConfigReader } from '@backstage/config'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { ConfigApi } from '@backstage/core-plugin-api'; + +/** + * MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader} + * that can be used to mock configuration using a plain object. + * + * @public + * @example + * ```tsx + * const mockConfig = new MockConfigApi({ + * app: { baseUrl: 'https://example.com' }, + * }); + * + * const rendered = await renderInTestApp( + * + * + * , + * ); + * ``` + */ +export class MockConfigApi implements ConfigApi { + readonly #config: ConfigReader; + + // NOTE: not extending in order to avoid inheriting the static `.fromConfigs` + constructor(data: JsonObject) { + this.#config = new ConfigReader(data); + } + + /** {@inheritdoc @backstage/config#Config.has} */ + has(key: string): boolean { + return this.#config.has(key); + } + /** {@inheritdoc @backstage/config#Config.keys} */ + keys(): string[] { + return this.#config.keys(); + } + /** {@inheritdoc @backstage/config#Config.get} */ + get(key?: string): T { + return this.#config.get(key); + } + /** {@inheritdoc @backstage/config#Config.getOptional} */ + getOptional(key?: string): T | undefined { + return this.#config.getOptional(key); + } + /** {@inheritdoc @backstage/config#Config.getConfig} */ + getConfig(key: string): Config { + return this.#config.getConfig(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */ + getOptionalConfig(key: string): Config | undefined { + return this.#config.getOptionalConfig(key); + } + /** {@inheritdoc @backstage/config#Config.getConfigArray} */ + getConfigArray(key: string): Config[] { + return this.#config.getConfigArray(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */ + getOptionalConfigArray(key: string): Config[] | undefined { + return this.#config.getOptionalConfigArray(key); + } + /** {@inheritdoc @backstage/config#Config.getNumber} */ + getNumber(key: string): number { + return this.#config.getNumber(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */ + getOptionalNumber(key: string): number | undefined { + return this.#config.getOptionalNumber(key); + } + /** {@inheritdoc @backstage/config#Config.getBoolean} */ + getBoolean(key: string): boolean { + return this.#config.getBoolean(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */ + getOptionalBoolean(key: string): boolean | undefined { + return this.#config.getOptionalBoolean(key); + } + /** {@inheritdoc @backstage/config#Config.getString} */ + getString(key: string): string { + return this.#config.getString(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalString} */ + getOptionalString(key: string): string | undefined { + return this.#config.getOptionalString(key); + } + /** {@inheritdoc @backstage/config#Config.getStringArray} */ + getStringArray(key: string): string[] { + return this.#config.getStringArray(key); + } + /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */ + getOptionalStringArray(key: string): string[] | undefined { + return this.#config.getOptionalStringArray(key); + } +} diff --git a/packages/test-utils/src/testUtils/apis/ConfigApi/index.ts b/packages/test-utils/src/testUtils/apis/ConfigApi/index.ts new file mode 100644 index 0000000000..6418899f79 --- /dev/null +++ b/packages/test-utils/src/testUtils/apis/ConfigApi/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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. + */ + +export { MockConfigApi } from './MockConfigApi'; diff --git a/packages/test-utils/src/testUtils/apis/index.ts b/packages/test-utils/src/testUtils/apis/index.ts index 44b423a264..8f6bb06f9e 100644 --- a/packages/test-utils/src/testUtils/apis/index.ts +++ b/packages/test-utils/src/testUtils/apis/index.ts @@ -15,5 +15,6 @@ */ export * from './AnalyticsApi'; +export * from './ConfigApi'; export * from './ErrorApi'; export * from './StorageApi'; From f302d24d340908b4a4d9e1af0df4a30fbacff406 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jan 2022 00:37:13 +0100 Subject: [PATCH 03/52] cli: switch to using esbuild for minifcation Signed-off-by: Patrik Oldsberg --- .changeset/olive-points-flash.md | 5 ++++ packages/cli/package.json | 3 ++- packages/cli/src/lib/bundler/optimization.ts | 20 ++++++--------- yarn.lock | 26 +++++++++++++++++--- 4 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 .changeset/olive-points-flash.md diff --git a/.changeset/olive-points-flash.md b/.changeset/olive-points-flash.md new file mode 100644 index 0000000000..64b23419de --- /dev/null +++ b/.changeset/olive-points-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Switch Webpack minification to use `esbuild` instead of `terser`. diff --git a/packages/cli/package.json b/packages/cli/package.json index 3b9cf52893..3c90a11e28 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -61,7 +61,8 @@ "commander": "^6.1.0", "css-loader": "^6.5.1", "diff": "^5.0.0", - "esbuild": "^0.14.1", + "esbuild": "^0.14.10", + "esbuild-loader": "^2.18.0", "eslint": "^7.30.0", "eslint-config-prettier": "^8.3.0", "eslint-formatter-friendly": "^7.0.0", diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index 254799f78c..b2119f5b3a 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -14,10 +14,9 @@ * limitations under the License. */ -import { WebpackOptionsNormalized, WebpackPluginInstance } from 'webpack'; -import TerserPlugin from 'terser-webpack-plugin'; +import { WebpackOptionsNormalized } from 'webpack'; import { BundlingOptions } from './types'; -import { isParallelDefault } from '../parallel'; +import { ESBuildMinifyPlugin } from 'esbuild-loader'; export const optimization = ( options: BundlingOptions, @@ -26,16 +25,11 @@ export const optimization = ( return { minimize: !isDev, - // Only configure when parallel is explicitly overridden from the default - ...(!isParallelDefault(options.parallel) - ? { - minimizer: [ - new TerserPlugin({ - parallel: options.parallel, - }) as unknown as WebpackPluginInstance, - ], - } - : {}), + minimizer: [ + new ESBuildMinifyPlugin({ + target: 'es2019', + }), + ], runtimeChunk: 'single', splitChunks: { automaticNameDelimiter: '-', diff --git a/yarn.lock b/yarn.lock index a4c4bb13c6..8901f5b685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13761,6 +13761,18 @@ esbuild-linux-s390x@0.14.10: resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.10.tgz#cc4228ac842febc48b84757814bed964a619be62" integrity sha512-knArKKZm0ypIYWOWyOT7+accVwbVV1LZnl2FWWy05u9Tyv5oqJ2F5+X2Vqe/gqd61enJXQWqoufXopvG3zULOg== +esbuild-loader@^2.18.0: + version "2.18.0" + resolved "https://registry.npmjs.org/esbuild-loader/-/esbuild-loader-2.18.0.tgz#7b9548578ab954574fd94655693d22aa5ec74120" + integrity sha512-AKqxM3bI+gvGPV8o6NAhR+cBxVO8+dh+O0OXBHIXXwuSGumckbPWHzZ17subjBGI2YEGyJ1STH7Haj8aCrwL/w== + dependencies: + esbuild "^0.14.6" + joycon "^3.0.1" + json5 "^2.2.0" + loader-utils "^2.0.0" + tapable "^2.2.0" + webpack-sources "^2.2.0" + esbuild-netbsd-64@0.14.10: version "0.14.10" resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.10.tgz#6ec50d9e4547a7579f447307b19f66bbedfd868b" @@ -13791,7 +13803,7 @@ esbuild-windows-arm64@0.14.10: resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.10.tgz#50ab9a83f6ccf71c272e58489ecc4d7375075f32" integrity sha512-OJOyxDtabvcUYTc+O4dR0JMzLBz6G9+gXIHA7Oc5d5Fv1xiYa0nUeo8+W5s2e6ZkPRdIwOseYoL70rZz80S5BA== -esbuild@^0.14.1: +esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.6: version "0.14.10" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.10.tgz#10268d2b576b25ed6f8554553413988628a7767b" integrity sha512-ibZb+NwFqBwHHJlpnFMtg4aNmVK+LUtYMFC9CuKs6lDCBEvCHpqCFZFEirpqt1jOugwKGx8gALNGvX56lQyfew== @@ -18606,7 +18618,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3: +json5@^2.1.2, json5@^2.1.3, json5@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== @@ -25902,7 +25914,7 @@ sort-keys@^4.0.0: dependencies: is-plain-obj "^2.0.0" -source-list-map@^2.0.0: +source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== @@ -28805,6 +28817,14 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^2.2.0: + version "2.3.1" + resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz#570de0af163949fe272233c2cefe1b56f74511fd" + integrity sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + webpack-sources@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.0.tgz#b16973bcf844ebcdb3afde32eda1c04d0b90f89d" From bee708209411e91a458adb2288b339fb43cdff77 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jan 2022 01:52:35 +0100 Subject: [PATCH 04/52] cli: detect edge-case for MUI icon imports Signed-off-by: Patrik Oldsberg --- .changeset/rotten-olives-shop.md | 5 +++++ packages/cli/config/eslint.js | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/rotten-olives-shop.md diff --git a/.changeset/rotten-olives-shop.md b/.changeset/rotten-olives-shop.md new file mode 100644 index 0000000000..b7fb51ad06 --- /dev/null +++ b/.changeset/rotten-olives-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Update `config/eslint.js` to forbid imports of `@material-ui/icons/` as well. diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 2a85c307b0..781af13577 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -80,6 +80,10 @@ module.exports = { name: '@material-ui/icons', message: "Please import '@material-ui/icons/' instead.", }, + { + name: '@material-ui/icons/', // because this is possible too ._. + message: "Please import '@material-ui/icons/' instead.", + }, ...require('module').builtinModules, ], // Avoid cross-package imports From ed08ab8df650f1774af2039d46b288e726abcf6e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jan 2022 01:54:41 +0100 Subject: [PATCH 05/52] search: fix import of all of MUI/icons Signed-off-by: Patrik Oldsberg --- plugins/search/src/components/SearchModal/SearchModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 8b4da51592..c7ca33005d 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -25,7 +25,7 @@ import { List, Paper, } from '@material-ui/core'; -import { Launch } from '@material-ui/icons/'; +import LaunchIcon from '@material-ui/icons/Launch'; import { makeStyles } from '@material-ui/core/styles'; import { SearchBar } from '../SearchBar'; import { DefaultResultListItem } from '../DefaultResultListItem'; @@ -96,7 +96,7 @@ export const Modal = ({ open = true, toggleModal }: SearchModalProps) => { View Full Results - + From a67ec8527f04fe56b70e09ad8dc4c0281e824c48 Mon Sep 17 00:00:00 2001 From: Lorenzo Orsatti <49567430+lorsatti@users.noreply.github.com> Date: Tue, 4 Jan 2022 23:13:07 +0100 Subject: [PATCH 06/52] make AWS session token optional Signed-off-by: Lorenzo Orsatti <49567430+lorsatti@users.noreply.github.com> --- .changeset/warm-crews-flash.md | 5 +++ .../AwsIamKubernetesAuthTranslator.test.ts | 38 ++++++++++++------- .../AwsIamKubernetesAuthTranslator.ts | 4 +- 3 files changed, 31 insertions(+), 16 deletions(-) create mode 100644 .changeset/warm-crews-flash.md diff --git a/.changeset/warm-crews-flash.md b/.changeset/warm-crews-flash.md new file mode 100644 index 0000000000..b21a3ada02 --- /dev/null +++ b/.changeset/warm-crews-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Exclude the AWS session token from credential validation, because it's not necessary in this context. diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts index 9060ce9239..7806160e58 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.test.ts @@ -34,7 +34,9 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { }, }; - let credentialsResponse: any = new AWS.Credentials(credentials); + let mockedCredentials: any = undefined; + + AWS.config.credentials = new AWS.Credentials(credentials); AWSMock.setSDKInstance(AWS); @@ -53,19 +55,25 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { const authTranslator = new AwsIamKubernetesAuthTranslator(); - jest - .spyOn(authTranslator, 'awsGetCredentials') - .mockImplementation(async () => credentialsResponse); + if (mockedCredentials) { + jest + .spyOn(authTranslator, 'awsGetCredentials') + .mockImplementation(async () => mockedCredentials); + } - return authTranslator.decorateClusterDetailsWithAuth({ + const response = authTranslator.decorateClusterDetailsWithAuth({ assumeRole: role, name: 'test-cluster', url: '', authProvider: 'aws', }); + + mockedCredentials = undefined; + + return response; }); - it('returns a signed url for aws credentials', async () => { + it('returns a signed url for AWS credentials', async () => { // These credentials are not real. // Pulled from example in docs: https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html AWS.config.credentials = new AWS.Credentials( @@ -87,7 +95,7 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { role = 'SomeRole'; describe('When the role is valid', () => { - it('returns a signed url for aws credentials', async () => { + it('returns a signed url for AWS credentials', async () => { const subject = await get('subject'); expect(subject.serviceAccountToken).toBeDefined(); }); @@ -101,16 +109,20 @@ describe('AwsIamKubernetesAuthTranslator tests', () => { }); }); - describe('When no creds are returned from AWS', () => { - it('throws unable to get aws credentials', async () => { - credentialsResponse = new Error(); + describe('When no AWS creds are available', () => { + it('throws unable to get AWS credentials', async () => { + mockedCredentials = new Error(); await expect(get('subject')).rejects.toThrow('No AWS credentials found.'); }); }); - describe('When invalid creds are returned from AWS', () => { - it('throws credentials are invalid to get aws credentials', async () => { - credentialsResponse = new AWS.Credentials(credentialsResponse); + describe('When invalid AWS creds are available', () => { + it('throws credentials are invalid to get AWS credentials', async () => { + const undefinedSecret: any = undefined; + AWS.config.credentials = new AWS.Credentials( + 'AKIAIOSFODNN7EXAMPLE', + undefinedSecret, + ); await expect(get('subject')).rejects.toThrow( 'Invalid AWS credentials found.', ); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index 916067ebc5..4af39d4f26 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -41,9 +41,7 @@ export class AwsIamKubernetesAuthTranslator implements KubernetesAuthTranslator { validCredentials(creds: SigningCreds): boolean { - return (creds?.accessKeyId && - creds?.secretAccessKey && - creds?.sessionToken) as unknown as boolean; + return (creds?.accessKeyId && creds?.secretAccessKey) as unknown as boolean; } awsGetCredentials = async (): Promise => { From 8b165b181d0d2b48cf936081a7465fe2b8c5147c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 5 Jan 2022 10:23:44 +0100 Subject: [PATCH 07/52] bump dependabot aggro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 59847fd65f..65a4b1a438 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: schedule: interval: daily time: '04:00' - open-pull-requests-limit: 5 + open-pull-requests-limit: 10 labels: - dependencies - package-ecosystem: npm @@ -13,6 +13,6 @@ updates: schedule: interval: daily time: '04:00' - open-pull-requests-limit: 2 + open-pull-requests-limit: 5 labels: - dependencies From 9135a581b911f7d14e7a3a8ba7d06a247fde548d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 10:22:53 +0000 Subject: [PATCH 08/52] build(deps-dev): bump concurrently from 6.2.0 to 7.0.0 Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 6.2.0 to 7.0.0. - [Release notes](https://github.com/open-cli-tools/concurrently/releases) - [Commits](https://github.com/open-cli-tools/concurrently/compare/v6.2.0...v7.0.0) --- updated-dependencies: - dependency-name: concurrently dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 33 ++++++--------------------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 57cf603090..ec815c0af7 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@types/webpack": "^5.28.0", "command-exists": "^1.2.9", "cross-env": "^7.0.0", - "concurrently": "^6.0.0", + "concurrently": "^7.0.0", "eslint-plugin-notice": "^0.9.10", "fs-extra": "9.1.0", "husky": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index a1e3fa5d79..01b27619fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11618,15 +11618,14 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" -concurrently@^6.0.0: - version "6.2.0" - resolved "https://registry.npmjs.org/concurrently/-/concurrently-6.2.0.tgz#587e2cb8afca7234172d8ea55176088632c4c56d" - integrity sha512-v9I4Y3wFoXCSY2L73yYgwA9ESrQMpRn80jMcqMgHx720Hecz2GZAvTI6bREVST6lkddNypDKRN22qhK0X8Y00g== +concurrently@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.0.0.tgz#78d31b441cec338dab03316c221a2f9a67c529b0" + integrity sha512-WKM7PUsI8wyXpF80H+zjHP32fsgsHNQfPLw/e70Z5dYkV7hF+rf8q3D+ScWJIEr57CpkO3OWBko6hwhQLPR8Pw== dependencies: chalk "^4.1.0" date-fns "^2.16.1" lodash "^4.17.21" - read-pkg "^5.2.0" rxjs "^6.6.3" spawn-command "^0.0.2-1" supports-color "^8.1.0" @@ -26736,14 +26735,7 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" - integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== - dependencies: - has-flag "^4.0.0" - -supports-color@^7.2.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -29390,20 +29382,7 @@ yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1: - version "17.2.1" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" - integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.3.0: +yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1, yargs@^17.3.0: version "17.3.0" resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.0.tgz#295c4ffd0eef148ef3e48f7a2e0f58d0e4f26b1c" integrity sha512-GQl1pWyDoGptFPJx9b9L6kmR33TGusZvXIZUT+BOz9f7X2L94oeAskFYLEg/FkhV06zZPBYLvLZRWeYId29lew== From ba4cb0eecb3f793b3c64cab6ac110d0ac4e445b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 10:30:46 +0000 Subject: [PATCH 09/52] build(deps): bump react-markdown from 7.0.1 to 7.1.2 Bumps [react-markdown](https://github.com/remarkjs/react-markdown) from 7.0.1 to 7.1.2. - [Release notes](https://github.com/remarkjs/react-markdown/releases) - [Changelog](https://github.com/remarkjs/react-markdown/blob/main/changelog.md) - [Commits](https://github.com/remarkjs/react-markdown/compare/7.0.1...7.1.2) --- updated-dependencies: - dependency-name: react-markdown dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1e3fa5d79..f3896e84a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16205,6 +16205,11 @@ hast-util-to-parse5@^6.0.0: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" + integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== + hastscript@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" @@ -23416,14 +23421,7 @@ property-expr@^2.0.4: resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== -property-information@^5.0.0: - version "5.4.0" - resolved "https://registry.npmjs.org/property-information/-/property-information-5.4.0.tgz#16e08f13f4e5c4a7be2e4ec431c01c4f8dba869a" - integrity sha512-nmMWAm/3vKFGmmOWOcdLjgq/Hlxa+hsuR/px1Lp/UGEyc5A22A6l78Shc2C0E71sPmAqglni+HrS7L7VJ7AUCA== - dependencies: - xtend "^4.0.0" - -property-information@^5.3.0: +property-information@^5.0.0, property-information@^5.3.0: version "5.6.0" resolved "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== @@ -24012,13 +24010,14 @@ react-lifecycles-compat@^3.0.4: integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== react-markdown@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-7.0.1.tgz#c7365fcd7d1813b3ae68f2200e8f92d47d865627" - integrity sha512-pthNPaoiwg0q7hukoE04F2ENwSzijIlWHJ4UMs/96LUe/G/P3FnbP4qHzx3FoNqae+2SqDG8vzniTLnJDeWneg== + version "7.1.2" + resolved "https://registry.npmjs.org/react-markdown/-/react-markdown-7.1.2.tgz#c9fa9d1c87e24529f028e1cdf731e81ccdd8e547" + integrity sha512-ibMcc0EbfmbwApqJD8AUr0yls8BSrKzIbHaUsPidQljxToCqFh34nwtu3CXNEItcVJNzpjDHrhK8A+MAh2JW3A== dependencies: "@types/hast" "^2.0.0" "@types/unist" "^2.0.0" comma-separated-tokens "^2.0.0" + hast-util-whitespace "^2.0.0" prop-types "^15.0.0" property-information "^6.0.0" react-is "^17.0.0" From 558b1d0ea420097949191fa01a74d6a6bf0b119f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 10:45:48 +0000 Subject: [PATCH 10/52] build(deps-dev): bump @types/react-helmet from 6.1.1 to 6.1.5 Bumps [@types/react-helmet](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-helmet) from 6.1.1 to 6.1.5. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-helmet) --- updated-dependencies: - dependency-name: "@types/react-helmet" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1e3fa5d79..629b74965d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7837,9 +7837,9 @@ "@types/react" "*" "@types/react-helmet@^6.1.0": - version "6.1.1" - resolved "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.1.tgz#4fde22cbcaa1b461642e1d719cc6162d95acb110" - integrity sha512-VmSCMz6jp/06DABoY60vQa++h1YFt0PfAI23llxBJHbowqFgLUL0dhS1AQeVPNqYfRp9LAfokrfWACTNeobOrg== + version "6.1.5" + resolved "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083" + integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA== dependencies: "@types/react" "*" From 54596bd67266432b30457d270090bd8092ac5865 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jan 2022 10:46:20 +0000 Subject: [PATCH 11/52] build(deps): bump rollup-plugin-dts from 4.0.1 to 4.1.0 Bumps [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/Swatinem/rollup-plugin-dts/releases) - [Changelog](https://github.com/Swatinem/rollup-plugin-dts/blob/master/CHANGELOG.md) - [Commits](https://github.com/Swatinem/rollup-plugin-dts/compare/v4.0.1...v4.1.0) --- updated-dependencies: - dependency-name: rollup-plugin-dts dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1e3fa5d79..cb5f1070d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -352,6 +352,13 @@ dependencies: "@babel/highlight" "^7.14.5" +"@babel/code-frame@^7.16.0": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== + dependencies: + "@babel/highlight" "^7.16.7" + "@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.4": version "7.14.4" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz#45720fe0cecf3fd42019e1d12cc3d27fadc98d58" @@ -777,6 +784,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + "@babel/helper-validator-option@^7.12.17": version "7.12.17" resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" @@ -834,6 +846,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz#81a01d7d675046f0d96f82450d9d9578bdfd6b0b" + integrity sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@7.12.16": version "7.12.16" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" @@ -25114,13 +25135,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rollup-plugin-dts@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.0.1.tgz#930cbd5aaaa64a55e895ecd6ae8234e1a5467710" - integrity sha512-DNv5F8pro/r0Hkx3JWKRtJZocDnqXfgypoajeiaNq134rYaFcEIl/oas5PogD1qexMadVijsHyVko1Chig0OOQ== + version "4.1.0" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-4.1.0.tgz#63b1e7de3970bb6d50877e60df2150a3892bc49c" + integrity sha512-rriXIm3jdUiYeiAAd1Fv+x2AxK6Kq6IybB2Z/IdoAW95fb4uRUurYsEYKa8L1seedezDeJhy8cfo8FEL9aZzqg== dependencies: magic-string "^0.25.7" optionalDependencies: - "@babel/code-frame" "^7.14.5" + "@babel/code-frame" "^7.16.0" rollup-plugin-esbuild@^4.7.2: version "4.7.2" From 326dfbd5653835b6bfbe715187ea3251c1271037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 5 Jan 2022 13:14:19 +0100 Subject: [PATCH 12/52] merj MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 48 +++--------------------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/yarn.lock b/yarn.lock index cb5f1070d0..9ad68eb645 100644 --- a/yarn.lock +++ b/yarn.lock @@ -338,21 +338,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.5.5": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - -"@babel/code-frame@^7.14.5", "@babel/code-frame@^7.8.3": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/code-frame@^7.16.0": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== @@ -774,17 +760,7 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.0": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288" - integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A== - -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8", "@babel/helper-validator-identifier@^7.14.9": - version "7.14.9" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz#6654d171b2024f6d8ee151bf2509699919131d48" - integrity sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g== - -"@babel/helper-validator-identifier@^7.16.7": +"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.0", "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== @@ -828,25 +804,7 @@ "@babel/traverse" "^7.14.8" "@babel/types" "^7.14.8" -"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13": - version "7.14.0" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf" - integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.0" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.16.7": +"@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz#81a01d7d675046f0d96f82450d9d9578bdfd6b0b" integrity sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw== From 449833d4b5cc3bfcf2803f55049be2a69fa05be6 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 5 Jan 2022 13:52:03 +0100 Subject: [PATCH 13/52] chore: importing the ESBuildMinifyPlugin with require instead Signed-off-by: blam --- packages/cli/src/lib/bundler/optimization.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index b2119f5b3a..a5cf38fc11 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -16,7 +16,8 @@ import { WebpackOptionsNormalized } from 'webpack'; import { BundlingOptions } from './types'; -import { ESBuildMinifyPlugin } from 'esbuild-loader'; + +const { ESBuildMinifyPlugin } = require('esbuild-loader'); export const optimization = ( options: BundlingOptions, From 14e980aceef900eb2de2ebd7c71c08afd27b755b Mon Sep 17 00:00:00 2001 From: Tomasz Szuba Date: Wed, 5 Jan 2022 12:48:32 +0100 Subject: [PATCH 14/52] Upgrade ESLint to version 8 and all it's plugin Signed-off-by: Tomasz Szuba --- .changeset/fair-bikes-scream.md | 28 +++ packages/cli/package.json | 16 +- yarn.lock | 406 +++++++++++++++----------------- 3 files changed, 230 insertions(+), 220 deletions(-) create mode 100644 .changeset/fair-bikes-scream.md diff --git a/.changeset/fair-bikes-scream.md b/.changeset/fair-bikes-scream.md new file mode 100644 index 0000000000..decdfd3828 --- /dev/null +++ b/.changeset/fair-bikes-scream.md @@ -0,0 +1,28 @@ +--- +'@backstage/cli': minor +--- + +ESLint upgraded to version 8 and all it's plugins updated to newest version. + +If you use any custom plugins for ESLint please check compatibility. + +```diff +- "@typescript-eslint/eslint-plugin": "^v4.33.0", +- "@typescript-eslint/parser": "^v4.28.3", ++ "@typescript-eslint/eslint-plugin": "^5.9.0", ++ "@typescript-eslint/parser": "^5.9.0", +- "eslint": "^7.30.0", ++ "eslint": "^8.6.0", +- "eslint-plugin-import": "^2.20.2", +- "eslint-plugin-jest": "^24.1.0", +- "eslint-plugin-jsx-a11y": "^6.2.1", ++ "eslint-plugin-import": "^2.25.4", ++ "eslint-plugin-jest": "^25.3.4", ++ "eslint-plugin-jsx-a11y": "^6.5.1", +- "eslint-plugin-react": "^7.12.4", +- "eslint-plugin-react-hooks": "^4.0.0", ++ "eslint-plugin-react": "^7.28.0", ++ "eslint-plugin-react-hooks": "^4.3.0", +``` + +Please consult changelogs from packages if you find any problems. diff --git a/packages/cli/package.json b/packages/cli/package.json index 3b9cf52893..4c81c213d9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,8 +51,8 @@ "@svgr/rollup": "5.5.x", "@svgr/webpack": "5.5.x", "@types/webpack-env": "^1.15.2", - "@typescript-eslint/eslint-plugin": "^v4.33.0", - "@typescript-eslint/parser": "^v4.28.3", + "@typescript-eslint/eslint-plugin": "^5.9.0", + "@typescript-eslint/parser": "^5.9.0", "@yarnpkg/lockfile": "^1.1.0", "bfj": "^7.0.2", "buffer": "^6.0.3", @@ -62,15 +62,15 @@ "css-loader": "^6.5.1", "diff": "^5.0.0", "esbuild": "^0.14.1", - "eslint": "^7.30.0", + "eslint": "^8.6.0", "eslint-config-prettier": "^8.3.0", "eslint-formatter-friendly": "^7.0.0", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-jsx-a11y": "^6.2.1", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-jest": "^25.3.4", + "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-monorepo": "^0.3.2", - "eslint-plugin-react": "^7.12.4", - "eslint-plugin-react-hooks": "^4.0.0", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0", "express": "^4.17.1", "fork-ts-checker-webpack-plugin": "^4.0.5", "fs-extra": "9.1.0", diff --git a/yarn.lock b/yarn.lock index 263c0beda9..2673acc549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -331,13 +331,6 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@7.12.11": - version "7.12.11" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== - dependencies: - "@babel/highlight" "^7.10.4" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" @@ -2059,13 +2052,20 @@ core-js-pure "^3.16.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.16.3" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.16.3": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.12.13", "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -2643,18 +2643,18 @@ ts-node "^9" tslib "^2" -"@eslint/eslintrc@^0.4.2": - version "0.4.2" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.2.tgz#f63d0ef06f5c0c57d76c4ab5f63d3835c51b0179" - integrity sha512-8nmGq/4ycLpIwzvhI4tNDmQztZ8sp+hI7cyG8i1nQDhkAbRzHpXPidRAHlNvCZQpJTKw5ItIpMw9RSToGF00mg== +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" - debug "^4.1.1" - espree "^7.3.0" + debug "^4.3.2" + espree "^9.2.0" globals "^13.9.0" ignore "^4.0.6" import-fresh "^3.2.1" - js-yaml "^3.13.1" + js-yaml "^4.1.0" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -3328,19 +3328,19 @@ prop-types "^15.6.2" scheduler "^0.19.0" -"@humanwhocodes/config-array@^0.5.0": - version "0.5.0" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" - integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== +"@humanwhocodes/config-array@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" + integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== dependencies: - "@humanwhocodes/object-schema" "^1.2.0" + "@humanwhocodes/object-schema" "^1.2.1" debug "^4.1.1" minimatch "^3.0.4" -"@humanwhocodes/object-schema@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz#87de7af9c231826fdd68ac7258f77c429e0e5fcf" - integrity sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w== +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@iarna/toml@^2.2.5": version "2.2.5" @@ -8356,109 +8356,85 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71" integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg== -"@typescript-eslint/eslint-plugin@^v4.33.0": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" - integrity sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg== +"@typescript-eslint/eslint-plugin@^5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.0.tgz#382182d5cb062f52aac54434cfc47c28898c8006" + integrity sha512-qT4lr2jysDQBQOPsCCvpPUZHjbABoTJW8V9ZzIYKHMfppJtpdtzszDYsldwhFxlhvrp7aCHeXD1Lb9M1zhwWwQ== dependencies: - "@typescript-eslint/experimental-utils" "4.33.0" - "@typescript-eslint/scope-manager" "4.33.0" - debug "^4.3.1" + "@typescript-eslint/experimental-utils" "5.9.0" + "@typescript-eslint/scope-manager" "5.9.0" + "@typescript-eslint/type-utils" "5.9.0" + debug "^4.3.2" functional-red-black-tree "^1.0.1" ignore "^5.1.8" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/experimental-utils@4.33.0", "@typescript-eslint/experimental-utils@^4.0.1": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz#6f2a786a4209fa2222989e9380b5331b2810f7fd" - integrity sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q== +"@typescript-eslint/experimental-utils@5.9.0", "@typescript-eslint/experimental-utils@^5.0.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.0.tgz#652762d37d6565ef07af285021b8347b6c79a827" + integrity sha512-ZnLVjBrf26dn7ElyaSKa6uDhqwvAi4jBBmHK1VxuFGPRAxhdi18ubQYSGA7SRiFiES3q9JiBOBHEBStOFkwD2g== dependencies: - "@types/json-schema" "^7.0.7" - "@typescript-eslint/scope-manager" "4.33.0" - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/typescript-estree" "4.33.0" + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.9.0" + "@typescript-eslint/types" "5.9.0" + "@typescript-eslint/typescript-estree" "5.9.0" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/parser@^v4.28.3": - version "4.28.3" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.28.3.tgz#95f1d475c08268edffdcb2779993c488b6434b44" - integrity sha512-ZyWEn34bJexn/JNYvLQab0Mo5e+qqQNhknxmc8azgNd4XqspVYR5oHq9O11fLwdZMRcj4by15ghSlIEq+H5ltQ== +"@typescript-eslint/parser@^5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.0.tgz#fdbb08767a4caa6ca6ccfed5f9ffe9387f0c7d97" + integrity sha512-/6pOPz8yAxEt4PLzgbFRDpZmHnXCeZgPDrh/1DaVKOjvn/UPMlWhbx/gA96xRi2JxY1kBl2AmwVbyROUqys5xQ== dependencies: - "@typescript-eslint/scope-manager" "4.28.3" - "@typescript-eslint/types" "4.28.3" - "@typescript-eslint/typescript-estree" "4.28.3" - debug "^4.3.1" + "@typescript-eslint/scope-manager" "5.9.0" + "@typescript-eslint/types" "5.9.0" + "@typescript-eslint/typescript-estree" "5.9.0" + debug "^4.3.2" -"@typescript-eslint/scope-manager@4.28.3": - version "4.28.3" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.28.3.tgz#c32ad4491b3726db1ba34030b59ea922c214e371" - integrity sha512-/8lMisZ5NGIzGtJB+QizQ5eX4Xd8uxedFfMBXOKuJGP0oaBBVEMbJVddQKDXyyB0bPlmt8i6bHV89KbwOelJiQ== +"@typescript-eslint/scope-manager@5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.0.tgz#02dfef920290c1dcd7b1999455a3eaae7a1a3117" + integrity sha512-DKtdIL49Qxk2a8icF6whRk7uThuVz4A6TCXfjdJSwOsf+9ree7vgQWcx0KOyCdk0i9ETX666p4aMhrRhxhUkyg== dependencies: - "@typescript-eslint/types" "4.28.3" - "@typescript-eslint/visitor-keys" "4.28.3" + "@typescript-eslint/types" "5.9.0" + "@typescript-eslint/visitor-keys" "5.9.0" -"@typescript-eslint/scope-manager@4.33.0": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz#d38e49280d983e8772e29121cf8c6e9221f280a3" - integrity sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ== +"@typescript-eslint/type-utils@5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.0.tgz#fd5963ead04bc9b7af9c3a8e534d8d39f1ce5f93" + integrity sha512-uVCb9dJXpBrK1071ri5aEW7ZHdDHAiqEjYznF3HSSvAJXyrkxGOw2Ejibz/q6BXdT8lea8CMI0CzKNFTNI6TEQ== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" + "@typescript-eslint/experimental-utils" "5.9.0" + debug "^4.3.2" + tsutils "^3.21.0" -"@typescript-eslint/types@4.28.3": - version "4.28.3" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.28.3.tgz#8fffd436a3bada422c2c1da56060a0566a9506c7" - integrity sha512-kQFaEsQBQVtA9VGVyciyTbIg7S3WoKHNuOp/UF5RG40900KtGqfoiETWD/v0lzRXc+euVE9NXmfer9dLkUJrkA== +"@typescript-eslint/types@5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.0.tgz#e5619803e39d24a03b3369506df196355736e1a3" + integrity sha512-mWp6/b56Umo1rwyGCk8fPIzb9Migo8YOniBGPAQDNC6C52SeyNGN4gsVwQTAR+RS2L5xyajON4hOLwAGwPtUwg== -"@typescript-eslint/types@4.33.0": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" - integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== - -"@typescript-eslint/typescript-estree@4.28.3": - version "4.28.3" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.28.3.tgz#253d7088100b2a38aefe3c8dd7bd1f8232ec46fb" - integrity sha512-YAb1JED41kJsqCQt1NcnX5ZdTA93vKFCMP4lQYG6CFxd0VzDJcKttRlMrlG+1qiWAw8+zowmHU1H0OzjWJzR2w== +"@typescript-eslint/typescript-estree@5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.0.tgz#0e5c6f03f982931abbfbc3c1b9df5fbf92a3490f" + integrity sha512-kxo3xL2mB7XmiVZcECbaDwYCt3qFXz99tBSuVJR4L/sR7CJ+UNAPrYILILktGj1ppfZ/jNt/cWYbziJUlHl1Pw== dependencies: - "@typescript-eslint/types" "4.28.3" - "@typescript-eslint/visitor-keys" "4.28.3" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" + "@typescript-eslint/types" "5.9.0" + "@typescript-eslint/visitor-keys" "5.9.0" + debug "^4.3.2" + globby "^11.0.4" + is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/typescript-estree@4.33.0": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" - integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== +"@typescript-eslint/visitor-keys@5.9.0": + version "5.9.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.0.tgz#7585677732365e9d27f1878150fab3922784a1a6" + integrity sha512-6zq0mb7LV0ThExKlecvpfepiB+XEtFv/bzx7/jKSgyXTFD7qjmSu1FoiS0x3OZaiS+UIXpH2vd9O89f02RCtgw== dependencies: - "@typescript-eslint/types" "4.33.0" - "@typescript-eslint/visitor-keys" "4.33.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/visitor-keys@4.28.3": - version "4.28.3" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.28.3.tgz#26ac91e84b23529968361045829da80a4e5251c4" - integrity sha512-ri1OzcLnk1HH4gORmr1dllxDzzrN6goUIz/P4MHFV0YZJDCADPR3RvYNp0PW2SetKTThar6wlbFTL00hV2Q+fg== - dependencies: - "@typescript-eslint/types" "4.28.3" - eslint-visitor-keys "^2.0.0" - -"@typescript-eslint/visitor-keys@4.33.0": - version "4.33.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" - integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== - dependencies: - "@typescript-eslint/types" "4.33.0" - eslint-visitor-keys "^2.0.0" + "@typescript-eslint/types" "5.9.0" + eslint-visitor-keys "^3.0.0" "@webassemblyjs/ast@1.11.1": version "1.11.1" @@ -8839,7 +8815,7 @@ acorn@^6.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA== -acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.1.1: version "7.4.1" resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -8854,6 +8830,11 @@ acorn@^8.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -8956,16 +8937,6 @@ ajv@^7.0.3: require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^8.0.1: - version "8.6.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.6.0.tgz#60cc45d9c46a477d80d92c48076d972c342e5720" - integrity sha512-cnUG4NSBiM4YFBxgZIj/In3/6KX+rQ2l2YPRVcvAMQGWEPKuXoPIhxzwqh31jA3IPbI4qEOp/5ILI4ynioXsGQ== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - alphanum-sort@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -9445,7 +9416,7 @@ array-ify@^1.0.0: resolved "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3, array-includes@^3.1.1, array-includes@^3.1.2, array-includes@^3.1.4: +array-includes@^3.0.3, array-includes@^3.1.2, array-includes@^3.1.3, array-includes@^3.1.4: version "3.1.4" resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== @@ -9727,11 +9698,16 @@ aws4@^1.11.0, aws4@^1.8.0: resolved "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axe-core@^4.0.2, axe-core@^4.2.0: +axe-core@^4.2.0: version "4.3.1" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.3.1.tgz#0c6a076e4a1c3e0544ba6a9479158f9be7a7928e" integrity sha512-3WVgVPs/7OnKU3s+lqMtkv3wQlg3WxK1YifmpJSDO0E1aPBrZWlrrTO6cxRqCXLuX2aYgCljqXIQd0VnRidV0g== +axe-core@^4.3.5: + version "4.3.5" + resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.3.5.tgz#78d6911ba317a8262bfee292aeafcc1e04b49cc5" + integrity sha512-WKTW1+xAzhMS5dJsxWkliixlO/PqC4VhmO9T4juNYcaTg9jzWiJsou6m5pxWYGfigWbwzJWeFY6z47a+4neRXA== + axios-cached-dns-resolve@0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/axios-cached-dns-resolve/-/axios-cached-dns-resolve-0.5.2.tgz#38cd89fd491fa7a48d04fb421291085c640fe79e" @@ -12600,10 +12576,10 @@ dagre@^0.8.5: graphlib "^2.1.8" lodash "^4.17.15" -damerau-levenshtein@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" - integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== +damerau-levenshtein@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz#64368003512a1a6992593741a09a9d31a836f55d" + integrity sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw== dargs@^7.0.0: version "7.0.0" @@ -12673,7 +12649,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: version "4.3.2" resolved "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== @@ -13409,7 +13385,7 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.0.0: +emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== @@ -13869,7 +13845,7 @@ eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-module-utils@^2.1.1, eslint-module-utils@^2.7.1: +eslint-module-utils@^2.1.1: version "2.7.1" resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.1.tgz#b435001c9f8dd4ab7f6d0efcae4b9696d4c24b7c" integrity sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ== @@ -13878,6 +13854,14 @@ eslint-module-utils@^2.1.1, eslint-module-utils@^2.7.1: find-up "^2.1.0" pkg-dir "^2.0.0" +eslint-module-utils@^2.7.2: + version "2.7.2" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + eslint-plugin-cypress@^2.10.3: version "2.12.1" resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.12.1.tgz#9aeee700708ca8c058e00cdafe215199918c2632" @@ -13895,48 +13879,49 @@ eslint-plugin-graphql@^4.0.0: lodash.flatten "^4.4.0" lodash.without "^4.4.0" -eslint-plugin-import@^2.20.2: - version "2.25.3" - resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.3.tgz#a554b5f66e08fb4f6dc99221866e57cfff824766" - integrity sha512-RzAVbby+72IB3iOEL8clzPLzL3wpDrlwjsTBAQXgyp5SeTqqY+0bFubwuo+y/HLhNZcXV4XqTBO4LGsfyHIDXg== +eslint-plugin-import@^2.25.4: + version "2.25.4" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.1" + eslint-module-utils "^2.7.2" has "^1.0.3" is-core-module "^2.8.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.11.0" + tsconfig-paths "^3.12.0" -eslint-plugin-jest@^24.1.0: - version "24.3.6" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-24.3.6.tgz#5f0ca019183c3188c5ad3af8e80b41de6c8e9173" - integrity sha512-WOVH4TIaBLIeCX576rLcOgjNXqP+jNlCiEmRgFTfQtJ52DpwnIQKAVGlGPAN7CZ33bW6eNfHD6s8ZbEUTQubJg== +eslint-plugin-jest@^25.3.4: + version "25.3.4" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.3.4.tgz#2031dfe495be1463330f8b80096ddc91f8e6387f" + integrity sha512-CCnwG71wvabmwq/qkz0HWIqBHQxw6pXB1uqt24dxqJ9WB34pVg49bL1sjXphlJHgTMWGhBjN1PicdyxDxrfP5A== dependencies: - "@typescript-eslint/experimental-utils" "^4.0.1" + "@typescript-eslint/experimental-utils" "^5.0.0" -eslint-plugin-jsx-a11y@^6.2.1: - version "6.4.1" - resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" - integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== +eslint-plugin-jsx-a11y@^6.5.1: + version "6.5.1" + resolved "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" + integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== dependencies: - "@babel/runtime" "^7.11.2" + "@babel/runtime" "^7.16.3" aria-query "^4.2.2" - array-includes "^3.1.1" + array-includes "^3.1.4" ast-types-flow "^0.0.7" - axe-core "^4.0.2" + axe-core "^4.3.5" axobject-query "^2.2.0" - damerau-levenshtein "^1.0.6" - emoji-regex "^9.0.0" + damerau-levenshtein "^1.0.7" + emoji-regex "^9.2.2" has "^1.0.3" - jsx-ast-utils "^3.1.0" + jsx-ast-utils "^3.2.1" language-tags "^1.0.5" + minimatch "^3.0.4" eslint-plugin-monorepo@^0.3.2: version "0.3.2" @@ -13960,15 +13945,15 @@ eslint-plugin-notice@^0.9.10: lodash "^4.17.15" metric-lcs "^0.1.2" -eslint-plugin-react-hooks@^4.0.0: - version "4.2.0" - resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556" - integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ== +eslint-plugin-react-hooks@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" + integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== -eslint-plugin-react@^7.12.4: - version "7.27.1" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.27.1.tgz#469202442506616f77a854d91babaae1ec174b45" - integrity sha512-meyunDjMMYeWr/4EBLTV1op3iSG3mjT/pz5gti38UzfM4OPpNc2m0t2xvKCOMU5D6FSdd34BIMFOvQbW+i8GAA== +eslint-plugin-react@^7.28.0: + version "7.28.0" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.28.0.tgz#8f3ff450677571a659ce76efc6d80b6a525adbdf" + integrity sha512-IOlFIRHzWfEQQKcAD4iyYDndHwTQiCMcJVJjxempf203jnNLUnW34AXLrV33+nEXoifJE2ZEGmcjKPL8957eSw== dependencies: array-includes "^3.1.4" array.prototype.flatmap "^1.2.5" @@ -14001,12 +13986,13 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" - integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: - eslint-visitor-keys "^1.1.0" + esrecurse "^4.3.0" + estraverse "^5.2.0" eslint-utils@^3.0.0: version "3.0.0" @@ -14015,47 +14001,46 @@ eslint-utils@^3.0.0: dependencies: eslint-visitor-keys "^2.0.0" -eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - eslint-visitor-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.30.0: - version "7.30.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-7.30.0.tgz#6d34ab51aaa56112fd97166226c9a97f505474f8" - integrity sha512-VLqz80i3as3NdloY44BQSJpFw534L9Oh+6zJOUaViV4JPd+DaHwutqP7tcpkW3YiXbK6s05RZl7yl7cQn+lijg== +eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" + integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== + +eslint@^8.6.0: + version "8.6.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz#4318c6a31c5584838c1a2e940c478190f58d558e" + integrity sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw== dependencies: - "@babel/code-frame" "7.12.11" - "@eslint/eslintrc" "^0.4.2" - "@humanwhocodes/config-array" "^0.5.0" + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" - debug "^4.0.1" + debug "^4.3.2" doctrine "^3.0.0" enquirer "^2.3.5" escape-string-regexp "^4.0.0" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.1" + eslint-scope "^7.1.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.1.0" + espree "^9.3.0" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" - glob-parent "^5.1.2" + glob-parent "^6.0.1" globals "^13.6.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - js-yaml "^3.13.1" + js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" @@ -14063,11 +14048,10 @@ eslint@^7.30.0: natural-compare "^1.4.0" optionator "^0.9.1" progress "^2.0.0" - regexpp "^3.1.0" + regexpp "^3.2.0" semver "^7.2.1" - strip-ansi "^6.0.0" + strip-ansi "^6.0.1" strip-json-comments "^3.1.0" - table "^6.0.9" text-table "^0.2.0" v8-compile-cache "^2.0.3" @@ -14076,14 +14060,14 @@ esm@^3.2.25: resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^7.3.0, espree@^7.3.1: - version "7.3.1" - resolved "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" - integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== dependencies: - acorn "^7.4.0" + acorn "^8.7.0" acorn-jsx "^5.3.1" - eslint-visitor-keys "^1.3.0" + eslint-visitor-keys "^3.1.0" esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" @@ -15486,13 +15470,20 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@^5.1.0, glob-parent@^5.1.1, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-promise@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" @@ -15626,7 +15617,7 @@ globby@11.0.3: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: version "11.0.4" resolved "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -18754,7 +18745,7 @@ jss@10.6.0, jss@^10.5.1: is-in-browser "^1.1.3" tiny-warning "^1.0.2" -"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: +"jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.2.0" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== @@ -18762,6 +18753,14 @@ jss@10.6.0, jss@^10.5.1: array-includes "^3.1.2" object.assign "^4.1.2" +jsx-ast-utils@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz#720b97bfe7d901b927d87c3773637ae8ea48781b" + integrity sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA== + dependencies: + array-includes "^3.1.3" + object.assign "^4.1.2" + junk@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" @@ -19471,11 +19470,6 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash.truncate@^4.4.2: - version "4.4.2" - resolved "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" - integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= - lodash.union@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" @@ -24592,10 +24586,10 @@ regexp.prototype.flags@^1.3.1: call-bind "^1.0.2" define-properties "^1.1.3" -regexpp@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" - integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== regexpu-core@^4.7.1: version "4.7.1" @@ -26873,18 +26867,6 @@ sync-fetch@0.3.1: buffer "^5.7.0" node-fetch "^2.6.1" -table@^6.0.9: - version "6.7.1" - resolved "https://registry.npmjs.org/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" - integrity sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg== - dependencies: - ajv "^8.0.1" - lodash.clonedeep "^4.5.0" - lodash.truncate "^4.4.2" - slice-ansi "^4.0.0" - string-width "^4.2.0" - strip-ansi "^6.0.0" - tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -27558,7 +27540,7 @@ ts-pnp@^1.1.6: resolved "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tsconfig-paths@^3.11.0: +tsconfig-paths@^3.12.0: version "3.12.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== From cf01627a3344e95f6410d72668aab1294bd2d9cd Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 5 Jan 2022 15:33:16 +0100 Subject: [PATCH 15/52] catalog-graphql: Bump graphql versions Signed-off-by: Johan Haals --- .changeset/funny-llamas-yell.md | 5 + plugins/catalog-graphql/package.json | 6 +- yarn.lock | 706 +++++++++++++++++++-------- 3 files changed, 502 insertions(+), 215 deletions(-) create mode 100644 .changeset/funny-llamas-yell.md diff --git a/.changeset/funny-llamas-yell.md b/.changeset/funny-llamas-yell.md new file mode 100644 index 0000000000..890caaa221 --- /dev/null +++ b/.changeset/funny-llamas-yell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-graphql': patch +--- + +Bump graphql versions diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index fe52727176..b95e65034c 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -45,9 +45,9 @@ "devDependencies": { "@backstage/cli": "^0.10.3", "@backstage/test-utils": "^0.2.0", - "@graphql-codegen/cli": "^1.21.3", - "@graphql-codegen/typescript": "^1.17.7", - "@graphql-codegen/typescript-resolvers": "^1.17.7", + "@graphql-codegen/cli": "^2.3.1", + "@graphql-codegen/typescript": "^2.4.2", + "@graphql-codegen/typescript-resolvers": "^2.4.2", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.35.0" }, diff --git a/yarn.lock b/yarn.lock index 2673acc549..bbe1c79f2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -331,7 +331,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.14.5", "@babel/code-frame@^7.16.0", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": version "7.16.7" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== @@ -348,6 +348,11 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.9.tgz#ac7996ceaafcf8f410119c8af0d1db4cf914a210" integrity sha512-p3QjZmMGHDGdpcwEYYWu7i7oJShJvtgMjJeb0W95PPhSm++3lm8YXYOh45Y6iCN9PkZLTZ7CIX5nFrp7pw7TXw== +"@babel/compat-data@^7.16.4": + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== + "@babel/core@7.12.9": version "7.12.9" resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" @@ -370,7 +375,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.7.5": +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.7.5": version "7.14.8" resolved "https://registry.npmjs.org/@babel/core/-/core-7.14.8.tgz#20cdf7c84b5d86d83fac8710a8bc605a7ba3f010" integrity sha512-/AtaeEhT6ErpDhInbXmjHcUQXH0L0TEgscfcxk1qbOvLuKCa5aZT0SOOtDKFY96/CLROwbLSKyFor6idgNaU4Q== @@ -391,6 +396,27 @@ semver "^6.3.0" source-map "^0.5.0" +"@babel/core@^7.14.0": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz#db990f931f6d40cb9b87a0dc7d2adc749f1dcbcf" + integrity sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.7" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helpers" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + "@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.14.8", "@babel/generator@^7.14.9": version "7.14.9" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.14.9.tgz#23b19c597d38b4f7dc2e3fe42a69c88d9ecfaa16" @@ -400,7 +426,16 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.12.13", "@babel/generator@^7.14.2", "@babel/generator@^7.5.0": +"@babel/generator@^7.14.0", "@babel/generator@^7.16.0", "@babel/generator@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz#b42bf46a3079fa65e1544135f32e7958f048adbb" + integrity sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg== + dependencies: + "@babel/types" "^7.16.7" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.14.2": version "7.14.3" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91" integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA== @@ -459,6 +494,16 @@ browserslist "^4.16.6" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" + integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== + dependencies: + "@babel/compat-data" "^7.16.4" + "@babel/helper-validator-option" "^7.16.7" + browserslist "^4.17.5" + semver "^6.3.0" + "@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.14.0", "@babel/helper-create-class-features-plugin@^7.14.3": version "7.14.4" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.4.tgz#abf888d836a441abee783c75229279748705dc42" @@ -527,6 +572,13 @@ resolve "^1.14.2" semver "^6.1.2" +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-explode-assignable-expression@^7.12.13": version "7.13.0" resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz#17b5c59ff473d9f956f40ef570cf3a76ca12657f" @@ -559,6 +611,15 @@ "@babel/template" "^7.14.5" "@babel/types" "^7.14.5" +"@babel/helper-function-name@^7.16.0", "@babel/helper-function-name@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== + dependencies: + "@babel/helper-get-function-arity" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/types" "^7.16.7" + "@babel/helper-get-function-arity@^7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz#bc63451d403a3b3082b97e1d8b3fe5bd4091e583" @@ -573,6 +634,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-get-function-arity@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-hoist-variables@^7.13.0": version "7.13.16" resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz#1b1651249e94b51f8f0d33439843e33e39775b30" @@ -588,6 +656,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-hoist-variables@^7.16.0", "@babel/helper-hoist-variables@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-member-expression-to-functions@^7.13.12": version "7.13.12" resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" @@ -616,6 +691,13 @@ dependencies: "@babel/types" "^7.14.5" +"@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.14.5", "@babel/helper-module-transforms@^7.14.8": version "7.14.8" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.8.tgz#d4279f7e3fd5f4d5d342d833af36d4dd87d7dc49" @@ -644,6 +726,20 @@ "@babel/traverse" "^7.14.2" "@babel/types" "^7.14.2" +"@babel/helper-module-transforms@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" + integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== + dependencies: + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-module-imports" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/helper-validator-identifier" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + "@babel/helper-optimise-call-expression@^7.12.13": version "7.12.13" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz#5c02d171b4c8615b1e7163f888c1c81c30a2aaea" @@ -725,6 +821,13 @@ dependencies: "@babel/types" "^7.14.8" +"@babel/helper-simple-access@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" + integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-skip-transparent-expression-wrappers@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz#462dc63a7e435ade8468385c63d2b84cce4b3cbf" @@ -753,7 +856,14 @@ dependencies: "@babel/types" "^7.14.5" -"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.0", "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.16.7": +"@babel/helper-split-export-declaration@^7.16.0", "@babel/helper-split-export-declaration@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.14.0", "@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7", "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== @@ -768,6 +878,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== + "@babel/helper-wrap-function@^7.13.0": version "7.13.0" resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz#bdb5c66fda8526ec235ab894ad53a1235c79fcc4" @@ -797,6 +912,15 @@ "@babel/traverse" "^7.14.8" "@babel/types" "^7.14.8" +"@babel/helpers@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz#7e3504d708d50344112767c3542fc5e357fffefc" + integrity sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + "@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz#81a01d7d675046f0d96f82450d9d9578bdfd6b0b" @@ -806,16 +930,21 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@7.12.16": - version "7.12.16" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.16.tgz#cc31257419d2c3189d394081635703f549fc1ed4" - integrity sha512-c/+u9cqV6F0+4Hpq01jnJO+GLp2DdT63ppz9Xa+6cHaajM9VFzK/iDXiKK65YtpeVwu+ctfS6iqlMqRgQRzeCw== +"@babel/parser@7.16.4": + version "7.16.4" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz#d5f92f57cf2c74ffe9b37981c0e72fee7311372e" + integrity sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng== -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.13", "@babel/parser@^7.12.7", "@babel/parser@^7.13.16", "@babel/parser@^7.14.2", "@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.14.9": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.12.13", "@babel/parser@^7.12.7", "@babel/parser@^7.13.16", "@babel/parser@^7.14.2", "@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.14.9": version "7.14.9" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.14.9.tgz#596c1ad67608070058ebf8df50c1eaf65db895a4" integrity sha512-RdUTOseXJ8POjjOeEBEvNMIZU/nm4yu2rufRkcibzkkg7DmQvXU8v3M4Xk9G7uuI86CDGkKcuDWgioqZm+mScQ== +"@babel/parser@^7.14.0", "@babel/parser@^7.16.3", "@babel/parser@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz#d372dda9c89fcec340a82630a9f533f2fe15877e" + integrity sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA== + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12": version "7.13.12" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a" @@ -2084,22 +2213,31 @@ "@babel/parser" "^7.14.5" "@babel/types" "^7.14.5" -"@babel/traverse@7.12.13": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz#689f0e4b4c08587ad26622832632735fb8c4e0c0" - integrity sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA== +"@babel/template@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: - "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.12.13" - "@babel/helper-function-name" "^7.12.13" - "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.12.13" - "@babel/types" "^7.12.13" + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/traverse@7.16.3": + version "7.16.3" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz#f63e8a938cc1b780f66d9ed3c54f532ca2d14787" + integrity sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag== + dependencies: + "@babel/code-frame" "^7.16.0" + "@babel/generator" "^7.16.0" + "@babel/helper-function-name" "^7.16.0" + "@babel/helper-hoist-variables" "^7.16.0" + "@babel/helper-split-export-declaration" "^7.16.0" + "@babel/parser" "^7.16.3" + "@babel/types" "^7.16.0" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.19" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.2", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.1.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.2", "@babel/traverse@^7.4.5": version "7.14.2" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b" integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA== @@ -2128,13 +2266,28 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@7.12.13": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz#8be1aa8f2c876da11a9cf650c0ecf656913ad611" - integrity sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ== +"@babel/traverse@^7.14.0", "@babel/traverse@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz#dac01236a72c2560073658dd1a285fe4e0865d76" + integrity sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ== dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - lodash "^4.17.19" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@7.16.0": + version "7.16.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz#db3b313804f96aadd0b776c4823e127ad67289ba" + integrity sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg== + dependencies: + "@babel/helper-validator-identifier" "^7.15.7" to-fast-properties "^2.0.0" "@babel/types@^7.0.0", "@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.6", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.16", "@babel/types@^7.14.2", "@babel/types@^7.14.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": @@ -2153,6 +2306,14 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" +"@babel/types@^7.16.0", "@babel/types@^7.16.7": + version "7.16.7" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz#4ed19d51f840ed4bd5645be6ce40775fecf03159" + integrity sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2786,23 +2947,23 @@ "@n1ru4l/push-pull-async-iterable-iterator" "^3.1.0" meros "^1.1.4" -"@graphql-codegen/cli@^1.21.3": - version "1.21.6" - resolved "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-1.21.6.tgz#d06b5f6cb625541f3981d69f99966e520b958072" - integrity sha512-wtBk4lk/YxG6MrxnBOxE9nCfR9PNDjaqA8CF9hi6Q8jiSl4sV03tC2R+gE7+2EI8J6Xa1bxZe15LnBhVwb/mUA== +"@graphql-codegen/cli@^2.3.1": + version "2.3.1" + resolved "https://registry.npmjs.org/@graphql-codegen/cli/-/cli-2.3.1.tgz#66083293b60e3182603d70031210d59e6f1a16e5" + integrity sha512-xMSvYqFtnRXOp/sVJSyqiFTm70X8ouLXiq5o/R/D3yQtA6NNudAC+Q4oxg9/LZKnRDL6pehwdC8CNnQk0Tf7Sw== dependencies: - "@graphql-codegen/core" "1.17.10" - "@graphql-codegen/plugin-helpers" "^1.18.7" - "@graphql-tools/apollo-engine-loader" "^6.2.5" - "@graphql-tools/code-file-loader" "^6.3.1" - "@graphql-tools/git-loader" "^6.2.6" - "@graphql-tools/github-loader" "^6.2.5" - "@graphql-tools/graphql-file-loader" "^6.2.7" - "@graphql-tools/json-file-loader" "^6.2.6" - "@graphql-tools/load" "^6.2.8" - "@graphql-tools/prisma-loader" "^6.3.0" - "@graphql-tools/url-loader" "^6.10.1" - "@graphql-tools/utils" "^7.9.1" + "@graphql-codegen/core" "2.4.0" + "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-tools/apollo-engine-loader" "^7.0.5" + "@graphql-tools/code-file-loader" "^7.0.6" + "@graphql-tools/git-loader" "^7.0.5" + "@graphql-tools/github-loader" "^7.0.5" + "@graphql-tools/graphql-file-loader" "^7.0.5" + "@graphql-tools/json-file-loader" "^7.1.2" + "@graphql-tools/load" "^7.3.0" + "@graphql-tools/prisma-loader" "^7.0.6" + "@graphql-tools/url-loader" "^7.0.11" + "@graphql-tools/utils" "^8.1.1" ansi-escapes "^4.3.1" chalk "^4.1.0" change-case-all "1.0.14" @@ -2813,8 +2974,9 @@ dependency-graph "^0.11.0" detect-indent "^6.0.0" glob "^7.1.6" - graphql-config "^3.3.0" - inquirer "^7.3.3" + globby "^11.0.4" + graphql-config "^4.1.0" + inquirer "^8.0.0" is-glob "^4.0.1" json-to-pretty-yaml "^1.2.2" latest-version "5.1.0" @@ -2831,74 +2993,69 @@ yaml "^1.10.0" yargs "^17.0.0" -"@graphql-codegen/core@1.17.10": - version "1.17.10" - resolved "https://registry.npmjs.org/@graphql-codegen/core/-/core-1.17.10.tgz#3b85b5bc2e84fcacbd25fced5af47a4bb2d7a8bd" - integrity sha512-RA3umgVDs/RI/+ztHh+H4GfJxrJUfWJQqoAkMfX4qPTVO5qsy3R4vPudE0oP8w+kFbL8dFsRfAAPUZxI4jV/hQ== +"@graphql-codegen/core@2.4.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@graphql-codegen/core/-/core-2.4.0.tgz#d94dcc088b5e117c847ce5b10c4fe1eb7325e180" + integrity sha512-5RiYE1+07jayp/3w/bkyaCXtfKNeKmRabpPP4aRi369WeH2cH37l2K8NbhkIU+zhpnhoqMID61TO56x2fKldZQ== dependencies: - "@graphql-codegen/plugin-helpers" "^1.18.7" - "@graphql-tools/merge" "^6.2.14" - "@graphql-tools/utils" "^7.9.1" - tslib "~2.2.0" + "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-tools/schema" "^8.1.2" + "@graphql-tools/utils" "^8.1.1" + tslib "~2.3.0" -"@graphql-codegen/plugin-helpers@^1.18.7", "@graphql-codegen/plugin-helpers@^1.18.8": - version "1.18.8" - resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.8.tgz#39aac745b9e22e28c781cc07cf74836896a3a905" - integrity sha512-mb4I9j9lMGqvGggYuZ0CV+Hme08nar68xkpPbAVotg/ZBmlhZIok/HqW2BcMQi7Rj+Il5HQMeQ1wQ1M7sv/TlQ== +"@graphql-codegen/plugin-helpers@^2.3.2": + version "2.3.2" + resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-2.3.2.tgz#3f9ba625791901d19be733db1dfc9a3dbd0dac44" + integrity sha512-19qFA5XMAWaAY64sBljjDPYfHjE+QMk/+oalCyY13WjSlcLDvYPfmFlCNgFSsydArDELlHR8T1GMbA7C42M8TA== dependencies: - "@graphql-tools/utils" "^7.9.1" - common-tags "1.8.0" + "@graphql-tools/utils" "^8.5.2" + change-case-all "1.0.14" + common-tags "1.8.2" import-from "4.0.0" lodash "~4.17.0" tslib "~2.3.0" -"@graphql-codegen/typescript-resolvers@^1.17.7": - version "1.19.3" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.19.3.tgz#9e5215bdc202350c4cb54d866f9f26d1e458d81b" - integrity sha512-wbc3hgULs7/gmlmVvbUpqxoOff2MjVnSvBllrldBIezGvcoj7Q265Cb0q/ki5MV8OzUWq28zpBrc3RMg7E5O9Q== +"@graphql-codegen/schema-ast@^2.4.1": + version "2.4.1" + resolved "https://registry.npmjs.org/@graphql-codegen/schema-ast/-/schema-ast-2.4.1.tgz#ad742b53e32f7a2fbff8ea8a91ba7e617e6ef236" + integrity sha512-bIWlKk/ShoVJfghA4Rt1OWnd34/dQmZM/vAe6fu6QKyOh44aAdqPtYQ2dbTyFXoknmu504etKJGEDllYNUJRfg== dependencies: - "@graphql-codegen/plugin-helpers" "^1.18.7" - "@graphql-codegen/typescript" "^1.22.2" - "@graphql-codegen/visitor-plugin-common" "1.21.1" - "@graphql-tools/utils" "^7.9.1" + "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-tools/utils" "^8.1.1" + tslib "~2.3.0" + +"@graphql-codegen/typescript-resolvers@^2.4.2": + version "2.4.3" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-2.4.3.tgz#556dbaf23eac0ff9c321d3ce7126d96a839f793f" + integrity sha512-4m3E0zKLSXjGirZcYHHaZ0bxjy/gxvuumShFCKFmYTkHwTfqBaeh/pMhWqLkwC9wimrH6mQoPIYSQHLaF6Eqng== + dependencies: + "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-codegen/typescript" "^2.4.2" + "@graphql-codegen/visitor-plugin-common" "2.5.2" + "@graphql-tools/utils" "^8.1.1" auto-bind "~4.0.0" tslib "~2.3.0" -"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.22.2": - version "1.23.0" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.23.0.tgz#48a5372bcbe81a442c71c1bb032c312c6586a59a" - integrity sha512-ZfFgk5mGfuOy4kEpy+dcuvJMphigMfJ4AkiP1qWmWFufDW3Sg2yayTSNmzeFdcXMrWGgfNW2dKtuuTmbmQhS5g== +"@graphql-codegen/typescript@^2.4.2": + version "2.4.2" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-2.4.2.tgz#a239d5fd8f11140d5d4c81cfae7ff02054b724dc" + integrity sha512-8ajWidiFqF1KNAywtb/692SjwTyjzrDHo1sf2Q7Z+rh9qDEIrh83VHB8naT/1CefOvxj3MS6GbcsvJMizaE/AQ== dependencies: - "@graphql-codegen/plugin-helpers" "^1.18.8" - "@graphql-codegen/visitor-plugin-common" "1.22.0" + "@graphql-codegen/plugin-helpers" "^2.3.2" + "@graphql-codegen/schema-ast" "^2.4.1" + "@graphql-codegen/visitor-plugin-common" "2.5.2" auto-bind "~4.0.0" tslib "~2.3.0" -"@graphql-codegen/visitor-plugin-common@1.21.1": - version "1.21.1" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.21.1.tgz#d080265e42c2a8867520b29baf283b1e1012bbb8" - integrity sha512-f6GakFkn6TEtuU//BrZfmdL5eyzlisE8x6LmNJvjPQig8pVBVt8ncJeWV42XV9iJpaCmrQaT4MtXPkjlCe0egA== +"@graphql-codegen/visitor-plugin-common@2.5.2": + version "2.5.2" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-2.5.2.tgz#90aa4add41e17bca83f1c7c8ad674f2a06065efd" + integrity sha512-qDMraPmumG+vEGAz42/asRkdgIRmQWH5HTc320UX+I6CY6eE/Ey85cgzoqeQGLV8gu4sj3UkNx/3/r79eX4u+Q== dependencies: - "@graphql-codegen/plugin-helpers" "^1.18.7" + "@graphql-codegen/plugin-helpers" "^2.3.2" "@graphql-tools/optimize" "^1.0.1" - "@graphql-tools/relay-operation-optimizer" "^6.3.0" - array.prototype.flatmap "^1.2.4" - auto-bind "~4.0.0" - change-case-all "1.0.14" - dependency-graph "^0.11.0" - graphql-tag "^2.11.0" - parse-filepath "^1.0.2" - tslib "~2.3.0" - -"@graphql-codegen/visitor-plugin-common@1.22.0": - version "1.22.0" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.22.0.tgz#75fc8b580143bccbec411eb92d5fef715ed22e42" - integrity sha512-2afJGb6d8iuZl9KizYsexPwraEKO1lAvt5eVHNM5Xew4vwz/AUHeqDR2uOeQgVV+27EzjjzSDd47IEdH0dLC2w== - dependencies: - "@graphql-codegen/plugin-helpers" "^1.18.8" - "@graphql-tools/optimize" "^1.0.1" - "@graphql-tools/relay-operation-optimizer" "^6.3.0" - array.prototype.flatmap "^1.2.4" + "@graphql-tools/relay-operation-optimizer" "^6.3.7" + "@graphql-tools/utils" "^8.3.0" auto-bind "~4.0.0" change-case-all "1.0.14" dependency-graph "^0.11.0" @@ -2947,14 +3104,15 @@ graphql-tools "5.0.0" tslib "1.11.1" -"@graphql-tools/apollo-engine-loader@^6.2.5": - version "6.2.5" - resolved "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-6.2.5.tgz#b9e65744f522bb9f6ca50651e5622820c4f059a8" - integrity sha512-CE4uef6PyxtSG+7OnLklIr2BZZDgjO89ZXK47EKdY7jQy/BQD/9o+8SxPsgiBc+2NsDJH2I6P/nqoaJMOEat6g== +"@graphql-tools/apollo-engine-loader@^7.0.5": + version "7.2.1" + resolved "https://registry.npmjs.org/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-7.2.1.tgz#14e5d0b1032a7d882d22a7533c8969ee3fa797f2" + integrity sha512-Fj/A8+9SXPTXkpKqhcSq7O9WZuMdy5zynGrnMyewbCuw1kSfzgC4pJB76ILSPa5ajOcC5bBmmvXm+yVFVRgVMg== dependencies: - "@graphql-tools/utils" "^7.0.0" - cross-fetch "3.0.6" - tslib "~2.0.1" + "@graphql-tools/utils" "^8.5.1" + cross-undici-fetch "^0.0.20" + sync-fetch "0.3.1" + tslib "~2.3.0" "@graphql-tools/batch-execute@^7.1.2": version "7.1.2" @@ -2976,14 +3134,16 @@ tslib "~2.3.0" value-or-promise "1.0.11" -"@graphql-tools/code-file-loader@^6.3.1": - version "6.3.1" - resolved "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-6.3.1.tgz#42dfd4db5b968acdb453382f172ec684fa0c34ed" - integrity sha512-ZJimcm2ig+avgsEOWWVvAaxZrXXhiiSZyYYOJi0hk9wh5BxZcLUNKkTp6EFnZE/jmGUwuos3pIjUD3Hwi3Bwhg== +"@graphql-tools/code-file-loader@^7.0.6": + version "7.2.3" + resolved "https://registry.npmjs.org/@graphql-tools/code-file-loader/-/code-file-loader-7.2.3.tgz#b53e8809528da07911423c3a511e5fccf9121a12" + integrity sha512-aNVG3/VG5cUpS389rpCum+z7RY98qvPwOzd+J4LVr+f5hWQbDREnSFM+5RVTDfULujrsi7edKaGxGKp68pGmAA== dependencies: - "@graphql-tools/graphql-tag-pluck" "^6.5.1" - "@graphql-tools/utils" "^7.0.0" - tslib "~2.1.0" + "@graphql-tools/graphql-tag-pluck" "^7.1.3" + "@graphql-tools/utils" "^8.5.1" + globby "^11.0.3" + tslib "~2.3.0" + unixify "^1.0.0" "@graphql-tools/delegate@^7.0.1", "@graphql-tools/delegate@^7.1.5": version "7.1.5" @@ -3010,26 +3170,30 @@ tslib "~2.3.0" value-or-promise "1.0.11" -"@graphql-tools/git-loader@^6.2.6": - version "6.2.6" - resolved "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-6.2.6.tgz#c2226f4b8f51f1c05c9ab2649ba32d49c68cd077" - integrity sha512-ooQTt2CaG47vEYPP3CPD+nbA0F+FYQXfzrB1Y1ABN9K3d3O2RK3g8qwslzZaI8VJQthvKwt0A95ZeE4XxteYfw== +"@graphql-tools/git-loader@^7.0.5": + version "7.1.2" + resolved "https://registry.npmjs.org/@graphql-tools/git-loader/-/git-loader-7.1.2.tgz#7a7b5fc366bcc9e2e14e0463ff73f1a19aafabbd" + integrity sha512-vIMrISQPKQgHS893b8K/pEE1InPV+7etzFhHoyQRhYkVHXP2RBkfI64Wq9bNPezF8Ss/dwIjI/keLaPp9EQDmA== dependencies: - "@graphql-tools/graphql-tag-pluck" "^6.2.6" - "@graphql-tools/utils" "^7.0.0" - tslib "~2.1.0" + "@graphql-tools/graphql-tag-pluck" "^7.1.3" + "@graphql-tools/utils" "^8.5.1" + is-glob "4.0.3" + micromatch "^4.0.4" + tslib "~2.3.0" + unixify "^1.0.0" -"@graphql-tools/github-loader@^6.2.5": - version "6.2.5" - resolved "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-6.2.5.tgz#460dff6f5bbaa26957a5ea3be4f452b89cc6a44b" - integrity sha512-DLuQmYeNNdPo8oWus8EePxWCfCAyUXPZ/p1PWqjrX/NGPyH2ZObdqtDAfRHztljt0F/qkBHbGHCEk2TKbRZTRw== +"@graphql-tools/github-loader@^7.0.5": + version "7.2.1" + resolved "https://registry.npmjs.org/@graphql-tools/github-loader/-/github-loader-7.2.1.tgz#53ce2bf215a0eb083ff985b213402a24f1302da2" + integrity sha512-vqwh2H11ZkAATDam/JqiP0CSqQRPUbjgCDxPdUu/xvST2QKyA4+uVXLBcpBRJc5kJCQjELijeRWVHSk9oN1q6g== dependencies: - "@graphql-tools/graphql-tag-pluck" "^6.2.6" - "@graphql-tools/utils" "^7.0.0" - cross-fetch "3.0.6" - tslib "~2.0.1" + "@graphql-tools/graphql-tag-pluck" "^7.1.3" + "@graphql-tools/utils" "^8.5.1" + cross-undici-fetch "^0.0.20" + sync-fetch "0.3.1" + tslib "~2.3.0" -"@graphql-tools/graphql-file-loader@^6.0.0", "@graphql-tools/graphql-file-loader@^6.2.7": +"@graphql-tools/graphql-file-loader@^6.0.0": version "6.2.7" resolved "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.7.tgz#d3720f2c4f4bb90eb2a03a7869a780c61945e143" integrity sha512-5k2SNz0W87tDcymhEMZMkd6/vs6QawDyjQXWtqkuLTBF3vxjxPD1I4dwHoxgWPIjjANhXybvulD7E+St/7s9TQ== @@ -3038,7 +3202,7 @@ "@graphql-tools/utils" "^7.0.0" tslib "~2.1.0" -"@graphql-tools/graphql-file-loader@^7.3.2": +"@graphql-tools/graphql-file-loader@^7.0.5", "@graphql-tools/graphql-file-loader@^7.3.2": version "7.3.3" resolved "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-7.3.3.tgz#7cee2f84f08dc13fa756820b510248b857583d36" integrity sha512-6kUJZiNpYKVhum9E5wfl5PyLLupEDYdH7c8l6oMrk6c7EPEVs6iSUyB7yQoWrtJccJLULBW2CRQ5IHp5JYK0mA== @@ -3049,16 +3213,16 @@ tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/graphql-tag-pluck@^6.2.6", "@graphql-tools/graphql-tag-pluck@^6.5.1": - version "6.5.1" - resolved "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.5.1.tgz#5fb227dbb1e19f4b037792b50f646f16a2d4c686" - integrity sha512-7qkm82iFmcpb8M6/yRgzjShtW6Qu2OlCSZp8uatA3J0eMl87TxyJoUmL3M3UMMOSundAK8GmoyNVFUrueueV5Q== +"@graphql-tools/graphql-tag-pluck@^7.1.3": + version "7.1.4" + resolved "https://registry.npmjs.org/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-7.1.4.tgz#174b69d40988c3450d310173c5be5af894929c41" + integrity sha512-0V2AY68ip3YmJ9rnIwQGxXsokCeGD9FTQOeSLzpwG74U0VY6bphfaCp5KVGW+W5sGJchTj3HvnmvdmWZnEZWZA== dependencies: - "@babel/parser" "7.12.16" - "@babel/traverse" "7.12.13" - "@babel/types" "7.12.13" - "@graphql-tools/utils" "^7.0.0" - tslib "~2.1.0" + "@babel/parser" "7.16.4" + "@babel/traverse" "7.16.3" + "@babel/types" "7.16.0" + "@graphql-tools/utils" "^8.5.1" + tslib "~2.3.0" "@graphql-tools/import@^6.2.6": version "6.3.1" @@ -3077,7 +3241,7 @@ resolve-from "5.0.0" tslib "~2.3.0" -"@graphql-tools/json-file-loader@^6.0.0", "@graphql-tools/json-file-loader@^6.2.6": +"@graphql-tools/json-file-loader@^6.0.0": version "6.2.6" resolved "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.2.6.tgz#830482cfd3721a0799cbf2fe5b09959d9332739a" integrity sha512-CnfwBSY5926zyb6fkDBHnlTblHnHI4hoBALFYXnrg0Ev4yWU8B04DZl/pBRUc459VNgO2x8/mxGIZj2hPJG1EA== @@ -3085,7 +3249,7 @@ "@graphql-tools/utils" "^7.0.0" tslib "~2.0.1" -"@graphql-tools/json-file-loader@^7.3.2": +"@graphql-tools/json-file-loader@^7.1.2", "@graphql-tools/json-file-loader@^7.3.2": version "7.3.3" resolved "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-7.3.3.tgz#45cfde77b9dc4ab6c21575305ae537d2814d237f" integrity sha512-CN2Qk9rt+Gepa3rb3X/mpxYA5MIYLwZBPj2Njw6lbZ6AaxG+O1ArDCL5ACoiWiBimn1FCOM778uhRM9znd0b3Q== @@ -3095,7 +3259,7 @@ tslib "~2.3.0" unixify "^1.0.0" -"@graphql-tools/load@^6.0.0", "@graphql-tools/load@^6.2.8": +"@graphql-tools/load@^6.0.0": version "6.2.8" resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-6.2.8.tgz#16900fb6e75e1d075cad8f7ea439b334feb0b96a" integrity sha512-JpbyXOXd8fJXdBh2ta0Q4w8ia6uK5FHzrTNmcvYBvflFuWly2LDTk2abbSl81zKkzswQMEd2UIYghXELRg8eTA== @@ -3110,6 +3274,16 @@ unixify "1.0.0" valid-url "1.0.9" +"@graphql-tools/load@^7.3.0": + version "7.5.1" + resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-7.5.1.tgz#8c7f846d2185ddc1d44fdfbf1ed9cb678f69e40b" + integrity sha512-j9XcLYZPZdl/TzzqA83qveJmwcCxgGizt5L1+C1/Z68brTEmQHLdQCOR3Ma3ewESJt6DU05kSTu2raKaunkjRg== + dependencies: + "@graphql-tools/schema" "8.3.1" + "@graphql-tools/utils" "^8.6.0" + p-limit "3.1.0" + tslib "~2.3.0" + "@graphql-tools/load@^7.4.1": version "7.4.1" resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-7.4.1.tgz#aa572fcef11d6028097b6ef39c13fa9d62e5a441" @@ -3120,7 +3294,7 @@ p-limit "3.1.0" tslib "~2.3.0" -"@graphql-tools/merge@^6.0.0", "@graphql-tools/merge@^6.2.12", "@graphql-tools/merge@^6.2.14": +"@graphql-tools/merge@^6.0.0", "@graphql-tools/merge@^6.2.12": version "6.2.14" resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.2.14.tgz#694e2a2785ba47558e5665687feddd2935e9d94e" integrity sha512-RWT4Td0ROJai2eR66NHejgf8UwnXJqZxXgDWDI+7hua5vNA2OW8Mf9K1Wav1ZkjWnuRp4ztNtkZGie5ISw55ow== @@ -3144,22 +3318,21 @@ dependencies: tslib "~2.0.1" -"@graphql-tools/prisma-loader@^6.3.0": - version "6.3.0" - resolved "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-6.3.0.tgz#c907e17751ff2b26e7c2bc75d0913ebf03f970da" - integrity sha512-9V3W/kzsFBmUQqOsd96V4a4k7Didz66yh/IK89B1/rrvy9rYj+ULjEqR73x9BYZ+ww9FV8yP8LasWAJwWaqqJQ== +"@graphql-tools/prisma-loader@^7.0.6": + version "7.1.1" + resolved "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-7.1.1.tgz#2a769919c97a3f7f7807668d3155c47999b0965c" + integrity sha512-9hVpG3BNsXAYMLPlZhSHubk6qBmiHLo/UlU0ldL100sMpqI46iBaHNhTNXZCSdd81hT+4HNqaDXNFqyKJ22OGQ== dependencies: - "@graphql-tools/url-loader" "^6.8.2" - "@graphql-tools/utils" "^7.0.0" - "@types/http-proxy-agent" "^2.0.2" + "@graphql-tools/url-loader" "^7.4.2" + "@graphql-tools/utils" "^8.5.1" "@types/js-yaml" "^4.0.0" "@types/json-stable-stringify" "^1.0.32" "@types/jsonwebtoken" "^8.5.0" chalk "^4.1.0" debug "^4.3.1" - dotenv "^8.2.0" + dotenv "^10.0.0" graphql-request "^3.3.0" - http-proxy-agent "^4.0.1" + http-proxy-agent "^5.0.0" https-proxy-agent "^5.0.0" isomorphic-fetch "^3.0.0" js-yaml "^4.0.0" @@ -3168,19 +3341,19 @@ lodash "^4.17.20" replaceall "^0.1.6" scuid "^1.1.0" - tslib "~2.1.0" + tslib "~2.3.0" yaml-ast-parser "^0.0.43" -"@graphql-tools/relay-operation-optimizer@^6.3.0": - version "6.3.0" - resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.3.0.tgz#f8c7f6c8aa4a9cf50ab151fbc5db4f4282a79532" - integrity sha512-Or3UgRvkY9Fq1AAx7q38oPqFmTepLz7kp6wDHKyR0ceG7AvHv5En22R12mAeISInbhff4Rpwgf6cE8zHRu6bCw== +"@graphql-tools/relay-operation-optimizer@^6.3.7": + version "6.4.1" + resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.4.1.tgz#28572444e2c00850c889a84472f3cc7405dc1ad8" + integrity sha512-2b9D5L+31sIBnvmcmIW5tfvNUV+nJFtbHpUyarTRDmFT6EZ2cXo4WZMm9XJcHQD/Z5qvMXfPHxzQ3/JUs4xI+w== dependencies: - "@graphql-tools/utils" "^7.1.0" - relay-compiler "10.1.0" - tslib "~2.0.1" + "@graphql-tools/utils" "^8.5.1" + relay-compiler "12.0.0" + tslib "~2.3.0" -"@graphql-tools/schema@8.3.1", "@graphql-tools/schema@^8.3.1": +"@graphql-tools/schema@8.3.1", "@graphql-tools/schema@^8.1.2", "@graphql-tools/schema@^8.3.1": version "8.3.1" resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.1.tgz#1ee9da494d2da457643b3c93502b94c3c4b68c74" integrity sha512-3R0AJFe715p4GwF067G5i0KCr/XIdvSfDLvTLEiTDQ8V/hwbOHEKHKWlEBHGRQwkG5lwFQlW1aOn7VnlPERnWQ== @@ -3199,7 +3372,7 @@ tslib "~2.2.0" value-or-promise "1.0.6" -"@graphql-tools/url-loader@^6.0.0", "@graphql-tools/url-loader@^6.10.1", "@graphql-tools/url-loader@^6.8.2": +"@graphql-tools/url-loader@^6.0.0": version "6.10.1" resolved "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.10.1.tgz#dc741e4299e0e7ddf435eba50a1f713b3e763b33" integrity sha512-DSDrbhQIv7fheQ60pfDpGD256ixUQIR6Hhf9Z5bRjVkXOCvO5XrkwoWLiU7iHL81GB1r0Ba31bf+sl+D4nyyfw== @@ -3224,6 +3397,31 @@ valid-url "1.0.9" ws "7.4.5" +"@graphql-tools/url-loader@^7.0.11": + version "7.7.0" + resolved "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.7.0.tgz#504f0030c75b61bca4ac07da49e8cd872c316972" + integrity sha512-mBBb+aJqI4E0MVEzyfi76Pi/G6lGxGTVt/tP1YtKJly7UnonNoWOtDusdL3zIVAGhGgLsNrLbGhLDbwSd6TV6A== + dependencies: + "@graphql-tools/delegate" "^8.4.1" + "@graphql-tools/utils" "^8.5.1" + "@graphql-tools/wrap" "^8.3.1" + "@n1ru4l/graphql-live-query" "^0.9.0" + "@types/websocket" "^1.0.4" + "@types/ws" "^8.0.0" + cross-undici-fetch "^0.1.4" + dset "^3.1.0" + extract-files "^11.0.0" + graphql-sse "^1.0.1" + graphql-ws "^5.4.1" + isomorphic-ws "^4.0.1" + meros "^1.1.4" + subscriptions-transport-ws "^0.11.0" + sync-fetch "^0.3.1" + tslib "^2.3.0" + valid-url "^1.0.9" + value-or-promise "^1.0.11" + ws "^8.3.0" + "@graphql-tools/url-loader@^7.4.2": version "7.5.3" resolved "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-7.5.3.tgz#a594be40e3bc68d22f76746356e7f0b8117b7137" @@ -3256,7 +3454,7 @@ dependencies: tslib "~2.3.0" -"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.0", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.5.0", "@graphql-tools/utils@^7.7.0", "@graphql-tools/utils@^7.7.1", "@graphql-tools/utils@^7.8.1", "@graphql-tools/utils@^7.9.0", "@graphql-tools/utils@^7.9.1": +"@graphql-tools/utils@^7.0.0", "@graphql-tools/utils@^7.1.2", "@graphql-tools/utils@^7.5.0", "@graphql-tools/utils@^7.7.0", "@graphql-tools/utils@^7.7.1", "@graphql-tools/utils@^7.8.1", "@graphql-tools/utils@^7.9.0": version "7.10.0" resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.10.0.tgz#07a4cb5d1bec1ff1dc1d47a935919ee6abd38699" integrity sha512-d334r6bo9mxdSqZW6zWboEnnOOFRrAPVQJ7LkU8/6grglrbcu6WhwCLzHb90E94JI3TD3ricC3YGbUqIi9Xg0w== @@ -3265,6 +3463,13 @@ camel-case "4.1.2" tslib "~2.2.0" +"@graphql-tools/utils@^8.1.1", "@graphql-tools/utils@^8.3.0", "@graphql-tools/utils@^8.5.2", "@graphql-tools/utils@^8.6.0": + version "8.6.0" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.0.tgz#f424256a1f3b87d1dcf6f9f675739b2d3627be33" + integrity sha512-rnk+RHaOCeWnfekeQGRh5ycXK1ZAI7Nm0pbeLjA3SiysTdqhWyxNCp5ON4Mvtlid84OY/KB253fQq/2rotznCA== + dependencies: + tslib "~2.3.0" + "@graphql-tools/wrap@^7.0.4": version "7.0.8" resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-7.0.8.tgz#ad41e487135ca3ea1ae0ea04bb3f596177fb4f50" @@ -4587,7 +4792,7 @@ outvariant "^1.2.0" strict-event-emitter "^0.2.0" -"@n1ru4l/graphql-live-query@0.9.0": +"@n1ru4l/graphql-live-query@0.9.0", "@n1ru4l/graphql-live-query@^0.9.0": version "0.9.0" resolved "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" integrity sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg== @@ -6726,6 +6931,11 @@ resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@trendyol-js/openstack-swift-sdk@^0.0.5": version "0.0.5" resolved "https://registry.npmjs.org/@trendyol-js/openstack-swift-sdk/-/openstack-swift-sdk-0.0.5.tgz#65be3c42b8dbafc57f2f2a46c327e2ad51e5a70e" @@ -7284,13 +7494,6 @@ resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.1.tgz#e81ad28a60bee0328c6d2384e029aec626f1ae67" integrity sha512-e+2rjEwK6KDaNOm5Aa9wNGgyS9oSZU/4pfSMMPYNOfjvFI0WVXm29+ITRFr6aKDvvKo7uU1jV68MW4ScsfDi7Q== -"@types/http-proxy-agent@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1" - integrity sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ== - dependencies: - "@types/node" "*" - "@types/http-proxy-middleware@^0.19.3": version "0.19.3" resolved "https://registry.npmjs.org/@types/http-proxy-middleware/-/http-proxy-middleware-0.19.3.tgz#b2eb96fbc0f9ac7250b5d9c4c53aade049497d03" @@ -8273,7 +8476,7 @@ dependencies: "@types/node" "*" -"@types/websocket@1.0.4": +"@types/websocket@1.0.4", "@types/websocket@^1.0.4": version "1.0.4" resolved "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.4.tgz#1dc497280d8049a5450854dd698ee7e6ea9e60b8" integrity sha512-qn1LkcFEKK8RPp459jkjzsfpbsx36BBt3oC3pITYtkoBw/aVX+EZFa5j3ThCRTNpLFvIMr5dSTD4RaMdilIOpA== @@ -9458,7 +9661,7 @@ array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" -array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.4, array.prototype.flatmap@^1.2.5: +array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.5: version "1.2.5" resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz#908dc82d8a406930fdf38598d51e7411d18d4446" integrity sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA== @@ -9942,10 +10145,10 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-fbjs@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.3.0.tgz#a6024764ea86c8e06a22d794ca8b69534d263541" - integrity sha512-7QTLTCd2gwB2qGoi5epSULMHugSVgpcVt5YAeiFO9ABLrutDQzKfGwzxgZHLpugq8qMdg/DhRZDZ5CLKxBkEbw== +babel-preset-fbjs@^3.4.0: + version "3.4.0" + resolved "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz#38a14e5a7a3b285a3f3a86552d650dca5cf6111c" + integrity sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow== dependencies: "@babel/plugin-proposal-class-properties" "^7.0.0" "@babel/plugin-proposal-object-rest-spread" "^7.0.0" @@ -10408,6 +10611,17 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4 node-releases "^2.0.1" picocolors "^1.0.0" +browserslist@^4.17.5: + version "4.19.1" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== + dependencies: + caniuse-lite "^1.0.30001286" + electron-to-chromium "^1.4.17" + escalade "^3.1.1" + node-releases "^2.0.1" + picocolors "^1.0.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -10772,6 +10986,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, can resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== +caniuse-lite@^1.0.30001286: + version "1.0.30001296" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001296.tgz#d99f0f3bee66544800b93d261c4be55a35f1cec8" + integrity sha512-WfrtPEoNSoeATDlf4y3QvkwiELl9GyPLISV5GejTbbQRtQx4LhsXmc9IQ6XCL2d7UxCyEzToEZNMeqR79OUw8Q== + canvas@^2.6.1: version "2.8.0" resolved "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz#f99ca7f25e6e26686661ffa4fec1239bbef74461" @@ -11464,7 +11683,12 @@ common-ancestor-path@^1.0.1: resolved "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== -common-tags@1.8.0, common-tags@^1.8.0: +common-tags@1.8.2: + version "1.8.2" + resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" + integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== + +common-tags@^1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" integrity sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw== @@ -11985,13 +12209,6 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6: - version "3.0.6" - resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" - integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== - dependencies: - node-fetch "2.6.1" - cross-fetch@3.1.4, cross-fetch@^3.0.4, cross-fetch@^3.0.6, cross-fetch@^3.1.3, cross-fetch@^3.1.4: version "3.1.4" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" @@ -12028,6 +12245,16 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +cross-undici-fetch@^0.0.20: + version "0.0.20" + resolved "https://registry.npmjs.org/cross-undici-fetch/-/cross-undici-fetch-0.0.20.tgz#6b7c5ac82a3601edd439f37275ac0319d77a120a" + integrity sha512-5d3WBC4VRHpFndECK9bx4TngXrw0OUXdhX561Ty1ZoqMASz9uf55BblhTC1CO6GhMWnvk9SOqYEXQliq6D2P4A== + dependencies: + abort-controller "^3.0.0" + form-data "^4.0.0" + node-fetch "^2.6.5" + undici "^4.9.3" + cross-undici-fetch@^0.0.26: version "0.0.26" resolved "https://registry.npmjs.org/cross-undici-fetch/-/cross-undici-fetch-0.0.26.tgz#29d93d56609f4d2334f9d5333d23ef7a242842a7" @@ -12038,6 +12265,17 @@ cross-undici-fetch@^0.0.26: node-fetch "^2.6.5" undici "^4.9.3" +cross-undici-fetch@^0.1.4: + version "0.1.13" + resolved "https://registry.npmjs.org/cross-undici-fetch/-/cross-undici-fetch-0.1.13.tgz#807d17ce5c524c21bc0a6486e97ecccb901c6529" + integrity sha512-nF+g932BrKPoK0RZQKRA9S2IKXeveGPJlaUWXyUEGjjSpAdxBhEHDrMDbiksP2iSNe8O5vn1bN3tTvrd6+yFSg== + dependencies: + abort-controller "^3.0.0" + form-data-encoder "^1.7.1" + formdata-node "^4.3.1" + node-fetch "^2.6.5" + undici "^4.9.3" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -13229,12 +13467,17 @@ dotenv-webpack@^1.8.0: dependencies: dotenv-defaults "^1.0.2" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + dotenv@^6.2.0: version "6.2.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w== -dotenv@^8.0.0, dotenv@^8.2.0: +dotenv@^8.0.0: version "8.2.0" resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== @@ -13345,6 +13588,11 @@ electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.896: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ== +electron-to-chromium@^1.4.17: + version "1.4.35" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.35.tgz#69aabb73d7030733e71c1e970ec16f5ceefbaea4" + integrity sha512-wzTOMh6HGFWeALMI3bif0mzgRrVGyP1BdFRx7IvWukFrSC5QVQELENuy+Fm2dCrAdQH9T3nuqr07n94nPDFBWA== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -14461,7 +14709,7 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-files@11.0.0: +extract-files@11.0.0, extract-files@^11.0.0: version "11.0.0" resolved "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz#b72d428712f787eef1f5193aff8ab5351ca8469a" integrity sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ== @@ -14986,6 +15234,11 @@ form-data-encoder@^1.4.3: resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.6.0.tgz#9dd1f479836c1b1b47201667c68f8daafa800943" integrity sha512-P97AVaOB8hZaniiKK3f46zxQcchQXI8EgBnX+2+719gLv5ZbDSf3J1XtIuAQ8xbGLU4vZYhy7xwhFtK8U5u9Nw== +form-data-encoder@^1.7.1: + version "1.7.1" + resolved "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.1.tgz#ac80660e4f87ee0d3d3c3638b7da8278ddb8ec96" + integrity sha512-EFRDrsMm/kyqbTQocNvRXMLjc7Es2Vk+IQFx/YW7hkUH1eBl4J1fqiP34l74Yt0pFLCNpc06fkbVk00008mzjg== + form-data@4.0.0, form-data@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -15035,6 +15288,14 @@ formdata-node@^4.0.0: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.1" +formdata-node@^4.3.1: + version "4.3.2" + resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.3.2.tgz#0262e94931e36db7239c2b08bdb6aaf18ec47d21" + integrity sha512-k7lYJyzDOSL6h917favP8j1L0/wNyylzU+x+1w4p5haGVHNlP58dbpdJhiCUsDbWsa9HwEtLp89obQgXl2e0qg== + dependencies: + node-domexception "1.0.0" + web-streams-polyfill "4.0.0-beta.1" + formidable@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9" @@ -15762,7 +16023,7 @@ graphlib@^2.1.8: dependencies: lodash "^4.17.15" -graphql-config@^3.0.2, graphql-config@^3.3.0: +graphql-config@^3.0.2: version "3.3.0" resolved "https://registry.npmjs.org/graphql-config/-/graphql-config-3.3.0.tgz#24c3672a427cb67c0c717ca3b9d70e9f0c9e752b" integrity sha512-mSQIsPMssr7QrgqhnjI+CyVH6oQgCrgS6irHsTvwf7RFDRnR2k9kqpQOQgVoOytBSn0DOYryS0w0SAg9xor/Jw== @@ -16440,6 +16701,15 @@ http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: agent-base "6" debug "4" +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + http-proxy-middleware@^1.0.0: version "1.3.1" resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.3.1.tgz#43700d6d9eecb7419bf086a128d0f7205d9eb665" @@ -17184,6 +17454,13 @@ is-glob@4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@4.0.3, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -17198,13 +17475,6 @@ is-glob@^3.0.0, is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" @@ -24639,35 +24909,37 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -relay-compiler@10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.1.0.tgz#fb4672cdbe9b54869a3a79759edd8c2d91609cbe" - integrity sha512-HPqc3N3tNgEgUH5+lTr5lnLbgnsZMt+MRiyS0uAVNhuPY2It0X1ZJG+9qdA3L9IqKFUNwVn6zTO7RArjMZbARQ== +relay-compiler@12.0.0: + version "12.0.0" + resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-12.0.0.tgz#9f292d483fb871976018704138423a96c8a45439" + integrity sha512-SWqeSQZ+AMU/Cr7iZsHi1e78Z7oh00I5SvR092iCJq79aupqJ6Ds+I1Pz/Vzo5uY5PY0jvC4rBJXzlIN5g9boQ== dependencies: - "@babel/core" "^7.0.0" - "@babel/generator" "^7.5.0" - "@babel/parser" "^7.0.0" + "@babel/core" "^7.14.0" + "@babel/generator" "^7.14.0" + "@babel/parser" "^7.14.0" "@babel/runtime" "^7.0.0" - "@babel/traverse" "^7.0.0" + "@babel/traverse" "^7.14.0" "@babel/types" "^7.0.0" - babel-preset-fbjs "^3.3.0" + babel-preset-fbjs "^3.4.0" chalk "^4.0.0" fb-watchman "^2.0.0" fbjs "^3.0.0" glob "^7.1.1" immutable "~3.7.6" + invariant "^2.2.4" nullthrows "^1.1.1" - relay-runtime "10.1.0" + relay-runtime "12.0.0" signedsource "^1.0.0" yargs "^15.3.1" -relay-runtime@10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.1.0.tgz#4753bf36e95e8d862cef33608e3d98b4ed730d16" - integrity sha512-bxznLnQ1ST6APN/cFi7l0FpjbZVchWQjjhj9mAuJBuUqNNCh9uV+UTRhpQF7Q8ycsPp19LHTpVyGhYb0ustuRQ== +relay-runtime@12.0.0: + version "12.0.0" + resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-12.0.0.tgz#1e039282bdb5e0c1b9a7dc7f6b9a09d4f4ff8237" + integrity sha512-QU6JKr1tMsry22DXNy9Whsq5rmvwr3LSZiiWV/9+DFpuTWvp+WFhobWMc8TC4OjKFfNhEZy7mOiqUAn5atQtug== dependencies: "@babel/runtime" "^7.0.0" fbjs "^3.0.0" + invariant "^2.2.4" remark-footnotes@2.0.0: version "2.0.0" @@ -26859,7 +27131,7 @@ sync-fetch@0.3.0: buffer "^5.7.0" node-fetch "^2.6.1" -sync-fetch@0.3.1: +sync-fetch@0.3.1, sync-fetch@^0.3.1: version "0.3.1" resolved "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.3.1.tgz#62aa82c4b4d43afd6906bfd7b5f92056458509f0" integrity sha512-xj5qiCDap/03kpci5a+qc5wSJjc8ZSixgG2EUmH1B8Ea2sfWclQA7eH40hiHPCtkCn6MCk4Wb+dqcXdCy2PP3g== @@ -27570,6 +27842,11 @@ tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== +tslib@^2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tslib@~2.0.1: version "2.0.3" resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" @@ -28439,7 +28716,7 @@ validator@^13.7.0: resolved "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== -value-or-promise@1.0.11: +value-or-promise@1.0.11, value-or-promise@^1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz#3e90299af31dd014fe843fe309cefa7c1d94b140" integrity sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg== @@ -29122,10 +29399,10 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@7.4.5, ws@^7.4.6: - version "7.5.6" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== +ws@7.4.5: + version "7.4.5" + resolved "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" + integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== ws@7.4.6: version "7.4.6" @@ -29142,6 +29419,11 @@ ws@8.3.0: resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== +ws@^7.4.6, ws@^8.3.0: + version "7.5.6" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== + ws@^8.1.0: version "8.2.3" resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" From 05f5bb5b530b49fd4d463a4242b19d67800348bf Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Jan 2022 16:08:01 +0100 Subject: [PATCH 16/52] docs: switch package architecture diagram to .drawio.svg Signed-off-by: Patrik Oldsberg --- .../package-architecture.drawio | 1 - .../package-architecture.drawio.svg | 746 ++++++++++++++++++ .../package-architecture.png | Bin 153035 -> 0 bytes docs/overview/architecture-overview.md | 8 +- 4 files changed, 747 insertions(+), 8 deletions(-) delete mode 100644 docs/assets/architecture-overview/package-architecture.drawio create mode 100644 docs/assets/architecture-overview/package-architecture.drawio.svg delete mode 100644 docs/assets/architecture-overview/package-architecture.png diff --git a/docs/assets/architecture-overview/package-architecture.drawio b/docs/assets/architecture-overview/package-architecture.drawio deleted file mode 100644 index c320c94c69..0000000000 --- a/docs/assets/architecture-overview/package-architecture.drawio +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/docs/assets/architecture-overview/package-architecture.drawio.svg b/docs/assets/architecture-overview/package-architecture.drawio.svg new file mode 100644 index 0000000000..df8c0f8820 --- /dev/null +++ b/docs/assets/architecture-overview/package-architecture.drawio.svg @@ -0,0 +1,746 @@ + + + + + + + + + + + + + + + + + +
+
+
+ app +
+
+
+
+ + app + +
+
+ + + + + + + + + + +
+
+
+ backend +
+
+
+
+ + backend + +
+
+ + + + + + + + + + +
+
+
+ plugin-<plugin-id> +
+
+
+
+ + plugin-<plugin-id> + +
+
+ + + + + + + + + + +
+
+
+ plugin-<plugin-id>-backend +
+
+
+
+ + plugin-<plugin-id>-backend + +
+
+ + + + + + Backend Libraries + + + + + + +
+
+
+ @backstage/backend-common +
+
+
+
+ + @backstage/backend-common + +
+
+ + + + +
+
+
+ @backstage/backend-test-utils +
+
+
+
+ + @backstage/backend-test-utils + +
+
+ + + + +
+
+
+ @backstage/backend-tasks +
+
+
+
+ + @backstage/backend-tasks + +
+
+ + + + + + Common Libraries + + + + + + +
+
+
+ @backstage/catalog-client +
+
+
+
+ + @backstage/catalog-client + +
+
+ + + + +
+
+
+ @backstage/types +
+
+
+
+ + @backstage/types + +
+
+ + + + +
+
+
+ @backstage/config +
+
+
+
+ + @backstage/config + +
+
+ + + + +
+
+
+ @backstage/errors +
+
+
+
+ + @backstage/errors + +
+
+ + + + +
+
+
+ @backstage/catalog-model +
+
+
+
+ + @backstage/catalog-model + +
+
+ + + + +
+
+
+ @backstage/integration +
+
+
+
+ + @backstage/integration + +
+
+ + + + + + Frontend App Core + + + + + + +
+
+
+ @backstage/core-app-api +
+
+
+
+ + @backstage/core-app-api + +
+
+ + + + +
+
+
+ @backstage/app-defaults +
+
+
+
+ + @backstage/app-defaults + +
+
+ + + + + + + + + + + + + +
+
+
+ plugin-<plugin-id>-backend-module-<module-id> +
+
+
+
+ + plugin-<plugin-id>-backend-module-<module-id> + +
+
+ + + + + + + + +
+
+
+ plugin-<plugin-id>-module-<module-id> +
+
+
+
+ + plugin-<plugin-id>-module-<module-id> + +
+
+ + + + + + Common Tooling + + + + + + +
+
+
+ @backstage/cli +
+
+
+
+ + @backstage/cli + +
+
+ + + + + + + + + + External Plugin Libraries + + + + + + +
+
+
+ plugin-<other-plugin-id>-react +
+
+
+
+ + plugin-<other-plugin-id>-react + +
+
+ + + + +
+
+
+ plugin-<other-plugin-id>-common +
+
+
+
+ + plugin-<other-plugin-id>-common + +
+
+ + + + +
+
+
+ plugin-<other-plugin-id>-node +
+
+
+
+ + plugin-<other-plugin-id>-node + +
+
+ + + + + + + + + + + + Plugin Libraries + + + + + + +
+
+
+ plugin-<plugin-id>-react +
+
+
+
+ + plugin-<plugin-id>-react + +
+
+ + + + +
+
+
+ plugin-<plugin-id>-common +
+
+
+
+ + plugin-<plugin-id>-common + +
+
+ + + + +
+
+
+ plugin-<plugin-id>-node +
+
+
+
+ + plugin-<plugin-id>-node + +
+
+ + + + + + + + + Frontend Plugin Core + + + + + + +
+
+
+ @backstage/core-plugin-api +
+
+
+
+ + @backstage/core-plugin-api + +
+
+ + + + +
+
+
+ @backstage/test-utils +
+
+
+
+ + @backstage/test-utils + +
+
+ + + + +
+
+
+ @backstage/dev-utils +
+
+
+
+ + @backstage/dev-utils + +
+
+ + + + + + Frontend Libraries + + + + + + +
+
+
+ @backstage/integration-react +
+
+
+
+ + @backstage/integration-react + +
+
+ + + + +
+
+
+ @backstage/core-components +
+
+
+
+ + @backstage/core-components + +
+
+ + + + +
+
+
+ @backstage/theme +
+
+
+
+ + @backstage/theme + +
+
+ + + + + + + +
+
+
+ Frontend Package +
+
+
+
+ + Frontend Package + +
+
+ + + +
+
+
+ Isomorphic Package +
+
+
+
+ + Isomorphic Package + +
+
+ + + +
+
+
+ Backend Package +
+
+
+
+ + Backend Package + +
+
+ + + + +
+
+
+ CLI Package +
+
+
+
+ + CLI Package + +
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ Compatibility +
+
+
+
+ + Compatibility + +
+
+ + + + +
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/docs/assets/architecture-overview/package-architecture.png b/docs/assets/architecture-overview/package-architecture.png deleted file mode 100644 index dfa48fb13b499f56e0ddb20d268b694e8fc948c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153035 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

rI-t619Ek3-+o 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 sKVGf{!;o*IURL +![Package architecture](../assets/architecture-overview/package-architecture.drawio.svg) ### Overview From 0ae759dad4503fe150680fa0c5a37bb806cefc0a Mon Sep 17 00:00:00 2001 From: Joon Park Date: Wed, 5 Jan 2022 17:36:33 +0000 Subject: [PATCH 17/52] Catalog permission rules (#8766) Add catalog permission rules Signed-off-by: Joon Park --- .changeset/eight-rats-raise.md | 5 + plugins/catalog-backend/api-report.md | 17 +++ plugins/catalog-backend/package.json | 1 + plugins/catalog-backend/src/index.ts | 1 + .../catalog-backend/src/permissions/index.ts | 18 +++ .../rules/createPropertyRule.test.ts | 132 ++++++++++++++++++ .../permissions/rules/createPropertyRule.ts | 40 ++++++ .../permissions/rules/hasAnnotation.test.ts | 81 +++++++++++ .../src/permissions/rules/hasAnnotation.ts | 35 +++++ .../src/permissions/rules/hasLabel.test.ts | 81 +++++++++++ .../src/permissions/rules/hasLabel.ts | 34 +++++ .../src/permissions/rules/hasMetadata.ts | 27 ++++ .../src/permissions/rules/hasSpec.ts | 27 ++++ .../src/permissions/rules/index.ts | 36 +++++ .../permissions/rules/isEntityKind.test.ts | 53 +++++++ .../src/permissions/rules/isEntityKind.ts | 38 +++++ .../permissions/rules/isEntityOwner.test.ts | 90 ++++++++++++ .../src/permissions/rules/isEntityOwner.ts | 46 ++++++ .../catalog-backend/src/permissions/types.ts | 30 ++++ 19 files changed, 792 insertions(+) create mode 100644 .changeset/eight-rats-raise.md create mode 100644 plugins/catalog-backend/src/permissions/index.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasLabel.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasMetadata.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/hasSpec.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/index.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/isEntityKind.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts create mode 100644 plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts create mode 100644 plugins/catalog-backend/src/permissions/types.ts diff --git a/.changeset/eight-rats-raise.md b/.changeset/eight-rats-raise.md new file mode 100644 index 0000000000..7bc2a07f04 --- /dev/null +++ b/.changeset/eight-rats-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add catalog permission rules. diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f773905ca0..17c7ad1d85 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -25,6 +25,7 @@ import { Location as Location_2 } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/catalog-model'; import { Logger as Logger_2 } from 'winston'; import { Organizations } from 'aws-sdk'; +import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ResourceEntityV1alpha1 } from '@backstage/catalog-model'; @@ -303,6 +304,12 @@ export type CatalogEnvironment = { reader: UrlReader; }; +// @public +export type CatalogPermissionRule = PermissionRule< + Entity, + EntitiesSearchFilter +>; + // Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1380,6 +1387,16 @@ export function parseEntityYaml( location: LocationSpec, ): Iterable; +// @public +export const permissionRules: { + hasAnnotation: CatalogPermissionRule; + hasLabel: CatalogPermissionRule; + hasMetadata: CatalogPermissionRule; + hasSpec: CatalogPermissionRule; + isEntityKind: CatalogPermissionRule; + isEntityOwner: CatalogPermissionRule; +}; + // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 01e79314c5..fe27ddb4c7 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -36,6 +36,7 @@ "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.5", "@backstage/integration": "^0.7.0", + "@backstage/plugin-permission-node": "^0.2.3", "@backstage/search-common": "^0.2.1", "@backstage/types": "^0.1.1", "@octokit/graphql": "^4.5.8", diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 7ad888bee4..43bbc19730 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -28,3 +28,4 @@ export * from './util'; export * from './processing'; export * from './providers'; export * from './service'; +export * from './permissions'; diff --git a/plugins/catalog-backend/src/permissions/index.ts b/plugins/catalog-backend/src/permissions/index.ts new file mode 100644 index 0000000000..e7ece1dbc7 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ + +export * from './rules'; +export type { CatalogPermissionRule } from './types'; diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts new file mode 100644 index 0000000000..80c86cf250 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { createPropertyRule } from './createPropertyRule'; + +describe('createPropertyRule', () => { + const { name, description, apply, toQuery } = createPropertyRule('metadata'); + + it('formats the rule name correctly', () => { + expect(name).toBe('HAS_METADATA'); + }); + + it('formats the rule description correctly', () => { + expect(description).toBe( + 'Allow entities which have the specified metadata subfield.', + ); + }); + + describe('apply', () => { + describe('key only', () => { + it('returns false when specified key is not present', () => { + expect( + apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + }, + 'org.name', + ), + ).toBe(false); + }); + + it('returns true when specified key is present', () => { + expect( + apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + org: { + name: 'test-org', + }, + }, + }, + 'org.name', + ), + ).toBe(true); + }); + }); + + describe('key and value', () => { + it('returns false when specified key is not present', () => { + expect( + apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + }, + 'org.name', + 'test-org', + ), + ).toBe(false); + }); + + it('returns false when specified value is not present', () => { + expect( + apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + org: { + name: 'another-org', + }, + }, + }, + 'org.name', + 'test-org', + ), + ).toBe(false); + }); + + it('returns true when specified key and value is present', () => { + expect( + apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + org: { + name: 'test-org', + }, + }, + }, + 'org.name', + 'test-org', + ), + ).toBe(true); + }); + }); + }); + + describe('toQuery', () => { + it('returns an appropriate catalog-backend filter', () => { + expect(toQuery('backstage.io/test-component')).toEqual({ + key: 'metadata.backstage.io/test-component', + }); + }); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts new file mode 100644 index 0000000000..92e53f930f --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; +import { get } from 'lodash'; + +export function createPropertyRule( + propertyType: 'metadata' | 'spec', +): CatalogPermissionRule { + return { + name: `HAS_${propertyType.toUpperCase()}`, + description: `Allow entities which have the specified ${propertyType} subfield.`, + apply: (resource: Entity, key: string, value?: string) => { + const foundValue = get(resource[propertyType], key); + if (value !== undefined) { + return value === foundValue; + } + return !!foundValue; + }, + toQuery: (key: string, value?: string): EntitiesSearchFilter => ({ + key: `${propertyType}.${key}`, + ...(value !== undefined && { values: [value] }), + }), + }; +} diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts new file mode 100644 index 0000000000..1596270907 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { hasAnnotation } from './hasAnnotation'; + +describe('hasAnnotation permission rule', () => { + describe('apply', () => { + it('returns false when specified annotation is not present', () => { + expect( + hasAnnotation.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'other-annotation': 'foo', + }, + }, + }, + 'backstage.io/test-annotation', + ), + ).toEqual(false); + }); + + it('returns false when no annotations are present', () => { + expect( + hasAnnotation.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + }, + 'backstage.io/test-annotation', + ), + ).toEqual(false); + }); + + it('returns true when specified annotation is present', () => { + expect( + hasAnnotation.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + annotations: { + 'other-annotation': 'foo', + 'backstage.io/test-annotation': 'bar', + }, + }, + }, + 'backstage.io/test-annotation', + ), + ).toEqual(true); + }); + }); + + describe('toQuery', () => { + it('returns an appropriate catalog-backend filter', () => { + expect(hasAnnotation.toQuery('backstage.io/test-annotation')).toEqual({ + key: 'metadata.annotations.backstage.io/test-annotation', + }); + }); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts new file mode 100644 index 0000000000..57ded3d779 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; + +/** + * A {@link CatalogPermissionRule} which filters for the presence of an + * annotation on a given entity. + * @public + */ +export const hasAnnotation: CatalogPermissionRule = { + name: 'HAS_ANNOTATION', + description: + 'Allow entities which are annotated with the specified annotation', + apply: (resource: Entity, annotation: string) => + !!resource.metadata.annotations?.hasOwnProperty(annotation), + toQuery: (annotation: string): EntitiesSearchFilter => ({ + key: `metadata.annotations.${annotation}`, + }), +}; diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts new file mode 100644 index 0000000000..a1b9cb5ad0 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { hasLabel } from './hasLabel'; + +describe('hasLabel permission rule', () => { + describe('apply', () => { + it('returns false when specified label is not present', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + somelabel: 'foo', + }, + }, + }, + 'backstage.io/testlabel', + ), + ).toEqual(false); + }); + + it('returns false when no annotations are present', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + }, + }, + 'backstage.io/testlabel', + ), + ).toEqual(false); + }); + + it('returns true when specified annotation is present', () => { + expect( + hasLabel.apply( + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test-component', + labels: { + somelabel: 'foo', + 'backstage.io/testlabel': 'bar', + }, + }, + }, + 'backstage.io/testlabel', + ), + ).toEqual(true); + }); + }); + + describe('toQuery', () => { + it('returns an appropriate catalog-backend filter', () => { + expect(hasLabel.toQuery('backstage.io/testlabel')).toEqual({ + key: 'metadata.labels.backstage.io/testlabel', + }); + }); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts new file mode 100644 index 0000000000..f93c5aeae6 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; + +/** + * A {@link CatalogPermissionRule} which filters for entities with a specified + * label in its metadata. + * @public + */ +export const hasLabel: CatalogPermissionRule = { + name: 'HAS_LABEL', + description: 'Allow entities which have the specified label metadata.', + apply: (resource: Entity, label: string) => + !!resource.metadata.labels?.hasOwnProperty(label), + toQuery: (label: string): EntitiesSearchFilter => ({ + key: `metadata.labels.${label}`, + }), +}; diff --git a/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts new file mode 100644 index 0000000000..fa592feb44 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { createPropertyRule } from './createPropertyRule'; + +/** + * A {@link CatalogPermissionRule} which filters for entities with the specified + * metadata subfield. Also matches on values if value is provided. + * + * The key argument to the `apply` and `toQuery` methods can be nested, such as + * 'field.nestedfield'. + * @public + */ +export const hasMetadata = createPropertyRule('metadata'); diff --git a/plugins/catalog-backend/src/permissions/rules/hasSpec.ts b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts new file mode 100644 index 0000000000..73a7519e1a --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { createPropertyRule } from './createPropertyRule'; + +/** + * A {@link CatalogPermissionRule} which filters for entities with the specified + * spec subfield. Also matches on values if value is provided. + * + * The key argument to the `apply` and `toQuery` methods can be nested, such as + * 'field.nestedfield'. + * @public + */ +export const hasSpec = createPropertyRule('spec'); diff --git a/plugins/catalog-backend/src/permissions/rules/index.ts b/plugins/catalog-backend/src/permissions/rules/index.ts new file mode 100644 index 0000000000..68d1e38715 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { hasAnnotation } from './hasAnnotation'; +import { isEntityKind } from './isEntityKind'; +import { isEntityOwner } from './isEntityOwner'; +import { hasLabel } from './hasLabel'; +import { hasMetadata } from './hasMetadata'; +import { hasSpec } from './hasSpec'; + +/** + * These permission rules can be used to conditionally filter catalog entities + * or describe a user's access to the entities. + * @public + */ +export const permissionRules = { + hasAnnotation, + hasLabel, + hasMetadata, + hasSpec, + isEntityKind, + isEntityOwner, +}; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts new file mode 100644 index 0000000000..e11e102728 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { isEntityKind } from './isEntityKind'; + +describe('isEntityKind', () => { + describe('apply', () => { + it('returns true when entity is the correct kind', () => { + const component: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'some-component', + }, + }; + expect(isEntityKind.apply(component, ['b'])).toBe(true); + }); + + it('returns false when entity is not the correct kind', () => { + const component: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'some-component', + }, + }; + expect(isEntityKind.apply(component, ['c'])).toBe(false); + }); + }); + + describe('toQuery', () => { + it('returns an appropriate catalog-backend filter', () => { + expect(isEntityKind.toQuery(['b'])).toEqual({ + key: 'kind', + values: ['b'], + }); + }); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts new file mode 100644 index 0000000000..64fcf7482d --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; + +/** + * A {@link CatalogPermissionRule} which filters for entities with a specified + * kind. + * @public + */ +export const isEntityKind: CatalogPermissionRule = { + name: 'IS_ENTITY_KIND', + description: 'Allow entities with the specified kind', + apply(resource: Entity, kinds: string[]) { + const resourceKind = resource.kind.toLocaleLowerCase('en-US'); + return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); + }, + toQuery(kinds: string[]): EntitiesSearchFilter { + return { + key: 'kind', + values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), + }; + }, +}; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts new file mode 100644 index 0000000000..282d2558ef --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { isEntityOwner } from './isEntityOwner'; + +describe('isEntityOwner', () => { + describe('apply', () => { + it('returns true when entity is owned by the given user', () => { + const component: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'some-component', + }, + relations: [ + { + type: 'ownedBy', + target: { + kind: 'user', + namespace: 'default', + name: 'spiderman', + }, + }, + ], + }; + expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( + true, + ); + }); + + it('returns false when entity is not owned by the given user', () => { + const component: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'some-component', + }, + relations: [ + { + type: 'ownedBy', + target: { + kind: 'user', + namespace: 'default', + name: 'green-goblin', + }, + }, + ], + }; + expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( + false, + ); + }); + + it('returns false when entity does not have an owner', () => { + const component: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'some-component', + }, + }; + expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( + false, + ); + }); + }); + + describe('toQuery', () => { + it('returns an appropriate catalog-backend filter', () => { + expect(isEntityOwner.toQuery(['user:default/spiderman'])).toEqual({ + key: 'relations.ownedBy', + values: ['user:default/spiderman'], + }); + }); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts new file mode 100644 index 0000000000..e89159e6b4 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { + Entity, + RELATION_OWNED_BY, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; + +/** + * A {@link CatalogPermissionRule} which filters for entities with a specified + * owner. + * @public + */ +export const isEntityOwner: CatalogPermissionRule = { + name: 'IS_ENTITY_OWNER', + description: 'Allow entities owned by the current user', + apply: (resource: Entity, claims: string[]) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === RELATION_OWNED_BY) + .some(relation => claims.includes(stringifyEntityRef(relation.target))); + }, + toQuery: (claims: string[]): EntitiesSearchFilter => ({ + key: 'relations.ownedBy', + values: claims, + }), +}; diff --git a/plugins/catalog-backend/src/permissions/types.ts b/plugins/catalog-backend/src/permissions/types.ts new file mode 100644 index 0000000000..cc2b8f4d45 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { PermissionRule } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '../catalog/types'; + +/** + * A conditional rule that can be used to filter catalog entities for an + * authorization request. See + * {@link @backstage/plugin-permission-node#PermissionRule} for more details. + * + * @public + */ +export type CatalogPermissionRule = PermissionRule< + Entity, + EntitiesSearchFilter +>; From 730d01ab1ad8b0107a37d0113aeb7b27c141b6f1 Mon Sep 17 00:00:00 2001 From: Joon Park Date: Wed, 5 Jan 2022 18:08:09 +0000 Subject: [PATCH 18/52] Add apply-conditions endpoint for evaluating conditional permissions (#8675) Add apply-conditions endpoint for evaluating conditional permissions in catalog backend. Signed-off-by: Joon Park --- .changeset/silver-falcons-nail.md | 5 ++ plugins/catalog-backend/api-report.md | 3 + plugins/catalog-backend/package.json | 2 + .../src/service/NextCatalogBuilder.ts | 16 +++++ .../src/service/NextRouter.test.ts | 72 +++++++++++++++++++ .../catalog-backend/src/service/NextRouter.ts | 32 +++++++++ 6 files changed, 130 insertions(+) create mode 100644 .changeset/silver-falcons-nail.md diff --git a/.changeset/silver-falcons-nail.md b/.changeset/silver-falcons-nail.md new file mode 100644 index 0000000000..970bbd7fee --- /dev/null +++ b/.changeset/silver-falcons-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add apply-conditions endpoint for evaluating conditional permissions in catalog backend. diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 17c7ad1d85..8536dafe76 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -1309,6 +1309,7 @@ export class NextCatalogBuilder { addEntityPolicy(...policies: EntityPolicy[]): NextCatalogBuilder; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder; + addPermissionRules(...permissionRules: CatalogPermissionRule[]): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder; build(): Promise<{ @@ -1356,6 +1357,8 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) + permissionRules?: CatalogPermissionRule[]; + // (undocumented) refreshService?: RefreshService; } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index fe27ddb4c7..8472c33369 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -32,6 +32,7 @@ "dependencies": { "@backstage/backend-common": "^0.10.1", "@backstage/catalog-client": "^0.5.3", + "@backstage/plugin-catalog-common": "^0.1.0", "@backstage/catalog-model": "^0.9.8", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.5", @@ -65,6 +66,7 @@ "devDependencies": { "@backstage/backend-test-utils": "^0.1.12", "@backstage/cli": "^0.10.4", + "@backstage/plugin-permission-common": "^0.3.0", "@backstage/test-utils": "^0.2.1", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index 1618ef8d7b..d0c01c3e71 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -82,6 +82,8 @@ import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; +import { CatalogPermissionRule } from '../permissions/types'; +import { permissionRules as catalogPermissionRules } from '../permissions/rules'; export type CatalogEnvironment = { logger: Logger; @@ -125,6 +127,7 @@ export class NextCatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; + private permissionRules: CatalogPermissionRule[]; constructor(env: CatalogEnvironment) { this.env = env; @@ -136,6 +139,7 @@ export class NextCatalogBuilder { this.processors = []; this.processorsReplace = false; this.parser = undefined; + this.permissionRules = Object.values(catalogPermissionRules); } /** @@ -317,6 +321,17 @@ export class NextCatalogBuilder { return this; } + /** + * Adds additional permission rules. Permission rules are used to evaluate + * catalog resources against queries. See + * {@link @backstage/plugin-permission-node#PermissionRule}. + * + * @param permissionRules - Additional permission rules + */ + addPermissionRules(...permissionRules: CatalogPermissionRule[]) { + this.permissionRules.push(...permissionRules); + } + /** * Wires up and returns all of the component parts of the catalog */ @@ -393,6 +408,7 @@ export class NextCatalogBuilder { refreshService, logger, config, + permissionRules: this.permissionRules, }); await connectEntityProviders(processingDatabase, entityProviders); diff --git a/plugins/catalog-backend/src/service/NextRouter.test.ts b/plugins/catalog-backend/src/service/NextRouter.test.ts index 6bc4481425..c31de07718 100644 --- a/plugins/catalog-backend/src/service/NextRouter.test.ts +++ b/plugins/catalog-backend/src/service/NextRouter.test.ts @@ -24,6 +24,7 @@ import { EntitiesCatalog } from '../catalog'; import { LocationService, RefreshService } from './types'; import { basicEntityFilter } from './request'; import { createNextRouter } from './NextRouter'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; describe('createNextRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked; @@ -51,6 +52,7 @@ describe('createNextRouter readonly disabled', () => { logger: getVoidLogger(), refreshService, config: new ConfigReader(undefined), + permissionRules: [], }); app = express().use(router); }); @@ -336,6 +338,7 @@ describe('createNextRouter readonly enabled', () => { readonly: true, }, }), + permissionRules: [], }); app = express().use(router); }); @@ -429,3 +432,72 @@ describe('createNextRouter readonly enabled', () => { }); }); }); + +describe('NextRouter permissioning', () => { + let entitiesCatalog: jest.Mocked; + let locationService: jest.Mocked; + let app: express.Express; + let refreshService: RefreshService; + + const fakeRule = { + name: 'FAKE_RULE', + description: 'fake rule', + apply: () => true, + toQuery: () => ({ key: '', values: [] }), + }; + + beforeAll(async () => { + entitiesCatalog = { + entities: jest.fn(), + removeEntityByUid: jest.fn(), + batchAddOrUpdateEntities: jest.fn(), + entityAncestry: jest.fn(), + }; + locationService = { + getLocation: jest.fn(), + createLocation: jest.fn(), + listLocations: jest.fn(), + deleteLocation: jest.fn(), + }; + refreshService = { refresh: jest.fn() }; + const router = await createNextRouter({ + entitiesCatalog, + locationService, + logger: getVoidLogger(), + refreshService, + config: new ConfigReader(undefined), + permissionRules: [fakeRule], + }); + app = express().use(router); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('accepts and evaluates conditions at the apply-conditions endpoint', async () => { + const spideySense: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { + name: 'spidey-sense', + }, + }; + entitiesCatalog.entities.mockResolvedValueOnce({ + entities: [spideySense], + pageInfo: { hasNextPage: false }, + }); + + const requestBody = { + resourceType: 'catalog-entity', + resourceRef: 'component:default/spidey-sense', + conditions: { rule: 'FAKE_RULE', params: ['user:default/spiderman'] }, + }; + const response = await request(app) + .post('/.well-known/backstage/permissions/apply-conditions') + .send(requestBody); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ result: AuthorizeResult.ALLOW }); + }); +}); diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 6352f96c59..059f937dd7 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -17,17 +17,22 @@ import { errorHandler } from '@backstage/backend-common'; import { analyzeLocationSchema, + Entity, locationSpecSchema, + parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import yn from 'yn'; import { EntitiesCatalog } from '../catalog'; import { LocationAnalyzer } from '../ingestion/types'; +import { CatalogPermissionRule } from '../permissions/types'; import { basicEntityFilter, parseEntityFilterParams, @@ -44,6 +49,7 @@ export interface NextRouterOptions { refreshService?: RefreshService; logger: Logger; config: Config; + permissionRules?: CatalogPermissionRule[]; } export async function createNextRouter( @@ -56,6 +62,7 @@ export async function createNextRouter( refreshService, config, logger, + permissionRules, } = options; const router = Router(); @@ -77,6 +84,14 @@ export async function createNextRouter( if (entitiesCatalog) { router + .use( + createPermissionIntegrationRouter({ + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + getResource: resourceRef => + getEntityResource(resourceRef, entitiesCatalog), + rules: permissionRules ?? [], + }), + ) .get('/entities', async (req, res) => { const { entities, pageInfo } = await entitiesCatalog.entities({ filter: parseEntityFilterParams(req.query), @@ -182,3 +197,20 @@ export async function createNextRouter( router.use(errorHandler()); return router; } + +async function getEntityResource( + resourceRef: string, + entitiesCatalog: EntitiesCatalog, +): Promise { + const parsed = parseEntityRef(resourceRef); + + const { entities } = await entitiesCatalog.entities({ + filter: basicEntityFilter({ + kind: parsed.kind, + 'metadata.namespace': parsed.namespace, + 'metadata.name': parsed.name, + }), + }); + + return entities[0]; +} From 1deb4c20eb3c5a79aed26156e7b4ffb8194ef071 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jan 2022 04:11:49 +0000 Subject: [PATCH 19/52] build(deps): bump react-dev-utils from 12.0.0-next.47 to 12.0.0 Bumps [react-dev-utils](https://github.com/facebook/create-react-app/tree/HEAD/packages/react-dev-utils) from 12.0.0-next.47 to 12.0.0. - [Release notes](https://github.com/facebook/create-react-app/releases) - [Changelog](https://github.com/facebook/create-react-app/blob/main/CHANGELOG-1.x.md) - [Commits](https://github.com/facebook/create-react-app/commits/react-dev-utils@12.0.0/packages/react-dev-utils) --- updated-dependencies: - dependency-name: react-dev-utils dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 157 ++++++++++++++++++++++++------------------------------ 1 file changed, 71 insertions(+), 86 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c71db2235..becdb1f29c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10600,18 +10600,7 @@ browserslist@4.14.2: escalade "^3.0.2" node-releases "^1.1.61" -browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.5, browserslist@^4.16.6: - version "4.18.1" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f" - integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ== - dependencies: - caniuse-lite "^1.0.30001280" - electron-to-chromium "^1.3.896" - escalade "^3.1.1" - node-releases "^2.0.1" - picocolors "^1.0.0" - -browserslist@^4.17.5: +browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.18.1: version "4.19.1" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== @@ -10981,7 +10970,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001280: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125: version "1.0.30001282" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz#38c781ee0a90ccfe1fe7fefd00e43f5ffdcb96fd" integrity sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg== @@ -11052,7 +11041,7 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.2, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4.1.1, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: +chalk@4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== @@ -11079,6 +11068,14 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + change-case-all@1.0.14: version "1.0.14" resolved "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1" @@ -13521,6 +13518,11 @@ duplexer@^0.1.1, duplexer@~0.1.1: resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E= +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -13583,7 +13585,7 @@ elastic-builder@^2.16.0: lodash.isstring "^4.0.1" lodash.omit "^4.5.0" -electron-to-chromium@^1.3.564, electron-to-chromium@^1.3.896: +electron-to-chromium@^1.3.564: version "1.3.900" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.900.tgz#5be2c5818a2a012c511b4b43e87b6ab7a296d4f5" integrity sha512-SuXbQD8D4EjsaBaJJxySHbC+zq8JrFfxtb4GIr4E9n1BcROyMcRrJCYQNpJ9N+Wjf5mFp7Wp0OHykd14JNEzzQ== @@ -14988,10 +14990,10 @@ filesize@6.1.0: resolved "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00" integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg== -filesize@^6.1.0: - version "6.4.0" - resolved "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz#914f50471dd66fdca3cefe628bd0cde4ef769bcd" - integrity sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ== +filesize@^8.0.6: + version "8.0.6" + resolved "https://registry.npmjs.org/filesize/-/filesize-8.0.6.tgz#5f0c27aa1b507fa7d9f72c912a774ca6a44111b1" + integrity sha512-sHvRqTiwdmcuzqet7iVwsbwF6UrV3wIgDf2SHNdY1Hgl8PC45HZg/0xtdw6U2izIV4lccnrY9ftl6wZFNdjYMg== fill-range@^4.0.0: version "4.0.0" @@ -15203,29 +15205,10 @@ fork-ts-checker-webpack-plugin@4.1.6, fork-ts-checker-webpack-plugin@^4.0.5, for tapable "^1.0.0" worker-rpc "^0.1.0" -fork-ts-checker-webpack-plugin@^6.0.4: - version "6.3.1" - resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.3.1.tgz#938535f844cdddf6796211e5761da27341b9fcd3" - integrity sha512-uxqlKTEeSJ5/JRr0zaCiw2U+kOV8F4/MhCnnRf6vbxj4ZU3Or0DLl/0CNtXro7uLWDssnuR7wUN7fU9w1I0REA== - dependencies: - "@babel/code-frame" "^7.8.3" - "@types/json-schema" "^7.0.5" - chalk "^4.1.0" - chokidar "^3.4.2" - cosmiconfig "^6.0.0" - deepmerge "^4.2.2" - fs-extra "^9.0.0" - glob "^7.1.6" - memfs "^3.1.2" - minimatch "^3.0.4" - schema-utils "2.7.0" - semver "^7.3.2" - tapable "^1.0.0" - -fork-ts-checker-webpack-plugin@^6.0.5: - version "6.4.2" - resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.4.2.tgz#6d307fb4072ce4abe4d56a89c8ef060066f33d81" - integrity sha512-EqtzzRdx2mldr0KEydSN9jaNrf419gMpwkloumG6K/S7jtJc9Fl7wMJ+y+o7DLLGMMU/kouYr06agTD/YkxzIQ== +fork-ts-checker-webpack-plugin@^6.0.4, fork-ts-checker-webpack-plugin@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.0.tgz#0282b335fa495a97e167f69018f566ea7d2a2b5e" + integrity sha512-cS178Y+xxtIjEUorcHddKS7yCMlrDPV31mt47blKKRfMd70Kxu5xruAFE2o9sDY6wVC5deuob/u/alD04YYHnw== dependencies: "@babel/code-frame" "^7.8.3" "@types/json-schema" "^7.0.5" @@ -16227,7 +16210,7 @@ gud@^1.0.0: resolved "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gzip-size@5.1.1, gzip-size@^5.1.1: +gzip-size@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" integrity sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA== @@ -16235,6 +16218,13 @@ gzip-size@5.1.1, gzip-size@^5.1.1: duplexer "^0.1.1" pify "^4.0.1" +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + handle-thing@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" @@ -16914,7 +16904,7 @@ immer@8.0.1: resolved "https://registry.npmjs.org/immer/-/immer-8.0.1.tgz#9c73db683e2b3975c424fb0572af5889877ae656" integrity sha512-aqXhGP7//Gui2+UrEtvxZxSquQVXTpZ7KDxfCcKAF3Vysvw0CViVaW9RZ1j1xlIYqaaaipBoqdqeibkc18PNvA== -immer@^9.0.1, immer@^9.0.6: +immer@^9.0.1, immer@^9.0.7: version "9.0.7" resolved "https://registry.npmjs.org/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== @@ -19542,6 +19532,11 @@ loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz#bcecc51a7898bee7473d4bc6b845b23af8304d4f" + integrity sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ== + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -21987,7 +21982,7 @@ open@^7.0.2, open@^7.0.3: is-docker "^2.0.0" is-wsl "^2.1.1" -open@^8.0.0, open@^8.0.9: +open@^8.0.0, open@^8.0.9, open@^8.4.0: version "8.4.0" resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== @@ -23639,7 +23634,7 @@ prompts@2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prompts@^2.0.1, prompts@^2.4.0: +prompts@^2.0.1, prompts@^2.4.0, prompts@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== @@ -24084,33 +24079,33 @@ react-dev-utils@^11.0.3: text-table "0.2.0" react-dev-utils@^12.0.0-next.47: - version "12.0.0-next.47" - resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0-next.47.tgz#e55c31a05eb30cfd69ca516e8b87d61724e880fb" - integrity sha512-PsE71vP15TZMmp/RZKOJC4fYD5Pvt0+wCoyG3QHclto0d4FyIJI78xGRICOOThZFROqgXYlZP6ddmeybm+jO4w== + version "12.0.0" + resolved "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" + integrity sha512-xBQkitdxozPxt1YZ9O1097EJiVpwHr9FoAuEVURCKV0Av8NBERovJauzP7bo1ThvuhZ4shsQ1AJiu4vQpoT1AQ== dependencies: - "@babel/code-frame" "^7.10.4" + "@babel/code-frame" "^7.16.0" address "^1.1.2" - browserslist "^4.16.5" - chalk "^2.4.2" + browserslist "^4.18.1" + chalk "^4.1.2" cross-spawn "^7.0.3" detect-port-alt "^1.1.6" - escape-string-regexp "^2.0.0" - filesize "^6.1.0" - find-up "^4.1.0" - fork-ts-checker-webpack-plugin "^6.0.5" + escape-string-regexp "^4.0.0" + filesize "^8.0.6" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.5.0" global-modules "^2.0.0" - globby "^11.0.1" - gzip-size "^5.1.1" - immer "^9.0.6" + globby "^11.0.4" + gzip-size "^6.0.0" + immer "^9.0.7" is-root "^2.1.0" - loader-utils "^2.0.0" - open "^7.0.2" + loader-utils "^3.2.0" + open "^8.4.0" pkg-up "^3.1.0" - prompts "^2.4.0" - react-error-overlay "7.0.0-next.54+1465357b" + prompts "^2.4.2" + react-error-overlay "^6.0.10" recursive-readdir "^2.2.2" - shell-quote "^1.7.2" - strip-ansi "^6.0.0" + shell-quote "^1.7.3" + strip-ansi "^6.0.1" text-table "^0.2.0" react-docgen-typescript@^2.0.0: @@ -24162,15 +24157,10 @@ react-error-boundary@^3.1.0: dependencies: "@babel/runtime" "^7.12.5" -react-error-overlay@7.0.0-next.54+1465357b: - version "7.0.0-next.54" - resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-7.0.0-next.54.tgz#c1eb5ab86aee15e9552e6d97897b08f2bd06d140" - integrity sha512-b96CiTnZahXPDNH9MKplvt5+jD+BkxDw7q5R3jnkUXze/ux1pLv32BBZmlj0OfCUeMqyz4sAmF+0ccJGVMlpXw== - -react-error-overlay@^6.0.9: - version "6.0.9" - resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" - integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== +react-error-overlay@^6.0.10, react-error-overlay@^6.0.9: + version "6.0.10" + resolved "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" + integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0: version "3.2.0" @@ -25906,7 +25896,7 @@ shell-quote@1.7.2: resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== -shell-quote@^1.7.2: +shell-quote@^1.7.3: version "1.7.3" resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123" integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw== @@ -26747,7 +26737,7 @@ strip-ansi@5.2.0, strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@6.0.0, strip-ansi@^6.0.0: +strip-ansi@6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== @@ -26768,7 +26758,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -29429,21 +29419,16 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d" - integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw== +ws@8.3.0, ws@^7.4.6, ws@^8.3.0: + version "7.5.6" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== "ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.2.3, ws@^7.3.1: version "7.5.0" resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== -ws@^7.4.6, ws@^8.3.0: - version "7.5.6" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - ws@^8.1.0: version "8.2.3" resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" From 6011a234f0f201c0c5118490d759cf9c5e76ab5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jan 2022 04:16:56 +0000 Subject: [PATCH 20/52] build(deps-dev): bump @types/jenkins from 0.23.2 to 0.23.3 Bumps [@types/jenkins](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jenkins) from 0.23.2 to 0.23.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jenkins) --- updated-dependencies: - dependency-name: "@types/jenkins" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c71db2235..435c422577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7569,9 +7569,9 @@ "@types/istanbul-lib-report" "*" "@types/jenkins@^0.23.1": - version "0.23.2" - resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.2.tgz#96736a2be4904efdfe7fe2650569479fd77dc48e" - integrity sha512-BELmIZ6brxwGFqBHfLjLTjYRWAUqcT1d2BydH1CcRTZEjCYw3DRVfZkXU7BVlyIsKXm2ZMIKVkEjKKtVDvPiXA== + version "0.23.3" + resolved "https://registry.npmjs.org/@types/jenkins/-/jenkins-0.23.3.tgz#bca00a395147c8d448cfba6bb36241a56862f820" + integrity sha512-/uNktGT4d/NNIBZTur4cO4YU00fQ+iEe0Arm0l5v9Ixv6zwZR9u0V7XSuhWbWJkzKsh8aIeK66QxYqJvYF4yzA== dependencies: "@types/node" "*" @@ -29429,21 +29429,16 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d" - integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw== +ws@8.3.0, ws@^7.4.6, ws@^8.3.0: + version "7.5.6" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== "ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.2.3, ws@^7.3.1: version "7.5.0" resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== -ws@^7.4.6, ws@^8.3.0: - version "7.5.6" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - ws@^8.1.0: version "8.2.3" resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" From 6c3c70197faace6dbd18819a7d074797c62dbcfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jan 2022 04:44:12 +0000 Subject: [PATCH 21/52] build(deps): bump mime-types from 2.1.32 to 2.1.34 Bumps [mime-types](https://github.com/jshttp/mime-types) from 2.1.32 to 2.1.34. - [Release notes](https://github.com/jshttp/mime-types/releases) - [Changelog](https://github.com/jshttp/mime-types/blob/master/HISTORY.md) - [Commits](https://github.com/jshttp/mime-types/compare/2.1.32...2.1.34) --- updated-dependencies: - dependency-name: mime-types dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c71db2235..3b7d582cd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20739,15 +20739,10 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.49.0: - version "1.49.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" - integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== - -"mime-db@>= 1.43.0 < 2": - version "1.48.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" - integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== +mime-db@1.51.0, "mime-db@>= 1.43.0 < 2": + version "1.51.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== mime-db@~1.33.0: version "1.33.0" @@ -20762,11 +20757,11 @@ mime-types@2.1.18: mime-db "~1.33.0" mime-types@^2.0.8, mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: - version "2.1.32" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" - integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== + version "2.1.34" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== dependencies: - mime-db "1.49.0" + mime-db "1.51.0" mime@1.6.0: version "1.6.0" @@ -29429,21 +29424,16 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.3.0: - version "8.3.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d" - integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw== +ws@8.3.0, ws@^7.4.6, ws@^8.3.0: + version "7.5.6" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" + integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== "ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.2.3, ws@^7.3.1: version "7.5.0" resolved "https://registry.npmjs.org/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== -ws@^7.4.6, ws@^8.3.0: - version "7.5.6" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" - integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== - ws@^8.1.0: version "8.2.3" resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba" From 48580d0fbb4b1106ba0b725b14b7241361011f85 Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 6 Jan 2022 14:16:51 +0000 Subject: [PATCH 22/52] add missing react key Signed-off-by: goenning --- .changeset/warm-moles-battle.md | 5 +++++ .../src/components/BooleanCheck/BooleanCheck.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/warm-moles-battle.md diff --git a/.changeset/warm-moles-battle.md b/.changeset/warm-moles-battle.md new file mode 100644 index 0000000000..3d203ea1d3 --- /dev/null +++ b/.changeset/warm-moles-battle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +fix React warning because of missing `key` prop diff --git a/plugins/tech-insights/src/components/BooleanCheck/BooleanCheck.tsx b/plugins/tech-insights/src/components/BooleanCheck/BooleanCheck.tsx index e38e0aa7b3..a37a5ee0b4 100644 --- a/plugins/tech-insights/src/components/BooleanCheck/BooleanCheck.tsx +++ b/plugins/tech-insights/src/components/BooleanCheck/BooleanCheck.tsx @@ -41,7 +41,7 @@ export const BooleanCheck = ({ checkResult }: Prop) => { return ( {checkResult.map((check, index) => ( - + Date: Thu, 6 Jan 2022 16:40:13 +0100 Subject: [PATCH 23/52] todo-backend: fix API report warnings Signed-off-by: Patrik Oldsberg --- .changeset/dry-boats-tap.md | 5 ++ plugins/todo-backend/api-report.md | 86 +++++++++++-------- .../src/lib/TodoReader/TodoScmReader.ts | 11 ++- .../src/lib/TodoReader/createTodoParser.ts | 2 + .../todo-backend/src/lib/TodoReader/index.ts | 5 ++ .../todo-backend/src/lib/TodoReader/types.ts | 11 ++- .../src/service/TodoReaderService.ts | 6 +- plugins/todo-backend/src/service/index.ts | 2 + plugins/todo-backend/src/service/router.ts | 2 + plugins/todo-backend/src/service/types.ts | 9 +- 10 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 .changeset/dry-boats-tap.md diff --git a/.changeset/dry-boats-tap.md b/.changeset/dry-boats-tap.md new file mode 100644 index 0000000000..c69c4360b5 --- /dev/null +++ b/.changeset/dry-boats-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-todo-backend': patch +--- + +Properly exported all referenced types diff --git a/plugins/todo-backend/api-report.md b/plugins/todo-backend/api-report.md index 00e243d1a2..ac8b2ce957 100644 --- a/plugins/todo-backend/api-report.md +++ b/plugins/todo-backend/api-report.md @@ -11,38 +11,27 @@ import { Logger as Logger_2 } from 'winston'; import { ScmIntegrations } from '@backstage/integration'; import { UrlReader } from '@backstage/backend-common'; -// Warning: (ae-forgotten-export) The symbol "RouterOptions" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function createRouter(options: RouterOptions): Promise; -// Warning: (ae-forgotten-export) The symbol "TodoParserOptions" needs to be exported by the entry point index.d.ts -// Warning: (ae-forgotten-export) The symbol "TodoParser" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "createTodoParser" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function createTodoParser(options?: TodoParserOptions): TodoParser; -// Warning: (ae-missing-release-tag) "ListTodosRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type ListTodosRequest = { entity?: EntityName; offset?: number; limit?: number; orderBy?: { - field: Fields; + field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath'; direction: 'asc' | 'desc'; }; filters?: { - field: Fields; + field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath'; value: string; }[]; }; -// Warning: (ae-missing-release-tag) "ListTodosResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type ListTodosResponse = { items: TodoItem[]; @@ -51,22 +40,22 @@ export type ListTodosResponse = { limit: number; }; -// Warning: (ae-missing-release-tag) "ReadTodosOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type ReadTodosOptions = { url: string; }; -// Warning: (ae-missing-release-tag) "ReadTodosResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type ReadTodosResult = { items: TodoItem[]; }; -// Warning: (ae-missing-release-tag) "TodoItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public (undocumented) +export interface RouterOptions { + // (undocumented) + todoService: TodoService; +} + // @public (undocumented) export type TodoItem = { text: string; @@ -77,19 +66,36 @@ export type TodoItem = { repoFilePath?: string; }; -// Warning: (ae-missing-release-tag) "TodoReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public (undocumented) +export type TodoParser = (ctx: TodoParserContext) => TodoParserResult[]; + +// @public (undocumented) +export type TodoParserContext = { + content: string; + path: string; +}; + +// @public (undocumented) +export type TodoParserOptions = { + additionalTags?: string[]; +}; + +// @public (undocumented) +export type TodoParserResult = { + text: string; + tag: string; + author?: string; + lineNumber: number; +}; + // @public (undocumented) export interface TodoReader { readTodos(options: ReadTodosOptions): Promise; } -// Warning: (ae-missing-release-tag) "TodoReaderService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class TodoReaderService implements TodoService { - // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts - constructor(options: Options_2); + constructor(options: TodoReaderServiceOptions); // (undocumented) listTodos( req: ListTodosRequest, @@ -99,24 +105,34 @@ export class TodoReaderService implements TodoService { ): Promise; } -// Warning: (ae-missing-release-tag) "TodoScmReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public (undocumented) +export type TodoReaderServiceOptions = { + todoReader: TodoReader; + catalogClient: CatalogApi; + maxPageSize?: number; + defaultPageSize?: number; +}; + // @public (undocumented) export class TodoScmReader implements TodoReader { - constructor(options: Options); - // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts - // + constructor(options: TodoScmReaderOptions); // (undocumented) static fromConfig( config: Config, - options: Omit, + options: Omit, ): TodoScmReader; // (undocumented) readTodos(options: ReadTodosOptions): Promise; } -// Warning: (ae-missing-release-tag) "TodoService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public (undocumented) +export type TodoScmReaderOptions = { + logger: Logger_2; + reader: UrlReader; + integrations: ScmIntegrations; + parser?: TodoParser; +}; + // @public (undocumented) export interface TodoService { // (undocumented) @@ -127,8 +143,4 @@ export interface TodoService { }, ): Promise; } - -// Warnings were encountered during analysis: -// -// src/service/types.d.ts:9:9 - (ae-forgotten-export) The symbol "Fields" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts index 804dd1146d..19890a8d23 100644 --- a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts +++ b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.ts @@ -41,7 +41,8 @@ const excludedExtensions = [ ]; const MAX_FILE_SIZE = 200000; -type Options = { +/** @public */ +export type TodoScmReaderOptions = { logger: Logger; reader: UrlReader; integrations: ScmIntegrations; @@ -53,6 +54,7 @@ type CacheItem = { result: ReadTodosResult; }; +/** @public */ export class TodoScmReader implements TodoReader { private readonly logger: Logger; private readonly reader: UrlReader; @@ -62,14 +64,17 @@ export class TodoScmReader implements TodoReader { private readonly cache = new Map(); private readonly inFlightReads = new Map>(); - static fromConfig(config: Config, options: Omit) { + static fromConfig( + config: Config, + options: Omit, + ) { return new TodoScmReader({ ...options, integrations: ScmIntegrations.fromConfig(config), }); } - constructor(options: Options) { + constructor(options: TodoScmReaderOptions) { this.logger = options.logger; this.reader = options.reader; this.parser = options.parser ?? createTodoParser(); diff --git a/plugins/todo-backend/src/lib/TodoReader/createTodoParser.ts b/plugins/todo-backend/src/lib/TodoReader/createTodoParser.ts index 2835f0e8d5..720efe316b 100644 --- a/plugins/todo-backend/src/lib/TodoReader/createTodoParser.ts +++ b/plugins/todo-backend/src/lib/TodoReader/createTodoParser.ts @@ -18,11 +18,13 @@ import { extname } from 'path'; import { parse } from 'leasot'; import { TodoParser } from './types'; +/** @public */ export type TodoParserOptions = { /** Custom tags to support in addition to TODO and FIXME */ additionalTags?: string[]; }; +/** @public */ export function createTodoParser(options: TodoParserOptions = {}): TodoParser { return ({ content, path }) => { try { diff --git a/plugins/todo-backend/src/lib/TodoReader/index.ts b/plugins/todo-backend/src/lib/TodoReader/index.ts index aa7c36a09b..d0537861da 100644 --- a/plugins/todo-backend/src/lib/TodoReader/index.ts +++ b/plugins/todo-backend/src/lib/TodoReader/index.ts @@ -15,10 +15,15 @@ */ export { TodoScmReader } from './TodoScmReader'; +export type { TodoScmReaderOptions } from './TodoScmReader'; export { createTodoParser } from './createTodoParser'; +export type { TodoParserOptions } from './createTodoParser'; export type { TodoItem, TodoReader, ReadTodosOptions, ReadTodosResult, + TodoParser, + TodoParserContext, + TodoParserResult, } from './types'; diff --git a/plugins/todo-backend/src/lib/TodoReader/types.ts b/plugins/todo-backend/src/lib/TodoReader/types.ts index 14a622b482..628a78f0c2 100644 --- a/plugins/todo-backend/src/lib/TodoReader/types.ts +++ b/plugins/todo-backend/src/lib/TodoReader/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +/** @public */ export type TodoItem = { /** The contents of the TODO comment */ text: string; @@ -34,6 +35,7 @@ export type TodoItem = { repoFilePath?: string; }; +/** @public */ export type ReadTodosOptions = { /** * Base URLs defining the root at which to search for TODOs @@ -41,6 +43,7 @@ export type ReadTodosOptions = { url: string; }; +/** @public */ export type ReadTodosResult = { /** * TODO items found at the given locations @@ -48,6 +51,7 @@ export type ReadTodosResult = { items: TodoItem[]; }; +/** @public */ export interface TodoReader { /** * Searches for TODO items in code at a given location @@ -55,16 +59,19 @@ export interface TodoReader { readTodos(options: ReadTodosOptions): Promise; } -type TodoParserContext = { +/** @public */ +export type TodoParserContext = { content: string; path: string; }; -type TodoParserResult = { +/** @public */ +export type TodoParserResult = { text: string; tag: string; author?: string; lineNumber: number; }; +/** @public */ export type TodoParser = (ctx: TodoParserContext) => TodoParserResult[]; diff --git a/plugins/todo-backend/src/service/TodoReaderService.ts b/plugins/todo-backend/src/service/TodoReaderService.ts index 6623f8090f..54957d89c4 100644 --- a/plugins/todo-backend/src/service/TodoReaderService.ts +++ b/plugins/todo-backend/src/service/TodoReaderService.ts @@ -29,7 +29,8 @@ import { ListTodosRequest, ListTodosResponse, TodoService } from './types'; const DEFAULT_DEFAULT_PAGE_SIZE = 10; const DEFAULT_MAX_PAGE_SIZE = 50; -type Options = { +/** @public */ +export type TodoReaderServiceOptions = { todoReader: TodoReader; catalogClient: CatalogApi; maxPageSize?: number; @@ -43,13 +44,14 @@ function wildcardRegex(str: string): RegExp { return new RegExp(`^${pattern}$`, 'i'); } +/** @public */ export class TodoReaderService implements TodoService { private readonly todoReader: TodoReader; private readonly catalogClient: CatalogApi; private readonly maxPageSize: number; private readonly defaultPageSize: number; - constructor(options: Options) { + constructor(options: TodoReaderServiceOptions) { this.todoReader = options.todoReader; this.catalogClient = options.catalogClient; this.maxPageSize = options.maxPageSize ?? DEFAULT_MAX_PAGE_SIZE; diff --git a/plugins/todo-backend/src/service/index.ts b/plugins/todo-backend/src/service/index.ts index 893d615405..380d95dbe1 100644 --- a/plugins/todo-backend/src/service/index.ts +++ b/plugins/todo-backend/src/service/index.ts @@ -15,5 +15,7 @@ */ export { createRouter } from './router'; +export type { RouterOptions } from './router'; export type { TodoService, ListTodosRequest, ListTodosResponse } from './types'; export { TodoReaderService } from './TodoReaderService'; +export type { TodoReaderServiceOptions } from './TodoReaderService'; diff --git a/plugins/todo-backend/src/service/router.ts b/plugins/todo-backend/src/service/router.ts index 91de76e358..203e552161 100644 --- a/plugins/todo-backend/src/service/router.ts +++ b/plugins/todo-backend/src/service/router.ts @@ -28,10 +28,12 @@ const TODO_FIELDS = [ 'repoFilePath', ] as const; +/** @public */ export interface RouterOptions { todoService: TodoService; } +/** @public */ export async function createRouter( options: RouterOptions, ): Promise { diff --git a/plugins/todo-backend/src/service/types.ts b/plugins/todo-backend/src/service/types.ts index f29e0d9908..90ee6bc913 100644 --- a/plugins/todo-backend/src/service/types.ts +++ b/plugins/todo-backend/src/service/types.ts @@ -17,23 +17,23 @@ import { EntityName } from '@backstage/catalog-model'; import { TodoItem } from '../lib'; -type Fields = 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath'; - +/** @public */ export type ListTodosRequest = { entity?: EntityName; offset?: number; limit?: number; orderBy?: { - field: Fields; + field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath'; direction: 'asc' | 'desc'; }; filters?: { - field: Fields; + field: 'text' | 'tag' | 'author' | 'viewUrl' | 'repoFilePath'; /** Value to filter by, with '*' used as wildcard */ value: string; }[]; }; +/** @public */ export type ListTodosResponse = { items: TodoItem[]; totalCount: number; @@ -41,6 +41,7 @@ export type ListTodosResponse = { limit: number; }; +/** @public */ export interface TodoService { listTodos( req: ListTodosRequest, From 1628ca3f499e9fa42a5c416601248f5064a628bf Mon Sep 17 00:00:00 2001 From: Colton Padden Date: Thu, 6 Jan 2022 15:18:35 -0500 Subject: [PATCH 24/52] dynamically set techdocs sidebar position based on backstage sidebar pinned state Signed-off-by: Colton Padden --- .changeset/techdocs-light-onions-cover.md | 5 +++++ plugins/techdocs/src/reader/components/Reader.tsx | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/techdocs-light-onions-cover.md diff --git a/.changeset/techdocs-light-onions-cover.md b/.changeset/techdocs-light-onions-cover.md new file mode 100644 index 0000000000..55c0873575 --- /dev/null +++ b/.changeset/techdocs-light-onions-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix an issue where the TechDocs sidebar is hidden when the Backstage sidebar is pinned at smaller screen sizes diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index eb8f4d39e9..57d2786af5 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -31,6 +31,7 @@ import { EntityName } from '@backstage/catalog-model'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { BackstageTheme } from '@backstage/theme'; +import { SidebarPinStateContext } from '@backstage/core-components'; import { techdocsStorageApiRef } from '../../api'; @@ -144,6 +145,9 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { const [sidebars, setSidebars] = useState(); const [dom, setDom] = useState(null); + // sidebar pinned status to be used in computing CSS style injections + const { isPinned } = useContext(SidebarPinStateContext); + const updateSidebarPosition = useCallback(() => { if (!dom || !sidebars) return; // set sidebar height so they don't initially render in wrong position @@ -252,7 +256,9 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { transition: none !important } .md-sidebar--secondary { display: none; } - .md-sidebar--primary { left: 72px; width: 10rem } + .md-sidebar--primary { left: ${ + isPinned ? '242px' : '72px' + }; width: 10rem } .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); } .md-content__inner { font-size: 0.9rem } .md-footer { @@ -348,6 +354,7 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { theme.palette.success.main, theme.palette.text.primary, theme.typography.fontFamily, + isPinned, ], ); From 7f6a7fd274bef9a5270417cc70439675698175f5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 6 Jan 2022 18:22:56 +0100 Subject: [PATCH 25/52] docs: add bitbucket sign-in resolver docs Signed-off-by: Patrik Oldsberg --- docs/auth/bitbucket/provider.md | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/auth/bitbucket/provider.md b/docs/auth/bitbucket/provider.md index 63dfb815e0..ee87c2d4e6 100644 --- a/docs/auth/bitbucket/provider.md +++ b/docs/auth/bitbucket/provider.md @@ -50,3 +50,41 @@ The Bitbucket provider is a structure with two configuration keys: To add the provider to the frontend, add the `bitbucketAuthApi` reference and `SignInPage` component as shown in [Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). + +## Using Bitbucket for sign-in + +In order to use the Bitbucket provider for sign-in, you must configure it with a +`signIn.resolver`. See the +[Sign-In Resolver documentation](../identity-resolver.md) for more details on +how this is done. Note that for the Bitbucket provider, you'll want to use +`bitbucket` as the provider ID, and `createBitbucketProvider` for the provider +factory. + +The `@backstage/plugin-auth-backend` plugin also comes with two built-in +resolves that can be used if desired. The first one is the +`bitbucketUsernameSignInResolver`, which identifies users by matching their +Bitbucket username to `bitbucket.org/username` annotations of `User` entities in +the catalog. Note that you must populate your catalog with matching entities or +users will not be able to sign in. + +The second resolver is the `bitbucketUsernameSignInResolver`, which works the +same way, but uses the Bitbucket user ID instead, and matches on the +`bitbucket.org/user-id` annotation. + +The following is an example of how to use one of the built-in resolvers: + +```ts +import { + createBitbucketProvider, + bitbucketUsernameSignInResolver, +} from '@backstage/plugin-auth-backend'; + +// ... + providerFactories: { + bitbucket: createBitbucketProvider({ + signIn: { + resolver: bitbucketUsernameSignInResolver, + }, + }), + }, +``` From 1256bd408a10e9a70746c54f234b3bb2c2ffad3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 04:08:42 +0000 Subject: [PATCH 26/52] build(deps-dev): bump pgtools from 0.3.0 to 0.3.2 Bumps [pgtools](https://github.com/olalonde/pgtools) from 0.3.0 to 0.3.2. - [Release notes](https://github.com/olalonde/pgtools/releases) - [Changelog](https://github.com/olalonde/pgtools/blob/master/CHANGELOG.md) - [Commits](https://github.com/olalonde/pgtools/compare/v0.3.0...v0.3.2) --- updated-dependencies: - dependency-name: pgtools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- yarn.lock | 104 ++++++++++-------------------------------------------- 1 file changed, 18 insertions(+), 86 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5095c88cb0..ca60f2a656 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10664,11 +10664,6 @@ buffer-indexof@^1.0.0: resolved "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g== -buffer-writer@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz#22a936901e3029afcd7547eb4487ceb697a3bf08" - integrity sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg= - buffer-writer@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" @@ -15547,11 +15542,6 @@ generic-names@^2.0.1: dependencies: loader-utils "^1.1.0" -generic-pool@2.4.3: - version "2.4.3" - resolved "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff" - integrity sha1-eAw29p360FpaBF3Te+etyhGk9v8= - gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -18564,7 +18554,7 @@ js-levenshtein@^1.1.6: resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== -js-string-escape@1.0.1, js-string-escape@^1.0.1: +js-string-escape@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= @@ -21791,11 +21781,6 @@ oauth@0.9.x: resolved "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha1-vR/vr2hslrdUda7VGWQS/2DPucE= -object-assign@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" - integrity sha1-ejs9DpgGPUP0wD8uiubNUahog6A= - object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -22288,11 +22273,6 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" -packet-reader@0.3.1: - version "0.3.1" - resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz#cd62e60af8d7fea8a705ec4ff990871c46871f27" - integrity sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc= - packet-reader@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" @@ -22788,17 +22768,12 @@ performance-now@^2.1.0: resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pg-connection-string@0.1.3, pg-connection-string@^0.1.3: - version "0.1.3" - resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7" - integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc= - pg-connection-string@2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10" integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ== -pg-connection-string@^2.3.0, pg-connection-string@^2.5.0: +pg-connection-string@^2.3.0, pg-connection-string@^2.4.0, pg-connection-string@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34" integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ== @@ -22808,35 +22783,16 @@ pg-int8@1.0.1: resolved "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== -pg-pool@1.*: - version "1.8.0" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-1.8.0.tgz#f7ec73824c37a03f076f51bfdf70e340147c4f37" - integrity sha1-9+xzgkw3oD8Hb1G/33DjQBR8Tzc= - dependencies: - generic-pool "2.4.3" - object-assign "4.1.0" - -pg-pool@^3.3.0: - version "3.3.0" - resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz#12d5c7f65ea18a6e99ca9811bd18129071e562fc" - integrity sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg== +pg-pool@^3.4.1: + version "3.4.1" + resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz#0e71ce2c67b442a5e862a9c182172c37eda71e9c" + integrity sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ== pg-protocol@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== -pg-types@1.*: - version "1.13.0" - resolved "https://registry.npmjs.org/pg-types/-/pg-types-1.13.0.tgz#75f490b8a8abf75f1386ef5ec4455ecf6b345c63" - integrity sha512-lfKli0Gkl/+za/+b6lzENajczwZHc7D5kiUCZfgm914jipD2kIOIvEkAhZ8GrW3/TUoP9w8FHjwpPObBye5KQQ== - dependencies: - pg-int8 "1.0.1" - postgres-array "~1.0.0" - postgres-bytea "~1.0.0" - postgres-date "~1.0.0" - postgres-interval "^1.1.0" - pg-types@^2.1.0: version "2.2.0" resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" @@ -22848,34 +22804,20 @@ pg-types@^2.1.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^6.1.0: - version "6.4.2" - resolved "https://registry.npmjs.org/pg/-/pg-6.4.2.tgz#c364011060eac7a507a2ae063eb857ece910e27f" - integrity sha1-w2QBEGDqx6UHoq4GPrhX7OkQ4n8= - dependencies: - buffer-writer "1.0.1" - js-string-escape "1.0.1" - packet-reader "0.3.1" - pg-connection-string "0.1.3" - pg-pool "1.*" - pg-types "1.*" - pgpass "1.*" - semver "4.3.2" - -pg@^8.3.0: - version "8.6.0" - resolved "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz#e222296b0b079b280cce106ea991703335487db2" - integrity sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ== +pg@^8.3.0, pg@^8.4.0: + version "8.7.1" + resolved "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz#9ea9d1ec225980c36f94e181d009ab9f4ce4c471" + integrity sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" pg-connection-string "^2.5.0" - pg-pool "^3.3.0" + pg-pool "^3.4.1" pg-protocol "^1.5.0" pg-types "^2.1.0" pgpass "1.x" -pgpass@1.*, pgpass@1.x: +pgpass@1.x: version "1.0.2" resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= @@ -22883,13 +22825,13 @@ pgpass@1.*, pgpass@1.x: split "^1.0.0" pgtools@^0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/pgtools/-/pgtools-0.3.0.tgz#ee7decf4183ada28299c63df71e1e73c95b5bc53" - integrity sha512-8NxDCJ8xJ6hOp9hVNZqxi+TZl7hM1Jc8pQyj8DlAbyaWnk5OsGwf3gB/UyDODdOguiim9QzbzPsslp//apO+Uw== + version "0.3.2" + resolved "https://registry.npmjs.org/pgtools/-/pgtools-0.3.2.tgz#df11d54057c889e27ba891664efda69de1b7a0fe" + integrity sha512-o9iI8CrJohpjt3hgoJuEC18oYrt/iLsc3BYtW6kP/0T7EyQ9T/WlnuzyKcC2GtfutREfXCmwaUcbqPrLw8sjng== dependencies: bluebird "^3.3.5" - pg "^6.1.0" - pg-connection-string "^0.1.3" + pg "^8.4.0" + pg-connection-string "^2.4.0" yargs "^5.0.0" picocolors@^1.0.0: @@ -23404,11 +23346,6 @@ postcss@^8.1.0, postcss@^8.2.15: nanoid "^3.1.23" source-map-js "^0.6.2" -postgres-array@~1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.3.tgz#c561fc3b266b21451fc6555384f4986d78ec80f5" - integrity sha512-5wClXrAP0+78mcsNX3/ithQ5exKvCyK5lr5NEEEeGwwM6NJdQgzIJBVxLvRW+huFpX92F2QnZ5CcokH0VhK2qQ== - postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -23419,7 +23356,7 @@ postgres-bytea@~1.0.0: resolved "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= -postgres-date@~1.0.0, postgres-date@~1.0.4: +postgres-date@~1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz#710b27de5f27d550f6e80b5d34f7ba189213c2ee" integrity sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA== @@ -25661,11 +25598,6 @@ semver-store@^0.3.0: resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@4.3.2: - version "4.3.2" - resolved "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" - integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= - semver@7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" From a46fe68c1cb59ae9e27da1d6fb0e7803e088972d Mon Sep 17 00:00:00 2001 From: David Weber Date: Thu, 23 Dec 2021 23:11:23 +0100 Subject: [PATCH 27/52] Update to mkdocs-techdocs-core==0.2.2 Signed-off-by: David Weber --- docs/features/techdocs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index d79f6bb891..37e135d1dc 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -230,7 +230,7 @@ You can do so by including the following lines in the last step of your ```Dockerfile RUN apt-get update && apt-get install -y python3 python3-pip -RUN pip3 install mkdocs-techdocs-core==0.0.16 +RUN pip3 install mkdocs-techdocs-core==0.2.2 ``` Please be aware that the version requirement could change, you need to check our From da9c59d6e082054700a7c66bb45bd13c431be87a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Jan 2022 13:40:15 +0100 Subject: [PATCH 28/52] auth-backend: moved test-utils dependency to devDeps Signed-off-by: Patrik Oldsberg --- .changeset/short-shoes-fail.md | 5 +++++ plugins/auth-backend/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/short-shoes-fail.md diff --git a/.changeset/short-shoes-fail.md b/.changeset/short-shoes-fail.md new file mode 100644 index 0000000000..0b45d67a32 --- /dev/null +++ b/.changeset/short-shoes-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Removed `@backstage/test-utils` dependency. diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 71dbcf9f02..3afd6cefe3 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -35,7 +35,6 @@ "@backstage/catalog-model": "^0.9.8", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.5", - "@backstage/test-utils": "^0.2.1", "@backstage/types": "^0.1.1", "@google-cloud/firestore": "^4.15.1", "@types/express": "^4.17.6", @@ -75,6 +74,7 @@ }, "devDependencies": { "@backstage/cli": "^0.10.5", + "@backstage/test-utils": "^0.2.1", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", From eebe1df58296d9b1d8b52d3ec8939b2b689711fc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Jan 2022 10:46:50 +0100 Subject: [PATCH 29/52] test-utils: avoid usage of private field syntax Signed-off-by: Patrik Oldsberg --- .../testUtils/apis/ConfigApi/MockConfigApi.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts index 93d975c18a..372c295c02 100644 --- a/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts +++ b/packages/test-utils/src/testUtils/apis/ConfigApi/MockConfigApi.ts @@ -37,75 +37,75 @@ import { ConfigApi } from '@backstage/core-plugin-api'; * ``` */ export class MockConfigApi implements ConfigApi { - readonly #config: ConfigReader; + private readonly config: ConfigReader; // NOTE: not extending in order to avoid inheriting the static `.fromConfigs` constructor(data: JsonObject) { - this.#config = new ConfigReader(data); + this.config = new ConfigReader(data); } /** {@inheritdoc @backstage/config#Config.has} */ has(key: string): boolean { - return this.#config.has(key); + return this.config.has(key); } /** {@inheritdoc @backstage/config#Config.keys} */ keys(): string[] { - return this.#config.keys(); + return this.config.keys(); } /** {@inheritdoc @backstage/config#Config.get} */ get(key?: string): T { - return this.#config.get(key); + return this.config.get(key); } /** {@inheritdoc @backstage/config#Config.getOptional} */ getOptional(key?: string): T | undefined { - return this.#config.getOptional(key); + return this.config.getOptional(key); } /** {@inheritdoc @backstage/config#Config.getConfig} */ getConfig(key: string): Config { - return this.#config.getConfig(key); + return this.config.getConfig(key); } /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */ getOptionalConfig(key: string): Config | undefined { - return this.#config.getOptionalConfig(key); + return this.config.getOptionalConfig(key); } /** {@inheritdoc @backstage/config#Config.getConfigArray} */ getConfigArray(key: string): Config[] { - return this.#config.getConfigArray(key); + return this.config.getConfigArray(key); } /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */ getOptionalConfigArray(key: string): Config[] | undefined { - return this.#config.getOptionalConfigArray(key); + return this.config.getOptionalConfigArray(key); } /** {@inheritdoc @backstage/config#Config.getNumber} */ getNumber(key: string): number { - return this.#config.getNumber(key); + return this.config.getNumber(key); } /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */ getOptionalNumber(key: string): number | undefined { - return this.#config.getOptionalNumber(key); + return this.config.getOptionalNumber(key); } /** {@inheritdoc @backstage/config#Config.getBoolean} */ getBoolean(key: string): boolean { - return this.#config.getBoolean(key); + return this.config.getBoolean(key); } /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */ getOptionalBoolean(key: string): boolean | undefined { - return this.#config.getOptionalBoolean(key); + return this.config.getOptionalBoolean(key); } /** {@inheritdoc @backstage/config#Config.getString} */ getString(key: string): string { - return this.#config.getString(key); + return this.config.getString(key); } /** {@inheritdoc @backstage/config#Config.getOptionalString} */ getOptionalString(key: string): string | undefined { - return this.#config.getOptionalString(key); + return this.config.getOptionalString(key); } /** {@inheritdoc @backstage/config#Config.getStringArray} */ getStringArray(key: string): string[] { - return this.#config.getStringArray(key); + return this.config.getStringArray(key); } /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */ getOptionalStringArray(key: string): string[] | undefined { - return this.#config.getOptionalStringArray(key); + return this.config.getOptionalStringArray(key); } } From 2a5875ce5be71d4addae922663e3b523c0b3e53a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 7 Jan 2022 10:52:44 +0100 Subject: [PATCH 30/52] storybook: add missing dependency declarations Signed-off-by: Patrik Oldsberg --- .github/workflows/chromatic-storybook-test.yml | 8 ++++++++ packages/storybook/package.json | 3 +++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/chromatic-storybook-test.yml b/.github/workflows/chromatic-storybook-test.yml index 4c64db309d..355ad65bee 100644 --- a/.github/workflows/chromatic-storybook-test.yml +++ b/.github/workflows/chromatic-storybook-test.yml @@ -4,6 +4,14 @@ on: paths: - '.github/workflows/chromatic-storybook-test.yml' - 'packages/storybook/**' + - 'packages/config/src/**' + - 'packages/theme/src/**' + - 'packages/types/src/**' + - 'packages/errors/src/**' + - 'packages/version-bridge/src/**' + - 'packages/test-utils/src/**' + - 'packages/core-app-api/src/**' + - 'packages/core-plugin-api/src/**' - 'packages/core-components/src/**' - '**/*.stories.tsx' diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 5df0c06632..a0e9b25d76 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -9,6 +9,9 @@ }, "dependencies": { "@backstage/theme": "^0.2.0", + "@backstage/test-utils": "^0.2.1", + "@backstage/core-app-api": "^0.3.1", + "@backstage/core-plugin-api": "^0.4.1", "react": "^16.13.1", "react-dom": "^16.13.1" }, From 45df287eec039e84722ae838119408d625dbc465 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 7 Jan 2022 15:41:45 +0100 Subject: [PATCH 31/52] Show how to hide locations from search Signed-off-by: Eric Peterson --- docs/features/search/how-to-guides.md | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 4ba44b8ef4..a3c42c682c 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -97,3 +97,37 @@ of the `SearchType` component. ... ``` + +## How to limit what can be searched in the Software Catalog + +The Software Catalog includes a wealth of information about the components, +systems, groups, users, and other aspects of your software ecosystem. However, +you may not always want _every_ aspect to appear when a user searches the +catalog. Examples include: + +- Entities of kind `Location`, which are often not useful to Backstage users. +- Entities of kind `User` or `Group`, if you'd prefer that users and groups be + exposed to search in a different way (or not at all). + +It's possible to write your own [Collator](./concepts.md#collators) to control +exactly what's available to search, (or a [Decorator](./concepts.md#decorators) +to filter things out here and there), but the `DefaultCatalogCollator` that's +provided by `@backstage/plugin-catalog-backend` offers some configuration too! + +```diff +// packages/backend/src/plugins/search.ts + +indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + collator: DefaultCatalogCollator.fromConfig(config, { + discovery, + tokenManager, ++ filter: { ++ kind: ['API', 'Component', 'Domain', 'Group', 'System', 'User'], ++ }, + }), +}); +``` + +As shown above, you can add a catalog entity filter to narrow down what catalog +entities are indexed by the search engine. From cd529c409445c124fc3030f5250ba6db957d336c Mon Sep 17 00:00:00 2001 From: Joon Park Date: Fri, 7 Jan 2022 15:49:26 +0000 Subject: [PATCH 32/52] Integrate permissions with catalog-backend refresh endpoint (#8693) Integration permission framework with refresh in catalog-backend ... through the use of a new AuthorizedRefreshService. Signed-off-by: Joon Park --- .changeset/plenty-eyes-brush.md | 60 ++++++++++++++++ .changeset/three-sheep-sparkle.md | 7 ++ packages/create-app/package.json | 2 + packages/create-app/src/lib/versions.ts | 4 ++ .../packages/backend/package.json.hbs | 2 + .../default-app/packages/backend/src/index.ts | 6 ++ .../default-app/packages/backend/src/types.ts | 2 + plugins/catalog-backend/api-report.md | 3 + plugins/catalog-backend/package.json | 3 +- .../src/legacy/service/CatalogBuilder.test.ts | 24 ++++++- .../service/AuthorizedRefreshService.test.ts | 72 +++++++++++++++++++ .../src/service/AuthorizedRefreshService.ts | 47 ++++++++++++ .../src/service/NextCatalogBuilder.ts | 12 ++-- .../src/service/NextRouter.test.ts | 2 + .../catalog-backend/src/service/NextRouter.ts | 16 ++++- .../src/service/standaloneServer.ts | 10 +++ plugins/catalog-backend/src/service/types.ts | 1 + 17 files changed, 265 insertions(+), 8 deletions(-) create mode 100644 .changeset/plenty-eyes-brush.md create mode 100644 .changeset/three-sheep-sparkle.md create mode 100644 plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts create mode 100644 plugins/catalog-backend/src/service/AuthorizedRefreshService.ts diff --git a/.changeset/plenty-eyes-brush.md b/.changeset/plenty-eyes-brush.md new file mode 100644 index 0000000000..354c216c3c --- /dev/null +++ b/.changeset/plenty-eyes-brush.md @@ -0,0 +1,60 @@ +--- +'@backstage/create-app': patch +--- + +Add permissions to create-app's PluginEnvironment + +`CatalogEnvironment` now has a `permissions` field, which means that a permission client must now be provided as part of `PluginEnvironment`. To apply these changes to an existing app, add the following to the `makeCreateEnv` function in `packages/backend/src/index.ts`: + +```diff + // packages/backend/src/index.ts + ++ import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + + function makeCreateEnv(config: Config) { + ... ++ const permissions = ServerPerimssionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + + root.info(`Created UrlReader ${reader}`); + + return (plugin: string): PluginEnvironment => { + ... + return { + logger, + cache, + database, + config, + reader, + discovery, + tokenManager, + scheduler, ++ permissions, + }; + } + } +``` + +And add a permissions field to the `PluginEnvironment` type in `packages/backend/src/types.ts`: + +```diff + // packages/backend/src/types.ts + ++ import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; + + export type PluginEnvironment = { + ... ++ permissions: PermissionAuthorizer; + }; +``` + +[`@backstage/plugin-permission-common`](https://www.npmjs.com/package/@backstage/plugin-permission-common) and [`@backstage/plugin-permission-node`](https://www.npmjs.com/package/@backstage/plugin-permission-node) will need to be installed as dependencies: + +```diff + // packages/backend/package.json + ++ "@backstage/plugin-permission-common": "...", ++ "@backstage/plugin-permission-node": "...", +``` diff --git a/.changeset/three-sheep-sparkle.md b/.changeset/three-sheep-sparkle.md new file mode 100644 index 0000000000..db6d8148ce --- /dev/null +++ b/.changeset/three-sheep-sparkle.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +In order to integrate the permissions system with the refresh endpoint in catalog-backend, a new AuthorizedRefreshService was created as a thin wrapper around the existing refresh service which performs authorization and handles the case when authorization is denied. In order to instantiate AuthorizedRefreshService, a permission client is required, which was added as a new field to `CatalogEnvironment`. + +The new `permissions` field in `CatalogEnvironment` should already receive the permission client from the `PluginEnvrionment`, so there should be no changes required to the catalog backend setup. See [the create-app changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md) for more details. diff --git a/packages/create-app/package.json b/packages/create-app/package.json index e95dbb3a9d..639e0370d7 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -67,6 +67,8 @@ "@backstage/plugin-explore": "*", "@backstage/plugin-github-actions": "*", "@backstage/plugin-lighthouse": "*", + "@backstage/plugin-permission-common": "*", + "@backstage/plugin-permission-node": "*", "@backstage/plugin-proxy-backend": "*", "@backstage/plugin-rollbar-backend": "*", "@backstage/plugin-scaffolder": "*", diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 3765422923..f89ff7f391 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -57,6 +57,8 @@ import { version as pluginExplore } from '../../../../plugins/explore/package.js import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json'; import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json'; import { version as pluginOrg } from '../../../../plugins/org/package.json'; +import { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json'; +import { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json'; import { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json'; import { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json'; import { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json'; @@ -94,6 +96,8 @@ export const packageVersions = { '@backstage/plugin-github-actions': pluginGithubActions, '@backstage/plugin-lighthouse': pluginLighthouse, '@backstage/plugin-org': pluginOrg, + '@backstage/plugin-permission-common': pluginPermissionCommon, + '@backstage/plugin-permission-node': pluginPermissionNode, '@backstage/plugin-proxy-backend': pluginProxyBackend, '@backstage/plugin-rollbar-backend': pluginRollbarBackend, '@backstage/plugin-scaffolder': pluginScaffolder, diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index a2ee89c546..96dc724a1e 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -23,6 +23,8 @@ "@backstage/plugin-app-backend": "^{{version '@backstage/plugin-app-backend'}}", "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", + "@backstage/plugin-permission-common": "^{{version '@backstage/plugin-permission-common'}}", + "@backstage/plugin-permission-node": "^{{version '@backstage/plugin-permission-node'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 08d21e61f7..70bc66bcdd 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -29,6 +29,7 @@ import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; import search from './plugins/search'; import { PluginEnvironment } from './types'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -38,6 +39,10 @@ function makeCreateEnv(config: Config) { const databaseManager = DatabaseManager.fromConfig(config); const tokenManager = ServerTokenManager.noop(); const taskScheduler = TaskScheduler.fromConfig(config); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); root.info(`Created UrlReader ${reader}`); @@ -55,6 +60,7 @@ function makeCreateEnv(config: Config) { discovery, tokenManager, scheduler, + permissions, }; }; } diff --git a/packages/create-app/templates/default-app/packages/backend/src/types.ts b/packages/create-app/templates/default-app/packages/backend/src/types.ts index c3d0158dc6..0862b0e874 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/types.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/types.ts @@ -8,6 +8,7 @@ import { UrlReader, } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; export type PluginEnvironment = { logger: Logger; @@ -18,4 +19,5 @@ export type PluginEnvironment = { discovery: PluginEndpointDiscovery; tokenManager: TokenManager; scheduler: PluginTaskScheduler; + permissions: PermissionAuthorizer; }; diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 8536dafe76..6d7834001a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -25,6 +25,7 @@ import { Location as Location_2 } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/catalog-model'; import { Logger as Logger_2 } from 'winston'; import { Organizations } from 'aws-sdk'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -302,6 +303,7 @@ export type CatalogEnvironment = { database: PluginDatabaseManager; config: Config; reader: UrlReader; + permissions: PermissionAuthorizer; }; // @public @@ -1493,6 +1495,7 @@ export type RefreshIntervalFunction = () => number; // @public export type RefreshOptions = { entityRef: string; + authorizationToken?: string; }; // @public diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8472c33369..504e86a736 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -32,11 +32,12 @@ "dependencies": { "@backstage/backend-common": "^0.10.1", "@backstage/catalog-client": "^0.5.3", - "@backstage/plugin-catalog-common": "^0.1.0", "@backstage/catalog-model": "^0.9.8", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.5", "@backstage/integration": "^0.7.0", + "@backstage/plugin-catalog-common": "^0.1.0", + "@backstage/plugin-permission-common": "^0.3.0", "@backstage/plugin-permission-node": "^0.2.3", "@backstage/search-common": "^0.2.1", "@backstage/types": "^0.1.1", diff --git a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts index 926aa67635..728d35ea3d 100644 --- a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.test.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import { getVoidLogger, UrlReader } from '@backstage/backend-common'; +import { + getVoidLogger, + PluginEndpointDiscovery, + ServerTokenManager, + UrlReader, +} from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { Knex } from 'knex'; @@ -24,6 +29,7 @@ import { CatalogProcessorParser } from '../../ingestion'; import * as result from '../../ingestion/processors/results'; import { CatalogBuilder } from './CatalogBuilder'; import { CatalogEnvironment } from '../../service'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; const dummyEntity = { apiVersion: 'backstage.io/v1alpha1', @@ -47,11 +53,25 @@ describe('CatalogBuilder', () => { readTree: jest.fn(), search: jest.fn(), }; + const config = new ConfigReader({}); + const mockBaseUrl = 'http://backstage:9191/i-am-a-mock-base'; + const discovery: PluginEndpointDiscovery = { + async getBaseUrl() { + return mockBaseUrl; + }, + async getExternalBaseUrl() { + return mockBaseUrl; + }, + }; const env: CatalogEnvironment = { logger: getVoidLogger(), database: { getClient: async () => db }, - config: new ConfigReader({}), + config, reader, + permissions: ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: ServerTokenManager.noop(), + }), }; beforeEach(async () => { diff --git a/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts b/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts new file mode 100644 index 0000000000..82bedec573 --- /dev/null +++ b/plugins/catalog-backend/src/service/AuthorizedRefreshService.test.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { NotAllowedError } from '@backstage/errors'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { AuthorizedRefreshService } from './AuthorizedRefreshService'; + +describe('AuthorizedRefreshService', () => { + const refreshService = { + refresh: jest.fn(), + }; + const permissionApi = { + authorize: jest.fn(), + }; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('throws AuthorizationError on deny', async () => { + permissionApi.authorize.mockResolvedValueOnce([ + { + result: AuthorizeResult.DENY, + }, + ]); + const authorizedService = new AuthorizedRefreshService( + refreshService, + permissionApi as unknown as ServerPermissionClient, + ); + + await expect(() => + authorizedService.refresh({ + entityRef: 'some entity ref', + authorizationToken: 'some auth token', + }), + ).rejects.toThrowError(NotAllowedError); + }); + + it('calls refresh on allow', async () => { + permissionApi.authorize.mockResolvedValueOnce([ + { + result: AuthorizeResult.ALLOW, + }, + ]); + const authorizedService = new AuthorizedRefreshService( + refreshService, + permissionApi as unknown as ServerPermissionClient, + ); + + const options = { + entityRef: 'some entity ref', + authorizationToken: 'some auth token', + }; + await authorizedService.refresh(options); + + expect(refreshService.refresh).toHaveBeenCalledWith(options); + }); +}); diff --git a/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts b/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts new file mode 100644 index 0000000000..800bdcf1b9 --- /dev/null +++ b/plugins/catalog-backend/src/service/AuthorizedRefreshService.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { NotAllowedError } from '@backstage/errors'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common'; +import { + AuthorizeResult, + PermissionAuthorizer, +} from '@backstage/plugin-permission-common'; +import { RefreshOptions, RefreshService } from './types'; + +export class AuthorizedRefreshService implements RefreshService { + constructor( + private readonly service: RefreshService, + private readonly permissionApi: PermissionAuthorizer, + ) {} + + async refresh(options: RefreshOptions) { + const authorizeResponse = ( + await this.permissionApi.authorize( + [ + { + permission: catalogEntityRefreshPermission, + resourceRef: options.entityRef, + }, + ], + { token: options.authorizationToken }, + ) + )[0]; + if (authorizeResponse.result !== AuthorizeResult.ALLOW) { + throw new NotAllowedError(); + } + await this.service.refresh(options); + } +} diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index d0c01c3e71..1dcb632e64 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -77,6 +77,7 @@ import { } from '../processing/refresh'; import { createNextRouter } from './NextRouter'; import { DefaultRefreshService } from './DefaultRefreshService'; +import { AuthorizedRefreshService } from './AuthorizedRefreshService'; import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; @@ -84,12 +85,14 @@ import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; import { CatalogPermissionRule } from '../permissions/types'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; export type CatalogEnvironment = { logger: Logger; database: PluginDatabaseManager; config: Config; reader: UrlReader; + permissions: PermissionAuthorizer; }; /** @@ -344,7 +347,7 @@ export class NextCatalogBuilder { locationService: LocationService; router: Router; }> { - const { config, database, logger } = this.env; + const { config, database, logger, permissions } = this.env; const policy = this.buildEntityPolicy(); const processors = this.buildProcessors(); @@ -398,9 +401,10 @@ export class NextCatalogBuilder { locationStore, orchestrator, ); - const refreshService = new DefaultRefreshService({ - database: processingDatabase, - }); + const refreshService = new AuthorizedRefreshService( + new DefaultRefreshService({ database: processingDatabase }), + permissions, + ); const router = await createNextRouter({ entitiesCatalog, locationAnalyzer, diff --git a/plugins/catalog-backend/src/service/NextRouter.test.ts b/plugins/catalog-backend/src/service/NextRouter.test.ts index c31de07718..e835c4620a 100644 --- a/plugins/catalog-backend/src/service/NextRouter.test.ts +++ b/plugins/catalog-backend/src/service/NextRouter.test.ts @@ -66,10 +66,12 @@ describe('createNextRouter readonly disabled', () => { const response = await request(app) .post('/refresh') .set('Content-Type', 'application/json') + .set('authorization', 'Bearer someauthtoken') .send({ entityRef: 'Component/default:foo' }); expect(response.status).toBe(200); expect(refreshService.refresh).toHaveBeenCalledWith({ entityRef: 'Component/default:foo', + authorizationToken: 'someauthtoken', }); }); }); diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 059f937dd7..7aae564a45 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -40,7 +40,7 @@ import { parseEntityTransformParams, } from '../service/request'; import { disallowReadonlyMode, validateRequestBody } from '../service/util'; -import { RefreshService, RefreshOptions, LocationService } from './types'; +import { RefreshOptions, LocationService, RefreshService } from './types'; export interface NextRouterOptions { entitiesCatalog?: EntitiesCatalog; @@ -77,6 +77,10 @@ export async function createNextRouter( if (refreshService) { router.post('/refresh', async (req, res) => { const refreshOptions: RefreshOptions = req.body; + refreshOptions.authorizationToken = getBearerToken( + req.header('authorization'), + ); + await refreshService.refresh(refreshOptions); res.status(200).send(); }); @@ -214,3 +218,13 @@ async function getEntityResource( return entities[0]; } + +function getBearerToken( + authorizationHeader: string | undefined, +): string | undefined { + if (typeof authorizationHeader !== 'string') { + return undefined; + } + const matches = authorizationHeader.match(/Bearer\s+(\S+)/i); + return matches?.[1]; +} diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 7aae3cd47c..8824c3b0d6 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -17,6 +17,8 @@ import { createServiceBuilder, loadBackendConfig, + ServerTokenManager, + SingleHostDiscovery, UrlReaders, useHotMemoize, } from '@backstage/backend-common'; @@ -25,6 +27,7 @@ import { Logger } from 'winston'; import { DatabaseManager } from '../legacy/database'; import { CatalogBuilder } from '../legacy/service/CatalogBuilder'; import { createRouter } from '../legacy/service'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; export interface ServerOptions { port: number; @@ -42,6 +45,12 @@ export async function startStandaloneServer( const db = useHotMemoize(module, () => DatabaseManager.createInMemoryDatabaseConnection(), ); + const discovery = SingleHostDiscovery.fromConfig(config); + const tokenManager = ServerTokenManager.fromConfig(config, { logger }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); logger.debug('Creating application...'); const builder = new CatalogBuilder({ @@ -49,6 +58,7 @@ export async function startStandaloneServer( database: { getClient: () => db }, config, reader, + permissions, }); const { entitiesCatalog, locationsCatalog, higherOrderOperation } = await builder.build(); diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index 05d3d00f4d..5e2bf2a599 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -34,6 +34,7 @@ export interface LocationService { export type RefreshOptions = { /** The reference to a single entity that should be refreshed */ entityRef: string; + authorizationToken?: string; }; /** From f2134e7029f704331370dd7516d85063b4a7be7a Mon Sep 17 00:00:00 2001 From: Daan Boerlage Date: Fri, 7 Jan 2022 17:03:11 +0100 Subject: [PATCH 33/52] docs: Add EF Education First to the list of adopters --- ADOPTERS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index e0e6a818c6..b35f6a3d77 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -77,5 +77,6 @@ | [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon), [Gauthier Roebroeck](https://github.com/gauthier-roebroeck-mox) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | | [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 | | [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. | -| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | -| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | +| [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | +| [Hotmart](https://www.hotmart.com) | [@fabioviana-hotmart](https://github.com/fabioviana-hotmart) | The main Developers Portal to centralize docs, applications and technical metrics. | +| [EF Education First](https://www.ef.com) | [Daan Boerlage](https://github.com/runebaas), [Rafał Nowosielski](https://github.com/rnowosielski) | Our developer portal - primarily used for cataloging and scaffolding with the ambition to expand with more feature adoptions over time | From 8fe84eadaf5a414240aac2ba7d57141a4a5122a5 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 6 Jan 2022 16:46:17 +0000 Subject: [PATCH 34/52] catalog-backend: add type parameter to CatalogPermissionRule type Previously the CatalogPermissionRule type had a fixed type of unknown[] for the parameters expected in the `apply` and `toQuery` methods. This meant that conditions generated for these rules would always have unknown parameters too, which makes using them in policies much more difficult. To address this, this commit introduces a mandatory type parameter for CatalogPermissionRule which is expected to be set to a tuple corresponding to the expected parameters. Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 23 +++++++++++-------- .../permissions/rules/createPropertyRule.ts | 2 +- .../src/permissions/rules/hasAnnotation.ts | 2 +- .../src/permissions/rules/hasLabel.ts | 2 +- .../src/permissions/rules/isEntityKind.ts | 2 +- .../src/permissions/rules/isEntityOwner.ts | 2 +- .../catalog-backend/src/permissions/types.ts | 5 ++-- .../src/service/NextCatalogBuilder.ts | 4 ++-- .../catalog-backend/src/service/NextRouter.ts | 2 +- 9 files changed, 24 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 6d7834001a..33f0fab18e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -307,9 +307,10 @@ export type CatalogEnvironment = { }; // @public -export type CatalogPermissionRule = PermissionRule< +export type CatalogPermissionRule = PermissionRule< Entity, - EntitiesSearchFilter + EntitiesSearchFilter, + TParams >; // Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1311,7 +1312,9 @@ export class NextCatalogBuilder { addEntityPolicy(...policies: EntityPolicy[]): NextCatalogBuilder; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder; - addPermissionRules(...permissionRules: CatalogPermissionRule[]): void; + addPermissionRules( + ...permissionRules: CatalogPermissionRule[] + ): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder; build(): Promise<{ @@ -1359,7 +1362,7 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) - permissionRules?: CatalogPermissionRule[]; + permissionRules?: CatalogPermissionRule[]; // (undocumented) refreshService?: RefreshService; } @@ -1394,12 +1397,12 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule; - hasLabel: CatalogPermissionRule; - hasMetadata: CatalogPermissionRule; - hasSpec: CatalogPermissionRule; - isEntityKind: CatalogPermissionRule; - isEntityOwner: CatalogPermissionRule; + hasAnnotation: CatalogPermissionRule<[annotation: string]>; + hasLabel: CatalogPermissionRule<[label: string]>; + hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; + hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; + isEntityKind: CatalogPermissionRule<[kinds: string[]]>; + isEntityOwner: CatalogPermissionRule<[claims: string[]]>; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 92e53f930f..254eb164da 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -21,7 +21,7 @@ import { get } from 'lodash'; export function createPropertyRule( propertyType: 'metadata' | 'spec', -): CatalogPermissionRule { +): CatalogPermissionRule<[key: string, value?: string]> { return { name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 57ded3d779..baab28d838 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -23,7 +23,7 @@ import { CatalogPermissionRule } from '../types'; * annotation on a given entity. * @public */ -export const hasAnnotation: CatalogPermissionRule = { +export const hasAnnotation: CatalogPermissionRule<[annotation: string]> = { name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index f93c5aeae6..a790f1441f 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -23,7 +23,7 @@ import { CatalogPermissionRule } from '../types'; * label in its metadata. * @public */ -export const hasLabel: CatalogPermissionRule = { +export const hasLabel: CatalogPermissionRule<[label: string]> = { name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', apply: (resource: Entity, label: string) => diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 64fcf7482d..024188939f 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -22,7 +22,7 @@ import { CatalogPermissionRule } from '../types'; * kind. * @public */ -export const isEntityKind: CatalogPermissionRule = { +export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', apply(resource: Entity, kinds: string[]) { diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index e89159e6b4..e450cdf721 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -27,7 +27,7 @@ import { CatalogPermissionRule } from '../types'; * owner. * @public */ -export const isEntityOwner: CatalogPermissionRule = { +export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', apply: (resource: Entity, claims: string[]) => { diff --git a/plugins/catalog-backend/src/permissions/types.ts b/plugins/catalog-backend/src/permissions/types.ts index cc2b8f4d45..4a0d9dc1fc 100644 --- a/plugins/catalog-backend/src/permissions/types.ts +++ b/plugins/catalog-backend/src/permissions/types.ts @@ -24,7 +24,8 @@ import { EntitiesSearchFilter } from '../catalog/types'; * * @public */ -export type CatalogPermissionRule = PermissionRule< +export type CatalogPermissionRule = PermissionRule< Entity, - EntitiesSearchFilter + EntitiesSearchFilter, + TParams >; diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index 1dcb632e64..e707423670 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -130,7 +130,7 @@ export class NextCatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; - private permissionRules: CatalogPermissionRule[]; + private permissionRules: CatalogPermissionRule[]; constructor(env: CatalogEnvironment) { this.env = env; @@ -331,7 +331,7 @@ export class NextCatalogBuilder { * * @param permissionRules - Additional permission rules */ - addPermissionRules(...permissionRules: CatalogPermissionRule[]) { + addPermissionRules(...permissionRules: CatalogPermissionRule[]) { this.permissionRules.push(...permissionRules); } diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 7aae564a45..948424ec88 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -49,7 +49,7 @@ export interface NextRouterOptions { refreshService?: RefreshService; logger: Logger; config: Config; - permissionRules?: CatalogPermissionRule[]; + permissionRules?: CatalogPermissionRule[]; } export async function createNextRouter( From 9db1b86f3244579c0e5b1f5aced5371aabf08c59 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:26:54 +0000 Subject: [PATCH 35/52] permission-node: add helpers for creating PermissionRules Signed-off-by: MT Lewis --- .changeset/healthy-toes-laugh.md | 5 +++ plugins/permission-node/api-report.md | 16 +++++++ .../src/integration/createPermissionRule.ts | 45 +++++++++++++++++++ .../permission-node/src/integration/index.ts | 1 + 4 files changed, 67 insertions(+) create mode 100644 .changeset/healthy-toes-laugh.md create mode 100644 plugins/permission-node/src/integration/createPermissionRule.ts diff --git a/.changeset/healthy-toes-laugh.md b/.changeset/healthy-toes-laugh.md new file mode 100644 index 0000000000..e0db38e5e3 --- /dev/null +++ b/.changeset/healthy-toes-laugh.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-node': patch +--- + +Add helpers for creating PermissionRules with inferred types diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 25a8be06c8..e743f18311 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -95,6 +95,22 @@ export const createPermissionIntegrationRouter: (options: { getResource: (resourceRef: string) => Promise; }) => Router; +// @public +export const createPermissionRule: < + TResource, + TQuery, + TParams extends unknown[], +>( + rule: PermissionRule, +) => PermissionRule; + +// @public +export const makeCreatePermissionRule: () => < + TParams extends unknown[], +>( + rule: PermissionRule, +) => PermissionRule; + // @public export interface PermissionPolicy { // (undocumented) diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts new file mode 100644 index 0000000000..f3a0519a70 --- /dev/null +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { PermissionRule } from '../types'; + +/** + * Helper function to ensure that {@link PermissionRule} definitions are typed correctly. + * + * @public + */ +export const createPermissionRule = < + TResource, + TQuery, + TParams extends unknown[], +>( + rule: PermissionRule, +) => rule; + +/** + * Helper for making plugin-specific createPermissionRule functions, that have + * the TResource and TQuery type parameters populated but infer the params from + * the supplied rule. This helps ensure that rules created for this plugin use + * consistent types for the resource and query. + * + * @public + */ +export const makeCreatePermissionRule = + () => + ( + rule: PermissionRule, + ) => + createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/index.ts b/plugins/permission-node/src/integration/index.ts index f070d57c8f..978342e4ed 100644 --- a/plugins/permission-node/src/integration/index.ts +++ b/plugins/permission-node/src/integration/index.ts @@ -18,3 +18,4 @@ export * from './createConditionFactory'; export * from './createConditionExports'; export * from './createConditionTransformer'; export * from './createPermissionIntegrationRouter'; +export * from './createPermissionRule'; From 82cfdc8d02e5595d89604b698e10c9bdb52dadfb Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:47:14 +0000 Subject: [PATCH 36/52] catalog-backend: use createPermissionRule helper instead of manually typing rules Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 33 +++++++++++++++---- .../permissions/rules/createPropertyRule.ts | 16 ++++----- .../src/permissions/rules/hasAnnotation.ts | 9 +++-- .../src/permissions/rules/hasLabel.ts | 9 +++-- .../src/permissions/rules/index.ts | 2 ++ .../src/permissions/rules/isEntityKind.ts | 6 ++-- .../src/permissions/rules/isEntityOwner.ts | 9 +++-- .../src/permissions/rules/util.ts | 31 +++++++++++++++++ 8 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 plugins/catalog-backend/src/permissions/rules/util.ts diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 33f0fab18e..aafff2a22e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -529,6 +529,11 @@ export class CommonDatabase implements Database { ): Promise; } +// @public +export const createCatalogPermissionRule: ( + rule: PermissionRule, +) => PermissionRule; + // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @deprecated (undocumented) @@ -1397,12 +1402,28 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule<[annotation: string]>; - hasLabel: CatalogPermissionRule<[label: string]>; - hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; - hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; - isEntityKind: CatalogPermissionRule<[kinds: string[]]>; - isEntityOwner: CatalogPermissionRule<[claims: string[]]>; + hasAnnotation: PermissionRule< + Entity, + EntitiesSearchFilter, + [annotation: string] + >; + hasLabel: PermissionRule; + hasMetadata: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + hasSpec: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + isEntityKind: PermissionRule; + isEntityOwner: PermissionRule< + Entity, + EntitiesSearchFilter, + [claims: string[]] + >; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 254eb164da..27a6033362 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -14,15 +14,12 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; import { get } from 'lodash'; +import { Entity } from '@backstage/catalog-model'; +import { createCatalogPermissionRule } from './util'; -export function createPropertyRule( - propertyType: 'metadata' | 'spec', -): CatalogPermissionRule<[key: string, value?: string]> { - return { +export const createPropertyRule = (propertyType: 'metadata' | 'spec') => + createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, apply: (resource: Entity, key: string, value?: string) => { @@ -32,9 +29,8 @@ export function createPropertyRule( } return !!foundValue; }, - toQuery: (key: string, value?: string): EntitiesSearchFilter => ({ + toQuery: (key: string, value?: string) => ({ key: `${propertyType}.${key}`, ...(value !== undefined && { values: [value] }), }), - }; -} + }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index baab28d838..f82ace6580 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -15,21 +15,20 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for the presence of an * annotation on a given entity. * @public */ -export const hasAnnotation: CatalogPermissionRule<[annotation: string]> = { +export const hasAnnotation = createCatalogPermissionRule({ name: 'HAS_ANNOTATION', description: 'Allow entities which are annotated with the specified annotation', apply: (resource: Entity, annotation: string) => !!resource.metadata.annotations?.hasOwnProperty(annotation), - toQuery: (annotation: string): EntitiesSearchFilter => ({ + toQuery: (annotation: string) => ({ key: `metadata.annotations.${annotation}`, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index a790f1441f..8e5a3341d7 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -15,20 +15,19 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * label in its metadata. * @public */ -export const hasLabel: CatalogPermissionRule<[label: string]> = { +export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', apply: (resource: Entity, label: string) => !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: (label: string): EntitiesSearchFilter => ({ + toQuery: (label: string) => ({ key: `metadata.labels.${label}`, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/index.ts b/plugins/catalog-backend/src/permissions/rules/index.ts index 68d1e38715..4eec796c74 100644 --- a/plugins/catalog-backend/src/permissions/rules/index.ts +++ b/plugins/catalog-backend/src/permissions/rules/index.ts @@ -34,3 +34,5 @@ export const permissionRules = { isEntityKind, isEntityOwner, }; + +export { createCatalogPermissionRule } from './util'; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 024188939f..ddaecd314e 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -15,14 +15,14 @@ */ import { Entity } from '@backstage/catalog-model'; import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * kind. * @public */ -export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { +export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', apply(resource: Entity, kinds: string[]) { @@ -35,4 +35,4 @@ export const isEntityKind: CatalogPermissionRule<[kinds: string[]]> = { values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), }; }, -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index e450cdf721..a7413c4941 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -19,15 +19,14 @@ import { RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; -import { EntitiesSearchFilter } from '../../catalog/types'; -import { CatalogPermissionRule } from '../types'; +import { createCatalogPermissionRule } from './util'; /** * A {@link CatalogPermissionRule} which filters for entities with a specified * owner. * @public */ -export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { +export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', apply: (resource: Entity, claims: string[]) => { @@ -39,8 +38,8 @@ export const isEntityOwner: CatalogPermissionRule<[claims: string[]]> = { .filter(relation => relation.type === RELATION_OWNED_BY) .some(relation => claims.includes(stringifyEntityRef(relation.target))); }, - toQuery: (claims: string[]): EntitiesSearchFilter => ({ + toQuery: (claims: string[]) => ({ key: 'relations.ownedBy', values: claims, }), -}; +}); diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts new file mode 100644 index 0000000000..7ef0ca3546 --- /dev/null +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '../../catalog/types'; + +/** + * Helper function for creating correctly-typed + * {@link @backstage/plugin-permission-node#PermissionRule}s for the + * catalog-backend. + * + * @public + */ +export const createCatalogPermissionRule = makeCreatePermissionRule< + Entity, + EntitiesSearchFilter +>(); From bef617807bb39973f9d458dce9227e172232e80c Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 11:50:51 +0000 Subject: [PATCH 37/52] catalog-backend: custom createPermissionRule implementation Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 32 +++++-------------- .../src/permissions/rules/util.ts | 11 +++---- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index aafff2a22e..d310ab001f 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -531,8 +531,8 @@ export class CommonDatabase implements Database { // @public export const createCatalogPermissionRule: ( - rule: PermissionRule, -) => PermissionRule; + rule: CatalogPermissionRule, +) => CatalogPermissionRule; // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1402,28 +1402,12 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: PermissionRule< - Entity, - EntitiesSearchFilter, - [annotation: string] - >; - hasLabel: PermissionRule; - hasMetadata: PermissionRule< - Entity, - EntitiesSearchFilter, - [key: string, value?: string | undefined] - >; - hasSpec: PermissionRule< - Entity, - EntitiesSearchFilter, - [key: string, value?: string | undefined] - >; - isEntityKind: PermissionRule; - isEntityOwner: PermissionRule< - Entity, - EntitiesSearchFilter, - [claims: string[]] - >; + hasAnnotation: CatalogPermissionRule<[annotation: string]>; + hasLabel: CatalogPermissionRule<[label: string]>; + hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; + hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; + isEntityKind: CatalogPermissionRule<[kinds: string[]]>; + isEntityOwner: CatalogPermissionRule<[claims: string[]]>; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 7ef0ca3546..7d17d64e49 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -14,9 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../../catalog/types'; +import { CatalogPermissionRule } from '../types'; /** * Helper function for creating correctly-typed @@ -25,7 +23,6 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * * @public */ -export const createCatalogPermissionRule = makeCreatePermissionRule< - Entity, - EntitiesSearchFilter ->(); +export const createCatalogPermissionRule = ( + rule: CatalogPermissionRule, +) => rule; From 10b4ba686f234ef0b301a5204a71fba6f12c0637 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 7 Jan 2022 15:04:50 +0000 Subject: [PATCH 38/52] catalog-backend: remove CatalogPermissionRule type Signed-off-by: MT Lewis --- plugins/catalog-backend/api-report.md | 47 ++++++++++++------- .../catalog-backend/src/permissions/index.ts | 1 - .../src/permissions/rules/hasAnnotation.ts | 5 +- .../src/permissions/rules/hasLabel.ts | 4 +- .../src/permissions/rules/hasMetadata.ts | 5 +- .../src/permissions/rules/hasSpec.ts | 5 +- .../src/permissions/rules/isEntityKind.ts | 4 +- .../src/permissions/rules/isEntityOwner.ts | 4 +- .../src/permissions/rules/util.ts | 11 +++-- .../catalog-backend/src/permissions/types.ts | 31 ------------ .../src/service/NextCatalogBuilder.ts | 19 ++++++-- .../catalog-backend/src/service/NextRouter.ts | 10 ++-- 12 files changed, 73 insertions(+), 73 deletions(-) delete mode 100644 plugins/catalog-backend/src/permissions/types.ts diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d310ab001f..4b4036ee8a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -306,13 +306,6 @@ export type CatalogEnvironment = { permissions: PermissionAuthorizer; }; -// @public -export type CatalogPermissionRule = PermissionRule< - Entity, - EntitiesSearchFilter, - TParams ->; - // Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -531,8 +524,8 @@ export class CommonDatabase implements Database { // @public export const createCatalogPermissionRule: ( - rule: CatalogPermissionRule, -) => CatalogPermissionRule; + rule: PermissionRule, +) => PermissionRule; // Warning: (ae-missing-release-tag) "CreateDatabaseOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1318,7 +1311,11 @@ export class NextCatalogBuilder { // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addEntityProvider(...providers: EntityProvider[]): NextCatalogBuilder; addPermissionRules( - ...permissionRules: CatalogPermissionRule[] + ...permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[] ): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen addProcessor(...processors: CatalogProcessor[]): NextCatalogBuilder; @@ -1367,7 +1364,7 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) - permissionRules?: CatalogPermissionRule[]; + permissionRules?: PermissionRule[]; // (undocumented) refreshService?: RefreshService; } @@ -1402,12 +1399,28 @@ export function parseEntityYaml( // @public export const permissionRules: { - hasAnnotation: CatalogPermissionRule<[annotation: string]>; - hasLabel: CatalogPermissionRule<[label: string]>; - hasMetadata: CatalogPermissionRule<[key: string, value?: string | undefined]>; - hasSpec: CatalogPermissionRule<[key: string, value?: string | undefined]>; - isEntityKind: CatalogPermissionRule<[kinds: string[]]>; - isEntityOwner: CatalogPermissionRule<[claims: string[]]>; + hasAnnotation: PermissionRule< + Entity, + EntitiesSearchFilter, + [annotation: string] + >; + hasLabel: PermissionRule; + hasMetadata: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + hasSpec: PermissionRule< + Entity, + EntitiesSearchFilter, + [key: string, value?: string | undefined] + >; + isEntityKind: PermissionRule; + isEntityOwner: PermissionRule< + Entity, + EntitiesSearchFilter, + [claims: string[]] + >; }; // Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/catalog-backend/src/permissions/index.ts b/plugins/catalog-backend/src/permissions/index.ts index e7ece1dbc7..624d6dd7ac 100644 --- a/plugins/catalog-backend/src/permissions/index.ts +++ b/plugins/catalog-backend/src/permissions/index.ts @@ -15,4 +15,3 @@ */ export * from './rules'; -export type { CatalogPermissionRule } from './types'; diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index f82ace6580..81ebd52789 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -18,8 +18,9 @@ import { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for the presence of an - * annotation on a given entity. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for the presence of an annotation on a given entity. + * * @public */ export const hasAnnotation = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 8e5a3341d7..04b00d68fa 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -18,8 +18,8 @@ import { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * label in its metadata. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified label in its metadata. * @public */ export const hasLabel = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts index fa592feb44..f5f25a5ecf 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasMetadata.ts @@ -17,8 +17,9 @@ import { createPropertyRule } from './createPropertyRule'; /** - * A {@link CatalogPermissionRule} which filters for entities with the specified - * metadata subfield. Also matches on values if value is provided. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with the specified metadata subfield. Also matches on + * values if value is provided. * * The key argument to the `apply` and `toQuery` methods can be nested, such as * 'field.nestedfield'. diff --git a/plugins/catalog-backend/src/permissions/rules/hasSpec.ts b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts index 73a7519e1a..891cf1d58c 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasSpec.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasSpec.ts @@ -17,8 +17,9 @@ import { createPropertyRule } from './createPropertyRule'; /** - * A {@link CatalogPermissionRule} which filters for entities with the specified - * spec subfield. Also matches on values if value is provided. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with the specified spec subfield. Also matches on values + * if value is provided. * * The key argument to the `apply` and `toQuery` methods can be nested, such as * 'field.nestedfield'. diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index ddaecd314e..6356c94dc4 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -18,8 +18,8 @@ import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * kind. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified kind. * @public */ export const isEntityKind = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index a7413c4941..3176b31b87 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -22,8 +22,8 @@ import { import { createCatalogPermissionRule } from './util'; /** - * A {@link CatalogPermissionRule} which filters for entities with a specified - * owner. + * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which + * filters for entities with a specified owner. * @public */ export const isEntityOwner = createCatalogPermissionRule({ diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 7d17d64e49..7ef0ca3546 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import { CatalogPermissionRule } from '../types'; +import { Entity } from '@backstage/catalog-model'; +import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '../../catalog/types'; /** * Helper function for creating correctly-typed @@ -23,6 +25,7 @@ import { CatalogPermissionRule } from '../types'; * * @public */ -export const createCatalogPermissionRule = ( - rule: CatalogPermissionRule, -) => rule; +export const createCatalogPermissionRule = makeCreatePermissionRule< + Entity, + EntitiesSearchFilter +>(); diff --git a/plugins/catalog-backend/src/permissions/types.ts b/plugins/catalog-backend/src/permissions/types.ts deleted file mode 100644 index 4a0d9dc1fc..0000000000 --- a/plugins/catalog-backend/src/permissions/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * 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 { Entity } from '@backstage/catalog-model'; -import { PermissionRule } from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../catalog/types'; - -/** - * A conditional rule that can be used to filter catalog entities for an - * authorization request. See - * {@link @backstage/plugin-permission-node#PermissionRule} for more details. - * - * @public - */ -export type CatalogPermissionRule = PermissionRule< - Entity, - EntitiesSearchFilter, - TParams ->; diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts index e707423670..36d4347ede 100644 --- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts @@ -17,6 +17,7 @@ import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, + Entity, EntityPolicies, EntityPolicy, FieldFormatEntityPolicy, @@ -29,7 +30,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { createHash } from 'crypto'; import { Router } from 'express'; import lodash from 'lodash'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog'; import { DatabaseLocationsCatalog, LocationsCatalog, @@ -83,9 +84,9 @@ import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { LocationService } from './types'; import { connectEntityProviders } from '../processing/connectEntityProviders'; -import { CatalogPermissionRule } from '../permissions/types'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +import { PermissionRule } from '@backstage/plugin-permission-node'; export type CatalogEnvironment = { logger: Logger; @@ -130,7 +131,11 @@ export class NextCatalogBuilder { maxSeconds: 150, }); private locationAnalyzer: LocationAnalyzer | undefined = undefined; - private permissionRules: CatalogPermissionRule[]; + private permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[]; constructor(env: CatalogEnvironment) { this.env = env; @@ -331,7 +336,13 @@ export class NextCatalogBuilder { * * @param permissionRules - Additional permission rules */ - addPermissionRules(...permissionRules: CatalogPermissionRule[]) { + addPermissionRules( + ...permissionRules: PermissionRule< + Entity, + EntitiesSearchFilter, + unknown[] + >[] + ) { this.permissionRules.push(...permissionRules); } diff --git a/plugins/catalog-backend/src/service/NextRouter.ts b/plugins/catalog-backend/src/service/NextRouter.ts index 948424ec88..5338f58f91 100644 --- a/plugins/catalog-backend/src/service/NextRouter.ts +++ b/plugins/catalog-backend/src/service/NextRouter.ts @@ -25,14 +25,16 @@ import { import { Config } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; +import { + createPermissionIntegrationRouter, + PermissionRule, +} from '@backstage/plugin-permission-node'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import yn from 'yn'; -import { EntitiesCatalog } from '../catalog'; +import { EntitiesCatalog, EntitiesSearchFilter } from '../catalog'; import { LocationAnalyzer } from '../ingestion/types'; -import { CatalogPermissionRule } from '../permissions/types'; import { basicEntityFilter, parseEntityFilterParams, @@ -49,7 +51,7 @@ export interface NextRouterOptions { refreshService?: RefreshService; logger: Logger; config: Config; - permissionRules?: CatalogPermissionRule[]; + permissionRules?: PermissionRule[]; } export async function createNextRouter( From e2eb92c1095411f1162987de4ebde320ce7f96d9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 4 Jan 2022 01:57:59 +0100 Subject: [PATCH 39/52] removed deprecations from the 2021-11-25 release Signed-off-by: Patrik Oldsberg --- .changeset/lazy-gorillas-tell.md | 7 +++ .changeset/rotten-files-wink.md | 5 ++ .changeset/silver-mice-bake.md | 5 ++ .changeset/violet-dingos-relate.md | 7 +++ .../src/middleware/errorHandler.ts | 4 +- packages/core-app-api/api-report.md | 37 +++++-------- .../src/apis/system/ApiRegistry.ts | 4 +- .../core-app-api/src/apis/system/index.ts | 1 - .../core-app-api/src/app/AppManager.test.tsx | 26 +++++++-- packages/core-app-api/src/app/AppManager.tsx | 35 ++++++------ packages/core-app-api/src/app/types.ts | 11 ++-- .../ResponseErrorPanel/ResponseErrorPanel.tsx | 6 +- packages/core-plugin-api/api-report.md | 16 ------ .../src/plugin/Plugin.test.tsx | 55 ------------------- .../core-plugin-api/src/plugin/Plugin.tsx | 37 +------------ packages/core-plugin-api/src/plugin/index.ts | 3 - packages/core-plugin-api/src/plugin/types.ts | 34 ------------ packages/errors/api-report.md | 15 ----- .../errors/src/errors/ResponseError.test.ts | 4 +- packages/errors/src/errors/ResponseError.ts | 14 +---- packages/errors/src/serialization/index.ts | 4 +- packages/errors/src/serialization/response.ts | 27 --------- 22 files changed, 95 insertions(+), 262 deletions(-) create mode 100644 .changeset/lazy-gorillas-tell.md create mode 100644 .changeset/rotten-files-wink.md create mode 100644 .changeset/silver-mice-bake.md create mode 100644 .changeset/violet-dingos-relate.md diff --git a/.changeset/lazy-gorillas-tell.md b/.changeset/lazy-gorillas-tell.md new file mode 100644 index 0000000000..672d8c221b --- /dev/null +++ b/.changeset/lazy-gorillas-tell.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-plugin-api': minor +--- + +Removed previously deprecated exports: `PluginHooks`, `PluginOutput`, and `FeatureFlagOutput`. + +The deprecated `register` method of `PluginConfig` has been removed, as well as the deprecated `output` method of `BackstagePlugin`. diff --git a/.changeset/rotten-files-wink.md b/.changeset/rotten-files-wink.md new file mode 100644 index 0000000000..65bd92bcc5 --- /dev/null +++ b/.changeset/rotten-files-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Updated `ResponseErrorPanel` to not use the deprecated `data` property of `ResponseError`. diff --git a/.changeset/silver-mice-bake.md b/.changeset/silver-mice-bake.md new file mode 100644 index 0000000000..6cba2fdb0a --- /dev/null +++ b/.changeset/silver-mice-bake.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Removed previously deprecated `ApiRegistry` export. diff --git a/.changeset/violet-dingos-relate.md b/.changeset/violet-dingos-relate.md new file mode 100644 index 0000000000..c8b0c03e2c --- /dev/null +++ b/.changeset/violet-dingos-relate.md @@ -0,0 +1,7 @@ +--- +'@backstage/errors': minor +--- + +Removed the deprecated exports `ErrorResponse` and `parseErrorResponse`. + +Removed the deprecated `constructor` and the deprecated `data` property of `ResponseError`. diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 47c9285b18..179d86e4c2 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -17,7 +17,7 @@ import { AuthenticationError, ConflictError, - ErrorResponse, + ErrorResponseBody, InputError, NotAllowedError, NotFoundError, @@ -89,7 +89,7 @@ export function errorHandler( return; } - const body: ErrorResponse = { + const body: ErrorResponseBody = { error: serializeError(error, { includeStack: showStackTraces }), request: { method: req.method, url: req.url }, response: { statusCode }, diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index f94979f975..7a6b515255 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -50,7 +50,6 @@ import { oktaAuthApiRef } from '@backstage/core-plugin-api'; import { oneloginAuthApiRef } from '@backstage/core-plugin-api'; import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; -import { PluginOutput } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; @@ -129,21 +128,6 @@ export type ApiProviderProps = { children: ReactNode; }; -// @public @deprecated -export class ApiRegistry implements ApiHolder { - constructor(apis: Map); - // Warning: (ae-forgotten-export) The symbol "ApiRegistryBuilder" needs to be exported by the entry point index.d.ts - // - // (undocumented) - static builder(): ApiRegistryBuilder; - // Warning: (ae-forgotten-export) The symbol "ApiImpl" needs to be exported by the entry point index.d.ts - static from(apis: ApiImpl[]): ApiRegistry; - // (undocumented) - get(api: ApiRef): T | undefined; - static with(api: ApiRef, impl: T): ApiRegistry; - with(api: ApiRef, impl: T): ApiRegistry; -} - // @public export class ApiResolver implements ApiHolder { constructor(factories: ApiFactoryHolder); @@ -208,14 +192,19 @@ export type AppOptions = { icons: AppIcons & { [key in string]: IconComponent; }; - plugins?: (Omit, 'output'> & { - output(): ( - | PluginOutput - | { - type: string; - } - )[]; - })[]; + plugins?: Array< + BackstagePlugin & { + output?(): Array< + | { + type: 'feature-flag'; + name: string; + } + | { + type: string; + } + >; + } + >; components: AppComponents; themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; diff --git a/packages/core-app-api/src/apis/system/ApiRegistry.ts b/packages/core-app-api/src/apis/system/ApiRegistry.ts index e433381dd1..d4990a04c5 100644 --- a/packages/core-app-api/src/apis/system/ApiRegistry.ts +++ b/packages/core-app-api/src/apis/system/ApiRegistry.ts @@ -18,6 +18,7 @@ import { ApiRef, ApiHolder } from '@backstage/core-plugin-api'; type ApiImpl = readonly [ApiRef, T]; +/** @internal */ class ApiRegistryBuilder { private apis: [string, unknown][] = []; @@ -35,8 +36,7 @@ class ApiRegistryBuilder { /** * A registry for utility APIs. * - * @public - * @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead. + * @internal */ export class ApiRegistry implements ApiHolder { static builder() { diff --git a/packages/core-app-api/src/apis/system/index.ts b/packages/core-app-api/src/apis/system/index.ts index 56c42f1e2a..3c0b715249 100644 --- a/packages/core-app-api/src/apis/system/index.ts +++ b/packages/core-app-api/src/apis/system/index.ts @@ -16,7 +16,6 @@ export { ApiProvider } from './ApiProvider'; export type { ApiProviderProps } from './ApiProvider'; -export { ApiRegistry } from './ApiRegistry'; export { ApiResolver } from './ApiResolver'; export { ApiFactoryRegistry } from './ApiFactoryRegistry'; export type { ApiFactoryScope } from './ApiFactoryRegistry'; diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 20872b26f0..b09d424737 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -316,7 +316,7 @@ describe('Integration Test', () => { plugins: [ createPlugin({ id: 'test', - register: p => p.featureFlags.register('name'), + featureFlags: [{ name: 'name' }], }), ], components, @@ -384,8 +384,24 @@ describe('Integration Test', () => { name: 'foo', }, ], - register: p => p.featureFlags.register('name'), }), + // We still support consuming the old feature flag API for a little while longer + { + getId() { + return 'old-test'; + }, + getApis() { + return []; + }, + output() { + return [ + { + type: 'feature-flag', + name: 'old-feature-flag', + }, + ]; + }, + } as any, ], components, configLoader: async () => [], @@ -412,12 +428,12 @@ describe('Integration Test', () => { ); expect(storageFlags.registerFlag).toHaveBeenCalledWith({ - name: 'name', + name: 'foo', pluginId: 'test', }); expect(storageFlags.registerFlag).toHaveBeenCalledWith({ - name: 'foo', - pluginId: 'test', + name: 'old-feature-flag', + pluginId: 'old-test', }); }); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 7cdc06a094..ef5fe43b8b 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -27,7 +27,6 @@ import { Route, Routes } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { ApiProvider, - ApiRegistry, AppThemeSelector, ConfigReader, LocalStorageFeatureFlags, @@ -80,6 +79,13 @@ import { } from './types'; import { AppThemeProvider } from './AppThemeProvider'; import { defaultConfigLoader } from './defaultConfigLoader'; +import { ApiRegistry } from '../apis/system/ApiRegistry'; + +type CompatiblePlugin = + | BackstagePlugin + | (Omit, 'getFeatureFlags'> & { + output(): Array<{ type: 'feature-flag'; name: string }>; + }); export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { const result = new Map(); @@ -149,7 +155,7 @@ function useConfigLoader( if (noConfigNode) { return { node: ( - + {noConfigNode} ), @@ -183,7 +189,7 @@ export class AppManager implements BackstageApp { private readonly apis: Iterable; private readonly icons: NonNullable; - private readonly plugins: Set>; + private readonly plugins: Set; private readonly components: AppComponents; private readonly themes: AppTheme[]; private readonly configLoader?: AppConfigLoader; @@ -196,9 +202,7 @@ export class AppManager implements BackstageApp { constructor(options: AppOptions) { this.apis = options.apis ?? []; this.icons = options.icons; - this.plugins = new Set( - (options.plugins as BackstagePlugin[]) ?? [], - ); + this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []); this.components = options.components; this.themes = options.themes as AppTheme[]; this.configLoader = options.configLoader ?? defaultConfigLoader; @@ -208,7 +212,7 @@ export class AppManager implements BackstageApp { } getPlugins(): BackstagePlugin[] { - return Array.from(this.plugins); + return Array.from(this.plugins) as BackstagePlugin[]; } getSystemIcon(key: string): IconComponent | undefined { @@ -282,16 +286,11 @@ export class AppManager implements BackstageApp { } } else { for (const output of plugin.output()) { - switch (output.type) { - case 'feature-flag': { - featureFlagsApi.registerFlag({ - name: output.name, - pluginId: plugin.getId(), - }); - break; - } - default: - break; + if (output.type === 'feature-flag') { + featureFlagsApi.registerFlag({ + name: output.name, + pluginId: plugin.getId(), + }); } } } @@ -468,7 +467,7 @@ export class AppManager implements BackstageApp { return this.apiHolder; } - private verifyPlugins(plugins: Iterable) { + private verifyPlugins(plugins: Iterable) { const pluginIds = new Set(); for (const plugin of plugins) { diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 4b8d2741cb..bacaf83fbd 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -24,7 +24,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef, - PluginOutput, IdentityApi, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; @@ -232,9 +231,13 @@ export type AppOptions = { /** * A list of all plugins to include in the app. */ - plugins?: (Omit, 'output'> & { - output(): (PluginOutput | { type: string })[]; - })[]; + plugins?: Array< + BackstagePlugin & { + output?(): Array< + { type: 'feature-flag'; name: string } | { type: string } + >; // support for old plugins + } + >; /** * Supply components to the app to override the default ones. diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx index 68d7360400..9600a3f179 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx @@ -61,14 +61,14 @@ export function ResponseErrorPanel(props: ErrorPanelProps) { ); } - const { data, cause } = error as ResponseError; - const { request, response } = data; + const { body, cause } = error as ResponseError; + const { request, response } = body; const errorString = `${response.statusCode}: ${cause.name}`; const requestString = request && `${request.method} ${request.url}`; const messageString = cause.message.replace(/\\n/g, '\n'); const stackString = cause.stack?.replace(/\\n/g, '\n'); - const jsonString = JSON.stringify(data, undefined, 2); + const jsonString = JSON.stringify(body, undefined, 2); return ( = { getId(): string; - output(): PluginOutput[]; getApis(): Iterable; getFeatureFlags(): Iterable; provide(extension: Extension): T; @@ -446,12 +445,6 @@ export type FeatureFlag = { pluginId: string; }; -// @public @deprecated -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - // @public export interface FeatureFlagsApi { getRegisteredFlags(): FeatureFlag[]; @@ -687,7 +680,6 @@ export type PluginConfig< > = { id: string; apis?: Iterable; - register?(hooks: PluginHooks): void; routes?: Routes; externalRoutes?: ExternalRoutes; featureFlags?: PluginFeatureFlagConfig[]; @@ -698,14 +690,6 @@ export type PluginFeatureFlagConfig = { name: string; }; -// @public @deprecated -export type PluginHooks = { - featureFlags: FeatureFlagsHooks; -}; - -// @public @deprecated -export type PluginOutput = FeatureFlagOutput; - // @public export type ProfileInfo = { email?: string; diff --git a/packages/core-plugin-api/src/plugin/Plugin.test.tsx b/packages/core-plugin-api/src/plugin/Plugin.test.tsx index d3758543a1..b66235e210 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.test.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.test.tsx @@ -28,62 +28,7 @@ describe('Plugin Feature Flag', () => { expect( createPlugin({ id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, }).getFeatureFlags(), - ).toEqual([{ name: 'blob' }]); - - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - featureFlags: [{ name: 'test' }], - }).getFeatureFlags(), - ).toEqual([{ name: 'test' }, { name: 'blob' }]); - - expect( - createPlugin({ - id: 'test', - }).getFeatureFlags(), - ).toEqual([]); - - /* deprecated tests */ - - expect( - createPlugin({ - id: 'test', - featureFlags: [{ name: 'test' }], - }).output(), - ).toEqual([{ name: 'test', type: 'feature-flag' }]); - - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - }).output(), - ).toEqual([{ name: 'blob', type: 'feature-flag' }]); - expect( - createPlugin({ - id: 'test', - register({ featureFlags }) { - featureFlags.register('blob'); - }, - featureFlags: [{ name: 'test' }], - }).output(), - ).toEqual([ - { name: 'test', type: 'feature-flag' }, - { name: 'blob', type: 'feature-flag' }, - ]); - - expect( - createPlugin({ - id: 'test', - }).output(), ).toEqual([]); }); }); diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx index 0fb7574cd7..d97554b27b 100644 --- a/packages/core-plugin-api/src/plugin/Plugin.tsx +++ b/packages/core-plugin-api/src/plugin/Plugin.tsx @@ -16,7 +16,6 @@ import { PluginConfig, - PluginOutput, BackstagePlugin, Extension, AnyRoutes, @@ -33,8 +32,6 @@ export class PluginImpl< ExternalRoutes extends AnyExternalRoutes, > implements BackstagePlugin { - private storedOutput?: PluginOutput[]; - constructor(private readonly config: PluginConfig) {} getId(): string { @@ -46,11 +43,7 @@ export class PluginImpl< } getFeatureFlags(): Iterable { - const registeredFlags = this.output() - .filter(({ type }) => type === 'feature-flag') - .map(({ name }) => ({ name })); - - return registeredFlags; + return this.config.featureFlags?.slice() ?? []; } get routes(): Routes { @@ -61,34 +54,6 @@ export class PluginImpl< return this.config.externalRoutes ?? ({} as ExternalRoutes); } - output(): PluginOutput[] { - if (this.storedOutput) { - return this.storedOutput; - } - const outputs = new Array(); - this.storedOutput = outputs; - - if (this.config.featureFlags) { - for (const flag of this.config.featureFlags) { - outputs.push({ type: 'feature-flag', name: flag.name }); - } - } - - if (!this.config.register) { - return outputs; - } - - this.config.register({ - featureFlags: { - register(name) { - outputs.push({ type: 'feature-flag', name }); - }, - }, - }); - - return this.storedOutput; - } - provide(extension: Extension): T { return extension.expose(this); } diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts index 4f7609fd9c..95f5a6b219 100644 --- a/packages/core-plugin-api/src/plugin/index.ts +++ b/packages/core-plugin-api/src/plugin/index.ts @@ -20,10 +20,7 @@ export type { AnyRoutes, BackstagePlugin, Extension, - FeatureFlagOutput, FeatureFlagsHooks, PluginConfig, - PluginHooks, - PluginOutput, PluginFeatureFlagConfig, } from './types'; diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts index 0f8d3404d8..d639327838 100644 --- a/packages/core-plugin-api/src/plugin/types.ts +++ b/packages/core-plugin-api/src/plugin/types.ts @@ -17,24 +17,6 @@ import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; import { AnyApiFactory } from '../apis/system'; -/** - * Replace with using {@link RouteRef}s. - * @deprecated will be removed - * @public - */ -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - -/** - * {@link FeatureFlagOutput} type. - * - * @public - * @deprecated Use {@link BackstagePlugin.getFeatureFlags} instead. - */ -export type PluginOutput = FeatureFlagOutput; - /** * Plugin extension type. * @@ -72,10 +54,6 @@ export type BackstagePlugin< ExternalRoutes extends AnyExternalRoutes = {}, > = { getId(): string; - /** - * @deprecated use getFeatureFlags instead. - * */ - output(): PluginOutput[]; getApis(): Iterable; /** * Returns all registered feature flags for this plugin. @@ -107,23 +85,11 @@ export type PluginConfig< > = { id: string; apis?: Iterable; - /** @deprecated use featureFlags property instead for defining feature flags */ - register?(hooks: PluginHooks): void; routes?: Routes; externalRoutes?: ExternalRoutes; featureFlags?: PluginFeatureFlagConfig[]; }; -/** - * Holds hooks registered by the plugin. - * - * @deprecated - feature flags are now registered in plugin config under featureFlags - * @public - */ -export type PluginHooks = { - featureFlags: FeatureFlagsHooks; -}; - /** * Interface for registering feature flags hooks. * diff --git a/packages/errors/api-report.md b/packages/errors/api-report.md index 97b011bad6..a01581135b 100644 --- a/packages/errors/api-report.md +++ b/packages/errors/api-report.md @@ -33,9 +33,6 @@ export type ErrorLike = { [unknownKeys: string]: unknown; }; -// @public @deprecated -export type ErrorResponse = ErrorResponseBody; - // @public export type ErrorResponseBody = { error: SerializedError; @@ -68,9 +65,6 @@ export class NotFoundError extends CustomErrorBase {} // @public export class NotModifiedError extends CustomErrorBase {} -// @public @deprecated -export function parseErrorResponse(response: Response): Promise; - // @public export function parseErrorResponseBody( response: Response, @@ -78,17 +72,8 @@ export function parseErrorResponseBody( // @public export class ResponseError extends Error { - // @deprecated - constructor(props: { - message: string; - response: Response; - data: ErrorResponseBody; - cause: Error; - }); readonly body: ErrorResponseBody; readonly cause: Error; - // @deprecated - get data(): ErrorResponseBody; static fromResponse(response: Response): Promise; readonly response: Response; } diff --git a/packages/errors/src/errors/ResponseError.test.ts b/packages/errors/src/errors/ResponseError.test.ts index 52a308dbb8..94f2e850d9 100644 --- a/packages/errors/src/errors/ResponseError.test.ts +++ b/packages/errors/src/errors/ResponseError.test.ts @@ -14,12 +14,12 @@ * limitations under the License. */ -import { ErrorResponse } from '../serialization'; +import { ErrorResponseBody } from '../serialization'; import { ResponseError } from './ResponseError'; describe('ResponseError', () => { it('constructs itself from a response', async () => { - const body: ErrorResponse = { + const body: ErrorResponseBody = { error: { name: 'Fours', message: 'Expected fives', stack: 'lines' }, request: { method: 'GET', url: '/' }, response: { statusCode: 444 }, diff --git a/packages/errors/src/errors/ResponseError.ts b/packages/errors/src/errors/ResponseError.ts index a20035cd8c..84c8137bb5 100644 --- a/packages/errors/src/errors/ResponseError.ts +++ b/packages/errors/src/errors/ResponseError.ts @@ -75,10 +75,7 @@ export class ResponseError extends Error { }); } - /** - * @deprecated will be removed. - **/ - constructor(props: { + private constructor(props: { message: string; response: Response; data: ErrorResponseBody; @@ -90,13 +87,4 @@ export class ResponseError extends Error { this.body = props.data; this.cause = props.cause; } - /** - * The parsed JSON error body, as sent by the server. - * @deprecated use body instead. - */ - get data(): ErrorResponseBody { - // eslint-disable-next-line no-console - console.warn('ErrorResponse.data is deprecated, use .body instead.'); - return this.body; - } } diff --git a/packages/errors/src/serialization/index.ts b/packages/errors/src/serialization/index.ts index b86661d496..b03514f749 100644 --- a/packages/errors/src/serialization/index.ts +++ b/packages/errors/src/serialization/index.ts @@ -16,5 +16,5 @@ export { deserializeError, serializeError, stringifyError } from './error'; export type { SerializedError } from './error'; -export { parseErrorResponse, parseErrorResponseBody } from './response'; -export type { ErrorResponse, ErrorResponseBody } from './response'; +export { parseErrorResponseBody } from './response'; +export type { ErrorResponseBody } from './response'; diff --git a/packages/errors/src/serialization/response.ts b/packages/errors/src/serialization/response.ts index 4a1731646d..3580220525 100644 --- a/packages/errors/src/serialization/response.ts +++ b/packages/errors/src/serialization/response.ts @@ -16,14 +16,6 @@ import { SerializedError } from './error'; -/** - * A standard shape of JSON data returned as the body of backend errors. - * - * @public - * @deprecated - Use {@link ErrorResponseBody} instead. - */ -export type ErrorResponse = ErrorResponseBody; - /** * A standard shape of JSON data returned as the body of backend errors. * @@ -48,25 +40,6 @@ export type ErrorResponseBody = { }; }; -/** - * Attempts to construct an ErrorResponse out of a failed server request. - * Assumes that the response has already been checked to be not ok. This - * function consumes the body of the response, and assumes that it hasn't - * been consumed before. - * - * The code is forgiving, and constructs a useful synthetic body as best it can - * if the response body wasn't on the expected form. - * - * @public - * @param response - The response of a failed request - * @deprecated - Use {@link parseErrorResponseBody} instead. - */ -export async function parseErrorResponse( - response: Response, -): Promise { - return parseErrorResponseBody(response); -} - /** * Attempts to construct an ErrorResponseBody out of a failed server request. * Assumes that the response has already been checked to be not ok. This From 23046ab18d1b83594c3f6ae88c97adbbb4de1d6b Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Fri, 7 Jan 2022 18:47:31 +0100 Subject: [PATCH 40/52] chore: we're generating api-reports not changesets Signed-off-by: Ben Lambert --- scripts/api-extractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/api-extractor.ts b/scripts/api-extractor.ts index c7aaede9b0..1a00ef4c5e 100644 --- a/scripts/api-extractor.ts +++ b/scripts/api-extractor.ts @@ -734,7 +734,7 @@ async function main() { if (!selectedPackageDirs && !isCiBuild && !isDocsBuild) { console.log(''); console.log( - 'TIP: You can generate changesets for select packages by passing package paths:', + 'TIP: You can generate api-reports for select packages by passing package paths:', ); console.log(''); console.log( From a95e7880590ba432b4705c6648a41ba2e626a4de Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 8 Jan 2022 16:22:02 +0100 Subject: [PATCH 41/52] config: move reader test to correct block Signed-off-by: Patrik Oldsberg --- packages/config/src/reader.test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 3cbab17637..2472222c2b 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -265,6 +265,19 @@ describe('ConfigReader', () => { withLogCollector(() => config.getOptionalConfigArray('b')), ).toMatchObject({ warn: [] }); }); + + it('should coerce number strings to numbers', () => { + const config = ConfigReader.fromConfigs([ + { + data: { + port: '123', + }, + context: '1', + }, + ]); + + expect(config.getNumber('port')).toEqual(123); + }); }); describe('ConfigReader with fallback', () => { @@ -660,17 +673,4 @@ describe('ConfigReader.get()', () => { }, }); }); - - it('coerces number strings to numbers', () => { - const config = ConfigReader.fromConfigs([ - { - data: { - port: '123', - }, - context: '1', - }, - ]); - - expect(config.getNumber('port')).toEqual(123); - }); }); From f5343e7c1aea3877df023d53481d49912c458e7e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 8 Jan 2022 16:23:26 +0100 Subject: [PATCH 42/52] config: make get always return a clone Signed-off-by: Patrik Oldsberg --- .changeset/clean-wolves-jog.md | 5 ++++ packages/config/src/reader.test.ts | 47 ++++++++++++++++++++++++++++++ packages/config/src/reader.ts | 9 ++---- 3 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 .changeset/clean-wolves-jog.md diff --git a/.changeset/clean-wolves-jog.md b/.changeset/clean-wolves-jog.md new file mode 100644 index 0000000000..70ab23715f --- /dev/null +++ b/.changeset/clean-wolves-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/config': patch +--- + +The `ConfigReader#get` method now always returns a deep clone of the configuration data. diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts index 2472222c2b..c0627a200b 100644 --- a/packages/config/src/reader.test.ts +++ b/packages/config/src/reader.test.ts @@ -673,4 +673,51 @@ describe('ConfigReader.get()', () => { }, }); }); + + it('should return deep clones of the backing data', () => { + const data1 = { + foo: { + bar: [], + baz: {}, + }, + }; + const data2 = { + x: { + y: { + z: {}, + }, + }, + }; + + const reader = ConfigReader.fromConfigs([ + { data: data1, context: '1' }, + { data: data2, context: '2' }, + ]); + + reader.get().foo.bar.push(1); + reader.get('foo').bar.push(1); + reader.get('foo.bar').push(1); + reader.get().foo.baz.x = 1; + reader.get('foo').baz.x = 1; + reader.get('foo.baz').x = 1; + reader.get().x.y.z.w = 1; + reader.get('x').y.z.w = 1; + reader.get('x.y').z.w = 1; + reader.get('x.y.z').w = 1; + + const readerSingle = ConfigReader.fromConfigs([ + { data: data1, context: '1' }, + ]); + + readerSingle.get().foo.bar.push(1); + readerSingle.get('foo').bar.push(1); + readerSingle.get('foo.bar').push(1); + readerSingle.get().foo.baz.x = 1; + readerSingle.get('foo').baz.x = 1; + readerSingle.get('foo.baz').x = 1; + + expect(data1.foo.bar).toEqual([]); + expect(data1.foo.baz).toEqual({}); + expect(data2.x.y.z).toEqual({}); + }); }); diff --git a/packages/config/src/reader.ts b/packages/config/src/reader.ts index 8c0c4140a5..129a703784 100644 --- a/packages/config/src/reader.ts +++ b/packages/config/src/reader.ts @@ -126,7 +126,7 @@ export class ConfigReader implements Config { /** {@inheritdoc Config.getOptional} */ getOptional(key?: string): T | undefined { - const value = this.readValue(key); + const value = cloneDeep(this.readValue(key)); const fallbackValue = this.fallback?.getOptional(key); if (value === undefined) { @@ -153,11 +153,8 @@ export class ConfigReader implements Config { // Avoid merging arrays and primitive values, since that's how merging works for other // methods for reading config. - return mergeWith( - {}, - { value: cloneDeep(fallbackValue) }, - { value }, - (into, from) => (!isObject(from) || !isObject(into) ? from : undefined), + return mergeWith({}, { value: fallbackValue }, { value }, (into, from) => + !isObject(from) || !isObject(into) ? from : undefined, ).value as T; } From 2b19fd2e941b6b3a00abc96a519597c40e290a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 8 Jan 2022 16:30:45 +0100 Subject: [PATCH 43/52] deep clone data read out of config, to avoid pollution / mutation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/fuzzy-llamas-collect.md | 5 + .../src/ldap/config.test.ts | 84 +++++++++ .../src/ldap/config.ts | 13 +- .../src/ldap/read.test.ts | 169 +++++++++++++++++- .../src/ldap/read.ts | 5 +- 5 files changed, 271 insertions(+), 5 deletions(-) create mode 100644 .changeset/fuzzy-llamas-collect.md diff --git a/.changeset/fuzzy-llamas-collect.md b/.changeset/fuzzy-llamas-collect.md new file mode 100644 index 0000000000..17ee925e56 --- /dev/null +++ b/.changeset/fuzzy-llamas-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Make sure to avoid accidental data sharing / mutation of `set` values diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 9fb536a87f..06ac24d3ae 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -209,4 +209,88 @@ describe('readLdapConfig', () => { const expected = '(|(cn=foo bar)(cn=bar))'; expect(actual[0].users.options.filter).toEqual(expected); }); + + it('supports a dot nested set structure', () => { + const config = { + providers: [ + { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + 'metadata.annotations': { + a: 'b', + }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + }, + ], + }; + const actual = readLdapConfig(new ConfigReader(config)); + + expect(actual[0].users.set).toEqual({ 'metadata.annotations': { a: 'b' } }); + }); + + it('throws on attempts to modify the set structure', () => { + const config = { + providers: [ + { + target: 'target', + users: { + dn: 'udn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + set: { + x: { a: 'b' }, + }, + }, + }, + ], + }; + const actual = readLdapConfig(new ConfigReader(config)); + + expect(() => { + (actual[0].users.set as any).y = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property y, object is not extensible"`, + ); + expect(() => { + (actual[0].users.set as any).x.b = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property b, object is not extensible"`, + ); + + expect(() => { + (actual[0].groups.set as any).y = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property y, object is not extensible"`, + ); + expect(() => { + (actual[0].groups.set as any).x.b = 2; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot add property b, object is not extensible"`, + ); + }); }); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index dc1ec4910d..b12c7c9fee 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -166,6 +166,15 @@ const defaultConfig = { * @param config The root of the LDAP config hierarchy */ export function readLdapConfig(config: Config): LdapProviderConfig[] { + function freeze(data: T): T { + return JSON.parse(JSON.stringify(data), (_key, value) => { + if (typeof value === 'object' && value !== null) { + Object.freeze(value); + } + return value; + }); + } + function readBindConfig( c: Config | undefined, ): LdapProviderConfig['bind'] | undefined { @@ -217,7 +226,7 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { if (!c) { return undefined; } - return Object.fromEntries(c.keys().map(path => [path, c.get(path)])); + return c.get(); } function readUserMapConfig( @@ -297,6 +306,6 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { // Replace arrays instead of merging, otherwise default behavior return Array.isArray(from) ? from : undefined; }); - return merged as LdapProviderConfig; + return freeze(merged) as LdapProviderConfig; }); } diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index c4f203c5db..875ffb7029 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -25,7 +25,13 @@ import { LDAP_RDN_ANNOTATION, LDAP_UUID_ANNOTATION, } from './constants'; -import { readLdapGroups, readLdapUsers, resolveRelations } from './read'; +import { + defaultGroupTransformer, + defaultUserTransformer, + readLdapGroups, + readLdapUsers, + resolveRelations, +} from './read'; import { ActiveDirectoryVendor, DefaultLdapVendor } from './vendors'; function user(data: RecursivePartial): UserEntity { @@ -264,6 +270,7 @@ describe('readLdapGroups', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + it('transfers all attributes from Microsoft Active Directory', async () => { client.getVendor.mockResolvedValue(ActiveDirectoryVendor); client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { @@ -358,6 +365,7 @@ describe('resolveRelations', () => { expect(parent.spec.children).toEqual(['child']); expect(child.spec.parent).toEqual('parent'); }); + it('matches by UUID', () => { const parent = group({ metadata: { @@ -539,3 +547,162 @@ describe('resolveRelations', () => { }); }); }); + +describe('defaultUserTransformer', () => { + it('can set things safely', async () => { + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, + set: { + 'metadata.annotations.a': 1, + 'metadata.annotations': { a: 2, b: 3 }, + }, + }; + + const entry = searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + let output = await defaultUserTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + name: 'uid-value', + }, + spec: { + memberOf: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + + (output!.metadata.annotations as any).c = 7; + + // exact same inputs again + output = await defaultUserTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + name: 'uid-value', + }, + spec: { + memberOf: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + }); +}); + +describe('defaultGroupTransformer', () => { + it('can set things safely', async () => { + const config: GroupConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + description: 'description', + type: 'type', + members: 'members', + memberOf: 'memberOf', + }, + set: { + 'metadata.annotations.a': 1, + 'metadata.annotations': { a: 2, b: 3 }, + }, + }; + + const entry = searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + let output = await defaultGroupTransformer( + DefaultLdapVendor, + config, + entry, + ); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + description: 'description-value', + name: 'uid-value', + }, + spec: { + type: 'unknown', + children: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + + (output!.metadata.annotations as any).c = 7; + + // exact same inputs again + output = await defaultGroupTransformer(DefaultLdapVendor, config, entry); + expect(output).toEqual({ + apiVersion: 'backstage.io/v1beta1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/ldap-dn': 'dn-value', + 'backstage.io/ldap-rdn': 'uid-value', + 'backstage.io/ldap-uuid': 'uuid-value', + a: 2, + b: 3, + }, + description: 'description-value', + name: 'uid-value', + }, + spec: { + type: 'unknown', + children: [], + profile: { displayName: 'cn-value', email: 'mail-value' }, + }, + }); + }); +}); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 2d6e3c1608..513d899304 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -17,6 +17,7 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { SearchEntry } from 'ldapjs'; import lodashSet from 'lodash/set'; +import cloneDeep from 'lodash/cloneDeep'; import { buildOrgHierarchy } from './org'; import { LdapClient } from './client'; import { GroupConfig, UserConfig } from './config'; @@ -52,7 +53,7 @@ export async function defaultUserTransformer( if (set) { for (const [path, value] of Object.entries(set)) { - lodashSet(entity, path, value); + lodashSet(entity, path, cloneDeep(value)); } } @@ -146,7 +147,7 @@ export async function defaultGroupTransformer( if (set) { for (const [path, value] of Object.entries(set)) { - lodashSet(entity, path, value); + lodashSet(entity, path, cloneDeep(value)); } } From 722681b1b13ff2192a0c3a3389d7fc7d0b752b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 9 Jan 2022 13:05:10 +0100 Subject: [PATCH 44/52] Clean up API reports in ldap and msgraph catalog modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/long-otters-promise.md | 6 ++ .gitignore | 3 + .../catalog-backend-module-ldap/api-report.md | 77 +++---------------- .../src/ldap/client.ts | 20 +++-- .../src/ldap/config.ts | 12 ++- .../src/ldap/constants.ts | 6 ++ .../src/ldap/index.ts | 7 +- .../src/ldap/read.ts | 54 ++++++++----- .../src/ldap/types.ts | 26 +++++-- .../src/ldap/util.ts | 18 +++-- .../src/ldap/vendors.ts | 6 +- .../src/processors/LdapOrgEntityProvider.ts | 13 ++++ .../src/processors/LdapOrgReaderProcessor.ts | 2 + .../api-report.md | 65 ++++------------ .../src/microsoftGraph/client.ts | 5 ++ .../src/microsoftGraph/config.ts | 11 ++- .../src/microsoftGraph/constants.ts | 6 ++ .../src/microsoftGraph/helper.ts | 5 ++ .../src/microsoftGraph/index.ts | 1 + .../src/microsoftGraph/read.ts | 24 ++++++ .../src/microsoftGraph/types.ts | 15 ++++ .../MicrosoftGraphOrgEntityProvider.ts | 9 +++ .../MicrosoftGraphOrgReaderProcessor.ts | 2 + 23 files changed, 225 insertions(+), 168 deletions(-) create mode 100644 .changeset/long-otters-promise.md diff --git a/.changeset/long-otters-promise.md b/.changeset/long-otters-promise.md new file mode 100644 index 0000000000..eecb97e8ac --- /dev/null +++ b/.changeset/long-otters-promise.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Clean up API report diff --git a/.gitignore b/.gitignore index 8c37280023..60d0d6da3b 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,6 @@ site # e2e tests cypress/cypress/* + +# Possible leftover from build:api-reports +tsconfig.tmp.json diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index bbf92944b9..e2dbef74e5 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -17,26 +17,26 @@ import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; import { UserEntity } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "defaultGroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type BindConfig = { + dn: string; + secret: string; +}; + +// @public export function defaultGroupTransformer( vendor: LdapVendor, config: GroupConfig, entry: SearchEntry, ): Promise; -// Warning: (ae-missing-release-tag) "defaultUserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultUserTransformer( vendor: LdapVendor, config: UserConfig, entry: SearchEntry, ): Promise; -// Warning: (ae-missing-release-tag) "GroupConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type GroupConfig = { dn: string; @@ -57,12 +57,6 @@ export type GroupConfig = { }; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration -// Warning: (ae-missing-release-tag) "GroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type GroupTransformer = ( vendor: LdapVendor, @@ -70,28 +64,18 @@ export type GroupTransformer = ( group: SearchEntry, ) => Promise; -// Warning: (ae-missing-release-tag) "LDAP_DN_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; -// Warning: (ae-missing-release-tag) "LDAP_RDN_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; -// Warning: (ae-missing-release-tag) "LDAP_UUID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; -// Warning: (ae-missing-release-tag) "LdapClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapClient { constructor(client: Client, logger: Logger_2); - // Warning: (ae-forgotten-export) The symbol "BindConfig" needs to be exported by the entry point index.d.ts - // // (undocumented) static create( logger: Logger_2, @@ -100,22 +84,14 @@ export class LdapClient { ): Promise; getRootDSE(): Promise; getVendor(): Promise; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen search(dn: string, options: SearchOptions): Promise; - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen - // Warning: (ae-forgotten-export) The symbol "SearchCallback" needs to be exported by the entry point index.d.ts searchStreaming( dn: string, options: SearchOptions, - f: SearchCallback, + f: (entry: SearchEntry) => void, ): Promise; } -// Warning: (ae-missing-release-tag) "LdapOrgEntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapOrgEntityProvider implements EntityProvider { constructor(options: { @@ -140,12 +116,9 @@ export class LdapOrgEntityProvider implements EntityProvider { ): LdapOrgEntityProvider; // (undocumented) getProviderName(): string; - // (undocumented) read(): Promise; } -// Warning: (ae-missing-release-tag) "LdapOrgReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class LdapOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -171,8 +144,6 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "LdapProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type LdapProviderConfig = { target: string; @@ -181,8 +152,6 @@ export type LdapProviderConfig = { groups: GroupConfig; }; -// Warning: (ae-missing-release-tag) "LdapVendor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type LdapVendor = { dnAttributeName: string; @@ -190,12 +159,6 @@ export type LdapVendor = { decodeStringAttribute: (entry: SearchEntry, name: string) => string[]; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "mapStringAttr" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function mapStringAttr( entry: SearchEntry, @@ -204,18 +167,9 @@ export function mapStringAttr( setter: (value: string) => void, ): void; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "readLdapConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function readLdapConfig(config: Config): LdapProviderConfig[]; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (ae-missing-release-tag) "readLdapOrg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function readLdapOrg( client: LdapClient, @@ -231,8 +185,6 @@ export function readLdapOrg( groups: GroupEntity[]; }>; -// Warning: (ae-missing-release-tag) "UserConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type UserConfig = { dn: string; @@ -251,21 +203,10 @@ export type UserConfig = { }; }; -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// Warning: (tsdoc-undefined-tag) The TSDoc tag "@return" is not defined in this configuration -// Warning: (ae-missing-release-tag) "UserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type UserTransformer = ( vendor: LdapVendor, config: UserConfig, user: SearchEntry, ) => Promise; - -// Warnings were encountered during analysis: -// -// src/ldap/vendors.d.ts:17:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/ldap/vendors.d.ts:18:8 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen ``` diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 553081f4b0..ea13ba49ee 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -25,14 +25,12 @@ import { LdapVendor, } from './vendors'; -export interface SearchCallback { - (entry: SearchEntry): void; -} - /** - * Basic wrapper for the ldapjs library. + * Basic wrapper for the `ldapjs` library. * * Helps out with promisifying calls, paging, binding etc. + * + * @public */ export class LdapClient { private vendor: Promise | undefined; @@ -75,8 +73,8 @@ export class LdapClient { /** * Performs an LDAP search operation. * - * @param dn The fully qualified base DN to search within - * @param options The search options + * @param dn - The fully qualified base DN to search within + * @param options - The search options */ async search(dn: string, options: SearchOptions): Promise { try { @@ -128,14 +126,14 @@ export class LdapClient { /** * Performs an LDAP search operation, calls a function on each entry to limit memory usage * - * @param dn The fully qualified base DN to search within - * @param options The search options - * @param f The callback to call on each search entry + * @param dn - The fully qualified base DN to search within + * @param options - The search options + * @param f - The callback to call on each search entry */ async searchStreaming( dn: string, options: SearchOptions, - f: SearchCallback, + f: (entry: SearchEntry) => void, ): Promise { try { return await new Promise((resolve, reject) => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index b12c7c9fee..526467962e 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -23,6 +23,8 @@ import { trimEnd } from 'lodash'; /** * The configuration parameters for a single LDAP provider. + * + * @public */ export type LdapProviderConfig = { // The prefix of the target that this matches on, e.g. @@ -39,6 +41,8 @@ export type LdapProviderConfig = { /** * The settings to use for the a command. + * + * @public */ export type BindConfig = { // The DN of the user to auth as, e.g. @@ -50,6 +54,8 @@ export type BindConfig = { /** * The settings that govern the reading and interpretation of users. + * + * @public */ export type UserConfig = { // The DN under which users are stored. @@ -88,6 +94,8 @@ export type UserConfig = { /** * The settings that govern the reading and interpretation of groups. + * + * @public */ export type GroupConfig = { // The DN under which groups are stored. @@ -163,7 +171,9 @@ const defaultConfig = { /** * Parses configuration. * - * @param config The root of the LDAP config hierarchy + * @param config - The root of the LDAP config hierarchy + * + * @public */ export function readLdapConfig(config: Config): LdapProviderConfig[] { function freeze(data: T): T { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/constants.ts b/plugins/catalog-backend-module-ldap/src/ldap/constants.ts index 73df5d6de8..cdc448b4dd 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/constants.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/constants.ts @@ -22,6 +22,8 @@ * example, for an item with the fully qualified DN * uid=john,ou=people,ou=spotify,dc=spotify,dc=net the generated entity would * have this annotation, with the value "john". + * + * @public */ export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; @@ -33,6 +35,8 @@ export const LDAP_RDN_ANNOTATION = 'backstage.io/ldap-rdn'; * for an item with the DN uid=john,ou=people,ou=spotify,dc=spotify,dc=net the * generated entity would have this annotation, with that full string as its * value. + * + * @public */ export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; @@ -44,5 +48,7 @@ export const LDAP_DN_ANNOTATION = 'backstage.io/ldap-dn'; * for an item with the UUID 76ef928a-b251-1037-9840-d78227f36a7e, the * generated entity would have this annotation, with that full string as its * value. + * + * @public */ export const LDAP_UUID_ANNOTATION = 'backstage.io/ldap-uuid'; diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index 70500299ae..c3aace492f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -17,7 +17,12 @@ export { LdapClient } from './client'; export { mapStringAttr } from './util'; export { readLdapConfig } from './config'; -export type { LdapProviderConfig, GroupConfig, UserConfig } from './config'; +export type { + LdapProviderConfig, + GroupConfig, + UserConfig, + BindConfig, +} from './config'; export type { LdapVendor } from './vendors'; export { LDAP_DN_ANNOTATION, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 513d899304..e15ec85554 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -31,6 +31,12 @@ import { Logger } from 'winston'; import { GroupTransformer, UserTransformer } from './types'; import { mapStringAttr } from './util'; +/** + * The default implementation of the transformation from an LDAP entry to a + * User entity. + * + * @public + */ export async function defaultUserTransformer( vendor: LdapVendor, config: UserConfig, @@ -88,9 +94,9 @@ export async function defaultUserTransformer( /** * Reads users out of an LDAP provider. * - * @param client The LDAP client - * @param config The user data configuration - * @param opts + * @param client - The LDAP client + * @param config - The user data configuration + * @param opts - Additional options */ export async function readLdapUsers( client: LdapClient, @@ -125,6 +131,12 @@ export async function readLdapUsers( return { users: entities, userMemberOf }; } +/** + * The default implementation of the transformation from an LDAP entry to a + * Group entity. + * + * @public + */ export async function defaultGroupTransformer( vendor: LdapVendor, config: GroupConfig, @@ -185,9 +197,9 @@ export async function defaultGroupTransformer( /** * Reads groups out of an LDAP provider. * - * @param client The LDAP client - * @param config The group data configuration - * @param opts + * @param client - The LDAP client + * @param config - The group data configuration + * @param opts - Additional options */ export async function readLdapGroups( client: LdapClient, @@ -240,13 +252,12 @@ export async function readLdapGroups( /** * Reads users and groups out of an LDAP provider. * - * Invokes the above "raw" read functions and stitches together the results - * with all relations etc filled in. + * @param client - The LDAP client + * @param userConfig - The user data configuration + * @param groupConfig - The group data configuration + * @param options - Additional options * - * @param client The LDAP client - * @param userConfig The user data configuration - * @param groupConfig The group data configuration - * @param options + * @public */ export async function readLdapOrg( client: LdapClient, @@ -261,6 +272,9 @@ export async function readLdapOrg( users: UserEntity[]; groups: GroupEntity[]; }> { + // Invokes the above "raw" read functions and stitches together the results + // with all relations etc filled in. + const { users, userMemberOf } = await readLdapUsers(client, userConfig, { transformer: options?.userTransformer, }); @@ -321,14 +335,14 @@ function ensureItems( * Takes groups and entities with empty relations, and fills in the various * relations that were returned by the readers, and forms the org hierarchy. * - * @param groups Group entities with empty relations; modified in place - * @param users User entities with empty relations; modified in place - * @param userMemberOf For a user DN, the set of group DNs or UUIDs that the - * user is a member of - * @param groupMemberOf For a group DN, the set of group DNs or UUIDs that the - * group is a member of (parents in the hierarchy) - * @param groupMember For a group DN, the set of group DNs or UUIDs that are - * members of the group (children in the hierarchy) + * @param groups - Group entities with empty relations; modified in place + * @param users - User entities with empty relations; modified in place + * @param userMemberOf - For a user DN, the set of group DNs or UUIDs that the + * user is a member of + * @param groupMemberOf - For a group DN, the set of group DNs or UUIDs that + * the group is a member of (parents in the hierarchy) + * @param groupMember - For a group DN, the set of group DNs or UUIDs that are + * members of the group (children in the hierarchy) */ export function resolveRelations( groups: GroupEntity[], diff --git a/plugins/catalog-backend-module-ldap/src/ldap/types.ts b/plugins/catalog-backend-module-ldap/src/ldap/types.ts index e32e4c5b42..9d7c326add 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/types.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/types.ts @@ -21,10 +21,15 @@ import { GroupConfig, UserConfig } from './config'; /** * Customize the ingested User entity * - * @param vendor The LDAP vendor that can be used to find and decode vendor specific attributes - * @param config The User specific config used by the default transformer. - * @param user The found LDAP entry in its source format. This is the entry that you want to transform - * @return A `UserEntity` or `undefined` if you want to ignore the found user for being ingested by the catalog + * @param vendor - The LDAP vendor that can be used to find and decode vendor + * specific attributes + * @param config - The User specific config used by the default transformer. + * @param user - The found LDAP entry in its source format. This is the entry + * that you want to transform + * @returns A `UserEntity` or `undefined` if you want to ignore the found user + * for being ingested by the catalog + * + * @public */ export type UserTransformer = ( vendor: LdapVendor, @@ -35,10 +40,15 @@ export type UserTransformer = ( /** * Customize the ingested Group entity * - * @param vendor The LDAP vendor that can be used to find and decode vendor specific attributes - * @param config The Group specific config used by the default transformer. - * @param group The found LDAP entry in its source format. This is the entry that you want to transform - * @return A `GroupEntity` or `undefined` if you want to ignore the found group for being ingested by the catalog + * @param vendor - The LDAP vendor that can be used to find and decode vendor + * specific attributes + * @param config - The Group specific config used by the default transformer. + * @param group - The found LDAP entry in its source format. This is the entry + * that you want to transform + * @returns A `GroupEntity` or `undefined` if you want to ignore the found group + * for being ingested by the catalog + * + * @public */ export type GroupTransformer = ( vendor: LdapVendor, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/util.ts b/plugins/catalog-backend-module-ldap/src/ldap/util.ts index 21ee40f52c..21829cef35 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/util.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/util.ts @@ -20,19 +20,25 @@ import { LdapVendor } from './vendors'; /** * Builds a string form of an LDAP Error structure. * - * @param error The error + * @param error - The error */ export function errorString(error: LDAPError) { return `${error.code} ${error.name}: ${error.message}`; } /** - * Maps a single-valued attribute to a consumer + * Maps a single-valued attribute to a consumer. * - * @param entry The LDAP source entry - * @param vendor The LDAP vendor - * @param attributeName The source attribute to map. If the attribute is undefined the mapping will be silently ignored. - * @param setter The function to be called with the decoded attribute from the source entry + * This helper can be useful when implementing a user or group transformer. + * + * @param entry - The LDAP source entry + * @param vendor - The LDAP vendor + * @param attributeName - The source attribute to map. If the attribute is + * undefined the mapping will be silently ignored. + * @param setter - The function to be called with the decoded attribute from the + * source entry + * + * @public */ export function mapStringAttr( entry: SearchEntry, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts index 3341497ca8..3df29a5fb9 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts @@ -18,6 +18,8 @@ import { SearchEntry } from 'ldapjs'; /** * An LDAP Vendor handles unique nuances between different vendors. + * + * @public */ export type LdapVendor = { /** @@ -31,8 +33,8 @@ export type LdapVendor = { /** * Decode ldap entry values for a given attribute name to their string representation. * - * @param entry The ldap entry - * @param name The attribute to decode + * @param entry - The ldap entry + * @param name - The attribute to decode */ decodeStringAttribute: (entry: SearchEntry, name: string) => string[]; }; diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index c0cd663052..c74e5b3aee 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -39,6 +39,13 @@ import { /** * Reads user and group entries out of an LDAP service, and provides them as * User and Group entities for the catalog. + * + * @remarks + * + * Add an instance of this class to your catalog builder, and then periodically + * call the {@link LdapOrgEntityProvider.read} method. + * + * @public */ export class LdapOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; @@ -113,14 +120,20 @@ export class LdapOrgEntityProvider implements EntityProvider { }, ) {} + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { return `LdapOrgEntityProvider:${this.options.id}`; } + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; } + /** + * Runs one complete ingestion loop. Call this method regularly at some + * appropriate cadence. + */ async read() { if (!this.connection) { throw new Error('Not initialized'); diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index f87afc38b9..be2d748840 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -33,6 +33,8 @@ import { /** * Extracts teams and users out of an LDAP server. + * + * @public */ export class LdapOrgReaderProcessor implements CatalogProcessor { private readonly providers: LdapProviderConfig[]; diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index e1a837d0c1..662056eff1 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -16,51 +16,37 @@ import * as msal from '@azure/msal-node'; import { Response as Response_2 } from 'node-fetch'; import { UserEntity } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "defaultGroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, ): Promise; -// Warning: (ae-missing-release-tag) "defaultOrganizationTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultOrganizationTransformer( organization: MicrosoftGraph.Organization, ): Promise; -// Warning: (ae-missing-release-tag) "defaultUserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, ): Promise; -// Warning: (ae-missing-release-tag) "GroupTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type GroupTransformer = ( group: MicrosoftGraph.Group, groupPhoto?: string, ) => Promise; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_GROUP_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION = 'graph.microsoft.com/group-id'; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_TENANT_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION = 'graph.microsoft.com/tenant-id'; -// Warning: (ae-missing-release-tag) "MICROSOFT_GRAPH_USER_ID_ANNOTATION" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; @@ -76,7 +62,6 @@ export class MicrosoftGraphClient { groupId: string, maxSize: number, ): Promise; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" getGroups(query?: ODataQuery): AsyncIterable; getOrganization(tenantId: string): Promise; // (undocumented) @@ -86,18 +71,12 @@ export class MicrosoftGraphClient { maxSize: number, ): Promise; getUserProfile(userId: string): Promise; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" getUsers(query?: ODataQuery): AsyncIterable; - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" requestApi(path: string, query?: ODataQuery): Promise; - // Warning: (ae-forgotten-export) The symbol "ODataQuery" needs to be exported by the entry point index.d.ts - // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/plugin-catalog-backend-module-msgraph" does not have an export "ODataQuery" requestCollection(path: string, query?: ODataQuery): AsyncIterable; requestRaw(url: string): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphOrgEntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class MicrosoftGraphOrgEntityProvider implements EntityProvider { constructor(options: { @@ -124,12 +103,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { ): MicrosoftGraphOrgEntityProvider; // (undocumented) getProviderName(): string; - // (undocumented) read(): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphOrgReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -157,8 +133,6 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "MicrosoftGraphProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type MicrosoftGraphProviderConfig = { target: string; @@ -171,28 +145,27 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; }; -// Warning: (ae-missing-release-tag) "normalizeEntityName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function normalizeEntityName(name: string): string; -// Warning: (ae-missing-release-tag) "OrganizationTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type ODataQuery = { + filter?: string; + expand?: string[]; + select?: string[]; +}; + +// @public export type OrganizationTransformer = ( organization: MicrosoftGraph.Organization, ) => Promise; -// Warning: (ae-missing-release-tag) "readMicrosoftGraphConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function readMicrosoftGraphConfig( config: Config, ): MicrosoftGraphProviderConfig[]; -// Warning: (ae-missing-release-tag) "readMicrosoftGraphOrg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, @@ -210,15 +183,9 @@ export function readMicrosoftGraphOrg( groups: GroupEntity[]; }>; -// Warning: (ae-missing-release-tag) "UserTransformer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type UserTransformer = ( user: MicrosoftGraph.User, userPhoto?: string, ) => Promise; - -// Warnings were encountered during analysis: -// -// src/microsoftGraph/config.d.ts:28:8 - (tsdoc-undefined-tag) The TSDoc tag "@visibility" is not defined in this configuration ``` diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 63f256afbd..827c9fc14d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -41,6 +41,11 @@ export type ODataQuery = { select?: string[]; }; +/** + * Extends the base msgraph types to include the odata type. + * + * @public + */ export type GroupMember = | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' }) | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 91a6d49b57..4d37e24632 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -19,6 +19,8 @@ import { trimEnd } from 'lodash'; /** * The configuration parameters for a single Microsoft Graph provider. + * + * @public */ export type MicrosoftGraphProviderConfig = { /** @@ -42,8 +44,6 @@ export type MicrosoftGraphProviderConfig = { clientId: string; /** * The OAuth client secret to use for authenticating requests. - * - * @visibility secret */ clientSecret: string; /** @@ -66,6 +66,13 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; }; +/** + * Parses configuration. + * + * @param config - The root of the msgraph config hierarchy + * + * @public + */ export function readMicrosoftGraphConfig( config: Config, ): MicrosoftGraphProviderConfig[] { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts index e6abbebb0a..bbfbd61efe 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/constants.ts @@ -16,17 +16,23 @@ /** * The tenant id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_TENANT_ID_ANNOTATION = 'graph.microsoft.com/tenant-id'; /** * The group id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_GROUP_ID_ANNOTATION = 'graph.microsoft.com/group-id'; /** * The user id used by the Microsoft Graph API + * + * @public */ export const MICROSOFT_GRAPH_USER_ID_ANNOTATION = 'graph.microsoft.com/user-id'; diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts index 41dbc1aed8..fd09d754d4 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/helper.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * Takes an input string and cleans it up to become suitable as an entity name. + * + * @public + */ export function normalizeEntityName(name: string): string { let cleaned = name .trim() diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts index eeb436d317..b0d53b43a2 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ export { MicrosoftGraphClient } from './client'; +export type { ODataQuery } from './client'; export { readMicrosoftGraphConfig } from './config'; export type { MicrosoftGraphProviderConfig } from './config'; export { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 7dd6f83e2e..b2845ac78d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { GroupEntity, stringifyEntityRef, @@ -35,6 +36,12 @@ import { UserTransformer, } from './types'; +/** + * The default implementation of the transformation from a graph user entry to + * a User entity. + * + * @public + */ export async function defaultUserTransformer( user: MicrosoftGraph.User, userPhoto?: string, @@ -208,6 +215,12 @@ export async function readMicrosoftGraphUsersInGroups( return { users }; } +/** + * The default implementation of the transformation from a graph organization + * entry to a Group entity. + * + * @public + */ export async function defaultOrganizationTransformer( organization: MicrosoftGraph.Organization, ): Promise { @@ -258,6 +271,12 @@ function extractGroupName(group: MicrosoftGraph.Group): string { return (group.mailNickname || group.displayName) as string; } +/** + * The default implementation of the transformation from a graph group entry to + * a Group entity. + * + * @public + */ export async function defaultGroupTransformer( group: MicrosoftGraph.Group, groupPhoto?: string, @@ -472,6 +491,11 @@ export function resolveRelations( buildMemberOf(groups, users); } +/** + * Reads an entire org as Group and User entities. + * + * @public + */ export async function readMicrosoftGraphOrg( client: MicrosoftGraphClient, tenantId: string, diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts index fab662cc3f..ef60d7b018 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts @@ -17,15 +17,30 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +/** + * Customize the ingested User entity + * + * @public + */ export type UserTransformer = ( user: MicrosoftGraph.User, userPhoto?: string, ) => Promise; +/** + * Customize the ingested organization Group entity + * + * @public + */ export type OrganizationTransformer = ( organization: MicrosoftGraph.Organization, ) => Promise; +/** + * Customize the ingested Group entity + * + * @public + */ export type GroupTransformer = ( group: MicrosoftGraph.Group, groupPhoto?: string, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index ea396162dd..6d66723970 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Entity, LOCATION_ANNOTATION, @@ -41,6 +42,8 @@ import { /** * Reads user and group entries out of Microsoft Graph, and provides them as * User and Group entities for the catalog. + * + * @public */ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; @@ -91,14 +94,20 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { }, ) {} + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { return `MicrosoftGraphOrgEntityProvider:${this.options.id}`; } + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ async connect(connection: EntityProviderConnection) { this.connection = connection; } + /** + * Runs one complete ingestion loop. Call this method regularly at some + * appropriate cadence. + */ async read() { if (!this.connection) { throw new Error('Not initialized'); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index cb7729e04a..351a4983d9 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -34,6 +34,8 @@ import { /** * Extracts teams and users out of a the Microsoft Graph API. + * + * @public */ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { private readonly providers: MicrosoftGraphProviderConfig[]; From f77bd5c8ff955d2842579706297cc70e25fd5f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 9 Jan 2022 15:21:44 +0100 Subject: [PATCH 45/52] Clean up API reports in backend-common MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/twelve-panthers-move.md | 5 ++ packages/backend-common/api-report.md | 53 ++++++------- .../backend-common/src/cache/CacheClient.ts | 6 +- .../backend-common/src/cache/CacheManager.ts | 7 +- packages/backend-common/src/cache/types.ts | 24 ++++-- .../src/database/DatabaseManager.ts | 75 +++++++++++-------- .../backend-common/src/database/connection.ts | 11 ++- packages/backend-common/src/database/types.ts | 2 +- .../backend-common/src/logging/formats.ts | 6 +- .../backend-common/src/logging/rootLogger.ts | 33 +++++++- .../src/middleware/errorHandler.ts | 6 +- .../src/middleware/statusCheckHandler.ts | 13 +++- .../src/reading/AwsS3UrlReader.ts | 5 ++ .../src/reading/AzureUrlReader.ts | 6 +- .../src/reading/BitbucketUrlReader.ts | 4 +- .../src/reading/FetchUrlReader.ts | 2 +- .../src/reading/GithubUrlReader.ts | 2 +- .../src/reading/GitlabUrlReader.ts | 6 +- .../src/reading/GoogleGcsUrlReader.ts | 6 +- .../backend-common/src/reading/UrlReaders.ts | 13 +++- packages/backend-common/src/reading/types.ts | 7 +- packages/backend-common/src/scm/git.ts | 6 +- .../src/service/createStatusCheckRouter.ts | 19 ++++- packages/backend-common/src/service/types.ts | 12 ++- .../src/util/ContainerRunner.ts | 15 +++- .../src/util/DockerContainerRunner.ts | 6 +- 26 files changed, 246 insertions(+), 104 deletions(-) create mode 100644 .changeset/twelve-panthers-move.md diff --git a/.changeset/twelve-panthers-move.md b/.changeset/twelve-panthers-move.md new file mode 100644 index 0000000000..f52050a488 --- /dev/null +++ b/.changeset/twelve-panthers-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Clean up API reports diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index a62a280dc9..d63416571d 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -34,9 +34,7 @@ import { Server } from 'http'; import * as winston from 'winston'; import { Writable } from 'stream'; -// Warning: (ae-missing-release-tag) "AwsS3UrlReader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class AwsS3UrlReader implements UrlReader { constructor( integration: AwsS3Integration, @@ -59,7 +57,7 @@ export class AwsS3UrlReader implements UrlReader { toString(): string; } -// @public (undocumented) +// @public export class AzureUrlReader implements UrlReader { constructor( integration: AzureIntegration, @@ -114,12 +112,12 @@ export interface CacheClient { ): Promise; } -// @public (undocumented) +// @public export type CacheClientOptions = { defaultTtl?: number; }; -// @public (undocumented) +// @public export type CacheClientSetOptions = { ttl?: number; }; @@ -133,18 +131,17 @@ export class CacheManager { ): CacheManager; } -// @public (undocumented) +// @public export type CacheManagerOptions = { logger?: Logger_2; onError?: (err: Error) => void; }; -// @public (undocumented) +// @public export const coloredFormat: winston.Logform.Format; -// @public (undocumented) +// @public export interface ContainerRunner { - // (undocumented) runContainer(opts: RunContainerOptions): Promise; } @@ -157,7 +154,7 @@ export function createDatabaseClient( overrides?: Partial, ): Knex; -// @public (undocumented) +// @public export function createRootLogger( options?: winston.LoggerOptions, env?: NodeJS.ProcessEnv, @@ -166,14 +163,14 @@ export function createRootLogger( // @public export function createServiceBuilder(_module: NodeModule): ServiceBuilder; -// @public (undocumented) +// @public export function createStatusCheckRouter(options: { logger: Logger_2; path?: string; statusCheck?: StatusCheck; }): Promise; -// @public (undocumented) +// @public export class DatabaseManager { forPlugin(pluginId: string): PluginDatabaseManager; static fromConfig( @@ -187,7 +184,7 @@ export type DatabaseManagerOptions = { migrations?: PluginDatabaseManager['migrations']; }; -// @public (undocumented) +// @public export class DockerContainerRunner implements ContainerRunner { constructor(options: { dockerClient: Docker }); // (undocumented) @@ -205,7 +202,7 @@ export function errorHandler( options?: ErrorHandlerOptions, ): ErrorRequestHandler; -// @public (undocumented) +// @public export type ErrorHandlerOptions = { showStackTraces?: boolean; logger?: Logger_2; @@ -218,13 +215,13 @@ export type FromReadableArrayOptions = Array<{ path: string; }>; -// @public (undocumented) +// @public export function getRootLogger(): winston.Logger; // @public export function getVoidLogger(): winston.Logger; -// @public (undocumented) +// @public export class Git { // (undocumented) add(options: { dir: string; filepath: string }): Promise; @@ -310,7 +307,7 @@ export class GithubUrlReader implements UrlReader { toString(): string; } -// @public (undocumented) +// @public export class GitlabUrlReader implements UrlReader { constructor( integration: GitLabIntegration, @@ -398,7 +395,7 @@ export type ReadTreeResponseDirOptions = { targetDir?: string; }; -// @public (undocumented) +// @public export interface ReadTreeResponseFactory { // (undocumented) fromReadableArray( @@ -448,7 +445,7 @@ export type ReadUrlResponse = { // @public export function requestLoggingHandler(logger?: Logger_2): RequestHandler; -// @public (undocumented) +// @public export type RequestLoggingHandlerFactory = ( logger?: Logger_2, ) => RequestHandler; @@ -459,7 +456,7 @@ export function resolvePackagePath(name: string, ...paths: string[]): string; // @public export function resolveSafeChildPath(base: string, path: string): string; -// @public (undocumented) +// @public export type RunContainerOptions = { imageName: string; command?: string | string[]; @@ -508,7 +505,7 @@ export class ServerTokenManager implements TokenManager { static noop(): TokenManager; } -// @public (undocumented) +// @public export type ServiceBuilder = { loadConfig(config: Config): ServiceBuilder; setPort(port: number): ServiceBuilder; @@ -534,7 +531,7 @@ export type ServiceBuilder = { start(): Promise; }; -// @public (undocumented) +// @public export function setRootLogger(newLogger: winston.Logger): void; // @public @deprecated @@ -554,7 +551,7 @@ export class SingleHostDiscovery implements PluginEndpointDiscovery { getExternalBaseUrl(pluginId: string): Promise; } -// @public (undocumented) +// @public export type StatusCheck = () => Promise; // @public @@ -562,7 +559,7 @@ export function statusCheckHandler( options?: StatusCheckHandlerOptions, ): Promise; -// @public (undocumented) +// @public export interface StatusCheckHandlerOptions { statusCheck?: StatusCheck; } @@ -597,7 +594,7 @@ export class UrlReaders { static default(options: UrlReadersOptions): UrlReader; } -// @public (undocumented) +// @public export type UrlReadersOptions = { config: Config; logger: Logger_2; @@ -612,8 +609,4 @@ export function useHotCleanup( // @public export function useHotMemoize(_module: NodeModule, valueFactory: () => T): T; - -// Warnings were encountered during analysis: -// -// src/database/types.d.ts:23:12 - (tsdoc-undefined-tag) The TSDoc tag "@default" is not defined in this configuration ``` diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts index 8e046f7f19..96982edeaa 100644 --- a/packages/backend-common/src/cache/CacheClient.ts +++ b/packages/backend-common/src/cache/CacheClient.ts @@ -22,7 +22,11 @@ type CacheClientArgs = { client: Keyv; }; -/** @public */ +/** + * Options passed to {@link CacheClient.set}. + * + * @public + */ export type CacheClientSetOptions = { /** * Optional TTL in milliseconds. Defaults to the TTL provided when the client diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index 896c08fb94..66e9849e77 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -55,8 +55,8 @@ export class CacheManager { private readonly errorHandler: CacheManagerOptions['onError']; /** - * Creates a new CacheManager instance by reading from the `backend` config - * section, specifically the `.cache` key. + * Creates a new {@link CacheManager} instance by reading from the `backend` + * config section, specifically the `.cache` key. * * @param config - The loaded application configuration. */ @@ -93,7 +93,8 @@ export class CacheManager { /** * Generates a PluginCacheManager for consumption by plugins. * - * @param pluginId - The plugin that the cache manager should be created for. Plugin names should be unique. + * @param pluginId - The plugin that the cache manager should be created for. + * Plugin names should be unique. */ forPlugin(pluginId: string): PluginCacheManager { return { diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts index 70c46770a8..5cf8323c52 100644 --- a/packages/backend-common/src/cache/types.ts +++ b/packages/backend-common/src/cache/types.ts @@ -17,7 +17,11 @@ import { Logger } from 'winston'; import { CacheClient } from './CacheClient'; -/** @public */ +/** + * Options given when constructing a {@link CacheClient}. + * + * @public + */ export type CacheClientOptions = { /** * An optional default TTL (in milliseconds) to be set when getting a client @@ -27,7 +31,11 @@ export type CacheClientOptions = { defaultTtl?: number; }; -/** @public */ +/** + * Options given when constructing a {@link CacheManager}. + * + * @public + */ export type CacheManagerOptions = { /** * An optional logger for use by the PluginCacheManager. @@ -42,17 +50,19 @@ export type CacheManagerOptions = { }; /** - * The PluginCacheManager manages access to cache stores that Plugins get. + * Manages access to cache stores that plugins get. * * @public */ export type PluginCacheManager = { /** - * getClient provides backend plugins cache connections for itself. + * Provides backend plugins cache connections for themselves. * - * The purpose of this method is to allow plugins to get isolated data - * stores so that plugins are discouraged from cache-level integration - * and/or cache key collisions. + * @remarks + * + * The purpose of this method is to allow plugins to get isolated data stores + * so that plugins are discouraged from cache-level integration and/or cache + * key collisions. */ getClient: (options?: CacheClientOptions) => CacheClient; }; diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 5959c60c83..3aadd30148 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -38,7 +38,7 @@ function pluginPath(pluginId: string): string { } /** - * Configuration options object. + * Creation options for {@link DatabaseManager}. * * @public */ @@ -46,15 +46,20 @@ export type DatabaseManagerOptions = { migrations?: PluginDatabaseManager['migrations']; }; -/** @public */ +/** + * Manages database connections for Backstage backend plugins. + * + * The database manager allows the user to set connection and client settings on + * a per pluginId basis by defining a database config block under + * `plugin.` in addition to top level defaults. Optionally, a user may + * set `prefix` which is used to prefix generated database names if config is + * not provided. + * + * @public + */ export class DatabaseManager { /** - * Creates a DatabaseManager from `backend.database` config. - * - * The database manager allows the user to set connection and client settings on a per pluginId - * basis by defining a database config block under `plugin.` in addition to top level - * defaults. Optionally, a user may set `prefix` which is used to prefix generated database - * names if config is not provided. + * Creates a {@link DatabaseManager} from `backend.database` config. * * @param config - The loaded application configuration. * @param options - An optional configuration object. @@ -108,7 +113,7 @@ export class DatabaseManager { * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is * `schema`, it will fallback to using the default database for the knex instance. * - * @param pluginId Lookup the database name for given plugin + * @param pluginId - Lookup the database name for given plugin * @returns String representing the plugin's database name */ private getDatabaseName(pluginId: string): string | undefined { @@ -143,12 +148,13 @@ export class DatabaseManager { /** * Provides the client type which should be used for a given plugin. * - * The client type is determined by plugin specific config if present. Otherwise the base - * client is used as the fallback. + * The client type is determined by plugin specific config if present. + * Otherwise the base client is used as the fallback. * - * @param pluginId Plugin to get the client type for - * @returns Object with client type returned as `client` and boolean representing whether - * or not the client was overridden as `overridden` + * @param pluginId - Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean + * representing whether or not the client was overridden as + * `overridden` */ private getClientType(pluginId: string): { client: string; @@ -169,8 +175,8 @@ export class DatabaseManager { /** * Provides the knexConfig which should be used for a given plugin. * - * @param pluginId Plugin to get the knexConfig for - * @returns the merged kexConfig value or undefined if it isn't specified + * @param pluginId - Plugin to get the knexConfig for + * @returns The merged knexConfig value or undefined if it isn't specified */ private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { const pluginConfig = this.config @@ -197,13 +203,15 @@ export class DatabaseManager { } /** - * Provides a Knex connection plugin config by combining base and plugin config. + * Provides a Knex connection plugin config by combining base and plugin + * config. * - * This method provides a baseConfig for a plugin database connector. If the client type - * has not been overridden, the global connection config will be included with plugin - * specific config as the base. Values from the plugin connection take precedence over the - * base. Base database name is omitted for all supported databases excluding SQLite unless - * `pluginDivisionMode` is set to `schema`. + * This method provides a baseConfig for a plugin database connector. If the + * client type has not been overridden, the global connection config will be + * included with plugin specific config as the base. Values from the plugin + * connection take precedence over the base. Base database name is omitted for + * all supported databases excluding SQLite unless `pluginDivisionMode` is set + * to `schema`. */ private getConnectionConfig( pluginId: string, @@ -249,9 +257,10 @@ export class DatabaseManager { /** * Provides a Knex database config for a given plugin. * - * This method provides a Knex configuration object along with the plugin's client type. + * This method provides a Knex configuration object along with the plugin's + * client type. * - * @param pluginId The plugin that the database config should correspond with + * @param pluginId - The plugin that the database config should correspond with */ private getConfigForPlugin(pluginId: string): Knex.Config { const { client } = this.getClientType(pluginId); @@ -264,20 +273,21 @@ export class DatabaseManager { } /** - * Provides a partial Knex.Config database schema override for a given plugin. + * Provides a partial `Knex.Config` database schema override for a given + * plugin. * - * @param pluginId Target plugin to get database schema override - * @returns Partial Knex.Config with database schema override + * @param pluginId - Target plugin to get database schema override + * @returns Partial `Knex.Config` with database schema override */ private getSchemaOverrides(pluginId: string): Knex.Config | undefined { return createSchemaOverride(this.getClientType(pluginId).client, pluginId); } /** - * Provides a partial Knex.Config database name override for a given plugin. + * Provides a partial `Knex.Config`• database name override for a given plugin. * - * @param pluginId Target plugin to get database name override - * @returns Partial Knex.Config with database name override + * @param pluginId - Target plugin to get database name override + * @returns Partial `Knex.Config` with database name override */ private getDatabaseOverrides(pluginId: string): Knex.Config { const databaseName = this.getDatabaseName(pluginId); @@ -289,8 +299,9 @@ export class DatabaseManager { /** * Provides a scoped Knex client for a plugin as per application config. * - * @param pluginId Plugin to get a Knex client for - * @returns Promise which resolves to a scoped Knex database client for a plugin + * @param pluginId - Plugin to get a Knex client for + * @returns Promise which resolves to a scoped Knex database client for a + * plugin */ private async getDatabase(pluginId: string): Promise { const pluginConfig = new ConfigReader( diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index 3308a26573..7fc9df060d 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -58,7 +58,7 @@ export function createDatabaseClient( } /** - * Alias for createDatabaseClient + * Alias for {@link createDatabaseClient} * * @public * @deprecated Use createDatabaseClient instead @@ -100,7 +100,8 @@ export async function ensureSchemaExists( } /** - * Provides a Knex.Config object with the provided database name for a given client. + * Provides a `Knex.Config` object with the provided database name for a given + * client. */ export function createNameOverride( client: string, @@ -117,7 +118,8 @@ export function createNameOverride( } /** - * Provides a Knex.Config object with the provided database schema for a given client. Currently only supported by `pg`. + * Provides a `Knex.Config` object with the provided database schema for a given + * client. Currently only supported by `pg`. */ export function createSchemaOverride( client: string, @@ -156,7 +158,8 @@ export function parseConnectionString( } /** - * Normalizes a connection config or string into an object which can be passed to Knex. + * Normalizes a connection config or string into an object which can be passed + * to Knex. */ export function normalizeConnection( connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 344f1088b8..2fa8b749a8 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -38,7 +38,7 @@ export interface PluginDatabaseManager { /** * skip database migrations. Useful if connecting to a read-only database. * - * @default false + * @defaultValue false */ skip?: boolean; }; diff --git a/packages/backend-common/src/logging/formats.ts b/packages/backend-common/src/logging/formats.ts index 0477136e63..53eb55e790 100644 --- a/packages/backend-common/src/logging/formats.ts +++ b/packages/backend-common/src/logging/formats.ts @@ -31,7 +31,11 @@ const coloredTemplate = (info: TransformableInfo) => { return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`; }; -/** @public */ +/** + * A logging format that adds coloring to console output. + * + * @public + */ export const coloredFormat = winston.format.combine( winston.format.timestamp(), winston.format.colorize({ diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index 12db7d42a1..87180bde8f 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -23,12 +23,29 @@ import { escapeRegExp } from '../util/escapeRegExp'; let rootLogger: winston.Logger; let redactionRegExp: RegExp | undefined; -/** @public */ +/** + * Gets the current root logger. + * + * @public + */ export function getRootLogger(): winston.Logger { return rootLogger; } -/** @public */ +/** + * Sets a completely custom default "root" logger. + * + * @remarks + * + * This is the logger instance that will be the foundation for all other logger + * instances passed to plugins etc, in a given backend. + * + * Only use this if you absolutely need to make a completely custom logger. + * Normally if you want to make light adaptations to the default logger + * behavior, you would instead call {@link createRootLogger}. + * + * @public + */ export function setRootLogger(newLogger: winston.Logger) { rootLogger = newLogger; } @@ -67,7 +84,17 @@ function redactLogLine(info: winston.Logform.TransformableInfo) { return info; } -/** @public */ +/** + * Creates a default "root" logger. This also calls {@link setRootLogger} under + * the hood. + * + * @remarks + * + * This is the logger instance that will be the foundation for all other logger + * instances passed to plugins etc, in a given backend. + * + * @public + */ export function createRootLogger( options: winston.LoggerOptions = {}, env = process.env, diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index 47c9285b18..6077b6a140 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -28,7 +28,11 @@ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; import { Logger } from 'winston'; import { getRootLogger } from '../logging'; -/** @public */ +/** + * Options passed to the {@link errorHandler} middleware. + * + * @public + */ export type ErrorHandlerOptions = { /** * Whether error response bodies should show error stack traces or not. diff --git a/packages/backend-common/src/middleware/statusCheckHandler.ts b/packages/backend-common/src/middleware/statusCheckHandler.ts index a0ba59fac4..4655d610fe 100644 --- a/packages/backend-common/src/middleware/statusCheckHandler.ts +++ b/packages/backend-common/src/middleware/statusCheckHandler.ts @@ -16,10 +16,19 @@ import { NextFunction, Request, Response, RequestHandler } from 'express'; -/** @public */ +/** + * A custom status checking function, passed to {@link statusCheckHandler} and + * {@link createStatusCheckRouter}. + * + * @public + */ export type StatusCheck = () => Promise; -/** @public */ +/** + * Options passed to {@link statusCheckHandler}. + * + * @public + */ export interface StatusCheckHandlerOptions { /** * Optional status function which returns a message. diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.ts b/packages/backend-common/src/reading/AwsS3UrlReader.ts index 1fca72e91c..e64334321d 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.ts @@ -94,6 +94,11 @@ const parseURL = ( }; }; +/** + * Implements a {@link UrlReader} for AWS S3 buckets. + * + * @public + */ export class AwsS3UrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index 37d97b8941..d6ceaec258 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -38,7 +38,11 @@ import { ReadUrlResponse, } from './types'; -/** @public */ +/** + * Implements a {@link UrlReader} for Azure repos. + * + * @public + */ export class AzureUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index b0e8056370..2006637545 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -41,8 +41,8 @@ import { } from './types'; /** - * A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as - * the one exposed by Bitbucket Cloud itself. + * Implements a {@link UrlReader} for files from Bitbucket v1 and v2 APIs, such + * as the one exposed by Bitbucket Cloud itself. * * @public */ diff --git a/packages/backend-common/src/reading/FetchUrlReader.ts b/packages/backend-common/src/reading/FetchUrlReader.ts index 7090d26d35..cb873ebdf8 100644 --- a/packages/backend-common/src/reading/FetchUrlReader.ts +++ b/packages/backend-common/src/reading/FetchUrlReader.ts @@ -27,7 +27,7 @@ import { import path from 'path'; /** - * A UrlReader that does a plain fetch of the URL. + * A {@link UrlReader} that does a plain fetch of the URL. * * @public */ diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 6b18cfbe1a..72a8f0b90e 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -50,7 +50,7 @@ export type GhBlobResponse = RestEndpointMethodTypes['git']['getBlob']['response']['data']; /** - * A processor that adds the ability to read files from GitHub v3 APIs, such as + * Implements a {@link UrlReader} for files through the GitHub v3 APIs, such as * the one exposed by GitHub itself. * * @public diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 2444e317a7..d0e43fea59 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -39,7 +39,11 @@ import { } from './types'; import { trimEnd } from 'lodash'; -/** @public */ +/** + * Implements a {@link UrlReader} for files on GitLab. + * + * @public + */ export class GitlabUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, treeResponseFactory }) => { const integrations = ScmIntegrations.fromConfig(config); diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts index f1684c2945..baa9477c6a 100644 --- a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts @@ -48,7 +48,11 @@ const parseURL = ( }; }; -/** @public */ +/** + * Implements a {@link UrlReader} for files on Google GCS. + * + * @public + */ export class GoogleGcsUrlReader implements UrlReader { static factory: ReaderFactory = ({ config, logger }) => { if (!config.has('integrations.googleGcs')) { diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index a920ec080b..c0aae25808 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -27,7 +27,11 @@ import { FetchUrlReader } from './FetchUrlReader'; import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; -/** @public */ +/** + * Creation options for {@link UrlReaders}. + * + * @public + */ export type UrlReadersOptions = { /** Root config object */ config: Config; @@ -38,13 +42,13 @@ export type UrlReadersOptions = { }; /** - * UrlReaders provide various utilities related to the UrlReader interface. + * Helps construct {@link UrlReader}s. * * @public */ export class UrlReaders { /** - * Creates a UrlReader without any known types. + * Creates a custom {@link UrlReader} wrapper for your own set of factories. */ static create(options: UrlReadersOptions): UrlReader { const { logger, config, factories } = options; @@ -65,7 +69,8 @@ export class UrlReaders { } /** - * Creates a UrlReader that includes all the default factories from this package. + * Creates a {@link UrlReader} wrapper that includes all the default factories + * from this package. * * Any additional factories passed will be loaded before the default ones. */ diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index 46be7aa1b6..9505778705 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -281,7 +281,12 @@ export type FromReadableArrayOptions = Array<{ path: string; }>; -/** @public */ +/** + * A factory for response factories that handle the unpacking and inspection of + * complex responses such as archive data. + * + * @public + */ export interface ReadTreeResponseFactory { fromTarArchive( options: ReadTreeResponseFactoryOptions, diff --git a/packages/backend-common/src/scm/git.ts b/packages/backend-common/src/scm/git.ts index 0446f08767..9cf84e9872 100644 --- a/packages/backend-common/src/scm/git.ts +++ b/packages/backend-common/src/scm/git.ts @@ -33,7 +33,11 @@ From : https://isomorphic-git.org/docs/en/onAuth with fix for GitHub Azure 'notempty' token */ -/** @public */ +/** + * A convenience wrapper around the `isomorphic-git` library. + * + * @public + */ export class Git { private constructor( private readonly config: { diff --git a/packages/backend-common/src/service/createStatusCheckRouter.ts b/packages/backend-common/src/service/createStatusCheckRouter.ts index 0d0f93f25c..7c916da0e7 100644 --- a/packages/backend-common/src/service/createStatusCheckRouter.ts +++ b/packages/backend-common/src/service/createStatusCheckRouter.ts @@ -19,9 +19,26 @@ import Router from 'express-promise-router'; import express from 'express'; import { errorHandler, statusCheckHandler, StatusCheck } from '../middleware'; -/** @public */ +/** + * Creates a default status checking router, that you can add to your express + * app. + * + * @remarks + * + * This adds a `/healthcheck` route (or any other path, if given as an + * argument), which your infra can call to see if the service is ready to serve + * requests. + * + * @public + */ export async function createStatusCheckRouter(options: { logger: Logger; + /** + * The path (including a leading slash) that the health check should be + * mounted on. + * + * @defaultValue '/healthcheck' + */ path?: string; /** * If not implemented, the default express middleware always returns 200. diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index 3e94196006..5df9f3fa5d 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -20,7 +20,11 @@ import { Router, RequestHandler, ErrorRequestHandler } from 'express'; import { Server } from 'http'; import { Logger } from 'winston'; -/** @public */ +/** + * A helper for building backend service instances. + * + * @public + */ export type ServiceBuilder = { /** * Sets the service parameters based on configuration. @@ -119,5 +123,9 @@ export type ServiceBuilder = { start(): Promise; }; -/** @public */ +/** + * A factory for request loggers. + * + * @public + */ export type RequestLoggingHandlerFactory = (logger?: Logger) => RequestHandler; diff --git a/packages/backend-common/src/util/ContainerRunner.ts b/packages/backend-common/src/util/ContainerRunner.ts index be861501c3..22e40ec455 100644 --- a/packages/backend-common/src/util/ContainerRunner.ts +++ b/packages/backend-common/src/util/ContainerRunner.ts @@ -16,7 +16,11 @@ import { Writable } from 'stream'; -/** @public */ +/** + * Options passed to the {@link ContainerRunner.runContainer} method. + * + * @public + */ export type RunContainerOptions = { imageName: string; command?: string | string[]; @@ -28,7 +32,14 @@ export type RunContainerOptions = { pullImage?: boolean; }; -/** @public */ +/** + * Handles the running of containers, on behalf of others. + * + * @public + */ export interface ContainerRunner { + /** + * Runs a container image to completion. + */ runContainer(opts: RunContainerOptions): Promise; } diff --git a/packages/backend-common/src/util/DockerContainerRunner.ts b/packages/backend-common/src/util/DockerContainerRunner.ts index 424913f316..522328c2ec 100644 --- a/packages/backend-common/src/util/DockerContainerRunner.ts +++ b/packages/backend-common/src/util/DockerContainerRunner.ts @@ -24,7 +24,11 @@ export type UserOptions = { User?: string; }; -/** @public */ +/** + * A {@link ContainerRunner} for Docker containers. + * + * @public + */ export class DockerContainerRunner implements ContainerRunner { private readonly dockerClient: Docker; From a1ac761a9d618836af85e6e9870e865014e10782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sun, 9 Jan 2022 22:14:51 +0100 Subject: [PATCH 46/52] Bump markdown-it to the fixed version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e409fba60a..292d02073c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20045,9 +20045,9 @@ markdown-escapes@^1.0.0: integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== markdown-it@^12.2.0: - version "12.2.0" - resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.2.0.tgz#091f720fd5db206f80de7a8d1f1a7035fd0d38db" - integrity sha512-Wjws+uCrVQRqOoJvze4HCqkKl1AsSh95iFAeQDwnyfxM09divCBSXlDR1uTvyUP3Grzpn4Ru8GeCxYPM8vkCQg== + version "12.3.2" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== dependencies: argparse "^2.0.1" entities "~2.1.0" From 9fbd3b90ae98e89f26386253178ee963f8da9fcc Mon Sep 17 00:00:00 2001 From: Bert Huang Date: Mon, 10 Jan 2022 13:22:53 +1300 Subject: [PATCH 47/52] fix: Register plugin to prioritise Component kind for entityRef Signed-off-by: Bert Huang --- .changeset/cold-steaks-flash.md | 5 + .../actions/builtin/catalog/register.test.ts | 129 +++++++++++++++--- .../actions/builtin/catalog/register.ts | 19 ++- 3 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 .changeset/cold-steaks-flash.md diff --git a/.changeset/cold-steaks-flash.md b/.changeset/cold-steaks-flash.md new file mode 100644 index 0000000000..74cdcbdec2 --- /dev/null +++ b/.changeset/cold-steaks-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +fix: Register plugin to prioritise Component kind for entityRef diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts index e1f8823487..d257bced38 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -117,7 +117,7 @@ describe('catalog:register', () => { ); }); - it('should register location in catalog and return the entity and not the generated location', async () => { + it('should return entityRef with the Component entity and not the generated location', async () => { addLocation .mockResolvedValueOnce({ entities: [], @@ -131,6 +131,13 @@ describe('catalog:register', () => { }, kind: 'Location', } as Entity, + { + metadata: { + namespace: 'default', + name: 'test', + }, + kind: 'Api', + } as Entity, { metadata: { namespace: 'default', @@ -138,6 +145,13 @@ describe('catalog:register', () => { }, kind: 'Component', } as Entity, + { + metadata: { + namespace: 'default', + name: 'test', + }, + kind: 'Template', + } as Entity, ], }); await action.handler({ @@ -146,32 +160,103 @@ describe('catalog:register', () => { catalogInfoUrl: 'http://foo/var', }, }); - - expect(addLocation).toHaveBeenNthCalledWith( - 1, - { - type: 'url', - target: 'http://foo/var', - }, - {}, - ); - expect(addLocation).toHaveBeenNthCalledWith( - 2, - { - dryRun: true, - type: 'url', - target: 'http://foo/var', - }, - {}, - ); - expect(mockContext.output).toBeCalledWith( 'entityRef', 'component:default/test', ); + }); + + it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => { + addLocation + .mockResolvedValueOnce({ + entities: [], + }) + .mockResolvedValueOnce({ + entities: [ + { + metadata: { + namespace: 'default', + name: 'generated-1238', + }, + kind: 'Location', + } as Entity, + { + metadata: { + namespace: 'default', + name: 'test', + }, + kind: 'Api', // should return this one + } as Entity, + { + metadata: { + namespace: 'default', + name: 'test', + }, + kind: 'Template', + } as Entity, + ], + }); + await action.handler({ + ...mockContext, + input: { + catalogInfoUrl: 'http://foo/var', + }, + }); + expect(mockContext.output).toBeCalledWith('entityRef', 'api:default/test'); + }); + + it('should return entityRef with the first entity if no non-generated entities can be found', async () => { + addLocation + .mockResolvedValueOnce({ + entities: [], + }) + .mockResolvedValueOnce({ + entities: [ + { + metadata: { + namespace: 'default', + name: 'generated-1238', + }, + kind: 'Location', + } as Entity, + { + metadata: { + namespace: 'default', + name: 'generated-1238', + }, + kind: 'Template', + } as Entity, + ], + }); + await action.handler({ + ...mockContext, + input: { + catalogInfoUrl: 'http://foo/var', + }, + }); expect(mockContext.output).toBeCalledWith( - 'catalogInfoUrl', - 'http://foo/var', + 'entityRef', + 'location:default/generated-1238', + ); + }); + + it('should not return entityRef if there are no entites', async () => { + addLocation + .mockResolvedValueOnce({ + entities: [], + }) + .mockResolvedValueOnce({ + entities: [], + }); + await action.handler({ + ...mockContext, + input: { + catalogInfoUrl: 'http://foo/var', + }, + }); + expect(mockContext.output).not.toBeCalledWith( + 'entityRef', + expect.any(String), ); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index 0c08131925..249939b0e6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -125,9 +125,22 @@ export function createCatalogRegisterAction(options: { if (result.entities.length > 0) { const { entities } = result; - const entity = - entities.find(e => !e.metadata.name.startsWith('generated-')) ?? - entities[0]; + let entity: any; + // prioritise 'Component' type as it is the most central kind of entity + entity = entities.find( + (e: any) => + !e.metadata.name.startsWith('generated-') && + e.kind === 'Component', + ); + if (!entity) { + entity = entities.find( + (e: any) => !e.metadata.name.startsWith('generated-'), + ); + } + if (!entity) { + entity = entities[0]; + } + ctx.output('entityRef', stringifyEntityRef(entity)); } } catch (e) { From 8319c54e28fe9923062c721f1994971e127a5693 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:13:59 +0000 Subject: [PATCH 48/52] build(deps): bump @octokit/webhooks from 9.18.0 to 9.22.0 Bumps [@octokit/webhooks](https://github.com/octokit/webhooks.js) from 9.18.0 to 9.22.0. - [Release notes](https://github.com/octokit/webhooks.js/releases) - [Commits](https://github.com/octokit/webhooks.js/compare/v9.18.0...v9.22.0) --- updated-dependencies: - dependency-name: "@octokit/webhooks" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index e409fba60a..14c04bd646 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5209,19 +5209,19 @@ resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db" integrity sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig== -"@octokit/webhooks-types@4.15.0": - version "4.15.0" - resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-4.15.0.tgz#1158cba6578237d60957a37963a4a05654f5668b" - integrity sha512-s9LgKsUzq/JH3PWDjaD/m1DIlC/QWgBWbmXVqjdxJXJQBA67KZrLWjStVlYPf0mWlVZ1MOKphDyHiOGCbs0+Kg== +"@octokit/webhooks-types@5.2.0": + version "5.2.0" + resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.2.0.tgz#9d1d451f37460107409c81cab04dd473108abb02" + integrity sha512-OZhKy1w8/GF4GWtdiJc+o8sloWAHRueGB78FWFLZnueK7EHV9MzDVr4weJZMflJwMK4uuYLzcnJVnAoy3yB35g== "@octokit/webhooks@^9.14.1": - version "9.18.0" - resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.18.0.tgz#19cc70e1ef281e33d830ea23e8011d25d8051f7f" - integrity sha512-N2hP7vCouKk9UWZxvqgWTPbp34i6g9Om/jk+TZeZ5Z+VsKjXvGtONlEd9H8DM1yOeEC+ARDpfhraX6UsK5tesQ== + version "9.22.0" + resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.22.0.tgz#07a36a10358d39c1870758fae2b1ad3c24ca578d" + integrity sha512-wUd7nGfDRHG6xkz311djmq6lIB2tQ+r94SNkyv9o0bQhOsrkwH8fQCM7uVsbpkGUU2lqCYsVoa8z/UC9HJgRaw== dependencies: "@octokit/request-error" "^2.0.2" "@octokit/webhooks-methods" "^2.0.0" - "@octokit/webhooks-types" "4.15.0" + "@octokit/webhooks-types" "5.2.0" aggregate-error "^3.1.0" "@open-draft/until@^1.0.3": From 06e2d79569667b8cd7b7e4be701d63ee52e099fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 04:16:23 +0000 Subject: [PATCH 49/52] build(deps): bump @microsoft/microsoft-graph-types from 2.8.0 to 2.11.0 Bumps [@microsoft/microsoft-graph-types](https://github.com/microsoftgraph/msgraph-typescript-typings) from 2.8.0 to 2.11.0. - [Release notes](https://github.com/microsoftgraph/msgraph-typescript-typings/releases) - [Commits](https://github.com/microsoftgraph/msgraph-typescript-typings/commits) --- updated-dependencies: - dependency-name: "@microsoft/microsoft-graph-types" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e409fba60a..15fc04072b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4745,9 +4745,9 @@ integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA== "@microsoft/microsoft-graph-types@^2.6.0": - version "2.8.0" - resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.8.0.tgz#c3b538f99028e8609c5ebf95a494318a8f3d9201" - integrity sha512-NDgLn9IhYD/+nCeeGAi1JM7xTFqaM6rkXfLfiC1xvXy48BGBUrAf8fNFq5fkzBvGY8HfjzdPIkrJkfvLL+rzDQ== + version "2.11.0" + resolved "https://registry.npmjs.org/@microsoft/microsoft-graph-types/-/microsoft-graph-types-2.11.0.tgz#0e1d3a0795855fc726e08836b1d3c4a72a8bcd00" + integrity sha512-v4Wuxp+kbcxeJGmb2UHbcukNr05XItFYXL+U3ReignI3Vl8tp1vfq0hkqP35Fun2QpqHJiu8Rkxj1MUF8d82ag== "@microsoft/tsdoc-config@~0.15.2": version "0.15.2" From 7848b5047854494d0c66a222c0aa748a1de0e89e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Jan 2022 09:55:50 +0100 Subject: [PATCH 50/52] chore: bump logform and fixing the colors issue Signed-off-by: blam --- packages/backend-common/package.json | 2 +- yarn.lock | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index adbb6fed18..7165992607 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -58,7 +58,7 @@ "keyv-memcache": "^1.2.5", "knex": "^0.95.1", "lodash": "^4.17.21", - "logform": "^2.1.1", + "logform": "^2.3.2", "minimatch": "^3.0.4", "minimist": "^1.2.5", "morgan": "^1.10.0", diff --git a/yarn.lock b/yarn.lock index e409fba60a..81d0ca441e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11587,7 +11587,7 @@ colors@1.0.3: resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= -colors@^1.1.2, colors@^1.2.1: +colors@1.4.0, colors@^1.1.2, colors@^1.2.1: version "1.4.0" resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== @@ -19796,7 +19796,7 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -logform@^2.1.1, logform@^2.2.0: +logform@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/logform/-/logform-2.3.0.tgz#a3997a05985de2ebd325ae0d166dffc9c6fe6b57" integrity sha512-graeoWUH2knKbGthMtuG1EfaSPMZFZBIrhuJHhkS5ZseFBrc7DupCzihOQAzsK/qIKPQaPJ/lFQFctILUY5ARQ== @@ -19807,6 +19807,17 @@ logform@^2.1.1, logform@^2.2.0: safe-stable-stringify "^1.1.0" triple-beam "^1.3.0" +logform@^2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/logform/-/logform-2.3.2.tgz#68babe6a74ab09a1fd15a9b1e6cbc7713d41cb5b" + integrity sha512-V6JiPThZzTsbVRspNO6TmHkR99oqYTs8fivMBYQkjZj6rxW92KxtDCPE6IkAk1DNBnYKNkjm4jYBm6JDUcyhOA== + dependencies: + colors "1.4.0" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^1.1.0" + triple-beam "^1.3.0" + loglevel@^1.6.7: version "1.6.8" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" From db5310e25e7faec0a179c1dcb3e2ad1ee8b40a59 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 10 Jan 2022 10:00:08 +0100 Subject: [PATCH 51/52] chore: added changeset Signed-off-by: blam --- .changeset/popular-cycles-work.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-cycles-work.md diff --git a/.changeset/popular-cycles-work.md b/.changeset/popular-cycles-work.md new file mode 100644 index 0000000000..939f59de94 --- /dev/null +++ b/.changeset/popular-cycles-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +bump `logform` to use fixed version of `color` dependency From bdf1419d20f91230a6b12bb2a96d252f45a6ca56 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 10 Jan 2022 11:40:07 +0100 Subject: [PATCH 52/52] [Home] first homepage template to storybook + two more home components (#8434) * add new toolkit component Signed-off-by: Emma Indal * add toolkit content test Signed-off-by: Emma Indal * add key to toolkit items Signed-off-by: Emma Indal * add reusable company logo component Signed-off-by: Emma Indal * export root ref from search plugin Signed-off-by: Emma Indal * add homepage template to storybook Signed-off-by: Emma Indal * export new homepage components from plugin Signed-off-by: Emma Indal * fix imports Signed-off-by: Emma Indal * add search plugin to dependencies Signed-off-by: Emma Indal * add home plugin changeset Signed-off-by: Emma Indal * add storybook changeset Signed-off-by: Emma Indal * delete changeset for storybook Signed-off-by: Emma Indal * delete duplicated search context provider Signed-off-by: Emma Indal * use list item text props Signed-off-by: Emma Indal * update home plugin documentation + add customize app docs Signed-off-by: Emma Indal * named exports Signed-off-by: Emma Indal * add homepage components to storybook Signed-off-by: Emma Indal * use app config as default for company logo Signed-off-by: Emma Indal * rename homepage template Signed-off-by: Emma Indal * use props instead of context for tools, update Content component of card extension to accept props Signed-off-by: Emma Indal * fix api reports + docstrings + markers Signed-off-by: Emma Indal * export ToolkitContentProps instead of Tool Signed-off-by: Emma Indal * docs and stories fixups Signed-off-by: Emma Indal * do not export rootRouteRef from search plugin Signed-off-by: Emma Indal * update docs links Signed-off-by: Emma Indal * change unpacking of props Signed-off-by: Emma Indal * delete extra fragment Signed-off-by: Emma Indal * fixup searchbar border styles Signed-off-by: Emma Indal --- .changeset/nervous-starfishes-report.md | 5 + docs/getting-started/app-custom-theme.md | 70 ++++++++++ packages/storybook/.storybook/main.js | 1 + plugins/home/README.md | 10 +- plugins/home/api-report.md | 50 ++++--- plugins/home/package.json | 1 + plugins/home/src/extensions.tsx | 18 ++- .../CompanyLogo/CompanyLogo.stories.tsx | 79 +++++++++++ .../CompanyLogo/CompanyLogo.test.tsx | 43 ++++++ .../CompanyLogo/CompanyLogo.tsx | 43 ++++++ .../homePageComponents/CompanyLogo/index.ts | 16 +++ .../Toolkit/Content.test.tsx | 36 +++++ .../homePageComponents/Toolkit/Content.tsx | 88 ++++++++++++ .../Toolkit/Toolkit.stories.tsx | 40 ++++++ .../src/homePageComponents/Toolkit/index.ts | 18 +++ plugins/home/src/homePageComponents/index.ts | 18 +++ plugins/home/src/index.ts | 2 + plugins/home/src/plugin.ts | 29 ++++ .../src/templates/DefaultTemplate.stories.tsx | 130 ++++++++++++++++++ .../src/templates/TemplateBackstageLogo.tsx | 37 +++++ .../templates/TemplateBackstageLogoIcon.tsx | 46 +++++++ plugins/home/src/templates/index.ts | 18 +++ .../HomePageSearchBar.stories.tsx | 68 +++++++++ 23 files changed, 833 insertions(+), 33 deletions(-) create mode 100644 .changeset/nervous-starfishes-report.md create mode 100644 plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx create mode 100644 plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.test.tsx create mode 100644 plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.tsx create mode 100644 plugins/home/src/homePageComponents/CompanyLogo/index.ts create mode 100644 plugins/home/src/homePageComponents/Toolkit/Content.test.tsx create mode 100644 plugins/home/src/homePageComponents/Toolkit/Content.tsx create mode 100644 plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx create mode 100644 plugins/home/src/homePageComponents/Toolkit/index.ts create mode 100644 plugins/home/src/homePageComponents/index.ts create mode 100644 plugins/home/src/templates/DefaultTemplate.stories.tsx create mode 100644 plugins/home/src/templates/TemplateBackstageLogo.tsx create mode 100644 plugins/home/src/templates/TemplateBackstageLogoIcon.tsx create mode 100644 plugins/home/src/templates/index.ts create mode 100644 plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx diff --git a/.changeset/nervous-starfishes-report.md b/.changeset/nervous-starfishes-report.md new file mode 100644 index 0000000000..2ac4179a2c --- /dev/null +++ b/.changeset/nervous-starfishes-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Adds two new home components - CompanyLogo and Toolkit. diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index b9b74f92dd..d05a14cc35 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -253,3 +253,73 @@ const LogoFull = () => { return ; }; ``` + +## Custom Homepage + +In addition to a custom theme, a custom logo, you can also customize the +homepage of your app. To do that we need to go through a few steps. + +### Setting up the Home Page + +1. Create a Home Page Component that will be used for composition. + +`packages/app/src/components/home/HomePage.tsx` + +```tsx +import React from 'react'; + +export const HomePage = () => { + return { + /* TODO: Compose a Home Page here */ + }; +}; +``` + +2. Add a route where the homepage will live, presumably `/`. + +`packages/app/src/App.tsx` + +```tsx +import { HomepageCompositionRoot } from '@backstage/plugin-home'; +import { HomePage } from './components/home/HomePage'; + +// ... +}> + +; +// ... +``` + +### Composing your Home Page + +Composing a Home Page is no different from creating a regular React Component, +i.e. the App Integrator is free to include whatever content they like. However, +there are components developed with the Home Page in mind. If you are looking +for components to use when composing your homepage, you can take a look at the +[collection of Homepage components](https://backstage.io/?path=/story/plugins-home-components) +in storybook. If you don't find a component that suits your needs but want to +contribute, check the +[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing). + +!!! tip If you want to use one of the available homepage templates you can find +the +[templates](https://backstage.io/storybook/?path=/story/plugins-home-templates) +in the storybook under the "Home" plugin. And if you would like to contribute a +template, please see the +[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing) + +```tsx +import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import { HomePageCompanyLogo } from '@backstage/plugin-home'; + +export const HomePage = () => { + return ( + + + + + + ); +}; +``` diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index f5852b0164..6e0a2b92fa 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -8,6 +8,7 @@ const BACKSTAGE_CORE_STORIES = [ 'packages/core-components', 'plugins/org', 'plugins/search', + 'plugins/home', ]; module.exports = ({ args }) => { diff --git a/plugins/home/README.md b/plugins/home/README.md index a09c85775b..b3b492be8e 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -1,9 +1,5 @@ # Home -Development is ongoing. You can follow the progress and contribute at the Backstage [Home Project Board](https://github.com/backstage/backstage/projects/7) or reach out to us in the [`#support` Discord channel](https://discord.com/channels/687207715902193673/687235481154617364). - -## Overview - The Home plugin introduces a system for composing a Home Page for Backstage in order to surface relevant info and provide convenient shortcuts for common tasks. It's designed with composability in mind with an open ecosystem that allows anyone to contribute with any component, to be included in any Home Page. For App Integrators, the system is designed to be composable to give total freedom in designing a Home Page that suits the needs of the organization. From the perspective of a Component Developer who wishes to contribute with building blocks to be included in Home Pages, there's a convenient interface for bundling the different parts and exporting them with both error boundary and lazy loading handled under the surface. @@ -90,6 +86,12 @@ Additionally, the App Integrator is provided an escape hatch in case the way the ## Contributing +### Homepage Components + We believe that people have great ideas for what makes a useful Home Page, and we want to make it easy for every to benefit from the effort you put in to create something cool for the Home Page. Therefore, a great way of contributing is by simply creating more Home Page Components, than can then be used by everyone when composing their own Home Page. If they are tightly coupled to an existing plugin, it is recommended to allow them to live within that plugin, for convenience and to limit complex dependencies. On the other hand, if there's no clear plugin that the component is based on, it's also fine to contribute them into the [home plugin](/plugins/home/src/homePageComponents) Additionally, the API is at a very early state, so contributing with additional use cases may expose weaknesses in the current solution that we may iterate on, to provide more flexibility and ease of use for those who wish to develop components for the Home Page. + +### Homepage Templates + +We are hoping that we together can build up a collection of Homepage templates. We therefore put together a place where we can collect all the templates for the Home Plugin in the [storybook](https://backstage.io/storybook/?path=/story/plugins-home-templates). If you would like to contribute with a template, start by taking a look at the [DefaultTemplate storybook example to create your own](/plugins/home/src/templates/DefaultTemplate.stories.tsx), and then open a PR with your suggestion. diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 99d88fa4b5..fab2b05bb6 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -7,6 +7,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Extension } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -64,10 +65,9 @@ export const ComponentTabs: ({ }[]; }) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "createCardExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-forgotten-export) The symbol "CardExtensionProps" needs to be exported by the entry point index.d.ts // -// @public (undocumented) +// @public export function createCardExtension({ title, components, @@ -76,15 +76,7 @@ export function createCardExtension({ title: string; components: () => Promise; name?: string; -}): Extension< - ({ - Renderer, - title: overrideTitle, - ...childProps - }: ComponentRenderer & { - title?: string; - } & T) => JSX.Element ->; +}): Extension<(props: CardExtensionProps) => JSX.Element>; // Warning: (ae-missing-release-tag) "HeaderWorldClock" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -95,6 +87,12 @@ export const HeaderWorldClock: ({ clockConfigs: ClockConfig[]; }) => JSX.Element | null; +// @public +export const HomePageCompanyLogo: (props: { + logo?: ReactNode; + className?: string | undefined; +}) => JSX.Element; + // Warning: (ae-missing-release-tag) "HomepageCompositionRoot" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -103,18 +101,26 @@ export const HomepageCompositionRoot: (props: { children?: ReactNode; }) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "ComponentRenderer" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "HomePageRandomJoke" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const HomePageRandomJoke: ({ - Renderer, - title: overrideTitle, - ...childProps -}: ComponentRenderer & { - title?: string | undefined; -} & { - defaultCategory?: 'any' | 'programming' | undefined; -}) => JSX.Element; +export const HomePageRandomJoke: ( + props: ComponentRenderer & { + title?: string | undefined; + } & { + defaultCategory?: 'any' | 'programming' | undefined; + }, +) => JSX.Element; + +// Warning: (ae-forgotten-export) The symbol "ToolkitContentProps" needs to be exported by the entry point index.d.ts +// +// @public +export const HomePageToolkit: ( + props: ComponentRenderer & { + title?: string | undefined; + } & ToolkitContentProps, +) => JSX.Element; // Warning: (ae-missing-release-tag) "homePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -146,5 +152,5 @@ export const WelcomeTitle: () => JSX.Element; // Warnings were encountered during analysis: // -// src/extensions.d.ts:16:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts +// src/extensions.d.ts:24:5 - (ae-forgotten-export) The symbol "ComponentParts" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/home/package.json b/plugins/home/package.json index 8b2f829efa..298b1860e9 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -24,6 +24,7 @@ "@backstage/core-components": "^0.8.3", "@backstage/core-plugin-api": "^0.4.1", "@backstage/theme": "^0.2.14", + "@backstage/plugin-search": "^0.5.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", diff --git a/plugins/home/src/extensions.tsx b/plugins/home/src/extensions.tsx index 82394cf1ea..4a234f9813 100644 --- a/plugins/home/src/extensions.tsx +++ b/plugins/home/src/extensions.tsx @@ -26,7 +26,7 @@ export type ComponentRenderer = { }; type ComponentParts = { - Content: () => JSX.Element; + Content: (props?: any) => JSX.Element; Actions?: () => JSX.Element; Settings?: () => JSX.Element; ContextProvider?: (props: any) => JSX.Element; @@ -34,6 +34,13 @@ type ComponentParts = { type RendererProps = { title: string } & ComponentParts; +type CardExtensionProps = ComponentRenderer & { title?: string } & T; + +/** + * An extension creator to create card based components for the homepage + * + * @public + */ export function createCardExtension({ title, components, @@ -48,11 +55,8 @@ export function createCardExtension({ component: { lazy: () => components().then(({ Content, Actions, Settings, ContextProvider }) => { - const CardExtension = ({ - Renderer, - title: overrideTitle, - ...childProps - }: ComponentRenderer & { title?: string } & T) => { + const CardExtension = (props: CardExtensionProps) => { + const { Renderer, title: overrideTitle, ...childProps } = props; const app = useApp(); const { Progress } = app.getComponents(); const [settingsOpen, setSettingsOpen] = React.useState(false); @@ -103,7 +107,7 @@ export function createCardExtension({ )} - + ); diff --git a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx new file mode 100644 index 0000000000..ab71eea54e --- /dev/null +++ b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.stories.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { TemplateBackstageLogo } from '../../templates'; +import { HomePageCompanyLogo } from '../../plugin'; +import { rootRouteRef } from '../../routes'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigReader } from '@backstage/core-app-api'; +import { Grid, makeStyles } from '@material-ui/core'; +import React, { ComponentType } from 'react'; + +export default { + title: 'Plugins/Home/Components/CompanyLogo', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + + + , + { + mountedRoutes: { '/hello-company-logo': rootRouteRef }, + }, + ), + ], +}; + +const useLogoStyles = makeStyles(theme => ({ + container: { + margin: theme.spacing(5, 0), + }, + svg: { + width: 'auto', + height: 100, + }, + path: { + fill: '#7df3e1', + }, +})); + +export const Default = () => { + const { container } = useLogoStyles(); + + return ( + + + + ); +}; + +export const CustomLogo = () => { + const { container, svg, path } = useLogoStyles(); + + return ( + + } + /> + + ); +}; diff --git a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.test.tsx b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.test.tsx new file mode 100644 index 0000000000..3cd08bc70b --- /dev/null +++ b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { CompanyLogo } from './CompanyLogo'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { ConfigReader } from '@backstage/core-app-api'; +import { Typography } from '@material-ui/core'; +import React from 'react'; + +describe('', () => { + it('should have a fall back if logo is not provided', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('heading', { name: 'My App' })).toBeInTheDocument(); + }); + + it('should show provided company logo', async () => { + const { getByRole } = await renderInTestApp( + Backstage} />, + ); + + expect(getByRole('heading', { name: 'Backstage' })).toBeInTheDocument(); + }); +}); diff --git a/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.tsx b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.tsx new file mode 100644 index 0000000000..998c2d46b1 --- /dev/null +++ b/plugins/home/src/homePageComponents/CompanyLogo/CompanyLogo.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Typography } from '@material-ui/core'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import React from 'react'; + +type CompanyLogoProps = { + logo?: React.ReactNode; + className?: string; +}; + +/** + * A component to display a company logo for the user. + * + * @public + */ +export const CompanyLogo = (props: CompanyLogoProps) => { + const { logo, className } = props; + const configApi = useApi(configApiRef); + + return ( +

+ {logo ? ( + <>{logo} + ) : ( + {configApi.getString('app.title')} + )} +
+ ); +}; diff --git a/plugins/home/src/homePageComponents/CompanyLogo/index.ts b/plugins/home/src/homePageComponents/CompanyLogo/index.ts new file mode 100644 index 0000000000..50a8869720 --- /dev/null +++ b/plugins/home/src/homePageComponents/CompanyLogo/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ +export { CompanyLogo } from './CompanyLogo'; diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx new file mode 100644 index 0000000000..7fbaff08f9 --- /dev/null +++ b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { Content } from './Content'; + +describe('', () => { + test('should render list of tools', async () => { + const { getByText } = await renderInTestApp( + icon }, + { label: 'tool 2', url: '/url-2', icon:
icon 2
}, + ]} + />, + ); + + expect(getByText('tool')).toBeInTheDocument(); + expect(getByText('tool 2')).toBeInTheDocument(); + expect(getByText('tool').closest('a')).toHaveAttribute('href', '/url'); + expect(getByText('tool 2').closest('a')).toHaveAttribute('href', '/url-2'); + }); +}); diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.tsx new file mode 100644 index 0000000000..4ee1884bd0 --- /dev/null +++ b/plugins/home/src/homePageComponents/Toolkit/Content.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Link } from '@backstage/core-components'; +import { + makeStyles, + List, + ListItemIcon, + ListItemText, +} from '@material-ui/core'; +import React from 'react'; + +const useStyles = makeStyles(theme => ({ + toolkit: { + display: 'flex', + flexWrap: 'wrap', + textAlign: 'center', + }, + tool: { + margin: theme.spacing(0.5, 1), + }, + label: { + marginTop: theme.spacing(1), + fontSize: '0.9em', + lineHeight: '1.25', + color: theme.palette.text.secondary, + }, + icon: { + width: '64px', + height: '64px', + borderRadius: '50px', + justifyContent: 'center', + alignItems: 'center', + boxShadow: theme.shadows[1], + backgroundColor: theme.palette.background.default, + }, +})); + +type Tool = { + label: string; + url: string; + icon: React.ReactNode; +}; + +/** + * Props for Toolkit content component {@link Content}. + * + * @public + */ +export type ToolkitContentProps = { + tools: Tool[]; +}; + +/** + * A component to display a list of tools for the user. + * + * @public + */ +export const Content = (props: ToolkitContentProps) => { + const classes = useStyles(); + + return ( + + {props.tools.map((tool: Tool) => ( + + {tool.icon} + + + ))} + + ); +}; diff --git a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx new file mode 100644 index 0000000000..0a8e6fffbe --- /dev/null +++ b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { TemplateBackstageLogoIcon } from '../../templates'; +import { HomePageToolkit } from '../../plugin'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { Grid } from '@material-ui/core'; +import React, { ComponentType } from 'react'; + +export default { + title: 'Plugins/Home/Components/Toolkit', + decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], +}; + +export const Default = () => { + return ( + + , + })} + /> + + ); +}; diff --git a/plugins/home/src/homePageComponents/Toolkit/index.ts b/plugins/home/src/homePageComponents/Toolkit/index.ts new file mode 100644 index 0000000000..98e0869825 --- /dev/null +++ b/plugins/home/src/homePageComponents/Toolkit/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ + +export { Content } from './Content'; +export type { ToolkitContentProps } from './Content'; diff --git a/plugins/home/src/homePageComponents/index.ts b/plugins/home/src/homePageComponents/index.ts new file mode 100644 index 0000000000..4d2802fb98 --- /dev/null +++ b/plugins/home/src/homePageComponents/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ + +export * from './CompanyLogo'; +export * from './Toolkit'; diff --git a/plugins/home/src/index.ts b/plugins/home/src/index.ts index a8b8e7ab14..dcd61f1fa3 100644 --- a/plugins/home/src/index.ts +++ b/plugins/home/src/index.ts @@ -24,6 +24,8 @@ export { homePlugin, HomepageCompositionRoot, HomePageRandomJoke, + HomePageToolkit, + HomePageCompanyLogo, ComponentAccordion, ComponentTabs, ComponentTab, diff --git a/plugins/home/src/plugin.ts b/plugins/home/src/plugin.ts index b4b9f9b6ee..135ac97af0 100644 --- a/plugins/home/src/plugin.ts +++ b/plugins/home/src/plugin.ts @@ -19,6 +19,7 @@ import { createRoutableExtension, } from '@backstage/core-plugin-api'; import { createCardExtension } from './extensions'; +import { ToolkitContentProps } from './homePageComponents'; import { rootRouteRef } from './routes'; @@ -79,6 +80,21 @@ export const WelcomeTitle = homePlugin.provide( }), ); +/** + * A component to display a company logo for the user. + * + * @public + */ +export const HomePageCompanyLogo = homePlugin.provide( + createComponentExtension({ + name: 'CompanyLogo', + component: { + lazy: () => + import('./homePageComponents/CompanyLogo').then(m => m.CompanyLogo), + }, + }), +); + export const HomePageRandomJoke = homePlugin.provide( createCardExtension<{ defaultCategory?: 'any' | 'programming' }>({ name: 'HomePageRandomJoke', @@ -86,3 +102,16 @@ export const HomePageRandomJoke = homePlugin.provide( components: () => import('./homePageComponents/RandomJoke'), }), ); + +/** + * A component to display a list of tools for the user. + * + * @public + */ +export const HomePageToolkit = homePlugin.provide( + createCardExtension({ + name: 'HomePageToolkit', + title: 'Toolkit', + components: () => import('./homePageComponents/Toolkit'), + }), +); diff --git a/plugins/home/src/templates/DefaultTemplate.stories.tsx b/plugins/home/src/templates/DefaultTemplate.stories.tsx new file mode 100644 index 0000000000..18518e47a9 --- /dev/null +++ b/plugins/home/src/templates/DefaultTemplate.stories.tsx @@ -0,0 +1,130 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 {TemplateBackstageLogo} from './TemplateBackstageLogo'; +import {TemplateBackstageLogoIcon} from './TemplateBackstageLogoIcon'; +import { HomePageToolkit, HomePageCompanyLogo } from '../plugin'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { Content, Page, InfoCard } from '@backstage/core-components'; +import { + HomePageSearchBar, + SearchContextProvider, + searchApiRef, + searchPlugin +} from '@backstage/plugin-search'; +import { Grid, makeStyles } from '@material-ui/core'; +import React, { ComponentType} from 'react'; + + +export default { + title: 'Plugins/Home/Templates', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + <> + Promise.resolve({results: []})}]]}> + + + , + { + mountedRoutes: {'/hello-company': searchPlugin.routes.root } + } + ), + ], +}; + +const useStyles = makeStyles(theme => ({ + search: { + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[1], + maxWidth: '60vw', + display: 'flex', + justifyContent: 'space-between', + padding: '8px 0', + borderColor: 'transparent', + borderRadius: '50px', + margin: 'auto', + }, +})); + +const useLogoStyles = makeStyles(theme => ({ + container: { + margin: theme.spacing(5, 0), + }, + svg: { + width: 'auto', + height: 100, + }, + path: { + fill: '#7df3e1', + }, +})); + +export const DefaultTemplate = () => { + const { search } = useStyles(); + const { svg, path, container } = useLogoStyles(); + + return ( + + + + + } + /> + + + + + + + {/* placeholder for content */} +
+ + + + , + })} + /> + + + + {/* placeholder for content */} +
+ + + + + {/* placeholder for content */} +
+ + + + + + + + ); +}; + diff --git a/plugins/home/src/templates/TemplateBackstageLogo.tsx b/plugins/home/src/templates/TemplateBackstageLogo.tsx new file mode 100644 index 0000000000..c5bca1f773 --- /dev/null +++ b/plugins/home/src/templates/TemplateBackstageLogo.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 React from 'react'; + +type Classes = { + svg: string; + path: string; +} + +export const TemplateBackstageLogo = ({ classes }: { classes: Classes }) => { + return ( + + + + ); +}; diff --git a/plugins/home/src/templates/TemplateBackstageLogoIcon.tsx b/plugins/home/src/templates/TemplateBackstageLogoIcon.tsx new file mode 100644 index 0000000000..2116b48784 --- /dev/null +++ b/plugins/home/src/templates/TemplateBackstageLogoIcon.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 React from 'react'; +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + svg: { + width: 'auto', + height: 28, + }, + path: { + fill: '#7df3e1', + }, +}); + +export const TemplateBackstageLogoIcon = () => { + const classes = useStyles(); + + return ( + + + + ); +}; + diff --git a/plugins/home/src/templates/index.ts b/plugins/home/src/templates/index.ts new file mode 100644 index 0000000000..bfebe73cd8 --- /dev/null +++ b/plugins/home/src/templates/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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. + */ + +export { TemplateBackstageLogoIcon } from './TemplateBackstageLogoIcon'; +export { TemplateBackstageLogo } from './TemplateBackstageLogo' diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx new file mode 100644 index 0000000000..48f0cbaa1e --- /dev/null +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.stories.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { rootRouteRef, HomePageSearchBar } from '../../plugin'; +import { searchApiRef } from '../../apis'; +import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { Grid, makeStyles } from '@material-ui/core'; +import React, { ComponentType } from 'react'; + +export default { + title: 'Plugins/Home/Components/SearchBar', + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + <> + Promise.resolve({ results: [] }) }], + ]} + > + + + , + { + mountedRoutes: { '/hello-search': rootRouteRef }, + }, + ), + ], +}; + +const useStyles = makeStyles(theme => ({ + search: { + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[1], + maxWidth: '60vw', + display: 'flex', + justifyContent: 'space-between', + padding: '8px 0', + borderColor: 'transparent', + borderRadius: '50px', + margin: 'auto', + }, +})); + +export const Default = () => { + const { search } = useStyles(); + + return ( + + + + + + ); +};