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/.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/.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/.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/.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/.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/.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/.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/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 @@ -7V3bktq4Fv0aHknhCwYe06Q7kzmdOZ2kp2ZyXk4JW4AntkXZopvO149sZLAt+YKvEiFVU4OFcdtaa2svSXtvj7Sle/jog932M7KgM1In1mGkfRipqqLMDfK/sOXt2DIPj8KGjW9b9KRzwzf7J6SNE9q6ty0YpE7ECDnY3qUbTeR50MSpNuD76DV92ho56b+6AxvINHwzgcO2/mVbeEtb1fnk/MVv0N5s6Z82DPrFCpg/Nj7ae/TvjVRtHf07fu2C+Fr0/GALLPSaaNLuR9rSRwgfP7mHJXTCvo277fi7h5xvT/ftQw9X+cHBgb9bB924/+ur+fXpXg2e/sFjZaodr/MCnD2MHyS6XfwWd1H0kDC8zGSk3b1ubQy/7YAZfvtKSEHatth1yJFCPgLH3njkswPX5LbuXqCPbdLb72kzRuH5a9txlshBPmnxkEeudMc+Dn3C8ArwkGiij/cRIhdi/42cEn+7oF1NqTiNOfaaAHZC27YJTBWNNgJKps3p2ucOJR9on17Qvwud6U1oEf7RQ/rs6Q5GPt6iDfKA84jC3oq69R+I8Ru1HrDHKN3p8GDjvxOfv4eXejelRx8O9MrRwVt84JEH/Dt5kPhVeHj+WXQU/26NPBxjRzg/if5FZ1nvQ3skzSsHmT+et7Z3bH6wnfhGAwx8TB9DP36bOMrlQID2vkn7788v8AG9HVYfPj/O0cvy95+fNto4HjmAv4G44DzK9hCDQkb50AHYfkmPEa2zQ4kJmkOPMxPuz63tseXMkO9JgnDZ0iHq5t5/iR5HaZsC1p+H5fjnf9RP31+8P/bgiwV/fh+rhmAcYAfccg6k+qwjQiiFhLj24YPLndlMLO7EXBaCO7Pqo0keeWbXwh4uXDN1KPYU3XZC+YHdrpn420HfJjcH/fAU29vQX2VFoGtbVkTFjFgMjr96jI4+zM8t5EjNyEaCvgXgfG1GwProB0x8Y5hzuFqfvomFfaGDqa4zMzJT1VmZOeeozM5EpqJoAg0DzUcBKQcBtaILmeqCuZB5Iwmq9DdhkQV27nmDCYf/Pn9Z6v9b2IuvfzyZS//ZmE2+xEN//fGiPaFQVWW+U6epUaLCGPEAXNsJG57BFrmAttIbVJQ+KJXBu9uhRR9STaiMmggXyEifyKQo1uu1anIVhWWsjKnRk6LQJ0MrCs4qoGQTi8x4ocioKTThJxZ89miNp6UDr4GqUkrQqnSZikaXmTB0UavypYYckZYvymArpkW3nVAaO2e/sb3xSDWAG+oHJ3T9tJFcKm7e4FN3ySFHLl7g0GKNSx9PLaBeC7JFnZWvhCizfpdCGo8lXQmXX2g5fXbJhEUgP1S8WdunH5rV9ENSylwOXQTzQzl8mQrDl3oyV87tlqp0GU7mFt12Tdky/hVWUwaWL7xll87kS5FDTHDkLoZ98mivfODbMGAoELzargOiSKKkzU0SAJqkl6DPgfoYj9VCZ56CreLOXLCdeYqFSwVfTbvqzSnTmyM9CtsjvUSGBPWB2tTYRK6LvCszLRbTQspVt5o0zhrHZrpaq+Q+gFEVZQwDPN5j22Et6IZ0BaRnQyPNetA8pEHw4wZyLZAVXsxsryjPGZSXdHgW3QUqEz2zHmJU9IEn22q9NxdlNmMCDBy0GZuOHT65REZjTeHc0nlGM1dXmnGB0cwvRTprNByt06/RxFabjzN+23Hs5gavDBJHYUP1slaMvLW9ucErpa6JI8Dz4YW+j/yb9UqqaBROjhXfCbvH9LobzDVgng4NMy+VLg2zTRToxgfYlmuxQRyQh3fFKrvc8OAjgmu0Rvc+DKCfLJEPGXyFmKGUCxv+Ep3SWXeysz1G2fhwDHY78p8tldGU791XNJoT5aTVr2rpLDQE2IJrsHewXDJHHJT7lLE5sRcMcr0ms160I0oOnmKmxG2p/UnO7mX5luflqaz8RTxOZFaRPxVlQ1y9EaA7AvC7fCgCFN52UTGJYAt24UfSU87bnR+u2OPyUT2dcLR27N1vowvqTQRUbEWkaEFHLeYZXcoRUpyhd7robuxd1LC9buLefonIlMLcPoFDU3IimeqM3ANHvqUJI3zEfmXCiBUpWXjbDWOZwuWuvQMzP6KNMsdtix74pBiZCSInbrvvFPY6acji+i85h6OZ+KGVOROPQesoXZRC1FchpUSIuK4bSXaWxYhHR9m5UQ+kGi6LsfC+6zq5a3VutZKSAjLLM7f0xjYOCAL6+VQVMbaNoVwgJ/a3ZxfI0O0U9/SMkBPiIeSeAhP1pHBWIrnT4TaCnrhdWb7f6ki1lwAVawpnPJNbGDMN8DfgCkkmw1ZCjqsXtdZRxfy+k/eVI7+vMBFL3GlrDncaJ/gNWyfrWrkjmBwsvO+EU7k/EAdAwCetT5EIHAkfojybZhauq0Yot5Klk1OCuprORngL/XGe2vYhMOWKXq6zc1zMzAskW5lTV3llsrtz6hXXE4spIF8WV63Aqm44wNPs/XKgYlmPYg54yGJDrwRmQK3En24YwIuT7ZcCxqABJJct+w5Q9bxwJVa64gmTOhvWg0zdhCp9X5kEUszHGhfQGLhucSJOQYbqK81GEMEmZPF9JwSDLLOwqVY+C+Muq7UyC+MXg2cTCC/a7bjG+Vebe/TFFG5x5nba2GhbsnFpwwZ2X8Saq5yyiU8bXqh4r7Rhsz4uos0VzvLEJw03j7Ir1hQKkMLw4nz/niZIauM5tSOd3Kq2QLAtF+gXbKdltp05gcM61/F3ViBCL0xoOymq3nLaipGvTd7Bi1Hp1TLd6JD3yya76dInu+mlyW5yFqISCOPBKzbMSkvLWfDlBrHcVRvYTamEY+x3haElp6hOB/SK3IlAafG+RMmEq1xjqGhPilR13bhQlw6Zkf4xkbtDHumMaxs4q803+qLD4CpJZUfXrEraQleqWb5A1j58LQCtQirwhoC5Y7skb1KtTkzkedDEYBVfYXJJp3BfB89bwGjldfD8aoycQbARwytlPyd5St84X1FinGEsZ+JwvTrnzLiufdxoDFf1dRIOlmpnBsJuQ8kEZYvhPI2h5EZ09Qklp2ilRFC2GJvXGEpeem6vUKoMlMk1YiLaIsmWAZc8K04jmO5K6oo43qnEq50Q5hEmTanU+j3LgO4Q1ErHVaVfCFk99ilALvJ3W9u8gVgJRN7bzfoFsej9IDcEyxHkzYu6QpD7CHIr1RbzEFtQqv05xZwIVgbL5eOnmyFW9IdDW6Kiyz3VqDVrrJH+m1lD4VRZ7hk2uacVtWaIjWEzepzX58DGTiFkgq3WbLC5tc0Hh63CMq7IsPUzSBrCDZIGg5KYbyYNgxofgGs7YcMz2CIX0Fb6ZxRlJGjexElAlKdexVZUmjkR022RIVQb9ddzbo2zDyeRgffkTg3h3Gnjcq83Ay83cKWygVdNrBvAwNvecrxK4ZX14MMLr5kkBt6oAqa0I0N11z8VdmSIVaoAFCupmdmoSri0HKvufcTlmN64rO/Aw9hVM0y9AoYZ7MLSErk7gO2V7dj4jeGfBMvwydS/SQvyhoGDk3am8Eq9dKdvjAo7YXEHP4IVdJ5QYEcvziOmgjBGLgeBY7hbEln6shH3sCEwbN+tQGCb785Dyv+xbwNvw6ktUi1ct5r15++WpbM+FVb+nnbcfIQBffxxGMrZkuxVSmVv/GKRVCjf4mJajMKIEYQT330MEfkcvfBSu/8X \ 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 dfa48fb13b..0000000000 Binary files a/docs/assets/architecture-overview/package-architecture.png and /dev/null differ 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, + }, + }), + }, +``` 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 diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 744316cf11..d3f171e4f8 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -179,13 +179,7 @@ Surrounding the plugin are different package groups which are the different possible interface points of the plugin. Note that not all library package lists are complete as packages have been omitted for brevity. - - - + ### Overview diff --git a/packages/cli/package.json b/packages/cli/package.json index 4c81c213d9..e1adc8f0e2 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": "^8.6.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..a5cf38fc11 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -14,10 +14,10 @@ * 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'; + +const { ESBuildMinifyPlugin } = require('esbuild-loader'); export const optimization = ( options: BundlingOptions, @@ -26,16 +26,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/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" }, 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); } } diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f773905ca0..8536dafe76 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) @@ -1302,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<{ @@ -1349,6 +1357,8 @@ export interface NextRouterOptions { // (undocumented) logger: Logger_2; // (undocumented) + permissionRules?: CatalogPermissionRule[]; + // (undocumented) refreshService?: RefreshService; } @@ -1380,6 +1390,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..8472c33369 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -32,10 +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-permission-node": "^0.2.3", "@backstage/search-common": "^0.2.1", "@backstage/types": "^0.1.1", "@octokit/graphql": "^4.5.8", @@ -64,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/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 +>; 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]; +} 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 => { 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) => ( - + { 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, ], ); 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, diff --git a/yarn.lock b/yarn.lock index bbe1c79f2d..e409fba60a 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" "*" @@ -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== @@ -10664,11 +10653,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" @@ -10981,7 +10965,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 +11036,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 +11063,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 +13513,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 +13580,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== @@ -13956,6 +13953,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" @@ -13986,7 +13995,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== @@ -14976,10 +14985,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" @@ -15191,29 +15200,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" @@ -15535,11 +15525,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" @@ -16215,7 +16200,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== @@ -16223,6 +16208,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" @@ -16902,7 +16894,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== @@ -18552,7 +18544,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= @@ -18843,7 +18835,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== @@ -19530,6 +19522,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" @@ -20727,15 +20724,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" @@ -20750,11 +20742,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" @@ -21784,11 +21776,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" @@ -21975,7 +21962,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== @@ -22281,11 +22268,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" @@ -22781,17 +22763,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== @@ -22801,35 +22778,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" @@ -22841,34 +22799,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= @@ -22876,13 +22820,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: @@ -23397,11 +23341,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" @@ -23412,7 +23351,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== @@ -23627,7 +23566,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== @@ -24072,33 +24011,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: @@ -24150,15 +24089,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" @@ -25654,11 +25588,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" @@ -25894,7 +25823,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== @@ -26145,7 +26074,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== @@ -26735,7 +26664,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== @@ -26756,7 +26685,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== @@ -29035,6 +28964,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" @@ -29409,21 +29346,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"