diff --git a/.eslintignore b/.eslintignore index dcf11353ec..2f59b98bca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,3 +5,4 @@ **/build/** **/.git/** **/public/** +**/microsite/** diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d737dd620e..de0655cdbc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,9 +4,11 @@ # The last matching pattern takes precedence. # https://help.github.com/articles/about-codeowners/ -* @spotify/backstage-core -/docs/features/techdocs @spotify/techdocs-core -/plugins/techdocs @spotify/techdocs-core -/plugins/techdocs-backend @spotify/techdocs-core -/packages/techdocs-cli @spotify/techdocs-core -/packages/techdocs-container @spotify/techdocs-core +* @spotify/backstage-core +/docs/features/techdocs @spotify/techdocs-core +/plugins/techdocs @spotify/techdocs-core +/plugins/techdocs-backend @spotify/techdocs-core +/packages/techdocs-cli @spotify/techdocs-core +/packages/techdocs-container @spotify/techdocs-core +/.github/workflows/techdocs.yml @spotify/techdocs-core +/.github/workflows/techdocs-pypi.yml @spotify/techdocs-core diff --git a/.github/workflows/cli-win.yml b/.github/workflows/cli-win.yml index 1355ed251e..4e7a186282 100644 --- a/.github/workflows/cli-win.yml +++ b/.github/workflows/cli-win.yml @@ -7,6 +7,7 @@ on: paths: - '.github/workflows/cli-win.yml' - 'packages/cli/**' + - 'packages/create-app/**' jobs: build: diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7704aa36cd..6e4fdad214 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -5,6 +5,7 @@ on: paths: - '.github/workflows/cli.yml' - 'packages/cli/**' + - 'packages/create-app/**' - 'packages/core/**' - 'packages/core-api/**' - 'yarn.lock' @@ -13,6 +14,18 @@ jobs: build: runs-on: ${{ matrix.os }} + services: + postgres: + image: postgres:latest + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432/tcp + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + strategy: matrix: os: [ubuntu-latest] @@ -44,6 +57,11 @@ jobs: - run: yarn tsc - run: yarn build - name: verify app and plugin creation + env: + POSTGRES_HOST: localhost + POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres run: | sudo sysctl fs.inotify.max_user_watches=524288 node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index f154c586fa..d7a862806a 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -2,7 +2,8 @@ name: Frontend CI on: pull_request: - + paths: + - '!microsite/**' jobs: build: runs-on: ubuntu-latest @@ -44,6 +45,9 @@ jobs: run: git diff --quiet origin/master HEAD -- yarn.lock continue-on-error: true + - name: verify doc links + run: node docs/verify-links.js + - name: yarn install run: yarn install --frozen-lockfile @@ -76,8 +80,5 @@ jobs: - name: verify plugin template run: yarn lerna -- run diff -- --check - - name: bundle example app - run: yarn bundle - - name: verify storybook run: yarn workspace storybook build-storybook diff --git a/.github/workflows/techdocs-pypi.yml b/.github/workflows/techdocs-pypi.yml new file mode 100644 index 0000000000..7680cb98b0 --- /dev/null +++ b/.github/workflows/techdocs-pypi.yml @@ -0,0 +1,37 @@ +name: Master Build TechDocs PyPI Publish + +on: + push: + branches: [master] + paths: + - '.github/workflows/techdocs-pypi.yml' + - 'packages/techdocs-container/**' + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.7] + + steps: + # Publish techdocs-core to PyPI + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@master + with: + python-version: 3.7 + - name: Build Python distribution + working-directory: ./packages/techdocs-container/techdocs-core + run: | + pip install wheel + rm -rf dist + python setup.py bdist_wheel sdist --formats gztar + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.PYPI_API_KEY }} + packages_dir: ./packages/techdocs-container/techdocs-core/dist diff --git a/.github/workflows/techdocs.yml b/.github/workflows/techdocs.yml index 491b5693de..95b68c9376 100644 --- a/.github/workflows/techdocs.yml +++ b/.github/workflows/techdocs.yml @@ -33,12 +33,12 @@ jobs: push: false # Lint Python code for techdocs-core package - - name: prepare python environment + - name: Prepare Python environment run: | python3 -m pip install --index-url https://pypi.org/simple/ setuptools python3 -m pip install --upgrade pip python3 -m pip install --index-url https://pypi.org/simple/ -r $TECHDOCS_CORE_PATH/requirements.txt - - name: lint techdocs-core package + - name: Lint techdocs-core package run: | python3 -m black --check $TECHDOCS_CORE_PATH/src diff --git a/.gitignore b/.gitignore index c1bcf5a4b4..9884edf96e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,6 @@ .vscode/ .vsls.json -# @spotify/web-script build output -cjs/ -esm/ -types/ -build/ - # Logs logs *.log @@ -125,3 +119,6 @@ dist # MkDocs build output site + +# Local configuration files +*.local.yaml diff --git a/ADOPTERS.md b/ADOPTERS.md index 7c05cdd5d7..7a96198529 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,10 +1,13 @@ -| Organization | Contact | Description of Use | -| ---------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------- | -| [Spotify](https://www.spotify.com) | [@alund](https://github.com/alund) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | -| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | -| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | -| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | -| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | -| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| Organization | Contact | Description of Use | +| --------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| [Spotify](https://www.spotify.com) | [@stefanalund](https://github.com/stefanalund) | Main interface towards all of Spotify's infrastructure and technical documentation. | +| [bol.com](https://www.bol.com) | [@RoyJacobs](https://github.com/RoyJacobs) | Initial work being done to unify platform tooling. | +| [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | +| [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | +| [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | +| [SDA SE](https://sda.se) | [@Fox32](https://github.com/Fox32) | Central place for developing and sharing services in our insurance ecosystem. | +| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| [American Airlines](https://www.aa.com) | [@paulpach](https://github.com/paulpach) | Central place for developers to develop and maintain applications | +| [Kiwi.com](https://kiwi.com) | [@aexvir](https://github.com/aexvir) | Replacing the frontend of [The Zoo](https://github.com/kiwicom/the-zoo), their service registry. | +| [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | +| [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..8c67eae825 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Backstage Changelog + +This is a best-effort changelog where we manually collect breaking changes. It is not an exhaustive list of all changes or even features added. + +If you encounter issues while upgrading to a newer version, don't hesitate to reach out on [Discord](https://discord.gg/EBHEGzX) or [open an issue](https://github.com/spotify/backstage/issues/new/choose)! + +## Next Release + +> Collect changes for the next release below + +## v0.1.1-alpha.18 + +### @backstage/catalog-backend + +- Fixed an issue with duplicated location logs. Applying the database migrations from this fix will clear the existing migration logs. [#1836](https://github.com/spotify/backstage/pull/1836) + +### @backstage/auth-backend + +This version fixes a breakage in CSP policies set by the auth backend. If you're facing trouble with auth in alpha.17, upgrade to alpha.18. + +- OAuth redirect URLs no longer receive the `env` parameter, as it is now passed through state instead. This will likely require a reconfiguration of the OAuth app, where a redirect URL like `http://localhost:7000/auth/google/handler/frame?env=development` should now be configured as `http://localhost:7000/auth/google/handler/frame`. [#1812](https://github.com/spotify/backstage/pull/1812) + +### @backstage/core + +- `SignInPage` props have been changed to receive a list of provider objects instead of simple string identifiers for all but the `'guest'` and `'custom'` providers. This opens up for configuration of custom providers, but may break existing configurations. See [packages/app/src/App.tsx](https://github.com/spotify/backstage/blob/032ba401af36a760efdac41668d7000ccf09bc57/packages/app/src/App.tsx#L36) and [packages/app/src/identityProviders.ts](https://github.com/spotify/backstage/blob/032ba401af36a760efdac41668d7000ccf09bc57/packages/app/src/identityProviders.ts#L24) for how to bring back the existing providers. [#1816](https://github.com/spotify/backstage/pull/1816) + +## v0.1.1-alpha.17 + +### @backstage/techdocs-backend + +- The techdocs backend now requires more configuration to be supplied when creating the router. See [packages/backend/src/plugins/techdocs.ts](https://github.com/spotify/backstage/blob/0201fd9b4a52429519dd59e9184106ba69456deb/packages/backend/src/plugins/techdocs.ts#L42) for an example. [#1736](https://github.com/spotify/backstage/pull/1736) + +### @backstage/cli + +- The `create-app` command was moved out from the CLI to a standalone package. It's now invoked with `npx @backstage/create-app` instead. [#1745](https://github.com/spotify/backstage/pull/1745) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6200f3235b..cf7622d692 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,6 +64,14 @@ So...feel ready to jump in? Let's do this. Head over to the [Getting Started gui If you need help, just jump into our [Discord chatroom](https://discord.gg/MUpMjP2). +# Coding Guidelines + +All code is formatted with `prettier` using the configuration in the repo. If possible we recommend configuring your editor to format automatically, but you can also use the `yarn prettier --write ` command to format files. + +If you're contributing to the backend or CLI tooling, be mindful of cross-platform support. [This](https://shapeshed.com/writing-cross-platform-node/) blog post is a good guide of what to keep in mind when writing cross-platform NodeJS. + +Also be sure to skim through our [ADRs](https://github.com/spotify/backstage/tree/master/docs/architecture-decisions) to see if they cover what you're working on. In particular [ADR006: Avoid React.FC and React.SFC](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr006-avoid-react-fc.md) is one to look out for. + # Code of Conduct This project adheres to the [Spotify FOSS Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. diff --git a/README.md b/README.md index 8e7574e128..56932a1741 100644 --- a/README.md +++ b/README.md @@ -10,33 +10,20 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. It’s based on the developer portal we’ve been using internally at Spotify for over four years. Backstage can be as simple as a services catalog or as powerful as the UX layer for your entire tech infrastructure. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure. So your product teams can ship high-quality code quickly — without compromising autonomy. -For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). - -### Features - -- Create and manage all of your organization’s software and microservices in one place. -- Services catalog keeps track of all software and its ownership. -- Visualizations provide information about your backend services and tooling, and help you monitor them. -- A unified method for managing microservices offers both visibility and control. -- Preset templates allow engineers to quickly create microservices in a standardized way ([coming soon](https://github.com/spotify/backstage/milestone/11)). -- Centralized, full-featured technical documentation with integrated tooling that makes it easy for developers to set up, publish, and maintain alongside their code ([coming soon](https://github.com/spotify/backstage/milestone/15)). - -### Benefits - -- For _engineering managers_, it allows you to maintain standards and best practices across the organization, and can help you manage your whole tech ecosystem, from migrations to test certification. -- For _end users_ (developers), it makes it fast and simple to build software components in a standardized way, and it provides a central place to manage all projects and documentation. -- For _platform engineers_, it enables extensibility and scalability by letting you easily integrate new tools and services (via plugins), as well as extending the functionality of existing ones. -- For _everyone_, it’s a single, consistent experience that ties all your infrastructure tooling, resources, standards, owners, contributors, and administrators together in one place. - -## Backstage Service Catalog (alpha) - -The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage. +Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. ![service-catalog](https://backstage.io/blog/assets/6/header.png) -We have also found that the service catalog is a great way to organise the infrastructure tools you use to manage the software as well. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UI’s (and incurring additional cognitive overhead each time they make a context switch), most of these tools can be organised around the entities in the catalog. +Out of the box, Backstage includes: + +- [Backstage Service Catalog](https://github.com/spotify/backstage/blob/master/docs/features/software-catalog/index.md) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) +- [Backstage Software Templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/index.md) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices +- [Backstage TechDocs](https://github.com/spotify/backstage/tree/master/docs/features/techdocs) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach +- Plus, a growing ecosystem of [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality + +For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). ## Project roadmap @@ -56,7 +43,7 @@ Our vision for Backstage is for it to become the trusted standard toolbox (read: The Backstage platform consists of a number of different components: -- **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/app`) and you can easily get started with your own app by [creating one](docs/create-an-app.md). +- **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/app`) and you can easily get started with your own app by [creating one](docs/getting-started/create-an-app.md). - [**plugins**](https://github.com/spotify/backstage/tree/master/plugins) - Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform API's and reusable UI components. Plugins can fetch data either from the _backend_ or through any RESTful API exposed through the _proxy_. - [**service catalog**](https://github.com/spotify/backstage/tree/master/packages/backend) - Service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph. - [**proxy**](https://github.com/spotify/backstage/tree/master/plugins/proxy-backend) - Terminates HTTPS and exposes any RESTful API to Plugins. diff --git a/app-config.yaml b/app-config.yaml index cd8494613a..f578fb3fca 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -15,10 +15,85 @@ proxy: target: 'https://circleci.com/api/v1.1' changeOrigin: true pathRewrite: - '^/circleci/api/': '/' + '^/proxy/circleci/api/': '/' organization: name: Spotify techdocs: - storageUrl: https://techdocs-mock-sites.storage.googleapis.com + storageUrl: http://localhost:7000/techdocs/static/docs + +sentry: + organization: spotify + +auth: + providers: + google: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GOOGLE_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GOOGLE_CLIENT_SECRET + github: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GITHUB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITHUB_CLIENT_SECRET + enterpriseInstanceUrl: + $secret: + env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL + gitlab: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_GITLAB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITLAB_CLIENT_SECRET + audience: + $secret: + env: GITLAB_BASE_URL + # saml: + # development: + # entryPoint: "http://localhost:7001/" + # issuer: "passport-saml" + okta: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_OKTA_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OKTA_CLIENT_SECRET + audience: + $secret: + env: AUTH_OKTA_AUDIENCE + oauth2: + development: + appOrigin: "http://localhost:3000/" + secure: false + clientId: + $secret: + env: AUTH_OAUTH2_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OAUTH2_CLIENT_SECRET + authorizationURL: + $secret: + env: AUTH_OAUTH2_AUTH_URL + tokenURL: + $secret: + env: AUTH_OAUTH2_TOKEN_URL diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000000..86b67f95cd --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,13 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: | + Backstage is an open-source developer portal that puts the developer experience first. + annotations: + github.com/project-slug: spotify/backstage + backstage.io/github-actions-id: spotify/backstage +spec: + type: library + owner: Spotify + lifecycle: experimental diff --git a/docker/default.conf.template b/docker/default.conf.template index 29252f9625..23fdf271d3 100644 --- a/docker/default.conf.template +++ b/docker/default.conf.template @@ -11,6 +11,10 @@ server { try_files $uri /index.html; } + location /healthcheck { + return 204; + } + #error_page 404 /404.html; # redirect server error pages to the static page /50x.html diff --git a/docker/run.sh b/docker/run.sh index 469fdd08cf..fdb1742a07 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -16,13 +16,13 @@ function inject_config() { with_entries(select(.key | startswith("APP_CONFIG_")) | .key |= sub("APP_CONFIG_"; "")) | to_entries | reduce .[] as $item ( - {}; setpath($item.key | split("_"); $item.value | fromjson) + {}; setpath($item.key | split("_"); $item.value | try fromjson catch $item.value) )')" >&2 echo "Runtime app config: $config" local main_js - if ! main_js="$(grep -l __APP_INJECTED_RUNTIME_CONFIG__ /usr/share/nginx/html/*.chunk.js)"; then + if ! main_js="$(grep -l __APP_INJECTED_RUNTIME_CONFIG__ /usr/share/nginx/html/static/*.js)"; then echo "Runtime config already written" return fi diff --git a/docs/README.md b/docs/README.md index 904fdf3356..cdbfbc5578 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ better yet, a pull request. - Overview - [What is Backstage?](overview/what-is-backstage.md) + - [Backstage architecture](overview/architecture-overview.md) - [Architecture and terminology](overview/architecture-terminology.md) - [Roadmap](overview/roadmap.md) - Getting started @@ -42,11 +43,7 @@ better yet, a pull request. - [Overview](features/techdocs/README.md) - [Getting Started](features/techdocs/getting-started.md) - [Concepts](features/techdocs/concepts.md) - - [Reading Documentation](features/techdocs/reading-documentation.md) - - [Writing Documentation](features/techdocs/writing-documentation.md) - - [Publishing Documentation](features/techdocs/publishing-documentation.md) - - [Contributing](features/techdocs/contributing.md) - - [Debugging](features/techdocs/debugging.md) + - [Creating and Publishing Documentation](features/techdocs/creating-and-publishing.md) - [FAQ](features/techdocs/FAQ.md) - Plugins - [Overview](plugins/index.md) @@ -63,6 +60,11 @@ better yet, a pull request. - Publishing - [Open source and NPM](plugins/publishing.md) - [Private/internal (non-open source)](plugins/publish-private.md) + - Configuration + - [Overview](conf/index.md) + - [Reading Configuration](conf/reading.md) + - [Writing Configuration](conf/writing.md) + - [Defining Configuration](conf/defining.md) - Authentication and identity - [Overview](auth/index.md) - [Add auth provider](auth/add-auth-provider.md) @@ -76,7 +78,8 @@ better yet, a pull request. - [Figma resources](dls/figma.md) - API references - TypeScript API - - [Utilities](api/utility-apis.md) + - [Utility APIs](api/utility-apis.md) + - [Utility API References](reference/utility-apis/README.md) - [createPlugin](reference/createPlugin.md) - [createPlugin-feature-flags](reference/createPlugin-feature-flags.md) - [createPlugin-router](reference/createPlugin-router.md) @@ -92,6 +95,8 @@ better yet, a pull request. - [ADR004 - Module Export Structure](architecture-decisions/adr004-module-export-structure.md) - [ADR005 - Catalog Core Entities](architecture-decisions/adr005-catalog-core-entities.md) - [ADR006 - Avoid React.FC and React.SFC](architecture-decisions/adr006-avoid-react-fc.md) + - [ADR007 - Use MSW for Mocking Network Requests](architecture-decisions/adr007-use-msw-to-mock-service-requests.md) + - [ADR008 - Default Catalog File Name](architecture-decisions/adr008-default-catalog-file-name.md) - [Contribute](../CONTRIBUTING.md) - [Support](overview/support.md) - [FAQ](FAQ.md) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 2b15da0295..f87db15e7e 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -19,7 +19,8 @@ during their entire life cycle. Each Utility API is tied to an `ApiRef` instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. `ApiRef`s are create using `createApiRef`, which is -exported by `@backstage/core`. There are many predefined Utility APIs defined in +exported by `@backstage/core`. There are many +[predefined Utility APIs](../reference/utility-apis/README.md) defined in `@backstage/core`, and they're all exported with a name of the pattern `*ApiRef`, for example `errorApiRef`. diff --git a/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md new file mode 100644 index 0000000000..bdb221cea3 --- /dev/null +++ b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md @@ -0,0 +1,60 @@ +# ADR007: Use MSW to mock http requests + +## Context + +Network request mocking can be a total pain sometimes, in all different types of +tests, unit tests to e2e tests always have their own implementation of mocking +these requests. There's been traction in the outer community towards using this +library to mock network requests by using an express style declaration for +routes. react-testing-library suggests using this library instead of mocking +fetch directly wether this be in a browser or in node. + +https://github.com/mswjs/msw + +## Decision + +Moving forward, we have decided that any `fetch` or `XMLHTTPRequest` that +happens, should be mocked by using `msw`. + +Here is an example: + +```ts +import { setupWorker, rest } from 'msw'; + +const worker = setupWorker( + rest.get('*/user/:userId', (req, res, ctx) => { + return res( + ctx.json({ + firstName: 'John', + lastName: 'Maverick', + }), + ); + }), +); + +// Start the Mock Service Worker +worker.start(); +``` + +and in a more real life scenario, taken from +[CatalogClient.test.ts](https://github.com/spotify/backstage/blob/f3245c4f8f0b6b2625c4a6d5d50161b612fb4757/plugins/catalog/src/api/CatalogClient.test.ts) + +```ts +beforeEach(() => { + server.use( + rest.get(`${mockApiOrigin}${mockBasePath}/entities`, (_, res, ctx) => { + return res(ctx.json(defaultResponse)); + }), + ); +}); + +it('should entities from correct endpoint', async () => { + const entities = await client.getEntities(); + expect(entities).toEqual(defaultResponse); +}); +``` + +## Consequences + +- A little more code to write +- Gradually will replace the codebase with `msw` diff --git a/docs/architecture-decisions/adr008-default-catalog-file-name.md b/docs/architecture-decisions/adr008-default-catalog-file-name.md new file mode 100644 index 0000000000..23910f634f --- /dev/null +++ b/docs/architecture-decisions/adr008-default-catalog-file-name.md @@ -0,0 +1,22 @@ +# ADR008: Default Catalog File Name + +## Background + +While the spec for the catalog file format is well described in +[ADR002](./adr002-default-catalog-file-format.md), guidance was note provided as +to the name of the catalog file. + +Following discussion in +[Issue 1822](https://github.com/spotify/backstage/pull/1822#pullrequestreview-461253670), +a decision was made. + +## Name + +The catalog file should be named + +```shell +catalog-info.yaml +``` + +This name is a default, **not a requirement**. The catalog file will work with +Backstage irregardless of its name. diff --git a/docs/auth/index.md b/docs/auth/index.md index 0cb56501ab..6f9af17cba 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -26,7 +26,8 @@ OAuth helps in that regard. The method with which frontend plugins request access to third party services is through [Utility APIs](../api/utility-apis.md) for each service provider. For a -full list of providers, see [TODO](#TODO). +full list of providers, see the +[Utility API References](../reference/utility-apis/README.md). ### Identity - WIP diff --git a/docs/conf/defining.md b/docs/conf/defining.md new file mode 100644 index 0000000000..0b15108f43 --- /dev/null +++ b/docs/conf/defining.md @@ -0,0 +1,15 @@ +# Defining Configuration for your Plugin + +There is currently no tooling support or helpers for defining plugin +configuration. But it's on the roadmap. + +Meanwhile, document the config values that you are reading in your plugin +README. + +## Format + +When defining configuration for your plugin, keep keys camelCased and stick to +existing casing conventions such as `baseUrl`. + +It is also usually best to prefer objects over arrays, as it makes it possible +to override individual values using separate files or environment variables. diff --git a/docs/conf/index.md b/docs/conf/index.md new file mode 100644 index 0000000000..af1635d627 --- /dev/null +++ b/docs/conf/index.md @@ -0,0 +1,48 @@ +# Static Configuration in Backstage + +## Summary + +Backstage ships with a flexible configuration system that provides a simple way +to configure Backstage apps and plugins for both local development and +production deployments. It helps get you up and running fast while adapting +Backstage for your specific environment. It also serves as a tool for plugin +authors to use to make it simple to pick up and install a plugin, while still +allowing for customization. + +## Supplying Configuration + +Configuration is stored in `app-config.yaml` files, with support for suffixes +such as `app-config.production.yaml` to override values for specific +environments. The configuration files themselves contain plain YAML, but with +support for loading in secrets from various sources using a `$secret` key. + +It is also possible to supply configuration through environment variables, for +example `APP_CONFIG_app_baseUrl=https://staging.example.com`. However these +should be used sparingly, usually just for temporary overrides during +development or small tweaks to be able to reuse deployment artifacts in +different environments. + +The configuration is shared between the frontend and backend, meaning that +values that are common between the two only needs to be defined once. Such as +the `backend.baseUrl`. + +For more details, see [Writing Configuration](./writing.md). + +## Reading Configuration + +As a plugin developer, you likely end up wanting to define configuration that +you want users of your plugin to supply, as well as reading that configuration +in frontend and backend plugins. For more details, see +[Reading Configuration](./reading.md) and +[Defining Configuration](./defining.md). + +## Further Reading + +More details are provided in dedicated sections of the documentation. + +- [Reading Configuration](./reading.md): How to read configuration in your + plugin. +- [Writing Configuration](./writing.md): How to provide configuration for your + Backstage deployment. +- [Defining Configuration](./defining.md): How to define configuration for users + of your plugin. diff --git a/docs/conf/reading.md b/docs/conf/reading.md new file mode 100644 index 0000000000..9979f0360e --- /dev/null +++ b/docs/conf/reading.md @@ -0,0 +1,128 @@ +# Reading Backstage Configuration + +## Config API + +There's a common configuration API for by both frontend and backend plugins. An +API reference can be found [here](../reference/utility-apis/Config.md). + +The configuration API is tailored towards failing fast in case of missing or bad +config. That's because configuration errors can always be considered programming +mistakes, and will fail deterministically. + +### Type Safety + +The methods for reading primitive values are typed, and validate that type at +runtime. For example `getNumber()` requires the underlying value to be a number, +and there will be no attempt to coerce other types into the desired one. If +`getNumber()` receives a string value, it will throw an error, explaining where +the bad config came from, and what the desired and actual types where. + +### Reading Nested Configuration + +The backing configuration data is a nested JSON structure, meaning there will be +object, within objects, arrays within objects, and so on. There are a couple of +different ways to access nested values when reading configuration, but the +primary one is to use dot-separated paths. + +For example, given the following configuration: + +```yaml +app: + baseUrl: http://localhost:3000 +``` + +We can access the `baseUrl` using `config.getString('app.baseUrl')`. Because of +this syntax, configuration keys are not allowed to contain dots. In fact, +configuration keys are validated using the following RegEx: +`/^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i`. + +Another option of accessing the `baseUrl` value is to create a sub-view of the +configuration, `config.getConfig('app').getString('baseUrl')`. When reading out +single values the dot-path pattern is preferred, but creating sub-views can be +useful for when you want to pass on parts of configuration to be read out by a +separate function. For example, given something like + +```yaml +my-plugin: + items: + a: + title: Item A + path: /a + b: + title: Item B + path: /b +``` + +You can get the list of all items using the `.keys()` method, and then pass on +each sub-view to be handled individually. + +```ts +for (const itemKey of config.keys('my-plugin.items')) { + const itemConfig = config.getConfig(`my-plugin.items`).getConfig(key); + const item = createItemFromConfig(itemConfig); +} +``` + +Another option for iterating through configuration keys is to call +`config.get('my-plugin.items')`, which simply returns the JSON structure for +that position without any validation. This can be handy to use sometimes, +especially if you're passing on config to an external library. There's a clear +benefit to the sub-view approach though, which is that the user will receive +much more detailed and relevant error messages. For example, if +`itemConfig.getString('title')` fails in the above example because a boolean was +supplied, the user will receive an error message with the full path, e.g. +`my-plugin.items.b.title`, as well as the name of the config file with the bad +value. + +Note that no matter what method is used for reading out nested config, the same +merging rules apply. You will always get the same value for any way of accessing +nested config: + +```ts +// Equivalent as long as a.b.c exists and is a string +config.getString('a.b.c'); +config.getConfig('a.b').getString('c'); +config.get('a').b.c; +``` + +### Required vs Optional Configuration + +Reading configuration can be divided into two categories: required, and +optional. When reading optional configuration you use the optional methods such +as `getOptionalString`. These methods will simply return `undefined` if +configuration values are missing, allowing the called to fall back to default +values. The optional methods still validate types however, so receiving a string +in a call to `config.getOptionalNumber` will still throw an error. + +A good pattern for reading optional configuration values is to use the `??` +operator. For example: + +```ts +const title = config.getString('my-plugin.title') ?? 'My Plugin'; +``` + +To read required configuration, simply use the methods without `Optional`, for +example `getString`. These will throw an error if there is no value available. + +## Accessing ConfigApi in Frontend Plugins + +The [ConfigApi](../reference/utility-apis/Config.md) in the frontend is a +[UtilityApi](../api/utility-apis.md). It's accessible as usual via the +`configApiRef` exported from `@backstage/core`. + +Depending on the config api in another API is slightly different though, as the +`ConfigApi` implementation is supplied via the App itself and not instantiated +like other APIs. See +[packages/app/src/apis.ts](https://github.com/spotify/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/app/src/apis.ts#L66) +for an example of how this wiring is done. + +For standalone plugin setups in `dev/index.ts`, register a factory with a +statically mocked implementation of the config API. Use the `ConfigReader` from +`@backstage/config` to create an instance and register it for the `configApiRef` +from `@backstage/core`. + +## Accessing ConfigApi in Backend Plugins + +In backend plugins the configuration is passed in via options from the main +backend package. See for example +[packages/backend/src/plugins/auth.ts](https://github.com/spotify/backstage/blob/244eef851f5aa19f91c7c9b5c12d5df95cf482ca/packages/backend/src/plugins/auth.ts#L23). diff --git a/docs/conf/writing.md b/docs/conf/writing.md new file mode 100644 index 0000000000..8ab397846d --- /dev/null +++ b/docs/conf/writing.md @@ -0,0 +1,157 @@ +# Writing Backstage Configuration Files + +## File Format + +Configuration is stored in YAML format in `app-config.yaml` files, looking +something like this: + +```yaml +app: + title: Backstage Example App + baseUrl: http://localhost:3000 + +backend: + listen: 0.0.0.0:7000 + baseUrl: http://localhost:7000 + +organization: + name: Spotify + +proxy: + /my/api: + target: https://example.com/api/ + changeOrigin: true + pathRewrite: + ^/proxy/my/api/: / +``` + +Configuration files are typically checked in and stored in the repo that houses +the rest of the Backstage application. + +## Environment Variable Overrides + +Individual configuration values can be overridden using environment variables +prefixed with `APP_CONFIG_`. Everything following that prefix in the environment +variable name will be used as the config key, with `_` replaced by `.`. For +example, to override the `app.baseUrl` value, set the `APP_CONFIG_app_baseUrl` +environment variable to the desired value. + +The value of the environment variable is parsed as JSON, but it will fall back +to being interpreted as a string if it fails to parse. Note that if you for +example want to pass on the string `"false"`, you need to wrap it in double +quotes, e.g. `export APP_CONFIG_example='"false"'`. + +While it may be tempting to use environment variable overrides for supplying a +lot of configuration values, we recommend using them sparingly. Try to stick to +using configuration files, and only use environment variables for things like +reusing deployment artifacts across staging and production environments. + +Note that environment variables work for frontend configuration too. They are +picked up by the serve tasks of `@backstage/cli` for local development, and are +injected by the entrypoint of the nginx container serving the frontend in a +production build. + +## File Resolution + +It is possible to have multiple configuration files, both to support different +environments, but also to define configuration that is local to specific +packages. + +All `app-config.yaml` files inside the monorepo root and package root are +considered, as are files with additional `local` and environment affixes such as +`development`, for example `app-config.local.yaml`, +`app-config.production.yaml`, and `app-config.development.local.yaml`. Which +environment config files are loaded is determined by the `NODE_ENV` environment +variable. Local configuration files are always loaded, but are meant for local +development overrides and should typically be `.gitignore`'d. + +All loaded configuration files are merged together using the following rules: + +- Configurations have different priority, higher priority means you replace + values from configurations with lower priority. +- Primitive values are completely replaced, as are arrays and all of their + contents. +- Objects are merged together deeply, meaning that if any of the included + configs contain a value for a given path, it will be found. + +The priority of the configurations is determined by the following rules, in +order: + +- Configuration from the `APP_CONFIG_` environment variables has the highest + priority, followed by files. +- Files inside package directories have higher priority than those in the root + directory. +- Files with environment affixes have higher priority than ones without. +- Files with the `local` affix have higher priority than ones without. + +## Secrets + +Secrets are supported via a special `$secret` key, which in turn provides a +number of different ways to read in secrets. To load a configuration value as a +secret, supply an object with a single `$secret` key, and within that supply an +object that describes how the secret is loaded. For example, the following will +read the config key `backend.mySecretKey` from the environment variable +`MY_SECRET_KEY`: + +```yaml +backend: + mySecretKey: + $secret: + env: MY_SECRET_KEY +``` + +With the above configuration, calling `config.getString('backend.mySecretKey')` +will return the value of the environment variable `MY_SECRET_KEY` when the +backend started up. All secrets are loaded at startup, so changing the contents +of secret files or environment variables will not be reflected at runtime. + +Note that secrets will never be included in the frontend bundle or development +builds. When loading configuration you have to explicitly enable reading of +secrets, which is only done for the backend configuration. + +As hinted at, secrets can be loaded from a bunch of different sources, and can +be extended with more. Below is a list of the currently supported methods for +loading secrets. + +### Env Secrets + +This reads a secret from an environment variable. For example, the following +config loads the secret from the `MY_SECRET` env var. + +```yaml +$secret: + env: MY_SECRET +``` + +### File Secrets + +This reads a secret from the entire contents of a file. The file path is +relative to the `app-config.yaml` the defines the secrets. For example, the +following reads the contents of `my-secret.txt` relative to the config file +itself: + +```yaml +$secret: + file: ./my-secret.txt +``` + +### Data File Secrets + +This reads secrets from a path within a JSON-like data file. The file path +behaves similar to file secrets, but in addition a `path` is used to point to a +specific value inside the file. Supported file extensions are `.json`, `.yaml`, +and `.yml`. For example, the following would read out `my-secret-key` from +`my-secrets.json`: + +```yaml +$secret: + data: ./my-secrets.json + path: deployment.key + +# my-secrets.json +{ + "deployment": { + "key": "my-secret-key" + } +} +``` diff --git a/docs/dls/design.md b/docs/dls/design.md index e3816bd2e5..d773a07a8f 100644 --- a/docs/dls/design.md +++ b/docs/dls/design.md @@ -110,13 +110,11 @@ picked up by our team as something to be added to our design system. components. If you’d like to help build up our design system, you can also add components we’ve designed to the Storybook as well. +**[Figma](https://www.figma.com/@backstage)** - we're stoked to be using Figma Community to share our design assets. You can duplicate our component library and design your own plugin for Backstage. + **[Discord](https://discord.gg/EBHEGzX)** - all design questions should be directed to the _#design_ channel. -**Documentation** - -- Patterns (stay tuned) -- Figma files/libraries (stay tuned) ## 🔮 Future diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index 4925f32e1a..d4d9bf1e2d 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -24,3 +24,7 @@ can be organised around the entities in the catalog. TODO ![](service-catalog-home.png) + +## Links + +- [[Blog post] Backstage Service Catalog released in alpha](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index e929ec4a97..bf540d656a 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -10,24 +10,24 @@ A simple `template.yaml` definition might look something like this: apiVersion: backstage.io/v1alpha1 kind: Template metadata: - # unique name per namespace for the template + # unique name per namespace for the template name: react-ssr-template - # title of the template + # title of the template title: React SSR Template - # a description of the template + # a description of the template description: Next.js application skeleton for creating isomorphic web applications. # some tags to display in the frontend - tags: + tags: - Recommended - React spec: # which templater key to use in the templaters builder templater: cookiecutter - # what does this template create + # what does this template create type: website - # if the template is not in the current directory where this definition is kept then specfiy + # if the template is not in the current directory where this definition is kept then specfiy path: './template' - # the schema for the form which is displayed in the frontend. + # the schema for the form which is displayed in the frontend. # should follow JSON schema for forms: https://jsonforms.io/ schema: required: diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 4514b4c106..f872e63718 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -1 +1,28 @@ -# FAQ +# TechDocs FAQ + +This page answers frequently asked questions about [TechDocs](README.md). + +_Got a question that you think others might be interested in knowing the answer to? Edit this file +[here](https://github.com/spotify/backstage/edit/master/docs/features/techdocs/FAQ.md)._ + +## Technology + +- [What static site generator is TechDocs using?](./#what-static-site-generator-is-techdocs-using) +- [What is the mkdocs-techdocs-core plugin?](./#what-is-the-mkdocs-techdocs-core-plugin) + +#### What static site generator is TechDocs using? + +TechDocs is using [MkDocs](https://www.mkdocs.org/) to build project +documentation under the hood. Documentation built with the +[techdocs-container](https://github.com/spotify/backstage/blob/master/packages/techdocs-container/README.md) +is using the MkDocs Material Theme. + +#### What is the mkdocs-techdocs-core plugin? + +The +[mkdocs-techdocs-core](https://github.com/spotify/backstage/blob/master/packages/techdocs-container/techdocs-core/README.md) +package is a MkDocs Plugin that works like a wrapper around multiple MkDocs +plugins (e.g. +[MkDocs Monorepo Plugin](https://github.com/spotify/mkdocs-monorepo-plugin)) as +well as a selection of Python Markdown extensions that TechDocs supports. + diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index d5d6db1240..8d0fb01ec3 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -22,25 +22,58 @@ Spotify’s developer experience offering with 2,400+ documentation sites and ## Project roadmap -| Version | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| [TechDocs V.0 ✅][v0] | Read docs in Backstage - Enable anyone to get a reader experience working in Backstage. | -| [TechDocs V.1 🚧][v1] | TechDocs end to end - First and minimum release of TechDocs that you can use end to end - and contribute to. | -| [TechDocs V.2 🔮⌛][v2] | Widget Architecture - TechDocs widget architecture available, so the community can create their own customized features. | +| Version | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| [TechDocs V.0 ✅][v0] | Read docs in Backstage - Enable anyone to get a reader experience working in Backstage. [See V.0 Use Cases.](./#techdocs-v0) | +| [TechDocs V.1 🚧][v1] | TechDocs end to end (alpha) - Alpha of TechDocs that you can use end to end - and contribute to. [See V.1 Use Cases.](./#techdocs-v1) | +| [TechDocs V.2 🔮⌛][v2] | Platform stability and compatibility improvements. [See V.2 Use Cases.](./#techdocs-v2) | +| TechDocs V.3 🔮⌛ | Widget Architecture - TechDocs widget architecture available, so the community can create their own customized features. | [v0]: https://github.com/spotify/backstage/milestone/15 [v1]: https://github.com/spotify/backstage/milestone/16 [v2]: https://github.com/spotify/backstage/milestone/17 + + +## Use Cases + +#### TechDocs V.0 + +- As a user I can navigate to a manually curated docs explore page. +- As a user I can navigte to and read mock documentation that is manually + uploaded by the TechDocs core team. + +#### TechDocs V.1 + +- As a user I can run TechDocs locally and read documentation. +- As a user I can create a docs folder in my entity project and add a reference + in the entity configuration file (of the owning entity) to my documentation. + - Backstage will automatically build my documentation and serve it in + TechDocs. + - Documentation will be displayed under the docs tab in the service catalog. +- As a user I can create a docs only repository that will be standalone from any + other service. +- As a user I can choose my own storage solution for the documentation (as + example GCS/AWS/Azure etc) +- As a user I can define my own API to interface my own documentation solution. + +#### TechDocs V.2 + +Platform stability and compatibility improvements + +- As a user I can define the metadata generated for my documentation. +- As a user I will be able to browse metadata from within my documentation in + Backstage. + +#### TechDocs V.3 + +more to come... + ## Structure - [Getting Started] - [Concepts] -- [Reading Documentation] -- [Writing Documentation] -- [Publishing Documentation] -- [Contributing] -- [Debugging] +- [Creating and Publishing Documentation] - [FAQ] ## Tech Stack @@ -54,11 +87,7 @@ Spotify’s developer experience offering with 2,400+ documentation sites and [getting started]: getting-started.md [concepts]: concepts.md -[reading documentation]: reading-documentation.md -[writing documentation]: writing-documentation.md -[publishing documentation]: publishing-documentation.md -[contributing]: contributing.md -[debugging]: debugging.md +[creating and publishing documentation]: creating-and-publishing.md [faq]: FAQ.md 'Frequently asked questions' [techdocs/frontend]: https://github.com/spotify/backstage/blob/master/plugins/techdocs diff --git a/docs/features/techdocs/concepts.md b/docs/features/techdocs/concepts.md index 74d42e13cd..f06ed8b4c0 100644 --- a/docs/features/techdocs/concepts.md +++ b/docs/features/techdocs/concepts.md @@ -1 +1,52 @@ # Concepts + +This page describes concepts that are introduced with Spotify's docs-like-code solution in Backstage. + +### TechDocs Core Plugin + +The TechDocs Core Plugin is a MkDocs plugin created as a wrapper around multiple +MkDocs plugins and Python Markdown extensions to standardize the configuration +of MkDocs used for TechDocs. + +[TechDocs Core](../../../packages/techdocs-container/techdocs-core/README.md) + +### TechDocs container + +The TechDocs container is a Docker container available at +[DockerHub](https://hub.docker.com/r/spotify/techdocs). It builds static HTML +pages, including stylesheets and scripts from Python flavored Markdown, through +MkDocs. + +[TechDocs Container](../../../packages/techdocs-container/README.md) + +### TechDocs publisher (coming soon) + +### TechDocs CLI + +The TechDocs CLI was created to make it easy to write, generate and preview +documentation for publishing. Currently it mostly acts as a wrapper around the +TechDocs container and provides an easy-to-use interface for our docker +container. + +[TechDocs CLI](../../../packages/techdocs-cli/README.md) + +### TechDocs Reader + +Documentation generated by TechDocs is generated as static HTML sites. The +TechDocs Reader was therefore created to be able to integrate pre-generated HTML +sites with the Backstage UI. + +The TechDocs Reader purpose is also to open up the opportunity to integrate +TechDocs widgets for a customized full-featured TechDocs experience. +([coming soon V.2](https://github.com/spotify/backstage/milestone/17)) + +[TechDocs Reader](../../../plugins/techdocs/src/reader/README.md) + +### Transformers + +Transformers are different pieces of functionality used inside the TechDocs +Reader. The reason why transformers were introduced was to provide a way to +transform the HTML content on pre and post render (e.g. rewrite docs links or +modify css). + +[Transformers API docs](../../../plugins/techdocs/src/reader/transformers/README.md) diff --git a/docs/features/techdocs/contributing.md b/docs/features/techdocs/contributing.md deleted file mode 100644 index 854139a319..0000000000 --- a/docs/features/techdocs/contributing.md +++ /dev/null @@ -1 +0,0 @@ -# Contributing diff --git a/docs/features/techdocs/publishing-documentation.md b/docs/features/techdocs/creating-and-publishing.md similarity index 54% rename from docs/features/techdocs/publishing-documentation.md rename to docs/features/techdocs/creating-and-publishing.md index 73ac74b458..b38fb77b08 100644 --- a/docs/features/techdocs/publishing-documentation.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -1,16 +1,22 @@ -# Publishing Documentation +# Creating and publishing your docs + +This section will guide you through: + +- Creating a basic setup for your documentation +- Writing and previewing your documentation in a local Backstage environment +- Creating a build ready for publication +- Publishing your documentation and making your Backstage instance read your published docs. ## Prerequisities - [Docker](https://docs.docker.com/get-docker/) - Static file hosting +- A working Backstage instance with TechDocs installed + (see [TechDocs getting started](getting-started.md)) -## Create documentation +## Create a basic documentation setup -Create a directory that contains your documentation. Inside this directory you -should create a file called `mkdocs.yml`. As an example you can create a -directory called `hello-docs` in your home directory (also known as `~`). Below -is a basic example of how it could look. +In your home directory (also known as `~`), create a directory that contains your documentation (for example, `hello-docs`). Inside this directory, create a file called `mkdocs.yml`. Below is a basic example of how it could look. The `~/hello-docs/mkdocs.yml` file should have the following content: @@ -24,7 +30,7 @@ plugins: - techdocs-core ``` -And then the `~/hello-docs/docs/index.md` should have the following content: +The `~/hello-docs/docs/index.md` should have the following content: ```md # example docs @@ -32,7 +38,23 @@ And then the `~/hello-docs/docs/index.md` should have the following content: This is a basic example of documentation. ``` -## Build documentation +## Writing and previewing your documentation + +Using the `techdocs-cli` you can preview your docs inside a local Backstage +instance and get automatic recompilation on changes. This is useful for when you +want to write your documentation. + +To do this you can run: + +```bash +cd ~/hello-docs/ +npx techdocs-cli serve +``` + +## Build production ready documentation + +To get a build suitable for publication you can build your docs using the +`spotify/techdocs` container: ```bash cd ~/hello-docs/ @@ -43,20 +65,15 @@ You should now have a folder called `~/hello-docs/site/`. ## Deploy to a file server -In order to serve documentation to TechDocs, our Backstage plugin needs to -download the HTML rendered from the -[Create documentation](#create-documentation) step above. This will likely exist -on an external file server, or a storage solution such as Google Cloud Storage. +In order to serve documentation to TechDocs, our Backstage plugin needs to download the HTML rendered from the previous step. This will likely exist on an external file server, or a storage solution such as Google Cloud Storage. -When deploying documentation, it should be deployed on that file server / -storage solution with the following convention: `{id}/{file}`. For example, if -we wanted to upload the `getting-started/index.html` file for the `backstage` +When deploying documentation, it should be deployed on that file server/storage solution with the following convention: `{id}/{file}`. For example, if +you want to upload the `getting-started/index.html` file for the `backstage` documentation site, we would upload it to our file server as `backstage/getting-started/index.html`. -To explain further how this would look like for multiple documentation sites, -take a look at this example file tree that would be represented on your file -server: +To explain further what this would look like for multiple documentation sites, +take a look at this example file tree that would be represented on your file server: ```md /backstage/index.html /backstage/getting-started/index.html @@ -70,7 +87,7 @@ In this file tree, we have two documentation sites available: `backstage` and on `http://example.com` as the server URL. When you configure the TechDocs plugin in Backstage to use `http://example.com` -as the file server / storage solution, it will translate the following URLs to +as the file server/storage solution, it will translate the following URLs to the file server: | Backstage URL | File Server URL | @@ -78,10 +95,8 @@ the file server: | https://demo.backstage.io/docs/backstage/ | http://example.com/backstage/index.html | | https://demo.backstage.io/docs/mkdocs/plugin-development/ | http://example.com/mkdocs/plugin-development/index.html | -Then deploying new sites is easy. It's as simple as copying over the `site/` -folder produced in the [Create documentation](#create-documentation) step above -and copying it over to the file server / storage solution under the ID of the -documentation site. It will then become immediately available in Backstage under +Then deploying new sites is easy: simply copy over the `site/` +folder produced in the [Create documentation](#build-production-ready-documentation) step above to the file server/storage solution under the ID of the documentation site. It will then become immediately available in Backstage under the same ID as you can see in the table above. So, if the URL to your file server is `http://example.com/`, your @@ -94,7 +109,7 @@ In order for Backstage to show your documentation, it needs to know where you uploaded it. Make sure you have Backstage set up using -[TechDocs getting started](getting-started.md) +[TechDocs getting started](getting-started.md). To point Backstage to your docs storage, add or change the following lines in your Backstage `app-config.yaml`: diff --git a/docs/features/techdocs/debugging.md b/docs/features/techdocs/debugging.md deleted file mode 100644 index 1e584c954d..0000000000 --- a/docs/features/techdocs/debugging.md +++ /dev/null @@ -1 +0,0 @@ -# Debugging diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 2ea8fc7fa9..90dcd423fa 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -1,27 +1,27 @@ # Getting Started -> TechDocs is not feature complete and currently you can't set up a complete +> TechDocs is not yet feature complete - currently you can't set up a complete > end-to-end working TechDocs plugin without customizing the plugin itself. -> With TechDocs V.0 you can expect a demonstration of how to integrate docs into -> Backstage. Currently it can create docs using -> [mkdocs](https://www.mkdocs.org/), as well as reading published docs. If you -> publish generated docs and passing in a storageUrl in your `app-config.yaml` -> you can view it in Backstage by going to +> What you can expect from TechDocs V.0 is a demonstration of how to integrate docs into +> Backstage. TechDocs can create docs using +> [mkdocs](https://www.mkdocs.org/), as well as read published docs. If you +> publish generated docs and pass in a `storageUrl` in your `app-config.yaml`, +> you can view them in Backstage by going to > `http://localhost:3000/docs/`. -Getting started with TechDocs is easy. TechDocs functions as a plugin to -Backstage, why you will need to use Backstage to use TechDocs. +TechDocs functions as a plugin to +Backstage, so you will need to use Backstage to use TechDocs. ## What is Backstage? Backstage is an open platform for building developer portals. It’s based on the developer portal we’ve been using internally at Spotify for over four years. -[Read more](https://github.com/spotify/backstage). +[Read more here](https://github.com/spotify/backstage). ## Prerequisities -In order to use Backstage and TechDocs, you will need to have the following +In order to use Backstage and TechDocs, you need to have the following installed: - [Node.js](https://nodejs.org) Active LTS (long term support), currently v12 @@ -29,48 +29,41 @@ installed: ## Creating a new Backstage app -> If you have already created a Backstage application for this purpose, jump to +> If you have already created a Backstage application, jump to > [Installing TechDocs](#installing-techdocs), otherwise complete this step. -To create a new Backstage application for us to set up TechDocs, you will need -to run the following command: +To create a new Backstage application for TechDocs, run the following command: ```bash npx @backstage/cli create-app ``` -You will then be prompted to enter a name for your application. Once you do so, -this will create a new Backstage application for you in a new folder. For -example, if we chose the name `hello-world` for our application, it would create -a new `hello-world` folder containing our new Backstage application. +You will then be prompted to enter a name for your application. Once that's done, a new Backstage application will be created in a new folder. For +example, if you choose the name `hello-world`, a new `hello-world` folder is created containing your new Backstage application. ## Installing TechDocs -Inside of our new Backstage application, TechDocs is not provided by default. -For this reason we will need to manually set up TechDocs. It should take less +TechDocs is not provided with the Backstage application by default, so you will now need to set up TechDocs manually. It should take less than a minute. ### Adding the package -We will need to add our plugin to your Backstage application. To do so, you can -navigate to your new Backstage application folder and then run a single command -to install TechDocs. +The first step is to add the TechDocs plugin to your Backstage application. Navigate to your new Backstage application folder: ```bash cd hello-world/ ``` -Then you need to navigate to your `packages/app` folder to install TechDocs: +Then navigate to your `packages/app` folder to install TechDocs: ```bash cd packages/app yarn add @backstage/plugin-techdocs ``` -After a short while, it should successfully install the TechDocs plugin. Now we -just need to set up some basic configuration! +After a short while, the TechDocs plugin should be successfully installed. -Enter the following command: +Next, you need to set up some basic configuration. Enter the following command: ```bash yarn install @@ -85,7 +78,7 @@ export { plugin as TechDocs } from '@backstage/plugin-techdocs'; ### Setting the configuration TechDocs allows for configuration of the docs storage URL through your -app-config file. The URL provided here is demo docs used to testing. +`app-config` file. The URL provided here is for demo docs to use for testing purposes. To use the demo docs, add the following lines to `app-config.yaml`: @@ -94,16 +87,17 @@ techdocs: storageUrl: https://techdocs-mock-sites.storage.googleapis.com ``` -## Run Backstage Locally +## Run Backstage locally -Change folder to your Backstage application root. +Change folder to your Backstage application root and run the following command: ```bash yarn start ``` -Open browser at [http://localhost:3000/docs/](http://localhost:3000/docs/) +Open your browser at [http://localhost:3000/docs/](http://localhost:3000/docs/). -## Extra Reading +## Additional reading -[Back to Docs](README.md) + * [Creating and publishing your docs](creating-and-publishing.md) + * [Back to README](README.md) diff --git a/docs/features/techdocs/reading-documentation.md b/docs/features/techdocs/reading-documentation.md deleted file mode 100644 index f872ed5df8..0000000000 --- a/docs/features/techdocs/reading-documentation.md +++ /dev/null @@ -1 +0,0 @@ -# Reading Documentation diff --git a/docs/features/techdocs/writing-documentation.md b/docs/features/techdocs/writing-documentation.md deleted file mode 100644 index 5fea01bcad..0000000000 --- a/docs/features/techdocs/writing-documentation.md +++ /dev/null @@ -1 +0,0 @@ -# Writing Documentation diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index b69ff1508c..32093ea811 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -15,14 +15,14 @@ To create a Backstage app, you will need to have With `npx`: ```bash -npx @backstage/cli create-app +npx @backstage/create-app ``` This will create a new Backstage App inside the current folder. The name of the app-folder is the name that was provided when prompted.

- create app + create app

Inside that directory, it will generate all the files and folder structure diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md index 02729e5bb4..e10ee4b24a 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/development-environment.md @@ -74,8 +74,6 @@ yarn test:all # test all packages yarn clean # Remove all output folders and @backstage/cli cache -yarn bundle # Build a production bundle of the example app - yarn diff # Make sure all plugins are up to date with the latest plugin template yarn create-plugin # Create a new plugin diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md new file mode 100644 index 0000000000..9093e98e2e --- /dev/null +++ b/docs/overview/architecture-overview.md @@ -0,0 +1,190 @@ +# Typical Backstage architecture + +The following diagram shows how Backstage might look when deployed inside a +company which uses the Tech Radar plugin, the Lighthouse plugin, the Circle CI +plugin and the service catalog. + +There are 3 main components in this architecture: + +1. The core Backstage UI +2. The UI plugins and their backing services +3. Databases + +Running this architecture in a real environment typically involves +containerising the components. Various commands are provided for accomplishing +this. + +![The architecture of a basic Backstage application](./architecture-overview/backstage-typical-architecture.png) + +# The UI + +The UI is a thin, client-side wrapper around a set of plugins. It provides some +core UI components and libraries for shared activities such as config +management. [[live demo](https://backstage-demo.roadie.io/)] + +![UI with different components highlighted](./architecture-overview/core-vs-plugin-components-highlighted.png) + +Each plugin typically makes itself available in the UI on a dedicated URL. For +example, the lighthouse plugin is registered with the UI on `/lighthouse`. +[[live demo](https://backstage-demo.roadie.io/lighthouse)] + +![The lighthouse plugin UI](./architecture-overview/lighthouse-plugin.png) + +The Circle CI plugin is available on `/circleci`. + +![Circle CI Plugin UI](./architecture-overview/circle-ci.png) + +# Plugins and plugin backends + +Each plugin is a client side application which mounts itself on the UI. Plugins +are written in TypeScript or JavaScript. They each live in their own directory +in `backstage/plugins`. For example, the source code for the lighthouse plugin +is available at +[backstage/plugins/lighthouse](https://github.com/spotify/backstage/tree/master/plugins/lighthouse). + +## Installing plugins + +Plugins are typically loaded by the UI in your Backstage applications +`plugins.ts` file. For example, +[here](https://github.com/spotify/backstage/blob/master/packages/app/src/plugins.ts) +is that file in the Backstage sample app. + +Plugins can be enabled, and passed configuration in `apis.ts`. For example, +[here](https://github.com/spotify/backstage/blob/master/packages/app/src/apis.ts) +is that file in the Backstage sample app. + +This is how the lighthouse plugin would be enabled in a typical Backstage +application: + +```tsx +import { ApiHolder, ApiRegistry } from '@backstage/core'; +import { + lighthouseApiRef, + LighthouseRestApi, +} from '@backstage/plugin-lighthouse'; + +const builder = ApiRegistry.builder(); + +export const lighthouseApi = new LighthouseRestApi(/* URL of the lighthouse microservice! */); +builder.add(lighthouseApiRef, lighthouseApi); + +export default builder.build() as ApiHolder; +``` + +As of this moment, there is no config based install procedure for plugins. Some +code changes are required. + +## Plugin architecture + +Architecturally, plugins can take three forms: + +1. Standalone +2. Service backed +3. Third-party backed + +### Standalone plugins + +Standalone plugins run entirely in the browser. +[The tech radar plugin](https://backstage-demo.roadie.io/tech-radar), for +example, simply renders hard-coded information. It doesn't make any API requests +to other services. + +![tech radar plugin ui](./architecture-overview/tech-radar-plugin.png) + +The architecture of the Tech Radar installed into a Backstage app is very +simple. + +![ui and tech radar plugin connected together](./architecture-overview/tech-radar-plugin-architecture.png) + +### Service backed plugins + +Service backed plugins make API requests to a service which is within the +purview of the organisation running Backstage. + +The lighthouse plugin, for example, makes requests to the +[lighthouse-audit-service](https://github.com/spotify/lighthouse-audit-service). +The lighthouse-audit-service is a microservice which runs a copy of Google's +[Lighthouse library](https://github.com/GoogleChrome/lighthouse/) and stores the +results in a PostgreSQL database. + +Its architecture looks like this: + +![lighthouse plugin backed to microservice and database](./architecture-overview/lighthouse-plugin-architecture.png) + +The service catalog in Backstage is another example of a service backed plugin. +It retrieves a list of services, or "entities", from the Backstage Backend +service and renders them in a table for the user. + +### Third-party backed plugins + +Third-party backed plugins are similar to service backed plugins. The main +difference is that the service which backs the plugin is hosted outside of the +ecosystem of the company hosting Backstage. + +The Circle CI plugin is an example of a third-party backed plugin. Circle CI is +a SaaS service which can be used without any knowledge of Backstage. It has an +API which a Backstage plugin consumes to display content. + +Requests which go to Circle CI from the users browser are passed through a proxy +service that Backstage provides. Without this, the requests would be blocked by +Cross Origin Resource Sharing policies which prevent a browser page served at +[https://example.com](https://example.com) from serving resources hosted at +https://circleci.com. + +![CircleCi plugin talking to proxy talking to SaaS Circle CI](./architecture-overview/circle-ci-plugin-architecture.png) + +# Databases + +As we have seen, both the lighthouse-audit-service and catalog-backend require a +database to work with. + +At the time of writing, the lighthouse-audit-service requires PostgreSQL to work +with. The service catalog backend uses an in-memory Sqlite3 instance. This is a +development oriented setup and there are plans to support other databases in the +future. + +To learn more about the future of databases and Backstage, see the following two +GitHub issues. + +[Knex + Plugins (Multiple vs Single Database) · Issue #1598 · spotify/backstage](https://github.com/spotify/backstage/issues/1598) + +[Update migrations to support postgres by dariddler · Pull Request #1527 · spotify/backstage](https://github.com/spotify/backstage/pull/1527#discussion_r450374145) + +# Containerization + +The example Backstage architecture shown above would Dockerize into three +separate docker images. + +1. The frontend container +2. The backend container +3. The lighthouse audit service container + +![Boxes around the architecture to indicate how it is containerised](./architecture-overview/containerised.png) + +The frontend container can be built with a provided command. + +```bash +yarn install +yarn tsc +yarn build +yarn run docker-build +``` + +Running this will simply generate a Docker container containing the contents of +the UIs `dist` directory. The resulting container will be about 50MB in size. + +The backend container can be built by running the following command in the +`packages/backend` directory. + +```bash +yarn run build-image +``` + +This will create a ~500MB container called `example-backend`. + +The lighthouse-audit-service container is already publicly available in Docker +Hub and can be downloaded and ran with + +```bash +docker run spotify/lighthouse-audit-service:latest +``` diff --git a/docs/overview/architecture-overview/backstage-typical-architecture.png b/docs/overview/architecture-overview/backstage-typical-architecture.png new file mode 100644 index 0000000000..44b9967d28 Binary files /dev/null and b/docs/overview/architecture-overview/backstage-typical-architecture.png differ diff --git a/docs/overview/architecture-overview/circle-ci-plugin-architecture.png b/docs/overview/architecture-overview/circle-ci-plugin-architecture.png new file mode 100644 index 0000000000..bc1f2d97cc Binary files /dev/null and b/docs/overview/architecture-overview/circle-ci-plugin-architecture.png differ diff --git a/docs/overview/architecture-overview/circle-ci.png b/docs/overview/architecture-overview/circle-ci.png new file mode 100644 index 0000000000..c5695196f1 Binary files /dev/null and b/docs/overview/architecture-overview/circle-ci.png differ diff --git a/docs/overview/architecture-overview/containerised.png b/docs/overview/architecture-overview/containerised.png new file mode 100644 index 0000000000..55da3c2c36 Binary files /dev/null and b/docs/overview/architecture-overview/containerised.png differ diff --git a/docs/overview/architecture-overview/core-vs-plugin-components-highlighted.png b/docs/overview/architecture-overview/core-vs-plugin-components-highlighted.png new file mode 100644 index 0000000000..7d08fc5e36 Binary files /dev/null and b/docs/overview/architecture-overview/core-vs-plugin-components-highlighted.png differ diff --git a/docs/overview/architecture-overview/lighthouse-plugin-architecture.png b/docs/overview/architecture-overview/lighthouse-plugin-architecture.png new file mode 100644 index 0000000000..0da5d6f042 Binary files /dev/null and b/docs/overview/architecture-overview/lighthouse-plugin-architecture.png differ diff --git a/docs/overview/architecture-overview/lighthouse-plugin.png b/docs/overview/architecture-overview/lighthouse-plugin.png new file mode 100644 index 0000000000..9692fc0833 Binary files /dev/null and b/docs/overview/architecture-overview/lighthouse-plugin.png differ diff --git a/docs/overview/architecture-overview/tech-radar-plugin-architecture.png b/docs/overview/architecture-overview/tech-radar-plugin-architecture.png new file mode 100644 index 0000000000..d55be5ee82 Binary files /dev/null and b/docs/overview/architecture-overview/tech-radar-plugin-architecture.png differ diff --git a/docs/overview/architecture-overview/tech-radar-plugin.png b/docs/overview/architecture-overview/tech-radar-plugin.png new file mode 100644 index 0000000000..dbf39b6c63 Binary files /dev/null and b/docs/overview/architecture-overview/tech-radar-plugin.png differ diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index eb8ce1f906..86b5417d5f 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -1,33 +1,35 @@ # [Backstage](https://backstage.io) -![headline](../headline.png) +![service-catalog](https://backstage.io/blog/assets/6/header.png) ## What is Backstage? [Backstage](https://backstage.io/) is an open platform for building developer -portals. It’s based on the developer portal we’ve been using internally at -Spotify for over four years. Backstage can be as simple as a services catalog or -as powerful as the UX layer for your entire tech infrastructure. +portals. Powered by a centralized service catalog, Backstage restores order to +your microservices and infrastructure. So your product teams can ship +high-quality code quickly — without compromising autonomy. + +Backstage unifies all your infrastructure tooling, services, and documentation +to create a streamlined development environment from end to end. + +Out of the box, Backstage includes: + +- [Backstage Service Catalog](https://github.com/spotify/backstage/blob/master/docs/features/software-catalog/index.md) + for managing all your software (microservices, libraries, data pipelines, + websites, ML models, etc.) +- [Backstage Software Templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/index.md) + for quickly spinning up new projects and standardizing your tooling with your + organization’s best practices +- [Backstage TechDocs](https://github.com/spotify/backstage/tree/master/docs/features/techdocs) + for making it easy to create, maintain, find, and use technical documentation, + using a "docs like code" approach +- Plus, a growing ecosystem of + [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) + that further expand Backstage’s customizability and functionality For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). -### Features - -- Create and manage all of your organization’s software and microservices in one - place. -- Services catalog keeps track of all software and its ownership. -- Visualizations provide information about your backend services and tooling, - and help you monitor them. -- A unified method for managing microservices offers both visibility and - control. -- Preset templates allow engineers to quickly create microservices in a - standardized way - ([coming soon](https://github.com/spotify/backstage/milestone/11)). -- Centralized, full-featured technical documentation with integrated tooling - that makes it easy for developers to set up, publish, and maintain alongside - their code ([coming soon](https://github.com/spotify/backstage/milestone/15)). - ### Benefits - For _engineering managers_, it allows you to maintain standards and best diff --git a/docs/reference/utility-apis/AlertApi.md b/docs/reference/utility-apis/AlertApi.md new file mode 100644 index 0000000000..3490c1900e --- /dev/null +++ b/docs/reference/utility-apis/AlertApi.md @@ -0,0 +1,114 @@ +# AlertApi + +The AlertApi type is defined at +[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L29). + +The following Utility API implements this type: [alertApiRef](./README.md#alert) + +## Members + +### post() + +Post an alert for handling by the application. + +
+post(alert: AlertMessage): void
+
+ +### alert\$() + +Observe alerts posted by other parts of the application. + +
+alert$(): Observable<AlertMessage>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AlertMessage + +
+export type AlertMessage = {
+  message: string;
+  // Severity will default to success since that is what material ui defaults the value to.
+  severity?: 'success' | 'info' | 'warning' | 'error';
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L19). + +Referenced by: [post](#post), [alert\$](#alert). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [alert\$](#alert). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/AppThemeApi.md b/docs/reference/utility-apis/AppThemeApi.md new file mode 100644 index 0000000000..eda1bafef0 --- /dev/null +++ b/docs/reference/utility-apis/AppThemeApi.md @@ -0,0 +1,232 @@ +# AppThemeApi + +The AppThemeApi type is defined at +[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50). + +The following Utility API implements this type: +[appThemeApiRef](./README.md#apptheme) + +## Members + +### getInstalledThemes() + +Get a list of available themes. + +
+getInstalledThemes(): AppTheme[]
+
+ +### activeThemeId\$() + +Observe the currently selected theme. A value of undefined means no specific +theme has been selected. + +
+activeThemeId$(): Observable<string | undefined>
+
+ +### getActiveThemeId() + +Get the current theme ID. Returns undefined if no specific theme is selected. + +
+getActiveThemeId(): string | undefined
+
+ +### setActiveThemeId() + +Set a specific theme to use in the app, overriding the default theme selection. + +Clear the selection by passing in undefined. + +
+setActiveThemeId(themeId?: string): void
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AppTheme + +Describes a theme provided by the app. + +
+export type AppTheme = {
+  /**
+   * ID used to remember theme selections.
+   */
+  id: string;
+
+  /**
+   * Title of the theme
+   */
+  title: string;
+
+  /**
+   * Theme variant
+   */
+  variant: 'light' | 'dark';
+
+  /**
+   * The specialized MaterialUI theme instance.
+   */
+  theme: BackstageTheme;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24). + +Referenced by: [getInstalledThemes](#getinstalledthemes). + +### BackstagePalette + +
+export type BackstagePalette = Palette & PaletteAdditions
+
+ +Defined at +[packages/theme/src/types.ts:67](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L67). + +Referenced by: [BackstageTheme](#backstagetheme). + +### BackstageTheme + +
+export interface BackstageTheme extends Theme {
+  palette: BackstagePalette;
+}
+
+ +Defined at +[packages/theme/src/types.ts:70](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L70). + +Referenced by: [AppTheme](#apptheme). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [activeThemeId\$](#activethemeid). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### PaletteAdditions + +
+type PaletteAdditions = {
+  status: {
+    ok: string;
+    warning: string;
+    error: string;
+    pending: string;
+    running: string;
+    aborted: string;
+  };
+  border: string;
+  textContrast: string;
+  textVerySubtle: string;
+  textSubtle: string;
+  highlight: string;
+  errorBackground: string;
+  warningBackground: string;
+  infoBackground: string;
+  errorText: string;
+  infoText: string;
+  warningText: string;
+  linkHover: string;
+  link: string;
+  gold: string;
+  navigation: {
+    background: string;
+    indicator: string;
+  };
+  tabbar: {
+    indicator: string;
+  };
+  bursts: {
+    fontColor: string;
+    slackChannelText: string;
+    backgroundColor: {
+      default: string;
+    };
+  };
+  pinSidebarButton: {
+    icon: string;
+    background: string;
+  };
+  banner: {
+    info: string;
+    error: string;
+  };
+}
+
+ +Defined at +[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L23). + +Referenced by: [BackstagePalette](#backstagepalette). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/BackstageIdentityApi.md b/docs/reference/utility-apis/BackstageIdentityApi.md new file mode 100644 index 0000000000..f75918a8b3 --- /dev/null +++ b/docs/reference/utility-apis/BackstageIdentityApi.md @@ -0,0 +1,88 @@ +# BackstageIdentityApi + +The BackstageIdentityApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L144). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauth) + +- [gitlabAuthApiRef](./README.md#gitlabauth) + +- [googleAuthApiRef](./README.md#googleauth) + +- [oktaAuthApiRef](./README.md#oktaauth) + +## Members + +### getBackstageIdentity() + +Get the user's identity within Backstage. This should normally not be called +directly, use the @IdentityApi instead. + +If the optional flag is not set, a session is guaranteed to be returned, while +if the optional flag is set, the session may be undefined. See +@AuthRequestOptions for more details. + +
+getBackstageIdentity(
+    options?: AuthRequestOptions,
+  ): Promise<BackstageIdentity | undefined>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getBackstageIdentity](#getbackstageidentity). + +### BackstageIdentity + +
+export type BackstageIdentity = {
+  /**
+   * The backstage user ID.
+   */
+  id: string;
+
+  /**
+   * An ID token that can be used to authenticate the user within Backstage.
+   */
+  idToken: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L157). + +Referenced by: [getBackstageIdentity](#getbackstageidentity). diff --git a/docs/reference/utility-apis/Config.md b/docs/reference/utility-apis/Config.md new file mode 100644 index 0000000000..c0264489fc --- /dev/null +++ b/docs/reference/utility-apis/Config.md @@ -0,0 +1,179 @@ +# Config + +The Config type is defined at +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L32). + +The following Utility API implements this type: +[configApiRef](./README.md#config) + +## Members + +### keys() + +
+keys(): string[]
+
+ +### get() + +
+get(key: string): JsonValue
+
+ +### getOptional() + +
+getOptional(key: string): JsonValue | undefined
+
+ +### getConfig() + +
+getConfig(key: string): Config
+
+ +### getOptionalConfig() + +
+getOptionalConfig(key: string): Config | undefined
+
+ +### getConfigArray() + +
+getConfigArray(key: string): Config[]
+
+ +### getOptionalConfigArray() + +
+getOptionalConfigArray(key: string): Config[] | undefined
+
+ +### getNumber() + +
+getNumber(key: string): number
+
+ +### getOptionalNumber() + +
+getOptionalNumber(key: string): number | undefined
+
+ +### getBoolean() + +
+getBoolean(key: string): boolean
+
+ +### getOptionalBoolean() + +
+getOptionalBoolean(key: string): boolean | undefined
+
+ +### getString() + +
+getString(key: string): string
+
+ +### getOptionalString() + +
+getOptionalString(key: string): string | undefined
+
+ +### getStringArray() + +
+getStringArray(key: string): string[]
+
+ +### getOptionalStringArray() + +
+getOptionalStringArray(key: string): string[] | undefined
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Config + +
+export type Config = {
+  keys(): string[];
+
+  get(key: string): JsonValue;
+  getOptional(key: string): JsonValue | undefined;
+
+  getConfig(key: string): Config;
+  getOptionalConfig(key: string): Config | undefined;
+
+  getConfigArray(key: string): Config[];
+  getOptionalConfigArray(key: string): Config[] | undefined;
+
+  getNumber(key: string): number;
+  getOptionalNumber(key: string): number | undefined;
+
+  getBoolean(key: string): boolean;
+  getOptionalBoolean(key: string): boolean | undefined;
+
+  getString(key: string): string;
+  getOptionalString(key: string): string | undefined;
+
+  getStringArray(key: string): string[];
+  getOptionalStringArray(key: string): string[] | undefined;
+}
+
+ +Defined at +[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L32). + +Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig), +[getConfigArray](#getconfigarray), +[getOptionalConfigArray](#getoptionalconfigarray), [Config](#config). + +### JsonArray + +
+export type JsonArray = JsonValue[]
+
+ +Defined at +[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L18). + +Referenced by: [JsonValue](#jsonvalue). + +### JsonObject + +
+export type JsonObject = { [key in string]?: JsonValue }
+
+ +Defined at +[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L17). + +Referenced by: [JsonValue](#jsonvalue). + +### JsonValue + +
+export type JsonValue =
+  | JsonObject
+  | JsonArray
+  | number
+  | string
+  | boolean
+  | null
+
+ +Defined at +[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L19). + +Referenced by: [get](#get), [getOptional](#getoptional), +[JsonObject](#jsonobject), [JsonArray](#jsonarray), [Config](#config). diff --git a/docs/reference/utility-apis/ErrorApi.md b/docs/reference/utility-apis/ErrorApi.md new file mode 100644 index 0000000000..a630e65592 --- /dev/null +++ b/docs/reference/utility-apis/ErrorApi.md @@ -0,0 +1,134 @@ +# ErrorApi + +The ErrorApi type is defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L53). + +The following Utility API implements this type: [errorApiRef](./README.md#error) + +## Members + +### post() + +Post an error for handling by the application. + +
+post(error: Error, context?: ErrorContext): void
+
+ +### error\$() + +Observe errors posted by other parts of the application. + +
+error$(): Observable<{ error: Error; context?: ErrorContext }>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Error + +Mirrors the javascript Error class, for the purpose of providing documentation +and optional fields. + +
+type Error = {
+  name: string;
+  message: string;
+  stack?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L24). + +Referenced by: [post](#post), [error\$](#error). + +### ErrorContext + +Provides additional information about an error that was posted to the +application. + +
+export type ErrorContext = {
+  // If set to true, this error should not be displayed to the user. Defaults to false.
+  hidden?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L33). + +Referenced by: [post](#post), [error\$](#error). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [error\$](#error). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/FeatureFlagsApi.md b/docs/reference/utility-apis/FeatureFlagsApi.md new file mode 100644 index 0000000000..16e30a1c60 --- /dev/null +++ b/docs/reference/utility-apis/FeatureFlagsApi.md @@ -0,0 +1,33 @@ +# FeatureFlagsApi + +The FeatureFlagsApi type is defined at +[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41). + +The following Utility API implements this type: +[featureFlagsApiRef](./README.md#featureflags) + +## Members + +### registeredFeatureFlags + +Store a list of registered feature flags. + +
+registeredFeatureFlags: FeatureFlagsRegistryItem[]
+
+ +### getFlags() + +Get a list of all feature flags from the current user. + +
+getFlags(): UserFlags
+
+ +### getRegisteredFlags() + +Get a list of all registered flags. + +
+getRegisteredFlags(): FeatureFlagsRegistry
+
diff --git a/docs/reference/utility-apis/IdentityApi.md b/docs/reference/utility-apis/IdentityApi.md new file mode 100644 index 0000000000..f26ad4b6b3 --- /dev/null +++ b/docs/reference/utility-apis/IdentityApi.md @@ -0,0 +1,81 @@ +# IdentityApi + +The IdentityApi type is defined at +[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/IdentityApi.ts#L22). + +The following Utility API implements this type: +[identityApiRef](./README.md#identity) + +## Members + +### getUserId() + +The ID of the signed in user. This ID is not meant to be presented to the user, +but used as an opaque string to pass on to backends or use in frontend logic. + +TODO: The intention of the user ID is to be able to tie the user to an identity +that is known by the catalog and/or identity backend. It should for example be +possible to fetch all owned components using this ID. + +
+getUserId(): string
+
+ +### getProfile() + +The profile of the signed in user. + +
+getProfile(): ProfileInfo
+
+ +### getIdToken() + +An OpenID Connect ID Token which proves the identity of the signed in user. + +The ID token will be undefined if the signed in user does not have a verified +identity, such as a demo user or mocked user for e2e tests. + +
+getIdToken(): Promise<string | undefined>
+
+ +### logout() + +Log out the current user + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### ProfileInfo + +Profile information of the user. + +
+export type ProfileInfo = {
+  /**
+   * Email ID.
+   */
+  email?: string;
+
+  /**
+   * Display name that can be presented to the user.
+   */
+  displayName?: string;
+
+  /**
+   * URL to an avatar image of the user.
+   */
+  picture?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L172). + +Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/OAuthApi.md b/docs/reference/utility-apis/OAuthApi.md new file mode 100644 index 0000000000..766124ccb4 --- /dev/null +++ b/docs/reference/utility-apis/OAuthApi.md @@ -0,0 +1,119 @@ +# OAuthApi + +The OAuthApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L67). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauth) + +- [gitlabAuthApiRef](./README.md#gitlabauth) + +- [googleAuthApiRef](./README.md#googleauth) + +- [oauth2ApiRef](./README.md#oauth2) + +- [oktaAuthApiRef](./README.md#oktaauth) + +## Members + +### getAccessToken() + +Requests an OAuth 2 Access Token, optionally with a set of scopes. The access +token allows you to make requests on behalf of the user, and the copes may grant +you broader access, depending on the auth provider. + +Each auth provider has separate handling of scope, so you need to look at the +documentation for each one to know what scope you need to request. + +This method is cheap and should be called each time an access token is used. Do +not for example store the access token in React component state, as that could +cause the token to expire. Instead fetch a new access token for each request. + +Be sure to include all required scopes when requesting an access token. When +testing your implementation it is best to log out the Backstage session and then +visit your plugin page directly, as you might already have some required scopes +in your existing session. Not requesting the correct scopes can lead to 403 or +other authorization errors, which can be tricky to debug. + +If the user has not yet granted access to the provider and the set of requested +scopes, the user will be prompted to log in. The returned promise will not +resolve until the user has successfully logged in. The returned promise can be +rejected, but only if the user rejects the login request. + +
+getAccessToken(
+    scope?: OAuthScope,
+    options?: AuthRequestOptions,
+  ): Promise<string>
+
+ +### logout() + +Log out the user's session. This will reload the page. + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getAccessToken](#getaccesstoken). + +### OAuthScope + +This file contains declarations for common interfaces of auth-related APIs. The +declarations should be used to signal which type of authentication and +authorization methods each separate auth provider supports. + +For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, +would be declared as follows: + +const googleAuthApiRef = createApiRef({ ... }) + +An array of scopes, or a scope string formatted according to the auth provider, +which is typically a space separated list. + +See the documentation for each auth provider for the list of scopes supported by +each provider. + +
+export type OAuthScope = string | string[]
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L38). + +Referenced by: [getAccessToken](#getaccesstoken). diff --git a/docs/reference/utility-apis/OAuthRequestApi.md b/docs/reference/utility-apis/OAuthRequestApi.md new file mode 100644 index 0000000000..b3e0d0d979 --- /dev/null +++ b/docs/reference/utility-apis/OAuthRequestApi.md @@ -0,0 +1,232 @@ +# OAuthRequestApi + +The OAuthRequestApi type is defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99). + +The following Utility API implements this type: +[oauthRequestApiRef](./README.md#oauthrequest) + +## Members + +### createAuthRequester() + +A utility for showing login popups or similar things, and merging together +multiple requests for different scopes into one request that inclues all scopes. + +The passed in options provide information about the login provider, and how to +handle auth requests. + +The returned AuthRequester function is used to request login with new scopes. +These requests are merged together and forwarded to the auth handler, as soon as +a consumer of auth requests triggers an auth flow. + +See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. + +
+createAuthRequester<AuthResponse>(
+    options: AuthRequesterOptions<AuthResponse>,
+  ): AuthRequester<AuthResponse>
+
+ +### authRequest\$() + +Observers panding auth requests. The returned observable will emit all current +active auth request, at most one for each created auth requester. + +Each request has its own info about the login provider, forwarded from the auth +requester options. + +Depending on user interaction, the request should either be rejected, or used to +trigger the auth handler. If the request is rejected, all pending AuthRequester +calls will fail with a "RejectedError". If a auth is triggered, and the auth +handler resolves successfully, then all currently pending AuthRequester calls +will resolve to the value returned by the onAuthRequest call. + +
+authRequest$(): Observable<PendingAuthRequest[]>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthProvider + +Information about the auth provider that we're requesting a login towards. + +This should be shown to the user so that they can be informed about what login +is being requested before a popup is shown. + +
+export type AuthProvider = {
+  /**
+   * Title for the auth provider, for example "GitHub"
+   */
+  title: string;
+
+  /**
+   * Icon for the auth provider.
+   */
+  icon: IconComponent;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27). + +Referenced by: [AuthRequesterOptions](#authrequesteroptions), +[PendingAuthRequest](#pendingauthrequest). + +### AuthRequester + +Function used to trigger new auth requests for a set of scopes. + +The returned promise will resolve to the same value returned by the +onAuthRequest in the AuthRequesterOptions. Or rejected, if the request is +rejected. + +This function can be called multiple times before the promise resolves. All +calls will be merged into one request, and the scopes forwarded to the +onAuthRequest will be the union of all requested scopes. + +
+export type AuthRequester<AuthResponse> = (
+  scopes: Set<string>,
+) => Promise<AuthResponse>
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66). + +Referenced by: [createAuthRequester](#createauthrequester). + +### AuthRequesterOptions + +Describes how to handle auth requests. Both how to show them to the user, and +what to do when the user accesses the auth request. + +
+export type AuthRequesterOptions<AuthResponse> = {
+  /**
+   * Information about the auth provider, which will be forwarded to auth requests.
+   */
+  provider: AuthProvider;
+
+  /**
+   * Implementation of the auth flow, which will be called synchronously when
+   * trigger() is called on an auth requests.
+   */
+  onAuthRequest(scopes: Set<string>): Promise<AuthResponse>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43). + +Referenced by: [createAuthRequester](#createauthrequester). + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [authRequest\$](#authrequest). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### PendingAuthRequest + +An pending auth request for a single auth provider. The request will remain in +this pending state until either reject() or trigger() is called. + +Any new requests for the same provider are merged into the existing pending +request, meaning there will only ever be a single pending request for a given +provider. + +
+export type PendingAuthRequest = {
+  /**
+   * Information about the auth provider, as given in the AuthRequesterOptions
+   */
+  provider: AuthProvider;
+
+  /**
+   * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError".
+   */
+  reject: () => void;
+
+  /**
+   * Trigger the auth request to continue the auth flow, by for example showing a popup.
+   *
+   * Synchronously calls onAuthRequest with all scope currently in the request.
+   */
+  trigger(): Promise<void>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77). + +Referenced by: [authRequest\$](#authrequest). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/OpenIdConnectApi.md b/docs/reference/utility-apis/OpenIdConnectApi.md new file mode 100644 index 0000000000..7bc571ecb9 --- /dev/null +++ b/docs/reference/utility-apis/OpenIdConnectApi.md @@ -0,0 +1,75 @@ +# OpenIdConnectApi + +The OpenIdConnectApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L104). + +The following Utility APIs implement this type: + +- [googleAuthApiRef](./README.md#googleauth) + +- [oauth2ApiRef](./README.md#oauth2) + +- [oktaAuthApiRef](./README.md#oktaauth) + +## Members + +### getIdToken() + +Requests an OpenID Connect ID Token. + +This method is cheap and should be called each time an ID token is used. Do not +for example store the id token in React component state, as that could cause the +token to expire. Instead fetch a new id token for each request. + +If the user has not yet logged in to Google inside Backstage, the user will be +prompted to log in. The returned promise will not resolve until the user has +successfully logged in. The returned promise can be rejected, but only if the +user rejects the login request. + +
+getIdToken(options?: AuthRequestOptions): Promise<string>
+
+ +### logout() + +Log out the user's session. This will reload the page. + +
+logout(): Promise<void>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getIdToken](#getidtoken). diff --git a/docs/reference/utility-apis/ProfileInfoApi.md b/docs/reference/utility-apis/ProfileInfoApi.md new file mode 100644 index 0000000000..68b0dea194 --- /dev/null +++ b/docs/reference/utility-apis/ProfileInfoApi.md @@ -0,0 +1,94 @@ +# ProfileInfoApi + +The ProfileInfoApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L127). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauth) + +- [gitlabAuthApiRef](./README.md#gitlabauth) + +- [googleAuthApiRef](./README.md#googleauth) + +- [oauth2ApiRef](./README.md#oauth2) + +- [oktaAuthApiRef](./README.md#oktaauth) + +## Members + +### getProfile() + +Get profile information for the user as supplied by this auth provider. + +If the optional flag is not set, a session is guaranteed to be returned, while +if the optional flag is set, the session may be undefined. See +@AuthRequestOptions for more details. + +
+getProfile(options?: AuthRequestOptions): Promise<ProfileInfo | undefined>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### AuthRequestOptions + +
+export type AuthRequestOptions = {
+  /**
+   * If this is set to true, the user will not be prompted to log in,
+   * and an empty response will be returned if there is no existing session.
+   *
+   * This can be used to perform a check whether the user is logged in, or if you don't
+   * want to force a user to be logged in, but provide functionality if they already are.
+   *
+   * @default false
+   */
+  optional?: boolean;
+
+  /**
+   * If this is set to true, the request will bypass the regular oauth login modal
+   * and open the login popup directly.
+   *
+   * The method must be called synchronously from a user action for this to work in all browsers.
+   *
+   * @default false
+   */
+  instantPopup?: boolean;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40). + +Referenced by: [getProfile](#getprofile). + +### ProfileInfo + +Profile information of the user. + +
+export type ProfileInfo = {
+  /**
+   * Email ID.
+   */
+  email?: string;
+
+  /**
+   * Display name that can be presented to the user.
+   */
+  displayName?: string;
+
+  /**
+   * URL to an avatar image of the user.
+   */
+  picture?: string;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L172). + +Referenced by: [getProfile](#getprofile). diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md new file mode 100644 index 0000000000..8b9ebcbdad --- /dev/null +++ b/docs/reference/utility-apis/README.md @@ -0,0 +1,139 @@ +# Backstage Core Utility APIs + +The following is a list of all Utility APIs defined by `@backstage/core`. They +are available to use by plugins and components, and can be accessed using the +`useApi` hook, also provided by `@backstage/core`. For more information, see +https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md. + +### alert + +Used to report alerts and forward them to the app + +Implemented type: [AlertApi](./AlertApi.md) + +ApiRef: +[alertApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L41) + +### appTheme + +API Used to configure the app theme, and enumerate options + +Implemented type: [AppThemeApi](./AppThemeApi.md) + +ApiRef: +[appThemeApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74) + +### config + +Used to access runtime configuration + +Implemented type: [Config](./Config.md) + +ApiRef: +[configApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ConfigApi.ts#L22) + +### error + +Used to report errors and forward them to the app + +Implemented type: [ErrorApi](./ErrorApi.md) + +ApiRef: +[errorApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L65) + +### featureFlags + +Used to toggle functionality in features across Backstage + +Implemented type: [FeatureFlagsApi](./FeatureFlagsApi.md) + +ApiRef: +[featureFlagsApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58) + +### githubAuth + +Provides authentication towards Github APIs + +Implemented types: [OAuthApi](./OAuthApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) + +ApiRef: +[githubAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L230) + +### gitlabAuth + +Provides authentication towards Gitlab APIs + +Implemented types: [OAuthApi](./OAuthApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) + +ApiRef: +[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L260) + +### googleAuth + +Provides authentication towards Google APIs and identities + +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) + +ApiRef: +[googleAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L213) + +### identity + +Provides access to the identity of the signed in user + +Implemented type: [IdentityApi](./IdentityApi.md) + +ApiRef: +[identityApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/IdentityApi.ts#L54) + +### oauth2 + +Example of how to use oauth2 custom provider + +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), [SessionStateApi](./SessionStateApi.md) + +ApiRef: +[oauth2ApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L270) + +### oauthRequest + +An API for implementing unified OAuth flows in Backstage + +Implemented type: [OAuthRequestApi](./OAuthRequestApi.md) + +ApiRef: +[oauthRequestApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130) + +### oktaAuth + +Provides authentication towards Okta APIs + +Implemented types: [OAuthApi](./OAuthApi.md), +[OpenIdConnectApi](./OpenIdConnectApi.md), +[ProfileInfoApi](./ProfileInfoApi.md), +[BackstageIdentityApi](./BackstageIdentityApi.md), +[SessionStateApi](./SessionStateApi.md) + +ApiRef: +[oktaAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L243) + +### storage + +Provides the ability to store data which is unique to the user + +Implemented type: [StorageApi](./StorageApi.md) + +ApiRef: +[storageApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L68) diff --git a/docs/reference/utility-apis/SessionStateApi.md b/docs/reference/utility-apis/SessionStateApi.md new file mode 100644 index 0000000000..44d329304a --- /dev/null +++ b/docs/reference/utility-apis/SessionStateApi.md @@ -0,0 +1,115 @@ +# SessionStateApi + +The SessionStateApi type is defined at +[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L201). + +The following Utility APIs implement this type: + +- [githubAuthApiRef](./README.md#githubauth) + +- [gitlabAuthApiRef](./README.md#gitlabauth) + +- [googleAuthApiRef](./README.md#googleauth) + +- [oauth2ApiRef](./README.md#oauth2) + +- [oktaAuthApiRef](./README.md#oktaauth) + +## Members + +### sessionState\$() + +
+sessionState$(): Observable<SessionState>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [sessionState\$](#sessionstate). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### SessionState + +Session state values passed to subscribers of the SessionStateApi. + +
+export enum SessionState {
+  SignedIn = 'SignedIn',
+  SignedOut = 'SignedOut',
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L192). + +Referenced by: [sessionState\$](#sessionstate). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/reference/utility-apis/StorageApi.md b/docs/reference/utility-apis/StorageApi.md new file mode 100644 index 0000000000..5bc871e7bf --- /dev/null +++ b/docs/reference/utility-apis/StorageApi.md @@ -0,0 +1,186 @@ +# StorageApi + +The StorageApi type is defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L31). + +The following Utility API implements this type: +[storageApiRef](./README.md#storage) + +## Members + +### forBucket() + +Create a bucket to store data in. + +
+forBucket(name: string): StorageApi
+
+ +### get() + +Get the current value for persistent data, use observe\$ to be notified of +updates. + +
+get<T>(key: string): T | undefined
+
+ +### remove() + +Remove persistent data. + +
+remove(key: string): Promise<void>
+
+ +### set() + +Save persistant data, and emit messages to anyone that is using observe\$ for +this key + +
+set(key: string, data: any): Promise<void>
+
+ +### observe\$() + +Observe changes on a particular key in the bucket + +
+observe$<T>(key: string): Observable<StorageValueChange<T>>
+
+ +## Supporting types + +These types are part of the API declaration, but may not be unique to this API. + +### Observable + +Observable sequence of values and errors, see TC39. + +https://github.com/tc39/proposal-observable + +This is used as a common return type for observable values and can be created +using many different observable implementations, such as zen-observable or +RxJS 5. + +
+export type Observable<T> = {
+  /**
+   * Subscribes to this observable to start receiving new values.
+   */
+  subscribe(observer: Observer<T>): Subscription;
+  subscribe(
+    onNext: (value: T) => void,
+    onError?: (error: Error) => void,
+    onComplete?: () => void,
+  ): Subscription;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53). + +Referenced by: [observe\$](#observe), [StorageApi](#storageapi). + +### Observer + +This file contains non-react related core types used throught Backstage. + +Observer interface for consuming an Observer, see TC39. + +
+export type Observer<T> = {
+  next?(value: T): void;
+  error?(error: Error): void;
+  complete?(): void;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24). + +Referenced by: [Observable](#observable). + +### StorageApi + +
+export interface StorageApi {
+  /**
+   * Create a bucket to store data in.
+   * @param {String} name Namespace for the storage to be stored under,
+   *                      will inherit previous namespaces too
+   */
+  forBucket(name: string): StorageApi;
+
+  /**
+   * Get the current value for persistent data, use observe$ to be notified of updates.
+   *
+   * @param {String} key Unique key associated with the data.
+   * @return {Object} data The data that should is stored.
+   */
+  get<T>(key: string): T | undefined;
+
+  /**
+   * Remove persistent data.
+   *
+   * @param {String} key Unique key associated with the data.
+   */
+  remove(key: string): Promise<void>;
+
+  /**
+   * Save persistant data, and emit messages to anyone that is using observe$ for this key
+   *
+   * @param {String} key Unique key associated with the data.
+   */
+  set(key: string, data: any): Promise<void>;
+
+  /**
+   * Observe changes on a particular key in the bucket
+   * @param {String} key Unique key associated with the data
+   */
+  observe$<T>(key: string): Observable<StorageValueChange<T>>;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L31). + +Referenced by: [forBucket](#forbucket). + +### StorageValueChange + +
+export type StorageValueChange<T = any> = {
+  key: string;
+  newValue?: T;
+}
+
+ +Defined at +[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L21). + +Referenced by: [observe\$](#observe), [StorageApi](#storageapi). + +### Subscription + +Subscription returned when subscribing to an Observable, see TC39. + +
+export type Subscription = {
+  /**
+   * Cancels the subscription
+   */
+  unsubscribe(): void;
+
+  /**
+   * Value indicating whether the subscription is closed.
+   */
+  readonly closed: Boolean;
+}
+
+ +Defined at +[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33). + +Referenced by: [Observable](#observable). diff --git a/docs/verify-links.js b/docs/verify-links.js new file mode 100755 index 0000000000..8c865309f9 --- /dev/null +++ b/docs/verify-links.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { resolve: resolvePath, dirname } = require('path'); +const fs = require('fs-extra'); +const fetch = require('node-fetch'); +const recursive = require('recursive-readdir'); + +const projectRoot = resolvePath(__dirname, '..'); + +async function verifyUrl(basePath, url) { + url = url.replace(/#.*$/, ''); + url = url.replace( + /https:\/\/github.com\/spotify\/backstage\/(tree|blob)\/master/, + '', + ); + if (!url) { + return; + } + + // Only verify existence of local files for now, so skip anything with a schema + if (!url.match(/[a-z]+:/)) { + const path = url.startsWith('/') + ? resolvePath(projectRoot, `.${url}`) + : resolvePath(dirname(resolvePath(projectRoot, basePath)), url); + const exists = await fs.pathExists(path); + if (!exists) { + return { url, basePath }; + } + } + + return; +} + +async function verifyFile(filePath) { + const content = await fs.readFile(filePath, 'utf8'); + const mdLinks = content.match(/\[.+?\]\(.+?\)/g) || []; + const badUrls = []; + + for (const mdLink of mdLinks) { + const url = mdLink.match(/\[.+\]\((.+)\)/)[1].trim(); + const badUrl = await verifyUrl(filePath, url); + if (badUrl) { + badUrls.push(badUrl); + } + } + + return badUrls; +} + +async function main() { + process.chdir(projectRoot); + + const files = await recursive('.', ['node_modules', 'dist', 'bin']); + const mdFiles = files.filter(f => f.endsWith('.md')); + const badUrls = []; + + for (const mdFile of mdFiles) { + const badFileUrls = await verifyFile(mdFile); + badUrls.push(...badFileUrls); + } + + if (badUrls.length) { + console.log(`Found ${badUrls.length} bad links within repo`); + for (const { url, basePath } of badUrls) { + console.error(`Unable to reach ${url}, linked from ${basePath}`); + } + process.exit(1); + } +} + +main().catch(error => { + console.error(error.stack); + process.exit(1); +}); diff --git a/install/kubernetes/app.yaml b/install/kubernetes/app.yaml index ed4108f11b..ce284afd31 100644 --- a/install/kubernetes/app.yaml +++ b/install/kubernetes/app.yaml @@ -11,17 +11,17 @@ spec: matchLabels: app: backstage component: frontend - template: - metadata: - labels: - app: backstage - component: frontend - spec: - containers: - - name: app - image: spotify/backstage:latest - imagePullPolicy: Always - ports: - - containerPort: 80 - name: app - protocol: TCP + template: + metadata: + labels: + app: backstage + component: frontend + spec: + containers: + - name: app + image: spotify/backstage:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + name: app + protocol: TCP diff --git a/install/kubernetes/backend.yaml b/install/kubernetes/backend.yaml index 669426fecd..bc82731958 100644 --- a/install/kubernetes/backend.yaml +++ b/install/kubernetes/backend.yaml @@ -11,17 +11,17 @@ spec: matchLabels: app: backstage component: backend - template: - metadata: - labels: - app: backstage - component: backend - spec: - containers: - - name: backend - image: spotify/backstage-backend:latest - imagePullPolicy: Always - ports: - - containerPort: 7000 - name: backend - protocol: TCP + template: + metadata: + labels: + app: backstage + component: backend + spec: + containers: + - name: backend + image: spotify/backstage-backend:latest + imagePullPolicy: IfNotPresent + ports: + - containerPort: 7000 + name: backend + protocol: TCP diff --git a/lerna.json b/lerna.json index 4b4e7d0298..35f84c54d0 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": ["packages/*", "plugins/*"], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.14" + "version": "0.1.1-alpha.18" } diff --git a/microsite/README copy.md b/microsite/README copy.md new file mode 100644 index 0000000000..d7187888a3 --- /dev/null +++ b/microsite/README copy.md @@ -0,0 +1,182 @@ +This website was created with [Docusaurus](https://docusaurus.io/). + +# What's In This Document + +- [Get Started in 5 Minutes](#get-started-in-5-minutes) +- [Directory Structure](#directory-structure) +- [Editing Content](#editing-content) +- [Adding Content](#adding-content) +- [Full Documentation](#full-documentation) + +## Directory Structure + +Your project file structure should look something like this + +``` +my-docusaurus/ + docs/ + doc-1.md + doc-2.md + doc-3.md + website/ + blog/ + 2016-3-11-oldest-post.md + 2017-10-24-newest-post.md + core/ + node_modules/ + pages/ + static/ + css/ + img/ + package.json + sidebars.json + siteConfig.js +``` + +# Editing Content + +## Editing an existing docs page + +Edit docs by navigating to `docs/` and editing the corresponding document: + +`docs/doc-to-be-edited.md` + +```markdown +--- +id: page-needs-edit +title: This Doc Needs To Be Edited +--- + +Edit me... +``` + +For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Editing an existing blog post + +Edit blog posts by navigating to `website/blog` and editing the corresponding post: + +`website/blog/post-to-be-edited.md` + +```markdown +--- +id: post-needs-edit +title: This Blog Post Needs To Be Edited +--- + +Edit me... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +# Adding Content + +## Adding a new docs page to an existing sidebar + +1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: + +```md +--- +id: newly-created-doc +title: This Doc Needs To Be Edited +--- + +My new content here.. +``` + +1. Refer to that doc's ID in an existing sidebar in `website/sidebars.json`: + +```javascript +// Add newly-created-doc to the Getting Started category of docs +{ + "docs": { + "Getting Started": [ + "quick-start", + "newly-created-doc" // new doc here + ], + ... + }, + ... +} +``` + +For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding a new blog post + +1. Make sure there is a header link to your blog in `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +headerLinks: [ + ... + { blog: true, label: 'Blog' }, + ... +] +``` + +2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: + +`website/blog/2018-05-21-New-Blog-Post.md` + +```markdown +--- +author: Frank Li +authorURL: https://twitter.com/foobarbaz +authorFBID: 503283835 +title: New Blog Post +--- + +Lorem Ipsum... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +## Adding items to your site's top navigation bar + +1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + /* you can add docs */ + { doc: 'my-examples', label: 'Examples' }, + /* you can add custom pages */ + { page: 'help', label: 'Help' }, + /* you can add external links */ + { href: 'https://github.com/facebook/docusaurus', label: 'GitHub' }, + ... + ], + ... +} +``` + +For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding custom pages + +1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: +1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + { page: 'my-new-custom-page', label: 'My New Custom Page' }, + ... + ], + ... +} +``` + +For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). + +# Full Documentation + +Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/microsite/README.md b/microsite/README.md new file mode 100644 index 0000000000..d7187888a3 --- /dev/null +++ b/microsite/README.md @@ -0,0 +1,182 @@ +This website was created with [Docusaurus](https://docusaurus.io/). + +# What's In This Document + +- [Get Started in 5 Minutes](#get-started-in-5-minutes) +- [Directory Structure](#directory-structure) +- [Editing Content](#editing-content) +- [Adding Content](#adding-content) +- [Full Documentation](#full-documentation) + +## Directory Structure + +Your project file structure should look something like this + +``` +my-docusaurus/ + docs/ + doc-1.md + doc-2.md + doc-3.md + website/ + blog/ + 2016-3-11-oldest-post.md + 2017-10-24-newest-post.md + core/ + node_modules/ + pages/ + static/ + css/ + img/ + package.json + sidebars.json + siteConfig.js +``` + +# Editing Content + +## Editing an existing docs page + +Edit docs by navigating to `docs/` and editing the corresponding document: + +`docs/doc-to-be-edited.md` + +```markdown +--- +id: page-needs-edit +title: This Doc Needs To Be Edited +--- + +Edit me... +``` + +For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Editing an existing blog post + +Edit blog posts by navigating to `website/blog` and editing the corresponding post: + +`website/blog/post-to-be-edited.md` + +```markdown +--- +id: post-needs-edit +title: This Blog Post Needs To Be Edited +--- + +Edit me... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +# Adding Content + +## Adding a new docs page to an existing sidebar + +1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: + +```md +--- +id: newly-created-doc +title: This Doc Needs To Be Edited +--- + +My new content here.. +``` + +1. Refer to that doc's ID in an existing sidebar in `website/sidebars.json`: + +```javascript +// Add newly-created-doc to the Getting Started category of docs +{ + "docs": { + "Getting Started": [ + "quick-start", + "newly-created-doc" // new doc here + ], + ... + }, + ... +} +``` + +For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding a new blog post + +1. Make sure there is a header link to your blog in `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +headerLinks: [ + ... + { blog: true, label: 'Blog' }, + ... +] +``` + +2. Create the blog post with the format `YYYY-MM-DD-My-Blog-Post-Title.md` in `website/blog`: + +`website/blog/2018-05-21-New-Blog-Post.md` + +```markdown +--- +author: Frank Li +authorURL: https://twitter.com/foobarbaz +authorFBID: 503283835 +title: New Blog Post +--- + +Lorem Ipsum... +``` + +For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) + +## Adding items to your site's top navigation bar + +1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + /* you can add docs */ + { doc: 'my-examples', label: 'Examples' }, + /* you can add custom pages */ + { page: 'help', label: 'Help' }, + /* you can add external links */ + { href: 'https://github.com/facebook/docusaurus', label: 'GitHub' }, + ... + ], + ... +} +``` + +For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) + +## Adding custom pages + +1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: +1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: + +`website/siteConfig.js` + +```javascript +{ + headerLinks: [ + ... + { page: 'my-new-custom-page', label: 'My New Custom Page' }, + ... + ], + ... +} +``` + +For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). + +# Full Documentation + +Full documentation can be found on the [website](https://docusaurus.io/). diff --git a/microsite/blog/2020-03-16-announcing-backstage.md b/microsite/blog/2020-03-16-announcing-backstage.md new file mode 100644 index 0000000000..3911debf08 --- /dev/null +++ b/microsite/blog/2020-03-16-announcing-backstage.md @@ -0,0 +1,44 @@ +--- +title: Announcing Backstage +author: Stefan Ålund +authorURL: http://twitter.com/stalund +authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg +--- + +## What is Backstage? + +Backstage is Spotify's open source platform for building developer portals. + +It’s the first open source infrastructure platform by Spotify that allows you to focus on building your application instead of reinventing the button. With an elegant and unified, yet opinionated UI/UX for all your tooling and infrastructure, Backstage enables engineers to get up and running faster, which ultimately makes their lives easier and more productive. + +![img](assets/blog_1.png) + + + +## As simple as writing a plugin. + +Backstage makes it easy to unify all of your infrastructure tooling, services, and documentation under a single, easy-to-use interface. So your engineers will always know where to find the right tool for the job. And engineers will already know how to use each tool — because everything uses the same, familiar UI. + +The number of open source infrastructure projects and tools [landscape](https://landscape.cncf.io/) is exploding. As the sheer volume of projects increases, companies and their engineers find it increasingly difficult to keep track and adopt all of the tooling fast enough to keep pace. Most of the tools were built by a different individual, team, or company, which means that there is no single UI/UX, and simply getting the tool installed and started can be a painful challenge- let alone wrangling each tool to work with one another within your existing ecosystem. Due to varying qualities and the varying UI/UX of each open source project, we'd like to introduce Backstage as a best-of-breed platform for developers to use... all in service of ensuring a flawless, consistent user experience. + +![illustration](assets/illustration.svg) + +## The Spotify story + +A best-in-class developer portal — from a music company? Since the very beginning, Spotify has been known for its agile, autonomous engineering culture. More than music, we’re a tech company that has always put engineers first, empowering our developers with the ability to innovate quickly and at scale. Backstage is the natural result of that focus. + +Since adopting Backstage internally at Spotify, we’ve seen a 55% decrease in onboarding time for our engineers (as measured by time until 10th pull request). Over 280 engineering teams inside Spotify are using Backstage to manage 2,000+ backend services, 300+ websites, 4,000+ data pipelines, and 200+ mobile features. + +## Project roadmap + +We created Backstage about 4 years ago, and today, we’ve decided to share the goodness with the greater engineering community. While our version of Backstage has had the benefit of time to mature and evolve, the first iteration of our open source version is still nascent. I wanted to take a moment to share with you what our vision for Backstage OSS is so that 1. users and our community gain a better understanding of where we’re envisioning the product to go and more importantly, 2. you can provide input and feedback so that together, we can create a better infrastructure experience for developers everywhere. + +We are envisioning three phases of the project and we have already begun work on various aspects of these phases: + +- **Phase 1:** Extensible frontend platform (now) - You will be able to easily create a single consistent UI layer for your internal infrastructure and tools. A set of reusable UX patterns and components help ensure a consistent experience between tools. + +- **Phase 2:** Manage your stuff (next 2-3 months) - Manage anything from microservices to software components to infrastructure and your service catalog. Regardless of whether you want to create a new library, view service deployment status in Kubernetes, or check the test coverage for a website -- Backstage will provide all of those tools - and many more - in a single developer portal. + +- **Phase 3:** Ecosystem (later) - Everyone's infrastructure stack is different. By fostering a vibrant community of contributors we hope to provide an ecosystem of Open Source plugins/integrations that allows you to pick the tools that match your stack. + +Our vision for Backstage is for it to become the trusted standard toolbox (read: UI layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We can’t do it alone. If this sounds interesting or you'd like to help us shape our product vision, we'd love to talk. You can email me directly: [alund@spotify.com](mailto:alund@spotify.com). diff --git a/microsite/blog/2020-03-18-what-is-backstage.md b/microsite/blog/2020-03-18-what-is-backstage.md new file mode 100644 index 0000000000..942adc607f --- /dev/null +++ b/microsite/blog/2020-03-18-what-is-backstage.md @@ -0,0 +1,88 @@ +--- +title: What the heck is Backstage anyway? +author: Stefan Ålund +authorURL: http://twitter.com/stalund +authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg +--- + +![img](assets/2/spotify-labs-header.png) + +Two days ago, we released the open source version of [Backstage](https://backstage.io/), our homegrown developer portal. And we learned a thing or two via the feedback we received. So, I wanted to take this opportunity to further explain what we’re trying to do with Backstage — and more importantly, what we want to give to the greater engineering community beyond Spotify. + + + +## What’s the big infrastructure problem? + +As companies grow, their infrastructure systems get messier. Consider a team that wants to deploy something to the cloud. While Spotify has many awesome engineers, not every engineer is well-versed in our chosen cloud-provider tooling. Yet everyone is required to know and understand Terraform, GCP/AWS/Azure CLIs, Gitlab CI, Prometheus, Kubernetes, Docker, various monitoring and alerting tools, and much, much more. Once other resources come into play (databases, queueing, etc.), each engineer requires even more tools and domain-specific knowledge (or “disciplines”), from backend to machine learning, to mobile and data. + +## What’s the fix? + +Backstage unifies all your infrastructure tooling, services, and documentation with a single, consistent UI. All of it! Imagine if all your tools — GCP, Bigtable, CI pipelines, TensorFlow Extended, and whatever else is hiding in your stack — all had the same, easy-to-use interface. That’s Backstage. One front end for all your infrastructure. + +![img](assets/2/screen.gif) + +Backstage gives developers a uniform overview of all their resources, regardless of how and where they are running, as well as an easy way to onboard and start using those tools. It also allows the creation of new resources, such as backend services running in Kubernetes, with a few clicks of a button — all without having to leave the same, familiar interface of Backstage. + +## Why did we build it? + +To some observers, it may seem odd that a music company is launching a best-in-class developer portal. But if you [dig deeper](https://backstage.io/background), you’ll find that since the very beginning, Spotify has been known for its agile, autonomous engineering culture. More than music, we’re a tech company that has always put engineers first, empowering our developers with the ability to innovate quickly and at scale. Backstage is the natural result of that focus. + +## What are examples of how Backstage is used at Spotify? + +Our internal installation of Backstage has over 100 different integrations — we call them “plugins”. Since the open-source version currently does not have any end-to-end use cases, it can be challenging to understand what problems Backstage can solve for you. To make things more tangible, let’s have a look at four of the common use-cases: + +1. Creating a new microservice +2. Following a pull request from review to production +3. Centralised technical documentation +4. Review performance of your team’s mobile features + +These are just a few examples. Expect us to continue providing examples of how Backstage is used inside Spotify while we build out more end-2-end use-cases in the open. + +### 1. Creating a new microservice + +Creating any new software component at Spotify, such as a new microservice, is done with a few clicks in Backstage. Developers choose between a number of standard templates — all with best-practices built in. + +![img](assets/2/1.png) + +After inputting some metadata about your service, a new repository is created with a “hello world” service that automatically builds and deploys in production on Kubernetes ([GKE](https://cloud.google.com/kubernetes-engine)). Ownership information is automatically captured in our service/software catalog and users can see a list of all the services they own. + +![img](assets/2/2.png) + +### 2. Following a pull request from review to production + +As soon as you submit a pull request to Spotify’s GitHub Enterprise, our CI system automatically posts a link to the CI/CD view in Backstage. The view provides you with all the information you need: build progress, test coverage changes, a re-trigger button, etc., so that you don’t have to look for this information across different systems. + +![img](assets/2/3.png) + +Our homegrown CI system uses Jenkins under the hood, but Spotify engineers don’t need to know that. They interact directly with GitHub Enterprise and Backstage. + +### 3. Centralised technical documentation + +Spotify uses a [docs-like-code](https://www.youtube.com/watch?v=uFGCaZmA6d4) approach. Engineers write technical documentation in Markdown files that live together with the code. During CI, a beautiful-looking documentation site is created using [MkDocs](https://www.mkdocs.org/), and all sites are rendered centrally in a Backstage plugin. + +![img](assets/2/4.png) + +On top of the static documentation we also incorporate additional metadata about the documentation site — such as owner, open issue and related Stack Overflow tags. + +### 4. Review performance of your team’s mobile features + +Our mobile apps are developed by many different teams. The codebase is divided up into different features, each owned and maintained by a separate team. If an app developer on one team wants to understand how their feature is affecting overall app performance, there’s a plugin for that: + +![img](assets/2/5.png) +_Figures above for illustrative purposes only._ + +Developers can also look at crashes, releases, test coverage over time and many more tools in the same location. + +## Why did we make Backstage open source? + +When discussing infrastructure challenges with peer companies, it’s clear that we are not alone in struggling with fragmentation across our developer ecosystem. As companies adopt more open-source tooling, and build more infrastructure internally, the complexity grows. It gets harder for individual engineers to find and use all these distinct tools. + +Similar to how Backstage ties together all of Spotify’s infrastructure, our ambition is to make the open-source version of Backstage the standard UX layer across the broader infrastructure landscape. We decided to release Backstage early so we could collaborate more closely with companies that have a similar problem — and that want to provide a better developer experience to their teams. + +## What’s next? + +We are envisioning [three phases](https://github.com/spotify/backstage/milestones) of the project (so far), and we have already begun work on various aspects of these phases. The best way to track the work and see where you can jump in and help out is: + +https://github.com/spotify/backstage/milestones + +Want to discuss the project or need support? Join us on [Discord](https://discord.gg/MUpMjP2) or reach out on [alund@spotify.com](mailto:alund@spotify.com). diff --git a/microsite/blog/2020-04-06-lighthouse-plugin.md b/microsite/blog/2020-04-06-lighthouse-plugin.md new file mode 100644 index 0000000000..2d5c7b96b7 --- /dev/null +++ b/microsite/blog/2020-04-06-lighthouse-plugin.md @@ -0,0 +1,40 @@ +--- +title: Introducing Lighthouse for Backstage +author: Paul Marbach +authorURL: http://twitter.com/fastfrwrd +authorImageURL: https://pbs.twimg.com/profile_images/1224058798958088192/JPxS8uzR_400x400.jpg +--- + +![image illustrating the Lighthouse plugin for Backstage](assets/3/lead.png) + +We’re proud to announce that our first internal plugin at Spotify has been open-sourced as part of Backstage. This plugin works with the newly open-sourced [lighthouse-audit-service](https://github.com/spotify/lighthouse-audit-service) to run and track Lighthouse audits for your websites. + + + +## What is Lighthouse? + +Google's [Lighthouse](https://developers.google.com/web/tools/lighthouse) auditing tool for websites is a great open-source resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your site. + +At Spotify, we keep track of Lighthouse audit scores over time to look at trends and areas for investment. We particularly look to Lighthouse to give us [accessibility recommendations](https://developers.google.com/web/tools/lighthouse/v3/scoring#a11y); in the next few months, we plan to roll out Lighthouse accessibility category scores as a benchmark metric for all websites at Spotify. + +## Lighthouse, tracked over time + +What makes the plugin unique is that we can track a website's audit performance over time using the main metrics that Lighthouse outputs, rather than simply running reports. The sparklines show, at a glance, how all of your websites are holding up over recent builds. + +![image of the audit list in the Lighthouse plugin](assets/3/audit-list.png) + +Lighthouse reports can be viewed directly in Backstage, with the ability to travel back and forth through your audit history, so you can quickly diagnose which release caused a performance or SEO regression. + +![image of the audit view in the Lighthouse plugin](assets/3/audit-view.png) + +Trigger an audit directly from Backstage, or trigger audits programmatically with your new lighthouse-audit-service instance. Schedule them after builds as a sort of smoke test, or trigger them on a schedule (as we do at Spotify) to get a daily snapshot of your website. + +![image of the create audit form in the Lighthouse plugin](assets/3/create-audit.png) + +## Using Lighthouse in Backstage + +To learn how you can enable Lighthouse auditing within Backstage, head over to the [README](https://github.com/spotify/backstage/tree/master/plugins/lighthouse) for the plugin to get started. + +## A personal note + +I want to thank the folks on the Backstage team for approaching me to open-source this plugin. I have found working on Backstage to be a really rewarding and fun time, and I'm so glad that the core team members have put in the effort to make Backstage something that anyone in the industry can use. I can't wait to play with all the plugins the community is going to create. I am hopeful that this plugin can help illustrate just a sliver of what we use Backstage for at Spotify. diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md new file mode 100644 index 0000000000..b4fe534a67 --- /dev/null +++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md @@ -0,0 +1,135 @@ +--- +title: How to quickly set up Backstage +author: Marcus Eide +authorURL: https://github.com/marcuseide +authorImageURL: https://secure.gravatar.com/avatar/20223f1e03673c7c1e6282fbebaf6942 +--- + +We wanted to make getting started with Backstage as easy as possible. Even though Backstage is still in the early phases of its development, we believe it is important for our users to get a feel for what Backstage really is. + +We want users to be able to create their own version of Backstage quickly and easily, so that they can take advantage of all the infrastructure that we’ve built into it — and start exploring. + +In this blog post we’ll look at what a Backstage app is and how to create one using our [CLI](https://www.npmjs.com/package/@backstage/cli). + + + +## What is a Backstage app? + +![](assets/4/welcome.png) + +A Backstage app is a modern monorepo web project that is built using Backstage packages. It includes all the configuration and architecture you need to run Backstage so that you don’t have to worry about setting everything up by yourself. + +More specifically, a Backstage app includes the core packages and APIs that provide base functionality to the app. The actual UX is provided by plugins. As an example, when you first load the `/` page of the app, the content is provided by the `welcome` plugin. + +Plugins are the essential building blocks of Backstage and extend the platform by providing additional features and functionality. Read more about [Backstage plugins](https://github.com/spotify/backstage/tree/master/docs/getting-started) on GitHub. + +## A personalized platform + +When you create a Backstage _app_, you are creating your own installation of Backstage, an application that is built on top of the Backstage _platform_. + +You get to take full advantage of a platform that we at Spotify have been using internally for years. But you also get to make it your own — starting with its name. You can rename the Backstage app anything you want, so that you can call it something that best fits your organization. Be creative! + +## How do I create an app? + +Just run the backstage-cli: + +```bash +npx @backstage/cli create-app +``` + +Name your app, and we will create everything you need: + +![](assets/4/create-app.png) + +The only thing you need to do is to start the app: + +```bash +cd my-app +yarn start +``` + +And you are good to go! 👍 + +Read the full documentation on how to [create an app](https://github.com/spotify/backstage/blob/master/docs/create-an-app.md) on GitHub. + +## What do I get? (Let's get technical...) + +We’ve been using Backstage internally for years, and we’ve spent a lot of time adding to and tweaking the infrastructure so that it fits our needs. After all that testing and trial and error, we think it will fit your needs, too! + +### 1. Lerna setup to manage multi-packages + +The monorepo and its packages are managed by [Lerna](https://lerna.js.org/). It lets you work with individual packages in a controlled way. + +### 2. Fast builds + +Behind the scenes we use [Rollup](https://rollupjs.org/) to build the modules. + +Each package is built individually. With the `--watch` flag you will be able to detect changes per package and therefore speed up the local development process. + +To further speed things up, we have also included our own caching system to avoid rebuilding unchanged packages. + +Our hope is that there will be thousands of Backstage plugins in the future, so we need a fast and stable build process. + +### 3. Full TypeScript support + +Most of the codebase is written in [TypeScript](https://www.typescriptlang.org/), and we aim for all of the core packages to be in TypeScript in the future. + +All the knobs and handles needed for a stable and functioning TypeScript project are included. + +Take a look at `@backstage/cli/config/tsconfig.json` for more details. + +### 4. Tests and coverage out of the box + +We include testing, linting, and end-to-end tests for your convenience. + +```bash +yarn lint:all +yarn test:all +yarn test:e2e +``` + +## Extend the app with plugins + +At Spotify, the main factor behind Backstage’s success has been our large and diverse collection of plugins — the result of contributions from various teams over the years. Internally, we have more than a hundred different plugins. + +There are two ways to add plugins to your Backstage app: use a publicly available plugin or create your own. + +### Using a public plugin + +We provide a collection of public Backstage plugins (look for packages with the `plugin-` prefix under the `@backstage` namespace on [npm](https://www.npmjs.com/) that you can start using immediately. + +Install in your app’s package folder (`/packages/app`) with: + +```bash +yarn add @backstage/plugin- +``` + +Then add it to your app's `plugin.ts` file to import and register it: + +`/packages/app/src/plugin.ts`: + +```js +export { plugin as PluginName } from '@backstage/plugin-'; +``` + +A plugin registers its own `route` in the app — read the documentation for the specific plugin you are installing for more information on that. + +### Creating an internal plugin + +We also know that each organization has different needs and will create their own plugins for internal purposes. To create an internal plugin, you can use our CLI again. + +In the root of your app directory (``) run: + +```bash +yarn create-plugin +``` + +This command will create a new plugin in `/plugins/` and register it to your app automatically. + +### Sharing is caring 🤗 + +If you are developing a plugin that might be useful for others, consider releasing it publicly. A large, diverse ecosystem of Backstage plugins benefits the whole community + +## Ready to get started? + +Head over to GitHub and check out the [project](https://github.com/spotify/backstage) or download our [CLI](https://www.npmjs.com/package/@backstage/cli). If you have more questions, join us on [Discord](https://discord.gg/MUpMjP2) or [create an issue](https://github.com/spotify/backstage/issues/new/choose). diff --git a/microsite/blog/2020-05-14-tech-radar-plugin.md b/microsite/blog/2020-05-14-tech-radar-plugin.md new file mode 100644 index 0000000000..d78d5d67fa --- /dev/null +++ b/microsite/blog/2020-05-14-tech-radar-plugin.md @@ -0,0 +1,44 @@ +--- +title: Introducing Tech Radar for Backstage +author: Bilawal Hameed +authorURL: http://twitter.com/bilawalhameed +authorImageURL: https://avatars0.githubusercontent.com/bih +--- + +![image illustrating the Tech Radar plugin for Backstage](assets/5/lead.png) + +Just a few weeks ago, we released our internal plugin for [Lighthouse website audits] as our first open source plugin, so the whole community could use it. Today, we’re excited to add a new plugin to that list — say hello to the [Tech Radar plugin]! + + + +## What is Tech Radar? + +The Technology Radar is a concept created by [ThoughtWorks] which allows you to visualize the official guidelines of software languages, processes, infrastructure, and platforms at that particular company. The particular visualization above was created by [Zalando]. + +At Spotify, our central committee of technical architects own the Tech Radar with the input of engineers across the company. Anyone can and is encouraged to give recommendations. We segment entries in our Tech Radar by languages, frameworks, processes, and infrastructure, although you should pick whatever works best for your organization. Each entry in the Tech Radar can have one of the following lifecycle values: Use, Trial, Assess, and Hold. + +We also assign clear definitions for each lifecycle: + +- **Use:** This technology is recommended for use by the majority of teams with a specific use case. +- **Trial:** This technology has been evaluated for specific use cases and has showed clear benefits. Some teams adopt it in production, although it should be limited to low-impact projects as it might incur a higher risk. +- **Assess:** This technology has the potential to be beneficial for the company. Some teams are evaluating it and using it in experimental projects. Using it in production comes with a high cost and risk due to lack of in-house knowledge, maintenance, and support. +- **Hold:** We don’t want to further invest in this technology or we evaluated it and we don’t see it as beneficial for the company. Teams should not use it in new projects and should plan on migrating to a supported alternative if they use it for historical reasons. For broadly adopted technologies, the Radar should refer to a migration path to a supported alternative. + +Since rolling out the Tech Radar, it has become the source of truth when creating, maintaining, or evolving our software ecosystem. Spotify has dozens of entries in our Radar and it can scale quite well whilst being easy for our engineers and engineering managers to consume. + +## Using the Tech Radar in Backstage + +To learn about how you can bring the Tech Radar to your Backstage installation, check out [the plugin README on GitHub][tech radar plugin]. + +## A personal note + +I want to thank both the Backstage team and Spotify. Firstly, I’ve been working with our internal version of Backstage for over a year, and the developer experience since open sourcing has been even more of a joy to work with. Secondly, the 10% hack time that Spotify generously provides to all engineers enabled me to open source the Tech Radar plugin. + +Since open sourcing it, the community has shown great interest in yet another powerful use case of Backstage. There was also an enthusiastic open source contributor who volunteered to migrate the plugin to TypeScript and React Hooks [in just 29 minutes](https://github.com/spotify/backstage/issues/661) of opening the issue! + +I can’t wait to see how others benefit from the Tech Radar in their organizations! + +[lighthouse website audits]: https://backstage.io/blog/2020/04/06/lighthouse-plugin +[tech radar plugin]: https://github.com/spotify/backstage/tree/master/plugins/tech-radar +[thoughtworks]: https://www.thoughtworks.com/radar +[zalando]: https://opensource.zalando.com/tech-radar/ diff --git a/microsite/blog/2020-05-14-weaveworks-covid-19-app-uses-backstage-ui.md b/microsite/blog/2020-05-14-weaveworks-covid-19-app-uses-backstage-ui.md new file mode 100644 index 0000000000..68e3f903c7 --- /dev/null +++ b/microsite/blog/2020-05-14-weaveworks-covid-19-app-uses-backstage-ui.md @@ -0,0 +1,33 @@ +--- +title: Weaveworks’ COVID-19 app uses Backstage UI +author: Jeff Feng +authorURL: https://github.com/fengypants +authorImageURL: https://avatars2.githubusercontent.com/u/46946747 +--- + +![fk-covid-screenshot](assets/20-05-14/weaveworks-firekube-covid-19-spotify-backstage.png) + +One of the great things about the open source community is once you put your work out there, you really never know where it might end up. That’s certainly the case here. + + + +When Weaveworks decided to build an X-ray diagnostics app to help fight COVID-19, they pulled together a lot of different open source resources — from projects big and small, built by both familiar tech partners and some brand new ones, too. + +At the heart of their app — called [fk-covid][] — there’s a TensorFlow-based deep neural network that was developed by researchers on the DarwinAI team and others in the COVID R&D community. To package that network up for doctors and software developers to use, the app combines open source tools from Google, AWS, Azure, MinIO, the CNCF, and Weaveworks’ own Firekube bundle for Kubernetes. + +And the user interface for all of this? Weaveworks built a custom plugin using the Backstage framework. + +“We chose Backstage as a modern UI toolkit that we knew would work with Kubernetes apps,” said Alexis Richardson, CEO of Weaveworks. “We were also experimenting with Backstage for microservices and ML, so it was natural to try it here.” + +Chanwit Kaewkasi, Weaveworks’ DX Engineer and a tech lead on the project, said, “Backstage offers very advanced plugin architecture which allows us to only focus on the plugin we're developing. Other things are taken care of by the framework.” + +In other words, here’s Backstage doing what Backstage does best: unifying a bunch of technologies with a cohesive frontend, so that the whole thing is easier to build and easier to use. + +Joining the fight against a global pandemic was not something the Backstage team at Spotify ever envisioned when we released our homegrown developer portal to the world back in March. But it’s a testament to the ingenuity (and serendipity) of the open source community that Backstage could be enlisted for such an unexpected use case. + +We’re proud to see Backstage adopted as the UX layer for this meaningful cause. And we can’t wait to see what the open source community will build next. + +To learn more about what fk-covid does, and how it works, jump on over to [the Weaveworks blog][] to hear it straight from the team that built it. It’s a great example of the possibilities that come from being a part of the open source community. + +[fk-covid]: https://github.com/weaveworks/fk-covid +[the weaveworks blog]: https://www.weave.works/blog/firekube-covid-ml diff --git a/microsite/blog/2020-05-22-phase-2-service-catalog.md b/microsite/blog/2020-05-22-phase-2-service-catalog.md new file mode 100644 index 0000000000..631931bb82 --- /dev/null +++ b/microsite/blog/2020-05-22-phase-2-service-catalog.md @@ -0,0 +1,54 @@ +--- +title: Starting Phase 2: The Service Catalog +author: Stefan Ålund +authorURL: http://twitter.com/stalund +authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg +--- + +**TL;DR** Thanks to the help from the Backstage community, we’ve made excellent progress and are now moving into Phase 2 of Backstage — building out a Service Catalog and the surrounding systems that will help unify the tools you use to manage your software. + +We released the open source version of Backstage a little less than two months ago, and have been thrilled to see so many people jumping in and contributing to the project in its early stages. We’re excited to see what the community can build together as we progress through [each phase of Backstage](https://github.com/spotify/backstage#project-roadmap). + +![img](assets/20-05-20/Service_Catalog_MVP.png) + + + +## Progress so far + +Phase 1 was all about building an extensible frontend platform, enabling teams to start creating a single, consistent UI layer for your internal infrastructure and tools in the form of [plugins](https://github.com/spotify/backstage/labels/plugin). In fact, thanks to our amazing (30+) [contributors](https://github.com/spotify/backstage/graphs/contributors), we were able to complete most of Phase 1 earlier than expected. 🎉 + +Today, we are happy to announce that we are shifting our focus to Phase 2! + +## So what is Phase 2? + +> _The core of building Platforms rests in versatile entity management. Entities represent the nouns or the "truths" of our world._ + +Quote from [Platform Nuts & Bolts: Extendable Data Models](https://www.kislayverma.com/post/platform-nuts-bolts-extendable-data-models) + +Entities, or what we refer to as “components” in Backstage, represent all software, including services, websites, libraries, data pipelines, and so forth. The focus of Phase 2 will be on adding an entity model in Backstage that makes it easy for engineers to create and manage the software components they own. + +With the ability to create a plethora of components in Backstage, how does one keep track of all the software in the ecosystem? Therein lies the highlight feature of Phase 2: the [Service Catalog](https://github.com/spotify/backstage/milestone/4). The service catalog — or software catalog — is a centralized system that keeps track of ownership and metadata about all software in your ecosystem. The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md) stored together with the code, which are then harvested and visualized in Backstage. + +![img](assets/20-05-20/Service_Catalog_MVP.png) + +![img](assets/20-05-20/Service_Catalog_MVP_service.png) + +With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. Because the system is practically self-organizing, it requires hardly any oversight from a governing or centralized team. Developers can get a uniform overview of all their software and related resources (such as server utilisation, data pipelines, pull request status), regardless of how and where they are running, as well as an easy way to onboard and manage those resources. + +On top of that, we have found that the service catalog is a great way to organise the infrastructure tools you use to manage the software as well. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UI’s (and incurring additional cognitive overhead each time they make a context switch), most of these tools can be organised around the entities in the catalog: + +![img](assets/20-05-20/tabs.png) + +More concretely, having this structure in place will allow plugins such as [CircleCI](https://github.com/spotify/backstage/tree/master/plugins/circleci) to show only the builds for the specific service you are viewing, or a [Spinnaker](https://github.com/spotify/backstage/issues/631) plugin to show running deployments, or an Open API plugin to [show documentation](https://github.com/spotify/backstage/issues/627) for endpoints exposed by the service, or the [Lighthouse](https://github.com/spotify/backstage/tree/master/plugins/lighthouse) plugin to show audit reports for your website. You get the point. + +## Timeline + +Our estimated timeline has us delivering these pieces in increments leading up to June 22. But with the support of the community we wouldn’t be surprised if things land earlier than that. 🙏 + +If you are interested in joining us, check out our [Milestones](https://github.com/spotify/backstage/milestones) and connected Issues. + +## Long-term vision + +Our vision for Backstage is for it to become the trusted, standard toolbox (read: UX layer) for the open source infrastructure landscape. Imagine a future where regardless of what infrastructure you use inside your company, there is an open source plugin available that you can pick up and add to your deployment of Backstage. + +Spotify will continue to release more of our [internal](https://backstage.io/blog/2020/04/06/lighthouse-plugin) [plugins](https://backstage.io/blog/2020/05/14/tech-radar-plugin), but participation from developers and companies can help us build a healthy community. We are excited to see how Backstage has helped many of you, and look forward to seeing all the new plugins you and your teams will build! diff --git a/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md new file mode 100644 index 0000000000..a352faf0cb --- /dev/null +++ b/microsite/blog/2020-06-22-backstage-service-catalog-alpha.md @@ -0,0 +1,54 @@ +--- +title: Backstage Service Catalog released in alpha +author: Stefan Ålund +authorURL: http://twitter.com/stalund +image: https://backstage.io/blog/assets/6/header.png +--- + +**TL;DR** Today we are announcing the availability of the Backstage Service Catalog in alpha. This has been the community’s most requested feature. Even if the catalog is not ready for production yet, we think this release already demonstrates how Backstage can provide value for your company right out of the box. With your early input and feedback, we hope to create a stronger generally available product. + +![img](assets/6/header.png) + + + +## You asked, we listened + +When we [released](https://backstage.io/blog/2020/03/16/announcing-backstage) Backstage as an open source project back in March, it didn’t have all of the features that our internal version of Backstage has today. One of the main reasons we pushed to release it, despite it being in such a nascent stage, was so that we could start building the next phase of Backstage around the community’s needs. We’ve had hours of conversations with so many of you — thank you to everyone who has jumped on a video call, attended one of our working sessions, or watched our [demo videos](https://backstage.io/demos) and provided feedback via [Discord](https://discord.com/invite/MUpMjP2). + +Today, we wanted to share what we’ve learned from talking with many of you at companies that have shown interest in adopting Backstage. Here it is in short: + +- The problem of scaling autonomous engineering organisations without creating too much complexity is not a unique problem to Spotify. +- The "extensible frontend platform" that we focused on in the first phase of the project is not the only thing you are looking for. + +With these insights we decided to re-focus our efforts towards the most requested feature: the Backstage Service Catalog. + +## What is the service catalog? + +The Backstage Service Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata yaml files](https://github.com/spotify/backstage/blob/master/docs/architecture-decisions/adr002-default-catalog-file-format.md#format) stored together with the code, which are then harvested and visualized in Backstage. + +This was our pitch for the virtues of a service catalog when we first [announced](https://backstage.io/blog/2020/05/22/phase-2-service-catalog) it as part of Phase 2: + +> With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. Because the system is practically self-organizing, it requires hardly any oversight from a governing or centralized team. Developers can get a uniform overview of all their software and related resources (such as server utilisation, data pipelines, pull request status), regardless of how and where they are running, as well as an easy way to onboard and manage those resources. + +> On top of that, we have found that the service catalog is a great way to organise the infrastructure tools you use to manage the software as well. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UI’s (and incurring additional cognitive overhead each time they make a context switch), most of these tools can be organised around the entities in the catalog: + +![img](assets/20-05-20/tabs.png) + +You’ll be able to see many of these virtues in action with this alpha release — though, with some caveats, of course, since it is, after all, an alpha. + +## What does alpha mean? + +Alpha is our shorthand for "we don’t yet think Backstage is ready for production, but we’d love for you to test it and provide us with feedback". However, you should be able to try out the functionality of the service catalog: + +1. Register software components ([examples](https://github.com/spotify/backstage/tree/master/packages/catalog-model/examples)) +2. See all components represented in the catalog +3. Search across all components +4. Get an overview of the metadata of the components +5. Click through and get more information about a specific component (service, website, etc) +6. See example tooling (plugins) that helps you manage the component + +As with most alpha releases, you should expect things to change quite a lot until we reach the beta stage (we’re targeting the end of summer). There are obviously many things missing as well, but we wanted to start collecting feedback early and make it easier to see the end-to-end flow. + +If you have feedback or questions, please open a [GitHub issue](https://github.com/spotify/backstage/issues), ping us on [Discord chat](https://discord.gg/EBHEGzX) or send me an email at [alund@spotify.com](mailto:alund@spotify.com) 🙏 + +To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://mailchi.mp/spotify/backstage-community). diff --git a/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md new file mode 100644 index 0000000000..5111f5c726 --- /dev/null +++ b/microsite/blog/2020-07-01-how-to-enable-authentication-in-backstage-using-passport.md @@ -0,0 +1,46 @@ +--- +title: How to enable authentication in Backstage using Passport +author: Lee Mills +authorURL: https://github.com/leemills83 +authorImageURL: https://avatars1.githubusercontent.com/u/1236238?s=460&v=4 +--- + +![auth-landing-page](assets/20-07-01/auth-landing.png) + +Getting authentication right is important. It helps keep your platform safe, it’s one of the first things users will interact with, and there are many different authentication providers to support. To this end, we chose to use [Passport](http://www.passportjs.org/) to provide an easy-to-use, out-of-the-box experience that can be extended to your own, pre-existing authentication providers (known as strategies). The Auth APIs in Backstage serve two purposes: identify the user and provide a way for plugins to request access to third-party services on behalf of the user. We’ve already implemented Google and GitHub authentication to provide examples and to get you started. + + + +## What is Passport? + +[Passport](http://www.passportjs.org/) is Express-compatible authentication middleware for Node.js that provides access to over 500 authentication providers, covering everything from Google, Facebook, and Twitter to generic OAuth, SAML, and local. Check out all of the currently available [strategies listed on the Passport site](http://www.passportjs.org/). + +Passport has allowed us to leverage an existing open-source authentication framework that will, in turn, give users the freedom to add and extend alternative authentication strategies to their instance of Backstage. + +## Using authentication in Backstage + +![auth-landing-page](assets/20-07-01/auth-sidebar.png) + +First, check out the provided Google and GitHub implementations! [Spin up a local copy of Backstage](https://backstage.io/blog/2020/04/30/how-to-quickly-set-up-backstage) along with our example-backend. You can find more documentation on setting up the example backend [here](https://github.com/spotify/backstage/tree/master/packages/backend), but be sure to include the relevant client IDs and secrets when running `yarn start`: + +``` +AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x SENTRY_TOKEN=x LOG_LEVEL=debug yarn start +``` + +You can find the implementation for these strategies along with a lightweight proof-of-concept implementation for SAML authentication at `/plugins/auth-backend/src/providers`. + +## Ready to get started by adding your chosen provider and implementation? + +Getting started is really straightforward, and can be broadly broken down into five steps: + +1. Install the [Passport-based provider package that best suits your needs](http://www.passportjs.org/). +2. Add a new provider to `plugins/auth-backend/src/providers/` +3. Implement the provider, extending the suitable framework, if needed. +4. Add the provider to the backend. +5. Add a frontend Auth Utility API. + +For full details, take a look at our [“Adding authentication providers” documentation](https://github.com/spotify/backstage/blob/master/docs/auth/add-auth-provider.md) and at the [excellent documentation](http://www.passportjs.org/docs/) provided by Passport. + +## Interested in contributing to the next steps for authentication? + +We’ve already seen both GitLab and Okta contributions from the community — and we’re thinking about a few more providers we’d like to add to Backstage, too. You can find those, and other authentication-related issues, in our repository by filtering with the [auth label](https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3Aauth). diff --git a/microsite/blog/2020-08-05-announcing-backstage-software-templates.md b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md new file mode 100644 index 0000000000..79b5ec45fd --- /dev/null +++ b/microsite/blog/2020-08-05-announcing-backstage-software-templates.md @@ -0,0 +1,78 @@ +--- +title: Announcing Backstage Software Templates +author: Stefan Ålund +authorURL: https://twitter.com/stalund +--- + +**TL;DR** Today we are announcing a new Backstage feature: Software Templates. Simplify setup, standardize tooling, and deploy with the click of a button. Using automated templates, your engineers can spin up a new microservice, website, or other software component with your organization’s best practices built-in, right from the start. + + + + + +## Balancing autonomy and standardization + +At Spotify, we’ve always believed in the speed and ingenuity that comes from having autonomous development teams. But as we learned firsthand, the faster you grow, the more fragmented and complex your software ecosystem becomes. And then everything slows down again. + +By centralizing services and standardizing your tooling, Backstage streamlines your development environment from end to end. Instead of restricting autonomy, standardization frees your engineers from infrastructure complexity. So you can return to building and scaling, quickly and safely. + +Today we are releasing one of the key features that helps balance autonomy and standardization: templates for creating software. + +## Backstage Software Templates: Push-button deployment + +Backstage Software Templates automate and standardize the process of creating software components. To show you how they work, we created four sample templates to get you started — just configure them to fit your tooling and off you go: + +- **Create React App Template** — create a new CRA website project +- **Golang Microservice** — create a Golang repo with this template built by members of the Go community +- **React SSR Template** — create a website powered with Next.js +- **Spring Boot GRPC** — create a simple microservice using gRPC and Spring Boot Java + +### The getting started guide gets automated + +Since the templates can be customized to integrate with your existing infrastructure, it’s easy to start a new project without ever having to leave Backstage. Let’s say you’re building a microservice. With three clicks in Backstage, you’ll have a new Spring Boot project with your repo automatically configured on GitHub and your CI already running the first build. + +### Golden Paths pave the way + +You can customize Backstage Software Templates to fit your organization’s standards. Using Go instead of Java? CircleCI instead of Jenkins? Serverless instead of Kubernetes? GCP instead of AWS? [Make your own recipes for any software component](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/adding-templates.md) and your best practices will be baked right in. + +## Getting started + +The sample Software Templates are available under `/create`. If you're setting up Backstage for the first time, follow [Getting Started with Backstage](https://github.com/spotify/backstage/blob/master/docs/getting-started/index.md) and go to `http://localhost:3000/create`. If you’ve already been running Backstage locally, run the command `yarn lerna run mock-data` to load the new sample templates into the Service Catalog first. + +![available-templates](assets/2020-08-05/templates.png) + +### Step 1: Choose a template + +When you select a template that you want to create, you can ask for different input variables. These are then passed to the templater internally. + +![template-form](assets/2020-08-05/template-form.png) + +After filling in these variables, additional fields will appear so Backstage can be used. You’ll specify the owner, which is a `user` in the Backstage system, and the `Location`, which must be a GitHub organization and a non-existing GitHub repository name, formatted as `organization/reponame`. + +### Step 2: Run! + +Once you've entered values and confirmed, you'll then get a modal with live progress of what is currently happening with the creation of your template. + +![create-component](assets/2020-08-05/create-component.png) + +It shouldn't take too long before you see a success screen. At this point, a piece of “Hello World” software has been created in your repo, and the CI automatically picks it up and starts building the code. + +Your engineers don’t have to bother with setting up underlying infrastructure, it’s all built into the template. They can start focusing on delivering business value. + +### View new components in the Service Catalog + +New components, of course, get added automatically to the Backstage Service Catalog. After creation, you'll see the `View in Catalog` button, which will take you to the registered component in the catalog: + +![service-catalog](assets/2020-08-05/catalog.png) + +## Define your standards + +Backstage ships with four example templates, but since these are likely not the (only) ones you want to promote inside your company, the next step is to add [your own templates](https://github.com/spotify/backstage/blob/master/docs/features/software-templates/adding-templates.md). Using Backstage’s Software Templates feature, it’s easy to help your engineers get started building software with your organization’s best practices built-in. + +We have learned that one of the keys to getting these standards adopted is to keep an open process. Templates are code. By making it clear to your engineers that you are open to pull requests, and that teams with different needs can add their own templates, you are on the path of striking a good balance between autonomy and standardization. + +If you have feedback or questions, please open a [GitHub issue](https://github.com/spotify/backstage/issues), ping us on [Discord chat](https://discord.gg/EBHEGzX) or send me an email at [alund@spotify.com](mailto:alund@spotify.com) 🙏 + +To get regular product updates and news about the Backstage community, sign up for the [Backstage newsletter](https://mailchi.mp/spotify/backstage-community). diff --git a/microsite/blog/assets/2/1.png b/microsite/blog/assets/2/1.png new file mode 100644 index 0000000000..21fb8bc26f Binary files /dev/null and b/microsite/blog/assets/2/1.png differ diff --git a/microsite/blog/assets/2/2.png b/microsite/blog/assets/2/2.png new file mode 100644 index 0000000000..16fa0cf5bf Binary files /dev/null and b/microsite/blog/assets/2/2.png differ diff --git a/microsite/blog/assets/2/3.png b/microsite/blog/assets/2/3.png new file mode 100644 index 0000000000..f610782186 Binary files /dev/null and b/microsite/blog/assets/2/3.png differ diff --git a/microsite/blog/assets/2/4.png b/microsite/blog/assets/2/4.png new file mode 100644 index 0000000000..d5e59ef92a Binary files /dev/null and b/microsite/blog/assets/2/4.png differ diff --git a/microsite/blog/assets/2/5.png b/microsite/blog/assets/2/5.png new file mode 100644 index 0000000000..00a6163c9a Binary files /dev/null and b/microsite/blog/assets/2/5.png differ diff --git a/microsite/blog/assets/2/screen.gif b/microsite/blog/assets/2/screen.gif new file mode 100644 index 0000000000..a0d52a23b4 Binary files /dev/null and b/microsite/blog/assets/2/screen.gif differ diff --git a/microsite/blog/assets/2/spotify-labs-header.png b/microsite/blog/assets/2/spotify-labs-header.png new file mode 100644 index 0000000000..8effd4b05a Binary files /dev/null and b/microsite/blog/assets/2/spotify-labs-header.png differ diff --git a/microsite/blog/assets/20-05-14/weaveworks-firekube-covid-19-spotify-backstage.png b/microsite/blog/assets/20-05-14/weaveworks-firekube-covid-19-spotify-backstage.png new file mode 100644 index 0000000000..fc5d05cd5d Binary files /dev/null and b/microsite/blog/assets/20-05-14/weaveworks-firekube-covid-19-spotify-backstage.png differ diff --git a/microsite/blog/assets/20-05-20/Service_Catalog_MVP.png b/microsite/blog/assets/20-05-20/Service_Catalog_MVP.png new file mode 100644 index 0000000000..4a50f17d15 Binary files /dev/null and b/microsite/blog/assets/20-05-20/Service_Catalog_MVP.png differ diff --git a/microsite/blog/assets/20-05-20/Service_Catalog_MVP_service.png b/microsite/blog/assets/20-05-20/Service_Catalog_MVP_service.png new file mode 100644 index 0000000000..8067f665c9 Binary files /dev/null and b/microsite/blog/assets/20-05-20/Service_Catalog_MVP_service.png differ diff --git a/microsite/blog/assets/20-05-20/tabs.png b/microsite/blog/assets/20-05-20/tabs.png new file mode 100644 index 0000000000..4a5b11f6fe Binary files /dev/null and b/microsite/blog/assets/20-05-20/tabs.png differ diff --git a/microsite/blog/assets/20-07-01/auth-landing.png b/microsite/blog/assets/20-07-01/auth-landing.png new file mode 100644 index 0000000000..e00f4c2e43 Binary files /dev/null and b/microsite/blog/assets/20-07-01/auth-landing.png differ diff --git a/microsite/blog/assets/20-07-01/auth-sidebar.png b/microsite/blog/assets/20-07-01/auth-sidebar.png new file mode 100644 index 0000000000..b089ab6c0a Binary files /dev/null and b/microsite/blog/assets/20-07-01/auth-sidebar.png differ diff --git a/microsite/blog/assets/2020-08-05/cards.png b/microsite/blog/assets/2020-08-05/cards.png new file mode 100644 index 0000000000..4779618b92 Binary files /dev/null and b/microsite/blog/assets/2020-08-05/cards.png differ diff --git a/microsite/blog/assets/2020-08-05/catalog.png b/microsite/blog/assets/2020-08-05/catalog.png new file mode 100644 index 0000000000..e9c0c65ade Binary files /dev/null and b/microsite/blog/assets/2020-08-05/catalog.png differ diff --git a/microsite/blog/assets/2020-08-05/create-component.png b/microsite/blog/assets/2020-08-05/create-component.png new file mode 100644 index 0000000000..4d815393fc Binary files /dev/null and b/microsite/blog/assets/2020-08-05/create-component.png differ diff --git a/microsite/blog/assets/2020-08-05/feature.mp4 b/microsite/blog/assets/2020-08-05/feature.mp4 new file mode 100644 index 0000000000..a42d9da0e6 Binary files /dev/null and b/microsite/blog/assets/2020-08-05/feature.mp4 differ diff --git a/microsite/blog/assets/2020-08-05/template-form.png b/microsite/blog/assets/2020-08-05/template-form.png new file mode 100644 index 0000000000..5805243f59 Binary files /dev/null and b/microsite/blog/assets/2020-08-05/template-form.png differ diff --git a/microsite/blog/assets/2020-08-05/templates.png b/microsite/blog/assets/2020-08-05/templates.png new file mode 100644 index 0000000000..e350d463f6 Binary files /dev/null and b/microsite/blog/assets/2020-08-05/templates.png differ diff --git a/microsite/blog/assets/3/audit-list.png b/microsite/blog/assets/3/audit-list.png new file mode 100644 index 0000000000..84b64f976a Binary files /dev/null and b/microsite/blog/assets/3/audit-list.png differ diff --git a/microsite/blog/assets/3/audit-view.png b/microsite/blog/assets/3/audit-view.png new file mode 100644 index 0000000000..a2e9716cf4 Binary files /dev/null and b/microsite/blog/assets/3/audit-view.png differ diff --git a/microsite/blog/assets/3/create-audit.png b/microsite/blog/assets/3/create-audit.png new file mode 100644 index 0000000000..cb28de73a4 Binary files /dev/null and b/microsite/blog/assets/3/create-audit.png differ diff --git a/microsite/blog/assets/3/lead-copy.png b/microsite/blog/assets/3/lead-copy.png new file mode 100644 index 0000000000..fbf247eec8 Binary files /dev/null and b/microsite/blog/assets/3/lead-copy.png differ diff --git a/microsite/blog/assets/3/lead.png b/microsite/blog/assets/3/lead.png new file mode 100644 index 0000000000..4b60c4961c Binary files /dev/null and b/microsite/blog/assets/3/lead.png differ diff --git a/microsite/blog/assets/4/create-app.png b/microsite/blog/assets/4/create-app.png new file mode 100644 index 0000000000..52dcc13097 Binary files /dev/null and b/microsite/blog/assets/4/create-app.png differ diff --git a/microsite/blog/assets/4/welcome.png b/microsite/blog/assets/4/welcome.png new file mode 100644 index 0000000000..5de0d57098 Binary files /dev/null and b/microsite/blog/assets/4/welcome.png differ diff --git a/microsite/blog/assets/5/lead.png b/microsite/blog/assets/5/lead.png new file mode 100644 index 0000000000..657268fc09 Binary files /dev/null and b/microsite/blog/assets/5/lead.png differ diff --git a/microsite/blog/assets/6/header.png b/microsite/blog/assets/6/header.png new file mode 100644 index 0000000000..6908e40dbc Binary files /dev/null and b/microsite/blog/assets/6/header.png differ diff --git a/microsite/blog/assets/blog_1.png b/microsite/blog/assets/blog_1.png new file mode 100644 index 0000000000..f8c3516fa7 Binary files /dev/null and b/microsite/blog/assets/blog_1.png differ diff --git a/microsite/blog/assets/illustration.svg b/microsite/blog/assets/illustration.svg new file mode 100644 index 0000000000..50e865ed4f --- /dev/null +++ b/microsite/blog/assets/illustration.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/microsite/core/Components.js b/microsite/core/Components.js new file mode 100644 index 0000000000..49e7d7daca --- /dev/null +++ b/microsite/core/Components.js @@ -0,0 +1,114 @@ +const React = require('react'); +const PropTypes = require('prop-types'); +const simpleComponent = (Component, baseClassName = '', mods = []) => { + const SimpleComponent = props => { + // Extra BEM modifiers, e.g. `Block__Container--reversed` + const modClasses = []; + const otherProps = {}; + for (const prop in props) { + if (mods.indexOf(prop) !== -1) { + modClasses.push(`${baseClassName}--${prop}`); + } else { + otherProps[prop] = props[prop]; + } + } + + return ( + + ); + }; + SimpleComponent.displayName = `SimpleComponent(${Component}, ${baseClassName})`; + + SimpleComponent.propTypes = {}; + for (const mod of mods) { + SimpleComponent.propTypes[mod] = PropTypes.bool; + } + + return SimpleComponent; +}; + +const Block = simpleComponent('section', 'Block', ['small', 'wrapped']); +Block.Container = simpleComponent('div', 'Block__Container', [ + 'reversed', + 'wrapped', + 'column', +]); +Block.TitleBox = simpleComponent('h1', 'Block__TitleBox', ['large', 'story']); +Block.TextBox = simpleComponent('div', 'Block__TextBox', ['wide', 'small']); + +Block.Title = simpleComponent('h1', 'Block__Title', ['half', 'main']); +Block.Subtitle = simpleComponent('h1', 'Block__Subtitle'); + +Block.SmallTitle = simpleComponent('h2', 'Block__SmallTitle'); +Block.SmallestTitle = simpleComponent('h3', 'Block__SmallestTitle'); + +const BulletLine = simpleComponent('div', 'BulletLine'); + +Block.Paragraph = simpleComponent('p', 'Block__Paragraph'); +Block.LinkButton = simpleComponent('a', 'Block__LinkButton', ['stretch']); +Block.QuoteContainer = simpleComponent('div', 'Block__QuoteContainer'); +Block.Quote = simpleComponent('p', 'Block__Quote'); +Block.Divider = simpleComponent('p', 'Block__Divider', ['quote']); +Block.MediaFrame = simpleComponent('div', 'Block__MediaFrame'); +Block.Graphics = ({ padding, children }) => { + const style = {}; + if (padding) { + style.padding = `${padding}% 0`; + } + return ( +
+
+
+ ); +}; +Block.Graphic = props => { + /* Coordinates and size are in % of graphics container size, e.g. width={50} is 50% of parent width */ + const { x = 0, y = 0, width = 0, src, className = '' } = props; + const style = Object.assign( + { left: `${x}%`, top: `${y}%`, width: `${width}%` }, + props.style, + ); + return ( + + ); +}; + +Block.Image = props => { + /* Coordinates and size are in % of graphics container size, e.g. width={50} is 50% of parent width */ + return ( +
+ ); +}; + +const ActionBlock = simpleComponent('section', 'ActionBlock'); +ActionBlock.Title = simpleComponent('h1', 'ActionBlock__Title'); +ActionBlock.Subtitle = simpleComponent('h2', 'ActionBlock__Subtitle'); +ActionBlock.Link = simpleComponent('a', 'ActionBlock__Link'); + +const Breakpoint = ({ narrow, wide }) => ( + +
{narrow}
+
{wide}
+
+); + +module.exports = { + Block, + ActionBlock, + Breakpoint, + BulletLine, +}; diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js new file mode 100644 index 0000000000..c44b8196a6 --- /dev/null +++ b/microsite/core/Footer.js @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); + +class Footer extends React.Component { + docUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + const docsUrl = this.props.config.docsUrl; + const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; + const langPart = `${language ? `${language}/` : ''}`; + return `${baseUrl}${docsPart}${langPart}${doc}`; + } + + pageUrl(doc, language) { + const baseUrl = this.props.config.baseUrl; + return baseUrl + (language ? `${language}/` : '') + doc; + } + + render() { + return ( + + ); + } +} + +module.exports = Footer; diff --git a/microsite/core/GridBlockWithButton.js b/microsite/core/GridBlockWithButton.js new file mode 100644 index 0000000000..0f77a6a148 --- /dev/null +++ b/microsite/core/GridBlockWithButton.js @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const classNames = require('classnames'); + +const CompLibrary = require(`${process.cwd()}/node_modules/docusaurus/lib/core/CompLibrary.js`); +const MarkdownBlock = CompLibrary.MarkdownBlock; /* Used to read markdown */ + +class GridBlockWithButton extends React.Component { + renderBlock(origBlock) { + const blockDefaults = { + imageAlign: 'left', + }; + + const block = { + ...blockDefaults, + ...origBlock, + }; + + const blockClasses = classNames('blockElement', this.props.className, { + alignCenter: this.props.align === 'center', + alignRight: this.props.align === 'right', + fourByGridBlock: this.props.layout === 'fourColumn', + imageAlignSide: + block.image && + (block.imageAlign === 'left' || block.imageAlign === 'right'), + imageAlignTop: block.image && block.imageAlign === 'top', + imageAlignRight: block.image && block.imageAlign === 'right', + imageAlignBottom: block.image && block.imageAlign === 'bottom', + imageAlignLeft: block.image && block.imageAlign === 'left', + threeByGridBlock: this.props.layout === 'threeColumn', + twoByGridBlock: this.props.layout === 'twoColumn', + }); + + const topLeftImage = + (block.imageAlign === 'top' || block.imageAlign === 'left') && + this.renderBlockImage(block.image, block.imageLink, block.imageAlt); + + const bottomRightImage = + (block.imageAlign === 'bottom' || block.imageAlign === 'right') && + this.renderBlockImage(block.image, block.imageLink, block.imageAlt); + + return ( +
+ {topLeftImage} +
+ {this.renderBlockTitle(block.title)} + {block.content} + +
+ {bottomRightImage} +
+ ); + } + + renderBlockImage(image) { + if (!image) { + return null; + } + + return
{image}
; + } + + renderBlockTitle(title) { + if (!title) { + return null; + } + + return ( +

+ {title} +

+ ); + } + + render() { + return ( +
+ {this.props.contents.map(this.renderBlock, this)} +
+ ); + } +} + +GridBlockWithButton.defaultProps = { + align: 'left', + contents: [], + layout: 'twoColumn', +}; + +module.exports = GridBlockWithButton; diff --git a/microsite/i18n/en.json b/microsite/i18n/en.json new file mode 100644 index 0000000000..f190546dfc --- /dev/null +++ b/microsite/i18n/en.json @@ -0,0 +1,290 @@ +{ + "_comment": "This file is auto-generated by write-translations.js", + "localized-strings": { + "next": "Next", + "previous": "Previous", + "tagline": "An open platform for building developer portals", + "docs": { + "api/backend": { + "title": "api/backend" + }, + "api/utility-apis": { + "title": "api/utility-apis" + }, + "architecture-decisions/adr001-add-adr-log": { + "title": "architecture-decisions/adr001-add-adr-log" + }, + "architecture-decisions/adr002-default-catalog-file-format": { + "title": "architecture-decisions/adr002-default-catalog-file-format" + }, + "architecture-decisions/adr003-avoid-default-exports": { + "title": "architecture-decisions/adr003-avoid-default-exports" + }, + "architecture-decisions/adr004-module-export-structure": { + "title": "architecture-decisions/adr004-module-export-structure" + }, + "architecture-decisions/adr005-catalog-core-entities": { + "title": "architecture-decisions/adr005-catalog-core-entities" + }, + "architecture-decisions/adr006-avoid-react-fc": { + "title": "architecture-decisions/adr006-avoid-react-fc" + }, + "architecture-decisions/adr007-use-msw-to-mock-service-requests": { + "title": "architecture-decisions/adr007-use-msw-to-mock-service-requests" + }, + "architecture-decisions/adr008-default-catalog-file-name": { + "title": "architecture-decisions/adr008-default-catalog-file-name" + }, + "architecture-decisions/index": { + "title": "architecture-decisions/index" + }, + "auth/add-auth-provider": { + "title": "auth/add-auth-provider" + }, + "auth/auth-backend": { + "title": "auth/auth-backend" + }, + "auth/glossary": { + "title": "auth/glossary" + }, + "auth/index": { + "title": "auth/index" + }, + "auth/oauth": { + "title": "auth/oauth" + }, + "conf/defining": { + "title": "conf/defining" + }, + "conf/index": { + "title": "conf/index" + }, + "conf/reading": { + "title": "conf/reading" + }, + "conf/writing": { + "title": "conf/writing" + }, + "dls/contributing-to-storybook": { + "title": "dls/contributing-to-storybook" + }, + "dls/design": { + "title": "dls/design" + }, + "dls/figma": { + "title": "dls/figma" + }, + "FAQ": { + "title": "FAQ" + }, + "features/software-catalog/api": { + "title": "features/software-catalog/api" + }, + "features/software-catalog/descriptor-format": { + "title": "features/software-catalog/descriptor-format" + }, + "features/software-catalog/extending-the-model": { + "title": "features/software-catalog/extending-the-model" + }, + "features/software-catalog/external-integrations": { + "title": "features/software-catalog/external-integrations" + }, + "features/software-catalog/index": { + "title": "features/software-catalog/index" + }, + "features/software-catalog/populating": { + "title": "features/software-catalog/populating" + }, + "features/software-catalog/system-model": { + "title": "features/software-catalog/system-model" + }, + "features/software-templates/adding-templates": { + "title": "features/software-templates/adding-templates" + }, + "features/software-templates/extending/create-your-own-preparer": { + "title": "features/software-templates/extending/create-your-own-preparer" + }, + "features/software-templates/extending/create-your-own-publisher": { + "title": "features/software-templates/extending/create-your-own-publisher" + }, + "features/software-templates/extending/create-your-own-templater": { + "title": "features/software-templates/extending/create-your-own-templater" + }, + "features/software-templates/extending/index": { + "title": "features/software-templates/extending/index" + }, + "features/software-templates/index": { + "title": "features/software-templates/index" + }, + "features/techdocs/concepts": { + "title": "features/techdocs/concepts" + }, + "features/techdocs/creating-and-publishing": { + "title": "features/techdocs/creating-and-publishing" + }, + "features/techdocs/FAQ": { + "title": "features/techdocs/FAQ" + }, + "features/techdocs/getting-started": { + "title": "features/techdocs/getting-started" + }, + "features/techdocs/README": { + "title": "features/techdocs/README" + }, + "getting-started/app-custom-theme": { + "title": "getting-started/app-custom-theme" + }, + "getting-started/configure-app-with-plugins": { + "title": "getting-started/configure-app-with-plugins" + }, + "getting-started/create-an-app": { + "title": "getting-started/create-an-app" + }, + "getting-started/deployment-k8s": { + "title": "getting-started/deployment-k8s" + }, + "getting-started/deployment-other": { + "title": "getting-started/deployment-other" + }, + "getting-started/development-environment": { + "title": "getting-started/development-environment" + }, + "getting-started/getting-started-index": { + "title": "Getting started", + "sidebar_label": "Getting started" + }, + "getting-started/installation": { + "title": "getting-started/installation" + }, + "journey": { + "title": "journey" + }, + "overview/architecture-overview": { + "title": "overview/architecture-overview" + }, + "overview/architecture-terminology": { + "title": "overview/architecture-terminology" + }, + "overview/roadmap": { + "title": "overview/roadmap" + }, + "overview/support": { + "title": "overview/support" + }, + "overview/what-is-backstage": { + "title": "overview/what-is-backstage" + }, + "plugins/backend-plugin": { + "title": "plugins/backend-plugin" + }, + "plugins/call-existing-api": { + "title": "plugins/call-existing-api" + }, + "plugins/create-a-plugin": { + "title": "plugins/create-a-plugin" + }, + "plugins/existing-plugins": { + "title": "plugins/existing-plugins" + }, + "plugins/index": { + "title": "plugins/index" + }, + "plugins/plugin-development": { + "title": "plugins/plugin-development" + }, + "plugins/proxying": { + "title": "plugins/proxying" + }, + "plugins/publish-private": { + "title": "plugins/publish-private" + }, + "plugins/publishing": { + "title": "plugins/publishing" + }, + "plugins/structure-of-a-plugin": { + "title": "plugins/structure-of-a-plugin" + }, + "plugins/testing": { + "title": "plugins/testing" + }, + "README": { + "title": "README" + }, + "reference/createPlugin-feature-flags": { + "title": "reference/createPlugin-feature-flags" + }, + "reference/createPlugin-router": { + "title": "reference/createPlugin-router" + }, + "reference/createPlugin": { + "title": "reference/createPlugin" + }, + "reference/utility-apis/AlertApi": { + "title": "reference/utility-apis/AlertApi" + }, + "reference/utility-apis/AppThemeApi": { + "title": "reference/utility-apis/AppThemeApi" + }, + "reference/utility-apis/BackstageIdentityApi": { + "title": "reference/utility-apis/BackstageIdentityApi" + }, + "reference/utility-apis/Config": { + "title": "reference/utility-apis/Config" + }, + "reference/utility-apis/ErrorApi": { + "title": "reference/utility-apis/ErrorApi" + }, + "reference/utility-apis/FeatureFlagsApi": { + "title": "reference/utility-apis/FeatureFlagsApi" + }, + "reference/utility-apis/IdentityApi": { + "title": "reference/utility-apis/IdentityApi" + }, + "reference/utility-apis/OAuthApi": { + "title": "reference/utility-apis/OAuthApi" + }, + "reference/utility-apis/OAuthRequestApi": { + "title": "reference/utility-apis/OAuthRequestApi" + }, + "reference/utility-apis/OpenIdConnectApi": { + "title": "reference/utility-apis/OpenIdConnectApi" + }, + "reference/utility-apis/ProfileInfoApi": { + "title": "reference/utility-apis/ProfileInfoApi" + }, + "reference/utility-apis/README": { + "title": "reference/utility-apis/README" + }, + "reference/utility-apis/SessionStateApi": { + "title": "reference/utility-apis/SessionStateApi" + }, + "reference/utility-apis/StorageApi": { + "title": "reference/utility-apis/StorageApi" + }, + "tutorials/index": { + "title": "tutorials/index" + } + }, + "links": { + "GitHub": "GitHub", + "Docs": "Docs", + "Blog": "Blog", + "Demos": "Demos", + "The Spotify story": "The Spotify story", + "Newsletter": "Newsletter" + }, + "categories": { + "Getting Started": "Getting Started", + "Plugins": "Plugins", + "Test": "Test", + "API Overview": "API Overview", + "Layout components": "Layout components", + "Testing": "Testing" + } + }, + "pages-strings": { + "Help Translate|recruit community translators for your project": "Help Translate", + "Edit this Doc|recruitment message asking to edit the doc source": "Edit", + "Translate this Doc|recruitment message asking to translate the docs": "Translate" + } +} diff --git a/microsite/package.json b/microsite/package.json new file mode 100644 index 0000000000..26c1b62f43 --- /dev/null +++ b/microsite/package.json @@ -0,0 +1,17 @@ +{ + "version": "1.0.0", + "name": "backstage-microsite", + "license": "Apache-2.0", + "scripts": { + "examples": "docusaurus-examples", + "start": "docusaurus-start", + "build": "docusaurus-build && cp static/img/*.svg build/backstage/img/", + "publish-gh-pages": "docusaurus-publish", + "write-translations": "docusaurus-write-translations", + "version": "docusaurus-version", + "rename-version": "docusaurus-rename-version" + }, + "devDependencies": { + "docusaurus": "^1.14.4" + } +} diff --git a/microsite/pages/en/background.js b/microsite/pages/en/background.js new file mode 100644 index 0000000000..70a15421ab --- /dev/null +++ b/microsite/pages/en/background.js @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; +const Breakpoint = Components.Breakpoint; + +const Background = props => { + const { config: siteConfig } = props; + const { baseUrl } = siteConfig; + return ( +
+ + + The Spotify Story + + + Backstage was born out of necessity at Spotify. We found that as + we grew, our infrastructure was becoming more fragmented, our + engineers less productive.{' '} + + + + Instead of building and testing code, teams were spending more + time looking for the right information just to get started. + “Where’s the API for that service we’re all supposed to be using?” + “What version of that framework is everyone on?” “This service + isn’t responding, who owns it?” “I can’t find documentation for + anything!”{' '} + + + + + + + One place for everything. Accessible to everyone. + + + + } + > + + Context switching and cognitive overload were dragging engineers + down, day by day. We needed to make it easier for our engineers to + do their work without having to become an expert in every aspect + of infrastructure tooling. + + + + Our idea was to centralize and simplify end-to-end software + development with an abstraction layer that sits on top of all of + our infrastructure and developer tooling. That’s Backstage. + + + + It’s a developer portal powered by a centralized service catalog — + with a plugin architecture that makes it endlessly extensible and + customizable. + + + + Manage all your services, software, tooling, and testing in + Backstage. Start building a new microservice using an automated + template in Backstage. Create, maintain, and find the + documentation for all that software in Backstage.{' '} + + + + One place for everything. Accessible to everyone. + + + + Explore Features + + + } + > + + + + + + + + + One place for everything. Accessible to everyone. + + + Explore Features + + + + } + > + +
+
+ ); +}; + +module.exports = Background; diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js new file mode 100644 index 0000000000..9f9dcec819 --- /dev/null +++ b/microsite/pages/en/demos.js @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; + +const Background = props => { + const { config: siteConfig } = props; + const { baseUrl } = siteConfig; + return ( +
+ + + + See us in action. + + To illustrate the potential of Backstage, we’re showing you{' '} + + how we use it here at Spotify + + . The videos below feature the custom plugins and templates that + we built for our internal version of Backstage. You can use + Backstage to build the developer portal you want — integrating the + tools that you use in your own infrastructure ecosystem. (Or get + started by building an open source plugin for Backstage that + everyone can use, like our{' '} + + Lighthouse Plugin + + .) + + + + + + + + + + + + Introduction to Backstage + + Backstage is an open source platform for building developer + portals. We’ve been using our homegrown version at Spotify for + years — so it’s already packed with features. (We have over 120 + internal plugins, built by 60 different teams.) In this live demo + recording, Stefan Ålund, product manager for Backstage, tells the + origin story of Backstage and gives you a tour of how we use it + here at Spotify. + + + Watch now + + + + + + + + + + + + Manage your tech health + + Instead of manually updating a spreadsheet, what if you had a + beautiful dashboard that could give you an instant, interactive + picture of your entire org’s tech stack? That’s how we do it at + Spotify. With our Tech Insights plugin for Backstage, anyone at + Spotify can see which version of which software anyone else at + Spotify is using — and a whole a lot more. From managing + migrations to fighting tech entropy, Backstage makes managing our + tech health actually kind of pleasant. + + + + Watch now + + + + + + + + + + + + Create a microservice + + You’re a Spotify engineer about to build a new microservice (or + any component) using Spring Boot. Where do you start? Search for a + quick start guide online? Create an empty repo on GitHub? Copy and + paste an old project? Nope. Just go to Backstage, and you’ll be up + and running in two minutes — with a “Hello World” app, CI, and + documentation all automatically set up and configured in a + standardized way. + + + + Watch now + + + + + + + + + + + + Search all your services + + All of Spotify’s services are automatically indexed in Backstage. + So our engineers can stop playing detective — no more spamming + Slack channels asking if anyone knows who owns a particular + service and where you can find its API, only to discover that the + owner went on sabbatical three months ago and you have to hunt + them down on a mountain in Tibet where they’re on a 12-day silent + meditation retreat. At Spotify, anyone can always find anyone + else’s service, inspect its APIs, and contact its current owner — + all with one search. + + + Watch now + + + + + + + + + + + + Manage data pipelines + + We manage a lot of data pipelines (also known as workflows) here + at Spotify. So, of course, we made a great workflows plugin for + our version of Backstage. All our workflow tools — including a + scheduler, log inspector, data lineage graph, and configurable + alerts — are integrated into one simple interface. + + + Watch now + + + + + + + +
+ ); +}; + +module.exports = Background; diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js new file mode 100644 index 0000000000..d140a2036b --- /dev/null +++ b/microsite/pages/en/index.js @@ -0,0 +1,474 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +const React = require('react'); +const Components = require(`${process.cwd()}/core/Components.js`); +const Block = Components.Block; +const ActionBlock = Components.ActionBlock; +const Breakpoint = Components.Breakpoint; +const BulletLine = Components.BulletLine; + +class Index extends React.Component { + render() { + const { config: siteConfig } = this.props; + const { baseUrl } = siteConfig; + + return ( +
+ + + + + An open platform for building developer portals + + + Powered by a centralized service catalog, Backstage restores + order to your infrastructure. So your product teams can ship + high-quality code quickly — without compromising autonomy. + + + GitHub + + + + + + + + + + + + + + The Speed Paradox + + At Spotify, we’ve always believed in the speed and ingenuity + that comes from having autonomous development teams. But as we + learned firsthand, the faster you grow, the more fragmented and + complex your software ecosystem becomes. And then everything + slows down again. + + + + + The Standards Paradox + + By centralizing services and standardizing your tooling, + Backstage streamlines your development environment from end to + end. Instead of restricting autonomy, standardization frees your + engineers from infrastructure complexity. So you can return to + building and scaling, quickly and safely. + + + + + + + + + {' '} + + + Backstage Service Catalog{' '} + + (alpha) + + + + Build an ecosystem, not a wilderness + + + + + + } + /> + + + + Manage all your software, all in one place{' '} + + + Backstage makes it easy for one team to manage 10 services — and + makes it possible for your company to manage thousands of them + + + + + A uniform overview + + Every team can see all the services they own and related + resources (deployments, data pipelines, pull request status, + etc.) + + + + + + Metadata on tap + + All that information can be shared with plugins inside Backstage + to enable other management features, like resource monitoring + and testing + + + + + + Not just services + + Libraries, websites, ML models — you name it, Backstage knows + all about it, including who owns it, dependencies, and more + + + + + + + Discoverability & accountability + + + No more orphan software hiding in the dark corners of your tech + stack + + + + + + + + + Learn more about the service catalog + + + Read + + + + + + + + + Backstage Software Templates{' '} + + (alpha) + + + Standards can set you free + + + + + } + /> + + + + Like automated getting started guides + + + Using templates, engineers can spin up a new microservice with + your organization’s best practices built-in, right from the + start + + + + + + Push-button deployment + + Click a button to create a Spring Boot project with your repo + automatically configured on GitHub and your CI already running + the first build + + + + + + Built to your standards + + Go instead of Java? CircleCI instead of Jenkins? Serverless + instead of Kubernetes? GCP instead of AWS? Customize your + recipes with your best practices baked-in + + + + + + + Golden Paths pave the way + + + When the right way is also the easiest way, engineers get up and + running faster — and more safely + + + + + + } + /> + + + + + + Build your own software templates + + + Contribute + + + + + + + + + + Backstage TechDocs (Coming Soon) + + Docs like code + + + + + + + } + /> + + + Free documentation + + Whenever you use a Backstage Software Template, your project + automatically gets a TechDocs site, for free + + + + + + Easy to write + + With our docs-like-code approach, engineers write their + documentation in Markdown files right alongside their code + + + + + + Easy to maintain + + Updating code? Update your documentation while you’re there — + with docs and code in the same place, it becomes a natural part + of your workstream + + + + + + Easy to find and use + + Since all your documentation is in Backstage, finding any + TechDoc is just a search query away + + + + } + /> + + + + + Subscribe to our newsletter + + TechDocs is our most used feature at Spotify. Be the first to know + when{' '} + + the open source version + {' '} + ships. + + + Subscribe + + + + + + + + + Customize Backstage with plugins + + An app store for your infrastructure + + + + + + } + /> + + + Add functionality + + Want scalable website testing? Add the{' '} + + Lighthouse + {' '} + plugin. Wondering about recommended frameworks? Add the{' '} + + Tech Radar + {' '} + plugin.{' '} + + + + + + BYO Plugins + + If you don’t see the plugin you need, it’s simple to build your + own + + + + + + + Integrate your own custom tooling + + + Building internal plugins lets you tailor your version of + Backstage to be a perfect fit for your infrastructure + + + + + + + Share with the community + + + Building{' '} + + open source plugins + {' '} + contributes to the entire Backstage ecosystem, which benefits + everyone + + + + } + /> + + + + + Build a plugin + + Contribute + + +
+ ); + } +} + +module.exports = Index; diff --git a/microsite/sidebars.json b/microsite/sidebars.json new file mode 100644 index 0000000000..1ac34b308a --- /dev/null +++ b/microsite/sidebars.json @@ -0,0 +1,17 @@ +{ + "docs": { + "Getting Started": ["getting-started/getting-started-index"], + "Plugins": [ + "developing-a-plugin", + "plugin-api", + "create-plugin-api-v2", + "plugin-structure", + "components", + "errors-notifications" + ], + "Test": ["test"], + "API Overview": ["apis"], + "Layout components": ["layout-components"], + "Testing": ["testing-with-jest"] + } +} diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js new file mode 100644 index 0000000000..f9dd3877bf --- /dev/null +++ b/microsite/siteConfig.js @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// See https://docusaurus.io/docs/site-config for all the possible +// site configuration options. + +// List of projects/orgs using your project for the users page. +const users = []; + +const siteConfig = { + title: 'Backstage', // Title for your website. + tagline: 'An open platform for building developer portals', + url: 'https://backstage.io', // Your website URL + cname: 'backstage.io', + baseUrl: '/', // Base URL for your project */ + // For github.io type URLs, you would set the url and baseUrl like: + // url: 'https://facebook.github.io', + // baseUrl: '/test-site/', + + // Used for publishing and more + projectName: 'backstage', + organizationName: 'Spotify', + fossWebsite: 'https://spotify.github.io/', + // For top-level user or org sites, the organization is still the same. + // e.g., for the https://JoelMarcey.github.io site, it would be set like... + // organizationName: 'JoelMarcey' + + // Google Analytics + gaTrackingId: 'UA-48912878-10', + + // For no header links in the top nav bar -> headerLinks: [], + headerLinks: [ + { + href: 'https://github.com/spotify/backstage#backstage', + label: 'GitHub', + }, + // { + // doc: 'getting-started/getting-started-index', + // label: 'Docs', + // }, + { + page: 'blog', + blog: true, + label: 'Blog', + }, + { + page: 'demos', + label: 'Demos', + }, + { + page: 'background', + label: 'The Spotify story', + }, + { + href: 'https://mailchi.mp/spotify/backstage-community', + label: 'Newsletter', + }, + ], + + /* path to images for header/footer */ + // headerIcon: "img/android-chrome-192x192.png", + footerIcon: 'img/android-chrome-192x192.png', + favicon: 'img/favicon.svg', + + /* Colors for website */ + colors: { + primaryColor: '#36BAA2', + secondaryColor: '#121212', + textColor: '#FFFFFF', + navigatorTitleTextColor: '#9e9e9e', + navigatorItemTextColor: '#616161', + }, + + /* Colors for syntax highlighting */ + highlight: { + theme: 'dark', + }, + + // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. + copyright: `Copyright © ${new Date().getFullYear()} Spotify AB`, + + highlight: { + // Highlight.js theme to use for syntax highlighting in code blocks. + theme: 'monokai', + }, + + // Add custom scripts here that would be placed in + `); @@ -103,9 +135,10 @@ export class OAuthProvider implements AuthProviderRouteHandlers { async start(req: express.Request, res: express.Response): Promise { // retrieve scopes from request const scope = req.query.scope?.toString() ?? ''; + const env = req.query.env?.toString(); - if (!scope) { - throw new InputError('missing scope parameter'); + if (!env) { + throw new InputError('No env provided in request query parameters'); } if (this.options.persistScopes) { @@ -116,9 +149,12 @@ export class OAuthProvider implements AuthProviderRouteHandlers { // set a nonce cookie before redirecting to oauth provider this.setNonceCookie(res, nonce); + const stateObject = { nonce: nonce, env: env }; + const stateParameter = encodeState(stateObject); + const queryParameters = { scope, - state: nonce, + state: stateParameter, }; const { url, status } = await this.providerHandlers.start( @@ -228,6 +264,19 @@ export class OAuthProvider implements AuthProviderRouteHandlers { } } + identifyEnv(req: express.Request): string | undefined { + const reqEnv = req.query.env?.toString(); + if (reqEnv) { + return reqEnv; + } + const stateParams = req.query.state?.toString(); + if (!stateParams) { + return undefined; + } + const env = readState(stateParams).env; + return env; + } + /** * If the response from the OAuth provider includes a Backstage identity, we * make sure it's populated with all the information we can derive from the user ID. @@ -248,7 +297,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers { res.cookie(`${this.options.providerId}-nonce`, nonce, { maxAge: TEN_MINUTES_MS, secure: this.options.secure, - sameSite: 'none', + sameSite: 'lax', domain: this.domain, path: `${this.basePath}/${this.options.providerId}/handler`, httpOnly: true, @@ -259,7 +308,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers { res.cookie(`${this.options.providerId}-scope`, scope, { maxAge: TEN_MINUTES_MS, secure: this.options.secure, - sameSite: 'none', + sameSite: 'lax', domain: this.domain, path: `${this.basePath}/${this.options.providerId}/handler`, httpOnly: true, @@ -277,7 +326,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers { res.cookie(`${this.options.providerId}-refresh-token`, refreshToken, { maxAge: THOUSAND_DAYS_MS, secure: this.options.secure, - sameSite: 'none', + sameSite: 'lax', domain: this.domain, path: `${this.basePath}/${this.options.providerId}`, httpOnly: true, @@ -288,7 +337,7 @@ export class OAuthProvider implements AuthProviderRouteHandlers { res.cookie(`${this.options.providerId}-refresh-token`, '', { maxAge: 0, secure: false, - sameSite: 'none', + sameSite: 'lax', domain: `${this.domain}`, path: `${this.basePath}/${this.options.providerId}`, httpOnly: true, diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index 77e5356721..8443a7928d 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -24,7 +24,16 @@ import { createOAuth2Provider } from './oauth2'; import { createOktaProvider } from './okta'; import { createSamlProvider } from './saml'; import { createAuth0Provider } from './auth0' -import { AuthProviderConfig, AuthProviderFactory } from './types'; +import { + AuthProviderConfig, + AuthProviderFactory, + EnvironmentIdentifierFn, +} from './types'; +import { Config } from '@backstage/config'; +import { + EnvironmentHandlers, + EnvironmentHandler, +} from '../lib/EnvironmentHandler'; const factories: { [providerId: string]: AuthProviderFactory } = { google: createGoogleProvider, @@ -39,7 +48,7 @@ const factories: { [providerId: string]: AuthProviderFactory } = { export const createAuthProviderRouter = ( providerId: string, globalConfig: AuthProviderConfig, - providerConfig: any, // TODO: make this a config reader object of sorts + providerConfig: Config, logger: Logger, issuer: TokenIssuer, ) => { @@ -48,17 +57,38 @@ export const createAuthProviderRouter = ( throw Error(`No auth provider available for '${providerId}'`); } - const provider = factory(globalConfig, providerConfig, logger, issuer); - const router = Router(); - router.get('/start', provider.start.bind(provider)); - router.get('/handler/frame', provider.frameHandler.bind(provider)); - router.post('/handler/frame', provider.frameHandler.bind(provider)); - if (provider.logout) { - router.post('/logout', provider.logout.bind(provider)); + const envs = providerConfig.keys(); + const envProviders: EnvironmentHandlers = {}; + let envIdentifier: EnvironmentIdentifierFn | undefined; + + for (const env of envs) { + const envConfig = providerConfig.getConfig(env); + const provider = factory(globalConfig, env, envConfig, logger, issuer); + if (provider) { + envProviders[env] = provider; + envIdentifier = provider.identifyEnv; + } } - if (provider.refresh) { - router.get('/refresh', provider.refresh.bind(provider)); + + if (typeof envIdentifier === 'undefined') { + throw Error(`No envIdentifier provided for '${providerId}'`); + } + + const handler = new EnvironmentHandler( + providerId, + envProviders, + envIdentifier, + ); + + router.get('/start', handler.start.bind(handler)); + router.get('/handler/frame', handler.frameHandler.bind(handler)); + router.post('/handler/frame', handler.frameHandler.bind(handler)); + if (handler.logout) { + router.post('/logout', handler.logout.bind(handler)); + } + if (handler.refresh) { + router.get('/refresh', handler.refresh.bind(handler)); } return router; diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index bd7d4aa712..863cfa9f5a 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -25,20 +25,15 @@ import { OAuthProviderHandlers, AuthProviderConfig, RedirectInfo, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; import passport from 'passport'; +import { Config } from '@backstage/config'; export class GithubAuthProvider implements OAuthProviderHandlers { private readonly _strategy: GithubStrategy; @@ -131,44 +126,58 @@ export class GithubAuthProvider implements OAuthProviderHandlers { export function createGithubProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'github'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const enterpriseInstanceUrl = envConfig.getOptionalString( + 'enterpriseInstanceUrl', + ); + const authorizationURL = enterpriseInstanceUrl + ? `${enterpriseInstanceUrl}/login/oauth/authorize` + : undefined; + const tokenURL = enterpriseInstanceUrl + ? `${enterpriseInstanceUrl}/login/oauth/access_token` + : undefined; + const userProfileURL = enterpriseInstanceUrl + ? `${enterpriseInstanceUrl}/api/v3/user` + : undefined; + const callbackURL = `${baseUrl}/${providerId}/handler/frame`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + clientID, + clientSecret, + authorizationURL, + tokenURL, + userProfileURL, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Github auth provider, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Github auth provider disabled, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Github auth provider, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GithubAuthProvider(opts), { - disableRefresh: true, - persistScopes: true, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Github auth provider disabled, set AUTH_GITHUB_CLIENT_ID and AUTH_GITHUB_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GithubAuthProvider(opts), { + disableRefresh: true, + persistScopes: true, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index af1d57fcce..22d0bd4f0a 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -25,20 +25,15 @@ import { OAuthProviderHandlers, AuthProviderConfig, RedirectInfo, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; import passport from 'passport'; +import { Config } from '@backstage/config'; export class GitlabAuthProvider implements OAuthProviderHandlers { private readonly _strategy: GitlabStrategy; @@ -138,49 +133,45 @@ export class GitlabAuthProvider implements OAuthProviderHandlers { export function createGitlabProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'gitlab'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const audience = envConfig.getString('audience'); + const baseURL = audience || 'https://gitlab.com'; + const callbackURL = `${baseUrl}/${providerId}/handler/frame`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const { - secure, - appOrigin, - clientId, - clientSecret, - audience, - } = (envConfig as unknown) as OAuthProviderConfig; - const opts = { - clientID: clientId, - clientSecret: clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - baseURL: audience, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + baseURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Gitlab auth provider, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Gitlab auth provider disabled, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Gitlab auth provider, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GitlabAuthProvider(opts), { - disableRefresh: true, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Gitlab auth provider disabled, set AUTH_GITLAB_CLIENT_ID and AUTH_GITLAB_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GitlabAuthProvider(opts), { + disableRefresh: true, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 3023c09886..b8070949f9 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -27,20 +27,15 @@ import { OAuthProviderHandlers, RedirectInfo, AuthProviderConfig, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; import { OAuthProvider } from '../../lib/OAuthProvider'; import passport from 'passport'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -150,43 +145,42 @@ export class GoogleAuthProvider implements OAuthProviderHandlers { export function createGoogleProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'google'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Google auth provider, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars', - ); - } - - logger.warn( - 'Google auth provider disabled, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars to enable', + if (!opts.clientID || !opts.clientSecret) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Google auth provider, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new GoogleAuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Google auth provider disabled, set AUTH_GOOGLE_CLIENT_ID and AUTH_GOOGLE_CLIENT_SECRET env vars to enable', + ); + return undefined; } - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new GoogleAuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 60b6bc605b..c03f7c4371 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -19,10 +19,6 @@ import passport from 'passport'; import { Strategy as OAuth2Strategy } from 'passport-oauth2'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { OAuthProvider } from '../../lib/OAuthProvider'; import { executeFetchUserProfileStrategy, @@ -33,14 +29,13 @@ import { } from '../../lib/PassportStrategyHelper'; import { AuthProviderConfig, - EnvironmentProviderConfig, - GenericOAuth2ProviderConfig, GenericOAuth2ProviderOptions, OAuthProviderHandlers, OAuthResponse, PassportDoneCallback, RedirectInfo, } from '../types'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -148,51 +143,51 @@ export class OAuth2AuthProvider implements OAuthProviderHandlers { export function createOAuth2Provider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'oauth2'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame`; + const authorizationURL = envConfig.getString('authorizationURL'); + const tokenURL = envConfig.getString('tokenURL'); - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as GenericOAuth2ProviderConfig; - const { secure, appOrigin } = config; - const opts = { - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - authorizationURL: config.authorizationURL, - tokenURL: config.tokenURL, - }; + const opts = { + clientID, + clientSecret, + callbackURL, + authorizationURL, + tokenURL, + }; - if ( - !opts.clientID || - !opts.clientSecret || - !opts.authorizationURL || - !opts.tokenURL - ) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize OAuth2 auth provider, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars', - ); - } - - logger.warn( - 'OAuth2 auth provider disabled, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars to enable', + if ( + !opts.clientID || + !opts.clientSecret || + !opts.authorizationURL || + !opts.tokenURL + ) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize OAuth2 auth provider, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new OAuth2AuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'OAuth2 auth provider disabled, set AUTH_OAUTH2_CLIENT_ID, AUTH_OAUTH2_CLIENT_SECRET, AUTH_OAUTH2_AUTH_URL, and AUTH_OAUTH2_TOKEN_URL env vars to enable', + ); + return undefined; } - - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new OAuth2AuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 01d7684e97..058300cef6 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -28,19 +28,14 @@ import { OAuthProviderHandlers, RedirectInfo, AuthProviderConfig, - EnvironmentProviderConfig, OAuthProviderOptions, - OAuthProviderConfig, OAuthResponse, PassportDoneCallback, } from '../types'; -import { - EnvironmentHandler, - EnvironmentHandlers, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { StateStore } from 'passport-oauth2'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type PrivateInfo = { refreshToken: string; @@ -170,45 +165,44 @@ export class OktaAuthProvider implements OAuthProviderHandlers { export function createOktaProvider( { baseUrl }: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { const providerId = 'okta'; - const envProviders: EnvironmentHandlers = {}; + const secure = envConfig.getBoolean('secure'); + const appOrigin = envConfig.getString('appOrigin'); + const clientID = envConfig.getString('clientId'); + const clientSecret = envConfig.getString('clientSecret'); + const audience = envConfig.getString('audience'); + const callbackURL = `${baseUrl}/${providerId}/handler/frame`; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as OAuthProviderConfig; - const { secure, appOrigin } = config; - const opts = { - audience: config.audience, - clientID: config.clientId, - clientSecret: config.clientSecret, - callbackURL: `${baseUrl}/${providerId}/handler/frame?env=${env}`, - }; + const opts = { + audience, + clientID, + clientSecret, + callbackURL, + }; - if (!opts.clientID || !opts.clientSecret || !opts.audience) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - 'Failed to initialize Okta auth provider, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars', - ); - } - - logger.warn( - 'Okta auth provider disabled, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars to enable', + if (!opts.clientID || !opts.clientSecret || !opts.audience) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + 'Failed to initialize Okta auth provider, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars', ); - continue; } - envProviders[env] = new OAuthProvider(new OktaAuthProvider(opts), { - disableRefresh: false, - providerId, - secure, - baseUrl, - appOrigin, - tokenIssuer, - }); + logger.warn( + 'Okta auth provider disabled, set AUTH_OKTA_CLIENT_ID, AUTH_OKTA_CLIENT_SECRET, and AUTH_OKTA_AUDIENCE env vars to enable', + ); + return undefined; } - - return new EnvironmentHandler(providerId, envProviders); + return new OAuthProvider(new OktaAuthProvider(opts), { + disableRefresh: false, + providerId, + secure, + baseUrl, + appOrigin, + tokenIssuer, + }); } diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 40c80de4e4..1ccee1a9cb 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -27,18 +27,13 @@ import { import { AuthProviderConfig, AuthProviderRouteHandlers, - EnvironmentProviderConfig, - SAMLProviderConfig, PassportDoneCallback, ProfileInfo, } from '../types'; import { postMessageResponse } from '../../lib/OAuthProvider'; -import { - EnvironmentHandlers, - EnvironmentHandler, -} from '../../lib/EnvironmentHandler'; import { Logger } from 'winston'; import { TokenIssuer } from '../../identity'; +import { Config } from '@backstage/config'; type SamlInfo = { userId: string; @@ -111,6 +106,10 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { async logout(_req: express.Request, res: express.Response): Promise { res.send('noop'); } + + identifyEnv(): string | undefined { + return undefined; + } } type SAMLProviderOptions = { @@ -122,30 +121,25 @@ type SAMLProviderOptions = { export function createSamlProvider( _authProviderConfig: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + _env: string, + envConfig: Config, logger: Logger, tokenIssuer: TokenIssuer, ) { - const envProviders: EnvironmentHandlers = {}; + const entryPoint = envConfig.getString('entryPoint'); + const issuer = envConfig.getString('issuer'); + const opts = { + entryPoint, + issuer, + path: '/auth/saml/handler/frame', + tokenIssuer, + }; - for (const [env, envConfig] of Object.entries(providerConfig)) { - const config = (envConfig as unknown) as SAMLProviderConfig; - const opts = { - entryPoint: config.entryPoint, - issuer: config.issuer, - path: '/auth/saml/handler/frame', - tokenIssuer, - }; - - if (!opts.entryPoint || !opts.issuer) { - logger.warn( - 'SAML auth provider disabled, set entryPoint and entryPoint in saml auth config to enable', - ); - continue; - } - - envProviders[env] = new SamlAuthProvider(opts); + if (!opts.entryPoint || !opts.issuer) { + logger.warn( + 'SAML auth provider disabled, set entryPoint and entryPoint in saml auth config to enable', + ); + return undefined; } - - return new EnvironmentHandler('saml', envProviders); + return new SamlAuthProvider(opts); } diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index de4e076919..d372fd1b94 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -17,6 +17,9 @@ import express from 'express'; import { Logger } from 'winston'; import { TokenIssuer } from '../identity'; +import { Config } from '@backstage/config'; +import { OAuthProvider } from '../lib/OAuthProvider'; +import { SamlAuthProvider } from './saml/provider'; export type OAuthProviderOptions = { /** @@ -200,14 +203,24 @@ export interface AuthProviderRouteHandlers { * @param {express.Response} res */ logout?(req: express.Request, res: express.Response): Promise; + + /** + *(Optional) A method to identify the environment Context of the Request + * + *Request + *- contains the environment context information encoded in the request + * @param {express.Request} req + */ + identifyEnv?(req: express.Request): string | undefined; } export type AuthProviderFactory = ( globalConfig: AuthProviderConfig, - providerConfig: EnvironmentProviderConfig, + env: string, + envConfig: Config, logger: Logger, issuer: TokenIssuer, -) => AuthProviderRouteHandlers; +) => OAuthProvider | SamlAuthProvider | undefined; export type AuthResponse = { providerInfo: ProviderInfo; @@ -328,3 +341,14 @@ export type SAMLProviderConfig = { export type SAMLEnvironmentProviderConfig = { [key: string]: SAMLProviderConfig; }; + +export type OAuthState = { + /* A type for the serialized value in the `state` parameter of the OAuth authorization flow + */ + nonce: string; + env: string; +}; + +export type EnvironmentIdentifierFn = ( + req: express.Request, +) => string | undefined; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 256545219d..69f058f6db 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -36,8 +36,6 @@ export async function createRouter( const router = Router(); const logger = options.logger.child({ plugin: 'auth' }); - const appUrl = new URL(options.config.getString('app.baseUrl')); - const appOrigin = appUrl.origin; const backendUrl = options.config.getString('backend.baseUrl'); const authUrl = `${backendUrl}/auth`; @@ -57,80 +55,13 @@ export async function createRouter( router.use(bodyParser.urlencoded({ extended: false })); router.use(bodyParser.json()); - const config = { - backend: { - baseUrl: backendUrl, - }, - auth: { - providers: { - google: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GOOGLE_CLIENT_ID!, - clientSecret: process.env.AUTH_GOOGLE_CLIENT_SECRET!, - }, - }, - github: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GITHUB_CLIENT_ID!, - clientSecret: process.env.AUTH_GITHUB_CLIENT_SECRET!, - }, - }, - gitlab: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_GITLAB_CLIENT_ID!, - clientSecret: process.env.AUTH_GITLAB_CLIENT_SECRET!, - audience: process.env.GITLAB_BASE_URL! || 'https://gitlab.com', - }, - }, - saml: { - development: { - entryPoint: 'http://localhost:7001/', - issuer: 'passport-saml', - }, - }, - okta: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_OKTA_CLIENT_ID!, - clientSecret: process.env.AUTH_OKTA_CLIENT_SECRET!, - audience: process.env.AUTH_OKTA_AUDIENCE, - }, - }, - oauth2: { - development: { - appOrigin, - secure: false, - clientId: process.env.AUTH_OAUTH2_CLIENT_ID!, - clientSecret: process.env.AUTH_OAUTH2_CLIENT_SECRET!, - authorizationURL: process.env.AUTH_OAUTH2_AUTH_URL!, - tokenURL: process.env.AUTH_OAUTH2_TOKEN_URL!, - }, - }, - auth0: { - development: { - appOrigin, - secure: false, - domain: process.env.AUTH_AUTH0_DOMAIN!, - clientId: process.env.AUTH_AUTH0_CLIENT_ID!, - clientSecret: process.env.AUTH_AUTH0_CLIENT_SECRET! - }, - }, - }, - }, - }; + const providersConfig = options.config.getConfig('auth.providers'); + const providers = providersConfig.keys(); - const providerConfigs = config.auth.providers; - - for (const [providerId, providerConfig] of Object.entries(providerConfigs)) { + for (const providerId of providers) { logger.info(`Configuring provider, ${providerId}`); try { + const providerConfig = providersConfig.getConfig(providerId); const providerRouter = createAuthProviderRouter( providerId, { baseUrl: authUrl }, diff --git a/plugins/auth-backend/src/service/standaloneServer.ts b/plugins/auth-backend/src/service/standaloneServer.ts index d2372a7e91..dc91c92631 100644 --- a/plugins/auth-backend/src/service/standaloneServer.ts +++ b/plugins/auth-backend/src/service/standaloneServer.ts @@ -18,9 +18,12 @@ import Knex from 'knex'; import { Server } from 'http'; import { Logger } from 'winston'; import { ConfigReader } from '@backstage/config'; -import { loadConfig } from '@backstage/config-loader'; import { createRouter } from './router'; -import { createServiceBuilder, useHotMemoize } from '@backstage/backend-common'; +import { + createServiceBuilder, + useHotMemoize, + loadBackendConfig, +} from '@backstage/backend-common'; export interface ServerOptions { logger: Logger; @@ -30,7 +33,7 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'auth-backend' }); - const config = ConfigReader.fromConfigs(await loadConfig()); + const config = ConfigReader.fromConfigs(await loadBackendConfig()); const database = useHotMemoize(module, () => { const knex = Knex({ diff --git a/plugins/catalog-backend/migrations/20200721115244_location_update_log_latest_deduplicate.js b/plugins/catalog-backend/migrations/20200721115244_location_update_log_latest_deduplicate.js new file mode 100644 index 0000000000..9046f85da0 --- /dev/null +++ b/plugins/catalog-backend/migrations/20200721115244_location_update_log_latest_deduplicate.js @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +exports.up = function up(knex) { + return knex.schema.raw(`DROP VIEW location_update_log_latest;`).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; +`); +}; + +exports.down = function down(knex) { + knex.schema.raw(`DROP VIEW location_update_log_latest;`); +}; diff --git a/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js b/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js new file mode 100644 index 0000000000..05c1658641 --- /dev/null +++ b/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = function up(knex) { + return knex.schema + .raw('DROP VIEW location_update_log_latest;') + .dropTable('location_update_log') + .createTable('location_update_log', table => { + table.bigIncrements('id').primary(); // instead of uuid, so we can MAX it + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(id) AS MAXID + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.id = t2.MAXID + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; + +/** + * @param {import('knex')} knex + */ +exports.down = function down(knex) { + return knex.schema + .raw('DROP VIEW location_update_log_latest;') + .dropTable('location_update_log') + .createTable('location_update_log', table => { + table.uuid('id').primary(); + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; diff --git a/plugins/catalog-backend/migrations/20200807120600_entitySearch.js b/plugins/catalog-backend/migrations/20200807120600_entitySearch.js new file mode 100644 index 0000000000..6e02975f92 --- /dev/null +++ b/plugins/catalog-backend/migrations/20200807120600_entitySearch.js @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = async function up(knex) { + try { + await knex.schema.alterTable('entities_search', table => { + table.text('value').nullable().alter(); + }); + } catch (e) { + // Sqlite does not support alter column. + } +}; + +/** + * @param {import('knex')} knex + */ +exports.down = async function down(knex) { + try { + await knex.schema.alterTable('entities_search', table => { + table.string('value').nullable().alter(); + }); + } catch (e) { + // Sqlite does not support alter column. + } +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 004d459b92..2445ee206e 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", - "@backstage/catalog-model": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -39,13 +39,14 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "@types/lodash": "^4.14.151", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", "@types/yup": "^0.28.2", "jest-fetch-mock": "^3.0.3", + "msw": "^0.19.5", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.test.ts new file mode 100644 index 0000000000..bcd1248a66 --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.test.ts @@ -0,0 +1,148 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { Logger } from 'winston'; +import { CoalescedEntitiesCatalog } from './CoalescedEntitiesCatalog'; +import { EntitiesCatalog } from './types'; + +describe('CoalescedEntitiesCatalog', () => { + const e1: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'n1' }, + }; + + const e2: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'n2' }, + }; + + const c1: jest.Mocked = { + entities: jest.fn(), + entityByUid: jest.fn(), + entityByName: jest.fn(), + addOrUpdateEntity: jest.fn(), + removeEntityByUid: jest.fn(), + }; + + const c2: jest.Mocked = { + entities: jest.fn(), + entityByUid: jest.fn(), + entityByName: jest.fn(), + addOrUpdateEntity: jest.fn(), + removeEntityByUid: jest.fn(), + }; + + const mockLogger = { + warn: jest.fn(), + }; + const logger = (mockLogger as unknown) as Logger; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('entities', () => { + it('flattens results from multiple sources', async () => { + c1.entities.mockResolvedValueOnce([e1]); + c2.entities.mockResolvedValueOnce([e2]); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entities()).resolves.toEqual( + expect.arrayContaining([e1, e2]), + ); + expect(c1.entities).toBeCalledTimes(1); + expect(c2.entities).toBeCalledTimes(1); + }); + + it('logs an error if any source throws', async () => { + c1.entities.mockResolvedValueOnce([e1]); + c2.entities.mockRejectedValueOnce(new Error('boo')); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entities()).resolves.toEqual([e1]); + expect(c1.entities).toBeCalledTimes(1); + expect(c2.entities).toBeCalledTimes(1); + expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/)); + }); + }); + + describe('entityByUid', () => { + it('returns the first non-undefined result', async () => { + c1.entityByUid.mockResolvedValueOnce(undefined); + c2.entityByUid.mockResolvedValueOnce(e2); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entityByUid('e2')).resolves.toBe(e2); + expect(c1.entityByUid).toBeCalledTimes(1); + expect(c2.entityByUid).toBeCalledTimes(1); + }); + + it('returns undefined if all results were undefined', async () => { + c1.entityByUid.mockResolvedValueOnce(undefined); + c2.entityByUid.mockResolvedValueOnce(undefined); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entityByUid('e2')).resolves.toBeUndefined(); + expect(c1.entityByUid).toBeCalledTimes(1); + expect(c2.entityByUid).toBeCalledTimes(1); + }); + + it('logs an error if any source throws', async () => { + c1.entityByUid.mockResolvedValueOnce(e1); + c2.entityByUid.mockRejectedValueOnce(new Error('boo')); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entityByUid('e2')).resolves.toBe(e1); + expect(c1.entityByUid).toBeCalledTimes(1); + expect(c2.entityByUid).toBeCalledTimes(1); + expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/)); + }); + }); + + describe('entityByName', () => { + it('returns the first non-undefined result', async () => { + c1.entityByName.mockResolvedValueOnce(undefined); + c2.entityByName.mockResolvedValueOnce(e2); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entityByName('k', undefined, 'n2')).resolves.toBe( + e2, + ); + expect(c1.entityByName).toBeCalledTimes(1); + expect(c2.entityByName).toBeCalledTimes(1); + }); + + it('returns undefined if all results were undefined', async () => { + c1.entityByName.mockResolvedValueOnce(undefined); + c2.entityByName.mockResolvedValueOnce(undefined); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect( + catalog.entityByName('k', undefined, 'n2'), + ).resolves.toBeUndefined(); + expect(c1.entityByName).toBeCalledTimes(1); + expect(c2.entityByName).toBeCalledTimes(1); + }); + + it('logs an error if any source throws', async () => { + c1.entityByName.mockResolvedValueOnce(e1); + c2.entityByName.mockRejectedValueOnce(new Error('boo')); + const catalog = new CoalescedEntitiesCatalog([c1, c2], logger); + await expect(catalog.entityByName('k', undefined, 'n2')).resolves.toBe( + e1, + ); + expect(c1.entityByName).toBeCalledTimes(1); + expect(c2.entityByName).toBeCalledTimes(1); + expect(mockLogger.warn).toBeCalledWith(expect.stringMatching(/boo/)); + }); + }); +}); diff --git a/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.ts b/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.ts new file mode 100644 index 0000000000..5a0922495e --- /dev/null +++ b/plugins/catalog-backend/src/catalog/CoalescedEntitiesCatalog.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { Logger } from 'winston'; +import { EntityFilters } from '../database'; +import { EntitiesCatalog } from './types'; + +/** + * A simple coalescing catalog wrapper, that acts as a front for collecting + * catalog data from multiple sources. + * + * One possible usage could be to have this as a front to both a + * DatabaseEntitiesCatalog that holds Component kinds, and another company- + * specific catalog that is a thin wrapper on top of LDAP that supplies Group + * and User entities. That way you'll get a coherent view of two very different + * entity sources. + * + * This is mainly meant as a functional example, and you may want to provide + * your own more specialized collector if you have this distinct need. This + * one does not support adding/updating entities through the API for example. + * A more competent implementation may direct the writes to different catalogs + * based on entity kind or similar. + */ +export class CoalescedEntitiesCatalog implements EntitiesCatalog { + private inner: EntitiesCatalog[]; + private logger: Logger; + + constructor(inner: EntitiesCatalog[], logger: Logger) { + this.inner = inner; + this.logger = logger; + } + + async entities(filters?: EntityFilters): Promise { + const ops = this.inner.map(async catalog => { + try { + return await catalog.entities(filters); + } catch (e) { + this.logger.warn(`Inner entities call failed, ${e}`); + return []; + } + }); + + const results = await Promise.all(ops); + return results.flat(); + } + + async entityByUid(uid: string): Promise { + const ops = this.inner.map(async catalog => { + try { + return await catalog.entityByUid(uid); + } catch (e) { + this.logger.warn(`Inner entityByUid call failed, ${e}`); + return undefined; + } + }); + + const results = await Promise.all(ops); + return results.find(Boolean); + } + + async entityByName( + kind: string, + namespace: string | undefined, + name: string, + ): Promise { + const ops = this.inner.map(async catalog => { + try { + return await catalog.entityByName(kind, namespace, name); + } catch (e) { + this.logger.warn(`Inner entityByName call failed, ${e}`); + return undefined; + } + }); + + const results = await Promise.all(ops); + return results.find(Boolean); + } + + addOrUpdateEntity(): Promise { + throw new Error('Method not implemented.'); + } + + removeEntityByUid(): Promise { + throw new Error('Method not implemented.'); + } +} diff --git a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts index 8c7897fa2c..679e5b43c1 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts @@ -39,4 +39,33 @@ describe('DatabaseLocationsCatalog', () => { expect.objectContaining({ data: location }), ]); }); + + it('does not return duplicates of rows because of logs', async () => { + const location1 = { + id: 'dd12620d-0436-422f-93bd-929aa0788123', + type: 'valid_type', + target: 'valid_target1', + }; + const location2 = { + id: '1a89c479-1a33-4f27-8927-6090ba488c42', + type: 'valid_type', + target: 'valid_target2', + }; + await expect(catalog.addLocation(location1)).resolves.toEqual(location1); + await expect(catalog.addLocation(location2)).resolves.toEqual(location2); + await expect( + catalog.logUpdateSuccess(location1.id), + ).resolves.toBeUndefined(); + await expect( + catalog.logUpdateSuccess(location1.id), + ).resolves.toBeUndefined(); + const locations = await catalog.locations(); + expect(locations.length).toBe(2); + expect(locations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ data: location1 }), + expect.objectContaining({ data: location2 }), + ]), + ); + }); }); diff --git a/plugins/catalog-backend/src/database/CommonDatabase.test.ts b/plugins/catalog-backend/src/database/CommonDatabase.test.ts index 3d1455a886..21dde4ca74 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.test.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.test.ts @@ -88,6 +88,31 @@ describe('CommonDatabase', () => { expect(locations).toEqual([output]); const location = await db.location(locations[0].id); expect(location).toEqual(output); + + // If we add 2 new update log events, + // this should not result in location duplication + // due to incorrect join in DB + await db.addLocationUpdateLogEvent( + 'dd12620d-0436-422f-93bd-929aa0788123', + DatabaseLocationUpdateLogStatus.SUCCESS, + ); + + // Have a second in-between + // To avoid having same timestamp on event + await new Promise(res => setTimeout(res, 1000)); + await db.addLocationUpdateLogEvent( + 'dd12620d-0436-422f-93bd-929aa0788123', + DatabaseLocationUpdateLogStatus.FAIL, + ); + + expect(await db.locations()).toEqual([ + { + ...output, + status: DatabaseLocationUpdateLogStatus.FAIL, + timestamp: expect.any(String), + }, + ]); + await db.transaction(tx => db.removeLocation(tx, locations[0].id)); await expect(db.locations()).resolves.toEqual([]); diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index c860f52d1e..0c56ca4370 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -29,7 +29,6 @@ import { } from '@backstage/catalog-model'; import Knex from 'knex'; import lodash from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; import type { Logger } from 'winston'; import { buildEntitySearch } from './search'; import type { @@ -347,7 +346,6 @@ export class CommonDatabase implements Database { return this.database( 'location_update_log', ).insert({ - id: uuidv4(), status, location_id: locationId, entity_name: entityName, diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts index 8ec575d6ec..e5ef410456 100644 --- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts +++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts @@ -27,7 +27,9 @@ import { EntityPolicyProcessor } from './processors/EntityPolicyProcessor'; import { FileReaderProcessor } from './processors/FileReaderProcessor'; import { GithubReaderProcessor } from './processors/GithubReaderProcessor'; import { GithubApiReaderProcessor } from './processors/GithubApiReaderProcessor'; +import { GitlabApiReaderProcessor } from './processors/GitlabApiReaderProcessor'; import { GitlabReaderProcessor } from './processors/GitlabReaderProcessor'; +import { UrlReaderProcessor } from './processors/UrlReaderProcessor'; import { LocationRefProcessor } from './processors/LocationEntityProcessor'; import * as result from './processors/results'; import { @@ -59,7 +61,9 @@ export class LocationReaders implements LocationReader { new FileReaderProcessor(), new GithubReaderProcessor(), new GithubApiReaderProcessor(), + new GitlabApiReaderProcessor(), new GitlabReaderProcessor(), + new UrlReaderProcessor(), new YamlProcessor(), new EntityPolicyProcessor(entityPolicy), new LocationRefProcessor(), diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 07e917b97b..4ebb2edc79 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -24,3 +24,4 @@ export type { ReadLocationError, ReadLocationResult, } from './types'; +export * from './processors'; diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts new file mode 100644 index 0000000000..a429a0e6fb --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { GitlabApiReaderProcessor } from './GitlabApiReaderProcessor'; + +describe('GitlabApiReaderProcessor', () => { + it('should build raw api', () => { + const processor = new GitlabApiReaderProcessor(); + + const tests = [ + { + target: + 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: new URL( + 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml', + url: new URL( + 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup + url: new URL( + 'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml?ref=branch', + ), + err: undefined, + }, + { + target: + 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/', + url: null, + err: + 'Incorrect url: https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/, Error: Gitlab url does not end in .ya?ml', + }, + ]; + + for (const test of tests) { + if (test.err) { + expect(() => processor.buildRawUrl(test.target, 12345)).toThrowError( + test.err, + ); + } else { + expect(processor.buildRawUrl(test.target, 12345)).toEqual(test.url); + } + } + }); + + it('should return request options', () => { + const tests = [ + { + token: '0123456789', + expect: { + headers: { + 'PRIVATE-TOKEN': '0123456789', + }, + }, + }, + { + token: '', + expect: { + headers: { + 'PRIVATE-TOKEN': '', + }, + }, + }, + ]; + + for (const test of tests) { + process.env.GITLAB_PRIVATE_TOKEN = test.token; + const processor = new GitlabApiReaderProcessor(); + expect(processor.getRequestOptions()).toEqual(test.expect); + } + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts new file mode 100644 index 0000000000..6964640eb4 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocationSpec } from '@backstage/catalog-model'; +import fetch, { RequestInit, HeadersInit } from 'node-fetch'; +import * as result from './results'; +import { LocationProcessor, LocationProcessorEmit } from './types'; + +export class GitlabApiReaderProcessor implements LocationProcessor { + private privateToken: string = process.env.GITLAB_PRIVATE_TOKEN || ''; + + getRequestOptions(): RequestInit { + const headers: HeadersInit = { 'PRIVATE-TOKEN': '' }; + if (this.privateToken !== '') { + headers['PRIVATE-TOKEN'] = this.privateToken; + } + + const requestOptions: RequestInit = { + headers, + }; + + return requestOptions; + } + + async readLocation( + location: LocationSpec, + optional: boolean, + emit: LocationProcessorEmit, + ): Promise { + if (location.type !== 'gitlab/api') { + return false; + } + + try { + const projectID = await this.getProjectID(location.target); + const url = this.buildRawUrl(location.target, projectID); + const response = await fetch(url.toString(), this.getRequestOptions()); + if (response.ok) { + const data = await response.buffer(); + emit(result.data(location, data)); + } else { + const message = `${location.target} could not be read as ${url}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + if (!optional) { + emit(result.notFoundError(location, message)); + } + } else { + emit(result.generalError(location, message)); + } + } + } catch (e) { + const message = `Unable to read ${location.type} ${location.target}, ${e}`; + emit(result.generalError(location, message)); + } + return true; + } + + // convert https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + // to https://gitlab.com/api/v4/projects//repository/files/filepath?ref=branch + buildRawUrl(target: string, projectID: Number): URL { + try { + const url = new URL(target); + + const branchAndfilePath = url.pathname.split('/-/blob/')[1]; + + if (!branchAndfilePath.match(/\.ya?ml$/)) { + throw new Error('Gitlab url does not end in .ya?ml'); + } + + const [branch, ...filePath] = branchAndfilePath.split('/'); + + url.pathname = [ + '/api/v4/projects', + projectID, + 'repository/files', + encodeURIComponent(filePath.join('/')), + 'raw', + ].join('/'); + url.search = `?ref=${branch}`; + + return url; + } catch (e) { + throw new Error(`Incorrect url: ${target}, ${e}`); + } + } + + async getProjectID(target: string): Promise { + const url = new URL(target); + + if ( + // absPaths to gitlab files should contain /-/blob + // ex: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + !url.pathname.match(/\/\-\/blob\//) + ) { + throw new Error('Please provide full path to yaml file from Gitlab'); + } + try { + const repo = url.pathname.split('/-/blob/')[0]; + + // Find ProjectID from url + // convert 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath' + // to 'https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo' + const repoIDLookup = new URL( + `${url.protocol + url.hostname}/api/v4/projects/${encodeURIComponent( + repo.replace(/^\//, ''), + )}`, + ); + const response = await fetch( + repoIDLookup.toString(), + this.getRequestOptions(), + ); + const projectIDJson = await response.json(); + const projectID: Number = projectIDJson.id; + + return projectID; + } catch (e) { + throw new Error(`Could not get Gitlab ProjectID for: ${target}, ${e}`); + } + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts new file mode 100644 index 0000000000..e30b320bf4 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UrlReaderProcessor } from './UrlReaderProcessor'; +import { + LocationProcessorDataResult, + LocationProcessorResult, + LocationProcessorErrorResult, +} from './types'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; + +describe('UrlReaderProcessor', () => { + const mockApiOrigin = 'http://localhost:23000'; + const server = setupServer(); + + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + + it('should load from url', async () => { + const processor = new UrlReaderProcessor(); + const spec = { + type: 'url', + target: `${mockApiOrigin}/component.yaml`, + }; + + server.use( + rest.get(`${mockApiOrigin}/component.yaml`, (_, res, ctx) => + res(ctx.body('Hello')), + ), + ); + + const generated = (await new Promise(emit => + processor.readLocation(spec, false, emit), + )) as LocationProcessorDataResult; + + expect(generated.type).toBe('data'); + expect(generated.location).toBe(spec); + expect(generated.data.toString('utf8')).toBe('Hello'); + }); + + it('should fail load from url with error', async () => { + const processor = new UrlReaderProcessor(); + const spec = { + type: 'url', + target: `${mockApiOrigin}/component-notfound.yaml`, + }; + + server.use( + rest.get(`${mockApiOrigin}/component-notfound.yaml`, (_, res, ctx) => { + return res(ctx.status(404)); + }), + ); + + const generated = (await new Promise(emit => + processor.readLocation(spec, false, emit), + )) as LocationProcessorErrorResult; + + expect(generated.type).toBe('error'); + expect(generated.location).toBe(spec); + expect(generated.error.name).toBe('NotFoundError'); + expect(generated.error.message).toBe( + `${mockApiOrigin}/component-notfound.yaml could not be read, 404 Not Found`, + ); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts new file mode 100644 index 0000000000..0c879ea70c --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LocationSpec } from '@backstage/catalog-model'; +import fetch from 'node-fetch'; +import * as result from './results'; +import { LocationProcessor, LocationProcessorEmit } from './types'; + +export class UrlReaderProcessor implements LocationProcessor { + async readLocation( + location: LocationSpec, + optional: boolean, + emit: LocationProcessorEmit, + ): Promise { + if (location.type !== 'url') { + return false; + } + + try { + const response = await fetch(location.target); + + if (response.ok) { + const data = await response.buffer(); + emit(result.data(location, data)); + } else { + const message = `${location.target} could not be read, ${response.status} ${response.statusText}`; + if (response.status === 404) { + if (!optional) { + emit(result.notFoundError(location, message)); + } + } else { + emit(result.generalError(location, message)); + } + } + } catch (e) { + const message = `Unable to read ${location.type} ${location.target}, ${e}`; + emit(result.generalError(location, message)); + } + + return true; + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts new file mode 100644 index 0000000000..ed5dc3bf8e --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as results from './results'; + +export { results }; +export * from './types'; diff --git a/plugins/catalog-backend/src/setupTests.ts b/plugins/catalog-backend/src/setupTests.ts index f7b6ca962d..ba33cf996b 100644 --- a/plugins/catalog-backend/src/setupTests.ts +++ b/plugins/catalog-backend/src/setupTests.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -require('jest-fetch-mock').enableMocks(); - export {}; diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 12a2faf1e1..a0e69a647d 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.14", - "@backstage/plugin-sentry": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/plugin-github-actions": "^0.1.1-alpha.18", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.18", + "@backstage/plugin-sentry": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -34,23 +35,23 @@ "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0", + "react-use": "^15.3.3", "swr": "^0.2.2" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3", "msw": "^0.19.0", "react-test-renderer": "^16.13.1", - "whatwg-fetch": "^3.0.0" + "whatwg-fetch": "^2.0.0" }, "files": [ "dist" diff --git a/plugins/catalog/src/components/CatalogPage/WelcomeBanner.tsx b/plugins/catalog/src/components/CatalogPage/WelcomeBanner.tsx index c7a280d106..677b1f7ec5 100644 --- a/plugins/catalog/src/components/CatalogPage/WelcomeBanner.tsx +++ b/plugins/catalog/src/components/CatalogPage/WelcomeBanner.tsx @@ -42,7 +42,7 @@ export const WelcomeBanner = () => { 🎉 Welcome to Backstage! Take a look around and check out our{' '} - + getting started {' '} page. diff --git a/plugins/catalog/src/components/EntityPage/EntityPage.tsx b/plugins/catalog/src/components/EntityPage/EntityPage.tsx index 1c56924589..c11844f722 100644 --- a/plugins/catalog/src/components/EntityPage/EntityPage.tsx +++ b/plugins/catalog/src/components/EntityPage/EntityPage.tsx @@ -28,6 +28,7 @@ import { useApi, } from '@backstage/core'; import { SentryIssuesWidget } from '@backstage/plugin-sentry'; +import { Widget as GithubActionsWidget } from '@backstage/plugin-github-actions'; import { Grid, Box } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React, { FC, useEffect, useState } from 'react'; @@ -86,7 +87,7 @@ export const EntityPage: FC<{}> = () => { const catalogApi = useApi(catalogApiRef); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); - const { value: entity, error, loading } = useAsync( + const { value: entity, error, loading } = useAsync( () => catalogApi.getEntityByName({ kind, namespace, name }), [catalogApi, kind, namespace, name], ); @@ -192,6 +193,13 @@ export const EntityPage: FC<{}> = () => { statsFor="24h" /> + {entity.metadata?.annotations?.[ + 'backstage.io/github-actions-id' + ] && ( + + + + )} diff --git a/packages/techdocs-cli/src/lib/version.ts b/plugins/catalog/src/components/useEntityCompoundName.ts similarity index 62% rename from packages/techdocs-cli/src/lib/version.ts rename to plugins/catalog/src/components/useEntityCompoundName.ts index 24734b87cc..2be23cb6d9 100644 --- a/packages/techdocs-cli/src/lib/version.ts +++ b/plugins/catalog/src/components/useEntityCompoundName.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useParams } from 'react-router'; -import fs from 'fs-extra'; -import { paths } from './paths'; - -export function findVersion() { - const pkgContent = fs.readFileSync(paths.resolveOwn('package.json'), 'utf8'); - return JSON.parse(pkgContent).version; -} - -export const version = findVersion(); -export const isDev = fs.pathExistsSync(paths.resolveOwn('src')); +/** + * Grabs entity kind and name + optional namespace from location + */ +export const useEntityCompoundName = () => { + const params = useParams(); + const { kind, optionalNamespaceAndName = '' } = params; + const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); + return { kind, name, namespace }; +}; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 701394df34..36fd69971d 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -18,3 +18,4 @@ export { plugin } from './plugin'; export * from './api/CatalogClient'; export * from './api/types'; export * from './routes'; +export { useEntityCompoundName } from './components/useEntityCompoundName'; diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index e5b88929d8..4fb7a28124 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,15 +33,15 @@ "react-lazylog": "^4.5.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "@types/react-lazylog": "^4.5.0", "jest-fetch-mock": "^3.0.3" diff --git a/plugins/explore/package.json b/plugins/explore/package.json index bdb5a28153..e7783675ae 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,24 +21,24 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "classnames": "^2.2.6", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/explore/src/components/ExplorePluginPage.tsx b/plugins/explore/src/components/ExplorePluginPage.tsx index c1d36f2544..1752f402be 100644 --- a/plugins/explore/src/components/ExplorePluginPage.tsx +++ b/plugins/explore/src/components/ExplorePluginPage.tsx @@ -88,6 +88,15 @@ const toolsCards = [ image: 'https://miro.medium.com/max/801/1*R28u8gj-hVdDFISoYqPhrQ.png', tags: ['gitops', 'dev'], }, + { + title: 'Rollbar', + description: + 'Error monitoring and crash reporting for agile development and continuous delivery', + url: '/rollbar', + image: + 'https://images.ctfassets.net/cj4mgtttlyx7/4DfiWj9CbuHBi10uWK7JHn/5e94a6c5dbd5d50bdcd8d9e78f88689b/rollbar-seo.png', + tags: ['rollbar', 'monitoring', 'errors'], + }, ]; const ExplorePluginPage: FC<{}> = () => { diff --git a/plugins/github-actions/README.md b/plugins/github-actions/README.md index b0b339a1b0..24bc96937f 100644 --- a/plugins/github-actions/README.md +++ b/plugins/github-actions/README.md @@ -1,13 +1,76 @@ -# github-actions +# GitHub Actions Plugin -Welcome to the github-actions plugin! +Website: [https://github.com/actions](https://github.com/actions) -_This plugin was created through the Backstage CLI_ +## Screenshots -## Getting started +TBD -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/github-actions](http://localhost:3000/github-actions). +## Setup -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. +### Generic Requirements + +1. Provide OAuth credentials: + 1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `https://localhost:3000/auth/github`. + 2. Take Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` env variables. +2. Annotate your component with a correct GitHub Actions repository and owner: + + The annotation key is `github.com/project-slug`. + + Example: + + ``` + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: backstage + description: backstage.io + annotations: + github.com/project-slug: 'spotify/backstage' + + spec: + type: website + lifecycle: production + owner: guest + ``` + +### Standalone app requirements + +If you didn't clone this repo you have to do some extra work. + +1. Add plugin API to your Backstage instance: + +```bash +yarn add @backstage/plugin-github-actions +``` + +```js +// packages/app/src/api.ts +import { ApiRegistry } from '@backstage/core'; +import { GithubActionsClient, githubActionsApiRef } from '@backstage/plugin-github-actions'; + +const builder = ApiRegistry.builder(); +builder.add(githubActionsApiRef, new GithubActionsClient()); + +export default builder.build() as ApiHolder; +``` + +2. Add plugin itself: + +```js +// packages/app/src/plugins.ts +export { plugin as GithubActions } from '@backstage/plugin-github-actions'; +``` + +3. Run the app with `yarn start` and the backend with `yarn --cwd packages/backend start`, navigate to `/github-actions/`. + +## Features + +- List workflow runs for a project +- Dive into one run to see a job steps +- Retry runs +- Pagination for runs + +## Limitations + +- There is a limit of 100 apps for one OAuth client/token pair diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 9f3cb09537..8fcc4591d2 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -18,30 +18,33 @@ "diff": "backstage-cli plugin:diff", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/core-api": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/core-api": "^0.1.1-alpha.18", + "@backstage/plugin-catalog": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.0.0", "@octokit/types": "^5.0.1", + "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/github-actions/scripts/mock-data.sh b/plugins/github-actions/scripts/mock-data.sh new file mode 100755 index 0000000000..2653a415dd --- /dev/null +++ b/plugins/github-actions/scripts/mock-data.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + + curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/scripts/sample.yaml\"}" + echo diff --git a/plugins/github-actions/scripts/sample.yaml b/plugins/github-actions/scripts/sample.yaml new file mode 100644 index 0000000000..d582bf9c35 --- /dev/null +++ b/plugins/github-actions/scripts/sample.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: backstage.io + annotations: + github.com/project-slug: 'spotify/backstage' +spec: + type: website + lifecycle: production + owner: guest diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index eedf9686f3..acdfb90cf3 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -33,12 +33,14 @@ export type GithubActionsApi = { repo, pageSize, page, + branch, }: { token: string; owner: string; repo: string; pageSize?: number; page?: number; + branch?: string; }) => Promise; getWorkflow: ({ token, @@ -72,5 +74,5 @@ export type GithubActionsApi = { owner: string; repo: string; runId: number; - }) => void; + }) => Promise; }; diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 1606c268f4..72f65075e6 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -23,7 +23,7 @@ import { } from '@octokit/types'; export class GithubActionsClient implements GithubActionsApi { - reRunWorkflow({ + async reRunWorkflow({ token, owner, repo, @@ -33,8 +33,8 @@ export class GithubActionsClient implements GithubActionsApi { owner: string; repo: string; runId: number; - }) { - new Octokit({ auth: token }).actions.reRunWorkflow({ + }): Promise { + return new Octokit({ auth: token }).actions.reRunWorkflow({ owner, repo, run_id: runId, @@ -46,12 +46,14 @@ export class GithubActionsClient implements GithubActionsApi { repo, pageSize = 100, page = 0, + branch, }: { token: string; owner: string; repo: string; pageSize?: number; page?: number; + branch?: string; }): Promise { const workflowRuns = await new Octokit({ auth: token, @@ -60,6 +62,7 @@ export class GithubActionsClient implements GithubActionsApi { repo, per_page: pageSize, page, + ...(branch ? { branch } : {}), }); return workflowRuns.data; } diff --git a/plugins/github-actions/src/api/types.ts b/plugins/github-actions/src/api/types.ts index 8a7b2ca548..cd74a20cda 100644 --- a/plugins/github-actions/src/api/types.ts +++ b/plugins/github-actions/src/api/types.ts @@ -14,211 +14,33 @@ * limitations under the License. */ -export enum BuildStatus { - Null, - Success, - Failure, - Pending, - Running, -} - -export type Build = { - commitId: string; - message: string; - branch: string; - status: BuildStatus; - uri: string; -}; - -export type BuildDetails = { - build: Build; - author: string; - logUrl: string; - overviewUrl: string; -}; - -export interface Author { +export type Step = { name: string; - email: string; -} - -export interface Committer { - name: string; - email: string; -} - -export interface HeadCommit { - id: string; - tree_id: string; - message: string; - timestamp: Date; - author: Author; - committer: Committer; -} - -export interface Owner { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -export interface Repository { - id: number; - node_id: string; - name: string; - full_name: string; - private: boolean; - owner: Owner; - html_url: string; - description?: any; - fork: boolean; - url: string; - forks_url: string; - keys_url: string; - collaborators_url: string; - teams_url: string; - hooks_url: string; - issue_events_url: string; - events_url: string; - assignees_url: string; - branches_url: string; - tags_url: string; - blobs_url: string; - git_tags_url: string; - git_refs_url: string; - trees_url: string; - statuses_url: string; - languages_url: string; - stargazers_url: string; - contributors_url: string; - subscribers_url: string; - subscription_url: string; - commits_url: string; - git_commits_url: string; - comments_url: string; - issue_comment_url: string; - contents_url: string; - compare_url: string; - merges_url: string; - archive_url: string; - downloads_url: string; - issues_url: string; - pulls_url: string; - milestones_url: string; - notifications_url: string; - labels_url: string; - releases_url: string; - deployments_url: string; -} - -export interface Owner2 { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -} - -export interface HeadRepository { - id: number; - node_id: string; - name: string; - full_name: string; - private: boolean; - owner: Owner2; - html_url: string; - description?: any; - fork: boolean; - url: string; - forks_url: string; - keys_url: string; - collaborators_url: string; - teams_url: string; - hooks_url: string; - issue_events_url: string; - events_url: string; - assignees_url: string; - branches_url: string; - tags_url: string; - blobs_url: string; - git_tags_url: string; - git_refs_url: string; - trees_url: string; - statuses_url: string; - languages_url: string; - stargazers_url: string; - contributors_url: string; - subscribers_url: string; - subscription_url: string; - commits_url: string; - git_commits_url: string; - comments_url: string; - issue_comment_url: string; - contents_url: string; - compare_url: string; - merges_url: string; - archive_url: string; - downloads_url: string; - issues_url: string; - pulls_url: string; - milestones_url: string; - notifications_url: string; - labels_url: string; - releases_url: string; - deployments_url: string; -} - -export interface WorkflowRun { - id: number; - node_id: string; - head_branch: string; - head_sha: string; - run_number: number; - event: string; status: string; conclusion: string; - workflow_id: number; - url: string; + number: number; // starts from 1 + started_at: string; + completed_at: string; +}; + +export type Job = { html_url: string; - pull_requests: any[]; - created_at: Date; - updated_at: Date; - jobs_url: string; - logs_url: string; - check_suite_url: string; - artifacts_url: string; - cancel_url: string; - rerun_url: string; - workflow_url: string; - head_commit: HeadCommit; - repository: Repository; - head_repository: HeadRepository; + status: string; + conclusion: string; + started_at: string; + completed_at: string; + name: string; + steps: Step[]; +}; + +export type Jobs = { + total_count: number; + jobs: Job[]; +}; + +export enum BuildStatus { + 'success', + 'failure', + 'pending', + 'running', } diff --git a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx deleted file mode 100644 index 55c568106c..0000000000 --- a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Button, - LinearProgress, - makeStyles, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableRow, - Theme, - Typography, -} from '@material-ui/core'; -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { Link, useApi, githubAuthApiRef } from '@backstage/core'; -import { githubActionsApiRef } from '../../api'; - -const useStyles = makeStyles(theme => ({ - root: { - maxWidth: 720, - margin: theme.spacing(2), - }, - title: { - padding: theme.spacing(1, 0, 2, 0), - }, - table: { - padding: theme.spacing(1), - }, -})); - -export const BuildDetailsPage = () => { - const repo = 'try-ssr'; - const owner = 'CircleCITest3'; - const api = useApi(githubActionsApiRef); - const auth = useApi(githubAuthApiRef); - - const classes = useStyles(); - const { id } = useParams(); - const status = useAsync(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - return api - .getWorkflowRun({ - token, - owner, - repo, - id: parseInt(id, 10), - }) - .then(data => { - return data; - }); - }, [location.search]); - - if (status.loading) { - return ; - } else if (status.error) { - return ( - - Failed to load build, {status.error.message} - - ); - } - - const details = status.value; - - return ( -
- - - - < - - - Build Details - - - - - - - Branch - - {details?.head_branch} - - - - Message - - {details?.head_commit.message} - - - - Commit ID - - {details?.head_commit.id} - - - - Status - - {details?.status} - - - - Author - - {`${details?.head_commit.author.name} (${details?.head_commit.author.email})`} - - - - Links - - - {details?.html_url && ( - - )} - - - -
-
-
- ); -}; diff --git a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx deleted file mode 100644 index acc1b496ec..0000000000 --- a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - LinearProgress, - makeStyles, - Table, - TableBody, - TableCell, - TableRow, - Theme, - Typography, -} from '@material-ui/core'; -import React from 'react'; -import { useAsync } from 'react-use'; -import { BuildStatusIndicator } from '../BuildStatusIndicator'; -import { githubActionsApiRef, BuildStatus } from '../../api'; -import { Link, useApi, githubAuthApiRef } from '@backstage/core'; - -const useStyles = makeStyles(theme => ({ - root: { - // height: 400, - }, - title: { - paddingBottom: theme.spacing(1), - }, -})); - -const BuildInfoCardContent = () => { - const api = useApi(githubActionsApiRef); - const auth = useApi(githubAuthApiRef); - - const status = useAsync(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - return api.listWorkflowRuns({ token, owner: 'spotify', repo: 'backstage' }); - }); - - if (status.loading) { - return ; - } else if (status.error) { - return ( - - Failed to load builds, {status.error.message} - - ); - } - - // const [build] = - // status.value?.filter(({ branch }) => branch === 'master') ?? []; - - return ( - - - - - Message - - - - build message - - - - - - Commit ID - - build commit id - - - - Status - - - - - - -
- ); -}; - -export const BuildInfoCard = () => { - const classes = useStyles(); - - return ( -
- - Master Build - - -
- ); -}; diff --git a/plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx b/plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx deleted file mode 100644 index 1a198f4df2..0000000000 --- a/plugins/github-actions/src/components/BuildStatusIndicator/BuildStatusIndicator.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IconComponent } from '@backstage/core'; -import { makeStyles, Theme } from '@material-ui/core'; -import ProgressIcon from '@material-ui/icons/Autorenew'; -import SuccessIcon from '@material-ui/icons/CheckCircle'; -import FailureIcon from '@material-ui/icons/Error'; -import UnknownIcon from '@material-ui/icons/Help'; -import React from 'react'; -import { BuildStatus } from '../../api/types'; - -type Props = { - status?: BuildStatus; -}; - -type StatusStyle = { - icon: IconComponent; - color: string; -}; - -const styles: { [key in BuildStatus]: StatusStyle } = { - [BuildStatus.Null]: { - icon: UnknownIcon, - color: '#f49b20', - }, - [BuildStatus.Success]: { - icon: SuccessIcon, - color: '#1db855', - }, - [BuildStatus.Failure]: { - icon: FailureIcon, - color: '#CA001B', - }, - [BuildStatus.Pending]: { - icon: UnknownIcon, - color: '#5BC0DE', - }, - [BuildStatus.Running]: { - icon: ProgressIcon, - color: '#BEBEBE', - }, -}; - -const useStyles = makeStyles({ - icon: style => ({ - color: style.color, - }), -}); - -export const BuildStatusIndicator = ({ status }: Props) => { - const style = (status && styles[status]) || styles[BuildStatus.Null]; - const classes = useStyles(style); - const Icon = style.icon; - - return ( -
- -
- ); -}; diff --git a/plugins/github-actions/src/components/Widget/Widget.tsx b/plugins/github-actions/src/components/Widget/Widget.tsx new file mode 100644 index 0000000000..031364c8ce --- /dev/null +++ b/plugins/github-actions/src/components/Widget/Widget.tsx @@ -0,0 +1,110 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useEffect } from 'react'; +import { useWorkflowRuns } from '../useWorkflowRuns'; +import { WorkflowRun } from '../WorkflowRunsTable'; +import { Entity } from '@backstage/catalog-model'; +import { WorkflowRunStatus } from '../WorkflowRunStatus'; +import { + Link, + Theme, + makeStyles, + LinearProgress, + Typography, +} from '@material-ui/core'; +import { + InfoCard, + StructuredMetadataTable, + errorApiRef, + useApi, +} from '@backstage/core'; +import ExternalLinkIcon from '@material-ui/icons/Launch'; + +const useStyles = makeStyles({ + externalLinkIcon: { + fontSize: 'inherit', + verticalAlign: 'bottom', + }, +}); + +const WidgetContent = ({ + error, + loading, + lastRun, + branch, +}: { + error?: Error; + loading?: boolean; + lastRun: WorkflowRun; + branch: string; +}) => { + const classes = useStyles(); + if (error) return Couldn't fetch latest {branch} run; + if (loading) return ; + return ( + + + + ), + message: lastRun.message, + url: ( + + See more on GitHub{' '} + + + ), + }} + /> + ); +}; + +export const Widget = ({ + entity, + branch = 'master', +}: { + entity: Entity; + branch: string; +}) => { + const errorApi = useApi(errorApiRef); + const [owner, repo] = ( + entity?.metadata.annotations?.['backstage.io/github-actions-id'] ?? '/' + ).split('/'); + const [{ runs, loading, error }] = useWorkflowRuns({ + owner, + repo, + branch, + }); + const lastRun = runs?.[0] ?? ({} as WorkflowRun); + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + + return ( + + + + ); +}; diff --git a/plugins/github-actions/src/components/BuildInfoCard/index.ts b/plugins/github-actions/src/components/Widget/index.ts similarity index 92% rename from plugins/github-actions/src/components/BuildInfoCard/index.ts rename to plugins/github-actions/src/components/Widget/index.ts index a6409e682a..2b34671ab5 100644 --- a/plugins/github-actions/src/components/BuildInfoCard/index.ts +++ b/plugins/github-actions/src/components/Widget/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { BuildInfoCard } from './BuildInfoCard'; +export { Widget } from './Widget'; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx new file mode 100644 index 0000000000..8c445ca9f6 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -0,0 +1,236 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { useEntityCompoundName } from '@backstage/plugin-catalog'; +import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; +import { useWorkflowRunJobs } from './useWorkflowRunJobs'; +import { useProjectName } from '../useProjectName'; +import { + makeStyles, + Box, + TableRow, + TableCell, + ListItemText, + ExpansionPanel, + ExpansionPanelSummary, + Typography, + ExpansionPanelDetails, + TableContainer, + Table, + Paper, + TableBody, + LinearProgress, + CircularProgress, + Theme, + Link, +} from '@material-ui/core'; +import { Jobs, Job, Step } from '../../api'; +import moment from 'moment'; +import { WorkflowRunStatus } from '../WorkflowRunStatus'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ExternalLinkIcon from '@material-ui/icons/Launch'; + +const useStyles = makeStyles(theme => ({ + root: { + maxWidth: 720, + margin: theme.spacing(2), + }, + title: { + padding: theme.spacing(1, 0, 2, 0), + }, + table: { + padding: theme.spacing(1), + }, + expansionPanelDetails: { + padding: 0, + }, + button: { + order: -1, + marginRight: 0, + marginLeft: '-20px', + }, + externalLinkIcon: { + fontSize: 'inherit', + verticalAlign: 'bottom', + }, +})); + +const JobsList = ({ jobs }: { jobs?: Jobs }) => { + const classes = useStyles(); + return ( + + {jobs && + jobs.total_count > 0 && + jobs.jobs.map((job: Job) => ( + + ))} + + ); +}; + +const getElapsedTime = (start: string, end: string) => { + const diff = moment(moment(end || moment()).diff(moment(start))); + const timeElapsed = diff.format('m [minutes] s [seconds]'); + return timeElapsed; +}; + +const StepView = ({ step }: { step: Step }) => { + return ( + + + + + + + + + ); +}; + +const JobListItem = ({ job, className }: { job: Job; className: string }) => { + const classes = useStyles(); + return ( + + } + aria-controls={`panel-${name}-content`} + id={`panel-${name}-header`} + IconButtonProps={{ + className: classes.button, + }} + > + + {job.name} ({getElapsedTime(job.started_at, job.completed_at)}) + + + + + + {job.steps.map((step: Step) => ( + + ))} +
+
+
+
+ ); +}; + +export const WorkflowRunDetails = () => { + let entityCompoundName = useEntityCompoundName(); + if (!entityCompoundName.name) { + // TODO(shmidt-i): remove when is fully integrated + // into the entity view + entityCompoundName = { + kind: 'Component', + name: 'backstage', + namespace: 'default', + }; + } + const projectName = useProjectName(entityCompoundName); + + const [owner, repo] = projectName.value ? projectName.value.split('/') : []; + const details = useWorkflowRunsDetails(repo, owner); + const jobs = useWorkflowRunJobs(details.value?.jobs_url); + + const error = projectName.error || (projectName.value && details.error); + const classes = useStyles(); + if (error) { + return ( + + Failed to load build, {error.message} + + ); + } else if (projectName.loading || details.loading) { + return ; + } + return ( +
+ + + + + + Branch + + {details.value?.head_branch} + + + + Message + + {details.value?.head_commit.message} + + + + Commit ID + + {details.value?.head_commit.id} + + + + Status + + + + + + + + Author + + {`${details.value?.head_commit.author.name} (${details.value?.head_commit.author.email})`} + + + + Links + + + {details.value?.html_url && ( + + Workflow runs on GitHub{' '} + + + )} + + + + + Jobs + {jobs.loading ? ( + + ) : ( + + )} + + + +
+
+
+ ); +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/index.ts b/plugins/github-actions/src/components/WorkflowRunDetails/index.ts new file mode 100644 index 0000000000..2886a26740 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetails/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { WorkflowRunDetails } from './WorkflowRunDetails'; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts new file mode 100644 index 0000000000..f471ce1875 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useAsync } from 'react-use'; +import { Jobs } from '../../api/types'; + +export const useWorkflowRunJobs = (jobsUrl?: string) => { + const jobs = useAsync(async (): Promise => { + if (jobsUrl === undefined) { + return { + total_count: 0, + jobs: [], + }; + } + + const data = await fetch(jobsUrl).then(d => d.json()); + return data; + }, [jobsUrl]); + return jobs; +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts new file mode 100644 index 0000000000..b38284d52f --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { useApi, githubAuthApiRef } from '@backstage/core'; +import { useParams } from 'react-router-dom'; +import { useAsync } from 'react-use'; +import { githubActionsApiRef } from '../../api'; + +export const useWorkflowRunsDetails = (repo: string, owner: string) => { + const api = useApi(githubActionsApiRef); + const auth = useApi(githubAuthApiRef); + const { id } = useParams(); + const details = useAsync(async () => { + const token = await auth.getAccessToken(['repo']); + return repo && owner + ? api.getWorkflowRun({ + token, + owner, + repo, + id: parseInt(id, 10), + }) + : Promise.reject('No repo/owner provided'); + }, [repo, owner, id]); + return details; +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx new file mode 100644 index 0000000000..ec9a3f484d --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Typography, Grid, Breadcrumbs } from '@material-ui/core'; + +import React from 'react'; +import { + Link, + Page, + Header, + HeaderLabel, + Content, + ContentHeader, + SupportButton, + pageTheme, +} from '@backstage/core'; + +import { WorkflowRunDetails } from '../WorkflowRunDetails'; + +/** + * A component for Jobs visualization. Jobs are a property of a Workflow Run. + */ +export const WorkflowRunDetailsPage = () => { + return ( + +
+ + +
+ + + + This plugin allows you to view and interact with your builds within + the GitHub Actions environment. + + + + Workflow runs + Workflow run details + + + + + + + +
+ ); +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts b/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts new file mode 100644 index 0000000000..443bcacc88 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { WorkflowRunDetailsPage } from './WorkflowRunDetailsPage'; diff --git a/plugins/github-actions/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx b/plugins/github-actions/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx new file mode 100644 index 0000000000..089c319802 --- /dev/null +++ b/plugins/github-actions/src/components/WorkflowRunStatus/WorkflowRunStatus.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { StatusPending, StatusRunning, StatusOK } from '@backstage/core'; +import React from 'react'; + +export const WorkflowRunStatus = ({ + status, +}: { + status: string | undefined; +}) => { + if (status === undefined) return null; + switch (status.toLowerCase()) { + case 'queued': + return ( + <> + Queued + + ); + case 'in_progress': + return ( + <> + In progress + + ); + case 'completed': + return ( + <> + Completed + + ); + default: + return ( + <> + Pending + + ); + } +}; diff --git a/plugins/github-actions/src/components/BuildStatusIndicator/index.ts b/plugins/github-actions/src/components/WorkflowRunStatus/index.ts similarity index 90% rename from plugins/github-actions/src/components/BuildStatusIndicator/index.ts rename to plugins/github-actions/src/components/WorkflowRunStatus/index.ts index 520de525ec..8ebca32cbd 100644 --- a/plugins/github-actions/src/components/BuildStatusIndicator/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunStatus/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { BuildStatusIndicator } from './BuildStatusIndicator'; +export { WorkflowRunStatus } from './WorkflowRunStatus'; diff --git a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx similarity index 84% rename from plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx rename to plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx index 172b3611bf..b75a70f326 100644 --- a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx @@ -26,20 +26,20 @@ import { import { Grid } from '@material-ui/core'; import React from 'react'; -import { BuildListTable } from '../BuildListTable'; +import { WorkflowRunsTable } from '../WorkflowRunsTable'; -export const BuildListPage = () => { +export const WorkflowRunsPage = () => { return (
- + This plugin allows you to view and interact with your builds within the GitHub Actions environment. @@ -47,7 +47,7 @@ export const BuildListPage = () => { - + diff --git a/plugins/github-actions/src/components/BuildDetailsPage/index.ts b/plugins/github-actions/src/components/WorkflowRunsPage/index.ts similarity index 91% rename from plugins/github-actions/src/components/BuildDetailsPage/index.ts rename to plugins/github-actions/src/components/WorkflowRunsPage/index.ts index d59d0fc396..ef511763f7 100644 --- a/plugins/github-actions/src/components/BuildDetailsPage/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunsPage/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { BuildDetailsPage } from './BuildDetailsPage'; +export { WorkflowRunsPage } from './WorkflowRunsPage'; diff --git a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx similarity index 53% rename from plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx rename to plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index b5fd4636f5..a2a6ead9b5 100644 --- a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -14,25 +14,23 @@ * limitations under the License. */ import React, { FC } from 'react'; -import { Link, Typography, Box, IconButton } from '@material-ui/core'; +import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GitHubIcon from '@material-ui/icons/GitHub'; -import { Link as RouterLink } from 'react-router-dom'; -import { - StatusError, - StatusWarning, - StatusOK, - StatusPending, - StatusRunning, - Table, - TableColumn, -} from '@backstage/core'; -import { useBuilds } from './useBuilds'; +import { Link as RouterLink, generatePath } from 'react-router-dom'; +import { Table, TableColumn } from '@backstage/core'; +import { useWorkflowRuns } from '../useWorkflowRuns'; +import { WorkflowRunStatus } from '../WorkflowRunStatus'; +import SyncIcon from '@material-ui/icons/Sync'; +import { buildRouteRef } from '../../plugin'; +import { useEntityCompoundName } from '@backstage/plugin-catalog'; +import { useProjectName } from '../useProjectName'; -export type Build = { +export type WorkflowRun = { id: string; - buildName: string; - buildUrl?: string; + message: string; + url?: string; + githubUrl?: string; source: { branchName: string; commit: { @@ -41,25 +39,7 @@ export type Build = { }; }; status: string; - onRestartClick: () => void; -}; - -// retried, canceled, infrastructure_fail, timedout, not_run, running, failed, queued, scheduled, not_running, no_tests, fixed, success -const getStatusComponent = (status: string | undefined = '') => { - switch (status.toLowerCase()) { - case 'queued': - case 'scheduled': - return ; - case 'running': - return ; - case 'failed': - return ; - case 'success': - return ; - case 'canceled': - default: - return ; - } + onReRunClick: () => void; }; const generatedColumns: TableColumn[] = [ @@ -70,40 +50,45 @@ const generatedColumns: TableColumn[] = [ width: '150px', }, { - title: 'Build', - field: 'buildName', + title: 'Message', + field: 'message', highlight: true, - render: (row: Partial) => ( - - {row.buildName} + render: (row: Partial) => ( + + {row.message} ), }, { title: 'Source', - render: (row: Partial) => ( - <> + render: (row: Partial) => ( +

{row.source?.branchName}

{row.source?.commit.hash}

- +
), }, { title: 'Status', - render: (row: Partial) => ( + width: '150px', + + render: (row: Partial) => ( - {getStatusComponent(row.status)} - - {row.status} + ), }, { title: 'Actions', - render: (row: Partial) => ( - - - + render: (row: Partial) => ( + + + + + ), width: '10%', }, @@ -112,7 +97,7 @@ const generatedColumns: TableColumn[] = [ type Props = { loading: boolean; retry: () => void; - builds?: Build[]; + runs?: WorkflowRun[]; projectName: string; page: number; onChangePage: (page: number) => void; @@ -121,13 +106,13 @@ type Props = { onChangePageSize: (pageSize: number) => void; }; -const BuildListTableView: FC = ({ +export const WorkflowRunsTableView: FC = ({ projectName, loading, pageSize, page, retry, - builds, + runs, onChangePage, onChangePageSize, total, @@ -140,13 +125,13 @@ const BuildListTableView: FC = ({ page={page} actions={[ { - icon: () => , - tooltip: 'Refresh Data', + icon: () => , + tooltip: 'Reload workflow runs', isFreeAction: true, onClick: () => retry(), }, ]} - data={builds ?? []} + data={runs ?? []} onChangePage={onChangePage} onChangeRowsPerPage={onChangePageSize} title={ @@ -161,20 +146,29 @@ const BuildListTableView: FC = ({ ); }; -export const BuildListTable = ({ - repo, - owner, -}: { - repo: string; - owner: string; -}) => { - const [tableProps, { retry, setPage, setPageSize }] = useBuilds({ - repo, +export const WorkflowRunsTable = () => { + let entityCompoundName = useEntityCompoundName(); + + if (!entityCompoundName.name) { + // TODO(shmidt-i): remove when is fully integrated + // into the entity view + entityCompoundName = { + kind: 'Component', + name: 'backstage', + namespace: 'default', + }; + } + + const { value: projectName, loading } = useProjectName(entityCompoundName); + const [owner, repo] = (projectName ?? '/').split('/'); + const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({ owner, + repo, }); return ( - { + const catalogApi = useApi(catalogApiRef); + + const { value, loading, error } = useAsync(async () => { + const entity = await catalogApi.getEntityByName(name); + return entity?.metadata.annotations?.['github.com/project-slug'] ?? ''; + }); + return { value, loading, error }; +}; diff --git a/plugins/github-actions/src/components/BuildListTable/useBuilds.ts b/plugins/github-actions/src/components/useWorkflowRuns.ts similarity index 53% rename from plugins/github-actions/src/components/BuildListTable/useBuilds.ts rename to plugins/github-actions/src/components/useWorkflowRuns.ts index 7b93e178d6..f935d57810 100644 --- a/plugins/github-actions/src/components/BuildListTable/useBuilds.ts +++ b/plugins/github-actions/src/components/useWorkflowRuns.ts @@ -15,42 +15,64 @@ */ import { useState } from 'react'; import { useAsyncRetry } from 'react-use'; -import { Build } from './BuildListTable'; -import { githubActionsApiRef } from '../../api/GithubActionsApi'; -import { useApi, githubAuthApiRef } from '@backstage/core'; +import { WorkflowRun } from './WorkflowRunsTable/WorkflowRunsTable'; +import { githubActionsApiRef } from '../api/GithubActionsApi'; +import { useApi, githubAuthApiRef, errorApiRef } from '@backstage/core'; import { ActionsListWorkflowRunsForRepoResponseData } from '@octokit/types'; -export function useBuilds({ repo, owner }: { repo: string; owner: string }) { +export function useWorkflowRuns({ + owner, + repo, + branch, +}: { + owner: string; + repo: string; + branch?: string; +}) { const api = useApi(githubActionsApiRef); const auth = useApi(githubAuthApiRef); + const errorApi = useApi(errorApiRef); + const [total, setTotal] = useState(0); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(5); - const restartBuild = async () => {}; - - const { loading, value: builds, retry } = useAsyncRetry(async () => { - const token = await auth.getAccessToken(['repo', 'user']); - + const { loading, value: runs, retry, error } = useAsyncRetry< + WorkflowRun[] + >(async () => { + const token = await auth.getAccessToken(['repo']); return ( api // GitHub API pagination count starts from 1 - .listWorkflowRuns({ token, owner, repo, pageSize, page: page + 1 }) + .listWorkflowRuns({ + token, + owner, + repo, + pageSize, + page: page + 1, + branch, + }) .then( - (allBuilds: ActionsListWorkflowRunsForRepoResponseData): Build[] => { - setTotal(allBuilds.total_count); + ( + workflowRunsData: ActionsListWorkflowRunsForRepoResponseData, + ): WorkflowRun[] => { + setTotal(workflowRunsData.total_count); // Transformation here - return allBuilds.workflow_runs.map(run => ({ - buildName: run.head_commit.message, + return workflowRunsData.workflow_runs.map(run => ({ + message: run.head_commit.message, id: `${run.id}`, - onRestartClick: () => { - api.reRunWorkflow({ - token, - owner, - repo, - runId: run.id, - }); + onReRunClick: async () => { + try { + await api.reRunWorkflow({ + token, + owner, + repo, + runId: run.id, + }); + } catch (e) { + errorApi.post(e); + } }, source: { branchName: run.head_branch, @@ -63,28 +85,28 @@ export function useBuilds({ repo, owner }: { repo: string; owner: string }) { }, }, status: run.status, - buildUrl: run.url, + url: run.url, + githubUrl: run.html_url, })); }, ) ); - }, [page, pageSize]); + }, [page, pageSize, repo, owner]); - const projectName = `${owner}/${repo}`; return [ { page, pageSize, loading, - builds, - projectName, + runs, + projectName: `${owner}/${repo}`, total, + error, }, { - builds, + runs, setPage, setPageSize, - restartBuild, retry, }, ] as const; diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index d67bc6a864..4a69c363cd 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -16,3 +16,4 @@ export { plugin } from './plugin'; export * from './api'; +export { Widget } from './components/Widget'; diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index b728296cbc..dbc366bd71 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -15,23 +15,28 @@ */ import { createPlugin, createRouteRef } from '@backstage/core'; -import { BuildDetailsPage } from './components/BuildDetailsPage'; -import { BuildListPage } from './components/BuildListPage'; +import { WorkflowRunDetailsPage } from './components/WorkflowRunDetailsPage'; +import { WorkflowRunsPage } from './components/WorkflowRunsPage'; // TODO(freben): This is just a demo route for now export const rootRouteRef = createRouteRef({ path: '/github-actions', title: 'GitHub Actions', }); +export const projectRouteRef = createRouteRef({ + path: '/github-actions/:kind/:optionalNamespaceAndName/', + title: 'GitHub Actions for project', +}); export const buildRouteRef = createRouteRef({ - path: '/github-actions/build/:id', - title: 'GitHub Actions Build', + path: '/github-actions/workflow-run/:id', + title: 'GitHub Actions Workflow Run', }); export const plugin = createPlugin({ id: 'github-actions', register({ router }) { - router.addRoute(rootRouteRef, BuildListPage); - router.addRoute(buildRouteRef, BuildDetailsPage); + router.addRoute(rootRouteRef, WorkflowRunsPage); + router.addRoute(projectRouteRef, WorkflowRunsPage); + router.addRoute(buildRouteRef, WorkflowRunDetailsPage); }, }); diff --git a/plugins/gitops-profiles/README.md b/plugins/gitops-profiles/README.md index d083c97b84..472ea3cc4e 100644 --- a/plugins/gitops-profiles/README.md +++ b/plugins/gitops-profiles/README.md @@ -11,7 +11,7 @@ Your plugin has been added to the example app in this repository, meaning you'll You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. ## Use GitOps-API backend with Backstage diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 33e7c7c60c..5815386014 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gitops-profiles", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,23 +21,23 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx index 44578560e6..baab018415 100644 --- a/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx +++ b/plugins/gitops-profiles/src/components/ClusterList/ClusterList.tsx @@ -31,7 +31,7 @@ import { import ClusterTable from '../ClusterTable/ClusterTable'; import { Button } from '@material-ui/core'; import { useAsync } from 'react-use'; -import { gitOpsApiRef, ListClusterStatusesResponse } from '../../api'; +import { gitOpsApiRef } from '../../api'; import { Alert } from '@material-ui/lab'; const ClusterList: FC<{}> = () => { @@ -39,19 +39,17 @@ const ClusterList: FC<{}> = () => { const githubAuth = useApi(githubAuthApiRef); const [githubUsername, setGithubUsername] = useState(String); - const { loading, error, value } = useAsync( - async () => { - const accessToken = await githubAuth.getAccessToken(['repo', 'user']); - if (!githubUsername) { - const userInfo = await api.fetchUserInfo({ accessToken }); - setGithubUsername(userInfo.login); - } - return api.listClusters({ - gitHubToken: accessToken, - gitHubUser: githubUsername, - }); - }, - ); + const { loading, error, value } = useAsync(async () => { + const accessToken = await githubAuth.getAccessToken(['repo', 'user']); + if (!githubUsername) { + const userInfo = await api.fetchUserInfo({ accessToken }); + setGithubUsername(userInfo.login); + } + return api.listClusters({ + gitHubToken: accessToken, + gitHubUser: githubUsername, + }); + }); let content: JSX.Element; if (loading) { content = ( diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index 3193bbea06..a5125fb006 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -201,7 +201,7 @@ const ProfileCatalog: FC<{}> = () => { setRunStatus([]); const cloneResponse = await api.cloneClusterFromTemplate({ - templateRepository: templateRepo, + templateRepository: templateRepo!, gitHubToken: githubAccessToken, gitHubUser: githubUsername, targetOrg: gitHubOrg, @@ -224,7 +224,7 @@ const ProfileCatalog: FC<{}> = () => { gitHubUser: githubUsername, targetOrg: gitHubOrg, targetRepo: gitHubRepo, - profiles: gitopsProfiles, + profiles: gitopsProfiles!, }); if (applyProfileResp.error === undefined) { diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index e2c5caca22..a034c14a11 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "private": false, "publishConfig": { "access": "public", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -40,17 +40,17 @@ "graphql": "15.1.0", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", "@types/codemirror": "^0.0.96", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3", "react-router-dom": "6.0.0-beta.0" diff --git a/plugins/graphql/.eslintrc.js b/plugins/graphql/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/graphql/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/graphql/README.md b/plugins/graphql/README.md new file mode 100644 index 0000000000..ae58396880 --- /dev/null +++ b/plugins/graphql/README.md @@ -0,0 +1,28 @@ +# GraphQL Backend + +## Getting Started + +This backend plugin can be started in a standalone mode from directly in this package +with `yarn start`. However, it will have limited functionality and that process is +most convenient when developing the plugin itself. + +To run it within the backend do: + +1. Register the router in `packages/backend/src/index.ts`: + +```ts +const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); + +const service = createServiceBuilder(module) + .loadConfig(configReader) + /** several different routers */ + .addRouter('/graphql', await graphql(graphqlEnv)); +``` + +2. Start the backend + +```bash +yarn workspace example-backend start +``` + +This will launch the full example backend. diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json new file mode 100644 index 0000000000..7a32883a44 --- /dev/null +++ b/plugins/graphql/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/plugin-graphql-backend", + "version": "0.1.1-alpha.18", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.18", + "@types/express": "^4.17.6", + "apollo-server": "^2.16.0", + "apollo-server-express": "^2.16.0", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "graphql": "^15.3.0", + "whatwg-fetch": "^2.0.0", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.18", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist", + "schema.gql" + ] +} diff --git a/plugins/graphql/schema.gql b/plugins/graphql/schema.gql new file mode 100644 index 0000000000..5568182f7f --- /dev/null +++ b/plugins/graphql/schema.gql @@ -0,0 +1,11 @@ +type CatalogEntity { + id: String +} + +type CatalogQuery { + list: [CatalogEntity!]! +} + +type Query { + catalog: CatalogQuery! +} diff --git a/plugins/graphql/scripts/mock-data.sh b/plugins/graphql/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/graphql/scripts/mock-data.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/graphql/src/index.ts b/plugins/graphql/src/index.ts new file mode 100644 index 0000000000..f12e894c84 --- /dev/null +++ b/plugins/graphql/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('whatwg-fetch'); + +export * from './service/router'; diff --git a/plugins/graphql/src/run.ts b/plugins/graphql/src/run.ts new file mode 100644 index 0000000000..b96989e4b8 --- /dev/null +++ b/plugins/graphql/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/graphql/src/service/router.test.ts b/plugins/graphql/src/service/router.test.ts new file mode 100644 index 0000000000..30bcb8e25d --- /dev/null +++ b/plugins/graphql/src/service/router.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import './router'; + +describe('Router', () => { + it('should pass the test', () => { + expect(true).toBeTruthy(); + }); +}); diff --git a/plugins/graphql/src/service/router.ts b/plugins/graphql/src/service/router.ts new file mode 100644 index 0000000000..69e2394f11 --- /dev/null +++ b/plugins/graphql/src/service/router.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import fs from 'fs'; +import path from 'path'; +import { ApolloServer } from 'apollo-server-express'; + +const schemaPath = path.resolve( + require.resolve('@backstage/plugin-graphql-backend/package.json'), + '../schema.gql', +); + +export interface RouterOptions { + logger: Logger; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const typeDefs = await fs.promises.readFile(schemaPath, 'utf-8'); + + const server = new ApolloServer({ typeDefs, logger: options.logger }); + const router = Router(); + + const apolloMiddlware = server.getMiddleware({ path: '/' }); + router.use(apolloMiddlware); + + router.get('/health', (_, response) => { + response.send({ status: 'ok' }); + }); + + router.use(errorHandler()); + + return router; +} diff --git a/plugins/graphql/src/service/standaloneServer.ts b/plugins/graphql/src/service/standaloneServer.ts new file mode 100644 index 0000000000..0f772a583f --- /dev/null +++ b/plugins/graphql/src/service/standaloneServer.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createServiceBuilder } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'graphql-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + }); + + const service = createServiceBuilder(module) + .enableCors({ origin: 'http://localhost:3000' }) + .addRouter('/graphql', router); + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/github-actions/src/components/BuildListPage/index.ts b/plugins/graphql/src/setupTests.ts similarity index 92% rename from plugins/github-actions/src/components/BuildListPage/index.ts rename to plugins/graphql/src/setupTests.ts index 2134913e51..ac41ef995a 100644 --- a/plugins/github-actions/src/components/BuildListPage/index.ts +++ b/plugins/graphql/src/setupTests.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +require('whatwg-fetch'); -export { BuildListPage } from './BuildListPage'; +export {}; diff --git a/plugins/identity-backend/package.json b/plugins/identity-backend/package.json index 2467663e48..c5c3bffc9b 100644 --- a/plugins/identity-backend/package.json +++ b/plugins/identity-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-identity-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,20 +20,20 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", - "helmet": "^3.22.0", + "helmet": "^4.0.0", "morgan": "^1.10.0", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "jest-fetch-mock": "^3.0.3" }, "files": [ diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 5f1ca9b170..cbf4a77f7e 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -30,16 +30,16 @@ "react-dom": "^16.13.1", "react-markdown": "^4.3.1", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx index 09ea882134..c83ce9f641 100644 --- a/plugins/lighthouse/src/components/AuditList/index.tsx +++ b/plugins/lighthouse/src/components/AuditList/index.tsx @@ -32,7 +32,7 @@ import { useApi, } from '@backstage/core'; -import { lighthouseApiRef, WebsiteListResponse } from '../../api'; +import { lighthouseApiRef } from '../../api'; import { useQuery } from '../../utils'; import LighthouseSupportButton from '../SupportButton'; import LighthouseIntro, { LIGHTHOUSE_INTRO_LOCAL_STORAGE } from '../Intro'; @@ -50,7 +50,7 @@ const AuditList: FC<{}> = () => { : 1; const lighthouseApi = useApi(lighthouseApiRef); - const { value, loading, error } = useAsync( + const { value, loading, error } = useAsync( async () => await lighthouseApi.getWebsiteList({ limit: LIMIT, diff --git a/plugins/lighthouse/src/components/AuditView/index.tsx b/plugins/lighthouse/src/components/AuditView/index.tsx index d323b1a005..12606482bf 100644 --- a/plugins/lighthouse/src/components/AuditView/index.tsx +++ b/plugins/lighthouse/src/components/AuditView/index.tsx @@ -117,7 +117,7 @@ const ConnectedAuditView: FC<{}> = () => { const params = useParams() as { id: string }; const classes = useStyles(); - const { loading, error, value: nextValue } = useAsync( + const { loading, error, value: nextValue } = useAsync( async () => await lighthouseApi.getWebsiteForAuditId(params.id), [params.id], ); diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 26fca94376..023fc81cf2 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,9 +19,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -35,7 +34,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 699ed9b99d..085647bc4e 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -15,14 +15,14 @@ */ import { createRouter } from './router'; -import winston from 'winston'; +import * as winston from 'winston'; import { ConfigReader } from '@backstage/config'; -import { loadConfig } from '@backstage/config-loader'; +import { loadBackendConfig } from '@backstage/backend-common'; describe('createRouter', () => { it('works', async () => { const logger = winston.createLogger(); - const config = ConfigReader.fromConfigs(await loadConfig()); + const config = ConfigReader.fromConfigs(await loadBackendConfig()); const router = await createRouter({ config, logger, diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 2c8af574bd..b71299ef7e 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -31,7 +31,7 @@ export async function createRouter( const router = Router(); const proxyConfig = options.config.get('proxy') ?? {}; Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { - router.use(createProxyMiddleware(route, proxyRouteConfig)); + router.use(route, createProxyMiddleware(proxyRouteConfig)); }); return router; diff --git a/plugins/proxy-backend/src/service/standaloneServer.ts b/plugins/proxy-backend/src/service/standaloneServer.ts index 84c803eeac..f75c044f0f 100644 --- a/plugins/proxy-backend/src/service/standaloneServer.ts +++ b/plugins/proxy-backend/src/service/standaloneServer.ts @@ -14,12 +14,14 @@ * limitations under the License. */ -import { createServiceBuilder } from '@backstage/backend-common'; +import { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; import { ConfigReader } from '@backstage/config'; -import { loadConfig } from '@backstage/config-loader'; export interface ServerOptions { port: number; @@ -34,7 +36,7 @@ export async function startStandaloneServer( logger.debug('Creating application...'); - const config = ConfigReader.fromConfigs(await loadConfig()); + const config = ConfigReader.fromConfigs(await loadBackendConfig()); const router = await createRouter({ config, logger, diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 71590cf3be..02fd3fe0ed 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/plugin-catalog": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/plugin-catalog": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,15 +33,15 @@ "react-hook-form": "^5.7.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 72c09faf6b..102e233756 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "axios": "^0.19.2", "camelcase-keys": "^6.2.2", @@ -29,14 +29,14 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", - "helmet": "^3.22.0", + "helmet": "^4.0.0", "lodash": "^4.17.15", "morgan": "^1.10.0", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "@types/supertest": "^2.0.8", "jest-fetch-mock": "^3.0.3", "supertest": "^4.0.2" diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 1ff0307725..1ca02cb528 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,17 +32,17 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-sparklines": "^1.7.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "@types/react": "^16.9", "jest-fetch-mock": "^3.0.3" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c416dd6828..fc956c9832 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", - "@backstage/catalog-model": "^0.1.1-alpha.14", - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", @@ -35,18 +35,19 @@ "fs-extra": "^9.0.0", "git-url-parse": "^11.1.2", "globby": "^11.0.0", - "helmet": "^3.22.0", + "helmet": "^4.0.0", "morgan": "^1.10.0", "nodegit": "0.26.5", "uuid": "^8.2.0", - "winston": "^3.2.1" + "winston": "^3.2.1", + "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "@octokit/types": "^5.0.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", - "@types/nodegit": "0.26.5", + "@types/nodegit": "0.26.7", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "yaml": "^1.10.0" diff --git a/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml b/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml new file mode 100644 index 0000000000..958882e9fd --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml @@ -0,0 +1,34 @@ +apiVersion: backstage.io/v1alpha1 +kind: Template +metadata: + name: create-react-app-template + title: Create React App Template + description: Create a new CRA website project + tags: + - Experimental + - React + - CRA +spec: + owner: web@example.com + templater: cra + type: website + path: '.' + + schema: + required: + - component_id + - use_typescript + properties: + component_id: + title: Name + type: string + description: Unique name of the component + description: + title: Description + type: string + description: Description of the component + use_typescript: + title: Use Typescript + type: boolean + description: Include typescript + default: true diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml index 71f75c7695..5594a3a919 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml @@ -3,7 +3,7 @@ kind: Template metadata: name: react-ssr-template title: React SSR Template - description: Next.js application skeleton for creating isomorphic web applications. + description: Create a website powered with Next.js tags: - Recommended - React @@ -13,7 +13,7 @@ spec: type: website path: '.' schema: - required: + required: - component_id - description properties: @@ -22,7 +22,6 @@ spec: type: string description: Unique name of the component description: - title: Description + title: Description type: string description: Description of the component - diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml index e3ca9f44e2..33dd3436d5 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/.github/workflows/build.yml @@ -1,6 +1,11 @@ name: Frontend CI -on: [push, pull_request] +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + jobs: build: diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml index 5f479d9e47..65a77d4a27 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/{{cookiecutter.component_id}}/component-info.yaml @@ -3,6 +3,9 @@ kind: Component metadata: name: {{cookiecutter.component_id}} description: {{cookiecutter.description}} + annotations: + github.com/project-slug: {{cookiecutter.storePath}} + backstage.io/github-actions-id: {{cookiecutter.storePath}} spec: type: website lifecycle: experimental diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/cookiecutter.json b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/cookiecutter.json new file mode 100644 index 0000000000..d662b6f46b --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/cookiecutter.json @@ -0,0 +1,7 @@ +{ + "component_id": "", + "artifact_id": "{{ cookiecutter.component_id }}", + "java_package_name": "{{ cookiecutter.component_id|replace('-', '') }}", + "description": "We promise to update this description /{{cookiecutter.owner}}", + "http_port": 8080 +} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-template/template.yaml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml similarity index 67% rename from plugins/scaffolder-backend/sample-templates/springboot-template/template.yaml rename to plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml index 0fefce37b9..520d0a62ac 100644 --- a/plugins/scaffolder-backend/sample-templates/springboot-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml @@ -2,8 +2,8 @@ apiVersion: backstage.io/v1alpha1 kind: Template metadata: name: springboot-template - title: Spring Boot Service - description: Standard Spring Boot (Java) microservice with recommended configuration. + title: Spring Boot GRPC Service + description: Create a simple microservice using gRPC and Spring Boot Java tags: - Recommended - Java @@ -25,3 +25,8 @@ spec: title: Description type: string description: Description of the component + http_port: + title: Port + type: integer + default: 8080 + description: The port to run the GRPC service on diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.editorconfig b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.editorconfig new file mode 100644 index 0000000000..03eb226ef0 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +continuation_indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.github/workflows/build.yml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.github/workflows/build.yml new file mode 100644 index 0000000000..04e1d10b47 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.github/workflows/build.yml @@ -0,0 +1,21 @@ +name: Java CI with Maven + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn -B package --file pom.xml diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.gitignore b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.gitignore new file mode 100644 index 0000000000..1504daa184 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/.gitignore @@ -0,0 +1,7 @@ +target/ +## IntelliJ +.idea +*.iml + +## Temp +dependency-reduced-pom.xml diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/Dockerfile b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/Dockerfile new file mode 100644 index 0000000000..1c8a969c8c --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:11-alpine +ENTRYPOINT ["/usr/bin/{{cookiecutter.artifact_id}}.sh"] + +COPY {{cookiecutter.artifact_id}}.sh /usr/bin/{{cookiecutter.artifact_id}}.sh +COPY target/{{cookiecutter.artifact_id}}.jar /usr/share/{{cookiecutter.artifact_id}}/{{cookiecutter.artifact_id}}.jar diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/README.md b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/README.md new file mode 100644 index 0000000000..710b6b557e --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/README.md @@ -0,0 +1,3 @@ +# {{cookiecutter.component_id}} + +{{cookiecutter.description}} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/SERVICE.marker b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/SERVICE.marker new file mode 100644 index 0000000000..1dcf39a3b3 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/SERVICE.marker @@ -0,0 +1 @@ +(This marker file causes Maven to build this artifact as a service) diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml new file mode 100644 index 0000000000..15c8fdcfff --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{cookiecutter.component_id}} + description: {{cookiecutter.description}} + annotations: + github.com/project-slug: {{cookiecutter.storePath}} + backstage.io/github-actions-id: {{cookiecutter.storePath}} +spec: + type: service + lifecycle: experimental + owner: {{cookiecutter.owner}} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/pom.xml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/pom.xml new file mode 100644 index 0000000000..d5e81cf5e0 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/pom.xml @@ -0,0 +1,221 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.2.4.RELEASE + + + com.example + {{cookiecutter.artifact_id}} + 0.0.1-SNAPSHOT + jar + + {{cookiecutter.artifact_id}} + {{cookiecutter.description}} + + + 11 + ${java.version} + ${java.version} + ${java.version} + UTF-8 + UTF-8 + + + + jcenter + https://jcenter.bintray.com/ + + + + + io.github.lognet + grpc-spring-boot-starter + 3.5.1 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.apache.tomcat + annotations-api + 6.0.53 + provided + + + + + + ${project.artifactId} + + + kr.motd.maven + os-maven-plugin + 1.4.0.Final + + + + + + maven-compiler-plugin + 3.8.1 + + true + true + + -Xlint:all + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + + ${project.basedir}/src/main/resources + + com.google.protobuf:protoc:3.9.1:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:1.13.1:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + + + + + + + maven-failsafe-plugin + 2.22.2 + + + **/*IT.java + **/integrationtests/*.java + **/it/**/*.java + + + + + + + com.coveo + fmt-maven-plugin + 2.8 + + + + format + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.5 + + + + prepare-agent + report + + + + + + maven-enforcer-plugin + + + enforce + + enforce + + + + + + + + + + + + maven-jar-plugin + + + true + + ${start-class} + true + true + + + + ${project.version}-${buildTimestamp}-${buildNumber} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + build-info + + build-info + + + + + + maven-source-plugin + + + attach-sources + + jar + + + + + + maven-javadoc-plugin + + private + + + + attach-javadocs + + jar + + + + + + + diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/Application.java b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/Application.java new file mode 100644 index 0000000000..7ead2562d0 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/Application.java @@ -0,0 +1,12 @@ +package com.example.{{cookiecutter.java_package_name}}; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/GreeterService.java b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/GreeterService.java new file mode 100644 index 0000000000..90b8412a04 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/java/com/example/{{cookiecutter.java_package_name}}/GreeterService.java @@ -0,0 +1,19 @@ +package com.example.{{cookiecutter.java_package_name}}; + +import com.example.{{cookiecutter.java_package_name}}.proto.GreeterGrpc; +import com.example.{{cookiecutter.java_package_name}}.proto.GreeterOuterClass; +import io.grpc.stub.StreamObserver; +import org.lognet.springboot.grpc.GRpcService; + +@GRpcService +public class GreeterService extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(final GreeterOuterClass.HelloRequest request, + final StreamObserver responseObserver) { + final GreeterOuterClass.HelloReply.Builder replyBuilder = + GreeterOuterClass.HelloReply.newBuilder().setMessage("Hello " + request.getName()); + responseObserver.onNext(replyBuilder.build()); + responseObserver.onCompleted(); + } +} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/application.properties b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/application.properties new file mode 100644 index 0000000000..03be3b6ebf --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.application.name={{cookiecutter.component_id}} +server.port={{cookiecutter.http_port}} +grpc.enableReflection=true diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/banner.txt b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/banner.txt new file mode 100644 index 0000000000..46846ba35e --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/banner.txt @@ -0,0 +1,22 @@ +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_GREEN}8${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} +${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} ${AnsiColor.BRIGHT_WHITE} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/greeter.proto b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/greeter.proto new file mode 100644 index 0000000000..8015d914ee --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/main/resources/greeter.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "com.example.{{cookiecutter.java_package_name}}.proto"; + +service Greeter { + rpc SayHello ( HelloRequest) returns ( HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/test/java/com/example/{{cookiecutter.java_package_name}}/ApplicationTest.java b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/test/java/com/example/{{cookiecutter.java_package_name}}/ApplicationTest.java new file mode 100644 index 0000000000..1d7298172a --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/src/test/java/com/example/{{cookiecutter.java_package_name}}/ApplicationTest.java @@ -0,0 +1,15 @@ +package com.example.{{cookiecutter.java_package_name}}; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } +} diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/{{cookiecutter.artifact_id}}.sh b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/{{cookiecutter.artifact_id}}.sh new file mode 100755 index 0000000000..c69b980dc8 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/{{cookiecutter.artifact_id}}.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/java $JVM_DEFAULT_ARGS $JVM_ARGS -jar /usr/share/{{cookiecutter.artifact_id}}/{{cookiecutter.artifact_id}}.jar "$@" diff --git a/plugins/scaffolder-backend/scripts/mock-data.sh b/plugins/scaffolder-backend/scripts/mock-data.sh index 2424d15094..f18d4b4838 100755 --- a/plugins/scaffolder-backend/scripts/mock-data.sh +++ b/plugins/scaffolder-backend/scripts/mock-data.sh @@ -2,7 +2,8 @@ for URL in \ 'react-ssr-template' \ - 'springboot-template' \ + 'springboot-grpc-template' \ + 'create-react-app' \ ; do \ curl \ --location \ @@ -11,3 +12,10 @@ for URL in \ --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/sample-templates/${URL}/template.yaml\"}" echo done + +curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml\"}" +echo diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts index 2e88dd34fe..5e38bd8f2f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { PassThrough } from 'stream'; -import winston from 'winston'; +import * as winston from 'winston'; import { JsonValue } from '@backstage/config'; export const makeLogStream = (meta: Record) => { @@ -23,7 +23,10 @@ export const makeLogStream = (meta: Record) => { // Create an empty stream to collect all the log lines into // one variable for the API. const stream = new PassThrough(); - stream.on('data', chunk => log.push(chunk.toString())); + stream.on('data', chunk => { + const textValue = chunk.toString().trim(); + if (textValue?.length > 1) log.push(textValue); + }); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts new file mode 100644 index 0000000000..da33149c24 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cra.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fs from 'fs-extra'; +import { runDockerContainer } from './helpers'; +import { TemplaterBase, TemplaterRunOptions } from '.'; +import path from 'path'; +import { TemplaterRunResult } from './types'; +import * as yaml from 'yaml'; + +export class CreateReactAppTemplater implements TemplaterBase { + public async run(options: TemplaterRunOptions): Promise { + const { + component_id: componentName, + use_typescript: withTypescript, + description, + owner, + } = options.values; + + const resultDir = await fs.promises.mkdtemp(`${options.directory}-result`); + + await runDockerContainer({ + imageName: 'node:lts-alpine', + args: [ + 'create-react-app', + componentName as string, + withTypescript ? ' --template typescript' : '', + ], + templateDir: options.directory, + resultDir, + logStream: options.logStream, + dockerClient: options.dockerClient, + createOptions: { + Entrypoint: ['npx'], + WorkingDir: '/result', + }, + }); + + // Need to also make a component-info.yaml to store the data about the service. + const componentInfo = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: componentName, + description, + }, + spec: { + type: 'website', + lifecycle: 'experimental', + owner, + }, + }; + + const finalDir = path.resolve( + resultDir, + options.values.component_id as string, + ); + + await fs.promises.writeFile( + `${finalDir}/component-info.yaml`, + yaml.stringify(componentInfo), + ); + + return { + resultDir: finalDir, + }; + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts index 02bc801385..b7e969cd47 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.test.ts @@ -26,9 +26,15 @@ describe('helpers', () => { jest .spyOn(mockDocker, 'run') .mockResolvedValue([{ Error: null, StatusCode: 0 }]); - jest - .spyOn(mockDocker, 'pull') - .mockResolvedValue([{ Error: null, StatusCode: 0 }]); + jest.spyOn(mockDocker, 'pull').mockImplementation((async ( + _image: string, + _something: any, + handler: (err: Error | undefined, stream: PassThrough) => void, + ) => { + const mockStream = new PassThrough(); + handler(undefined, mockStream); + mockStream.end(); + }) as any); }); describe('runDockerContainer', () => { @@ -46,7 +52,11 @@ describe('helpers', () => { dockerClient: mockDocker, }); - expect(mockDocker.pull).toHaveBeenCalledWith(imageName, {}); + expect(mockDocker.pull).toHaveBeenCalledWith( + imageName, + {}, + expect.any(Function), + ); }); it('should call the dockerClient run command with the correct arguments passed through', async () => { await runDockerContainer({ @@ -116,6 +126,26 @@ describe('helpers', () => { ); }); + it('should pass through the user and group id from the host machine and set the home dir', async () => { + await runDockerContainer({ + imageName, + args, + templateDir, + resultDir, + dockerClient: mockDocker, + }); + + expect(mockDocker.run).toHaveBeenCalledWith( + imageName, + args, + expect.any(Stream), + expect.objectContaining({ + User: `${process.getuid()}:${process.getgid()}`, + Env: ['HOME=/tmp'], + }), + ); + }); + it('should pass through the log stream to the docker client', async () => { const logStream = new PassThrough(); await runDockerContainer({ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index 0267d5eeab..a3dea7ea5a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -26,6 +26,7 @@ export type RunDockerContainerOptions = { resultDir: string; templateDir: string; dockerClient: Docker; + createOptions?: Docker.ContainerCreateOptions; }; /** @@ -59,8 +60,18 @@ export const runDockerContainer = async ({ resultDir, templateDir, dockerClient, + createOptions = {}, }: RunDockerContainerOptions) => { - await dockerClient.pull(imageName, {}); + await new Promise((resolve, reject) => { + dockerClient.pull(imageName, {}, (err, stream) => { + if (err) return reject(err); + stream.pipe(logStream, { end: false }); + stream.on('end', () => resolve()); + stream.on('error', (error: Error) => reject(error)); + return undefined; + }); + }); + const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run( imageName, args, @@ -75,6 +86,15 @@ export const runDockerContainer = async ({ `${await fs.promises.realpath(templateDir)}:/template`, ], }, + // Files that are created inside the Docker container will be owned by + // root on the host system on non Mac systems, because of reasons. Mainly the fact that + // volume sharing is done using NFS on Mac and actual mounts in Linux world. + // So we set the user in the container as the same user and group id as the host. + User: `${process.getuid()}:${process.getgid()}`, + // Set the home directory inside the container as something that applications can + // write to, otherwise they will just flop and fail trying to write to / + Env: ['HOME=/tmp'], + ...createOptions, }, ); diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts index 56358f5c5b..45b787543a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/index.ts @@ -17,3 +17,4 @@ export * from './cookiecutter'; export * from './types'; export * from './helpers'; export * from './templaters'; +export * from './cra'; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9569c10e91..822a620c8a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/plugin-catalog": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/plugin-catalog": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -37,17 +37,17 @@ "react-lazylog": "^4.5.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0", + "react-use": "^15.3.3", "swr": "^0.2.2" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/scaffolder/src/components/JobStage/JobStage.tsx b/plugins/scaffolder/src/components/JobStage/JobStage.tsx index 71b73b752c..1e9adafb18 100644 --- a/plugins/scaffolder/src/components/JobStage/JobStage.tsx +++ b/plugins/scaffolder/src/components/JobStage/JobStage.tsx @@ -126,7 +126,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => { ) : ( }>
- +
)} diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 14823dd396..12b3e303dd 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -28,7 +28,7 @@ import { useApi, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Button, Grid } from '@material-ui/core'; +import { Button, Grid, Typography, Link } from '@material-ui/core'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import useStaleWhileRevalidate from 'swr'; @@ -91,8 +91,23 @@ export const ScaffolderPage: React.FC<{}> = () => { {!templates && isValidating && } + {templates && !templates.length && ( + + Shoot! Looks like you don't have any templates. Check out the + documentation{' '} + + here! + + + )} + {error && ( + + Oops! Something went wrong loading the templates: {error.message} + + )} {templates && + templates?.length > 0 && templates.map(template => { return ( diff --git a/plugins/sentry-backend/package.json b/plugins/sentry-backend/package.json index 90d6b6273c..e199dd2f1d 100644 --- a/plugins/sentry-backend/package.json +++ b/plugins/sentry-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "axios": "^0.19.2", "compression": "^1.7.4", @@ -28,13 +28,13 @@ "express": "^4.17.1", "express-promise-router": "^3.0.3", "fs-extra": "^9.0.0", - "helmet": "^3.22.0", + "helmet": "^4.0.0", "morgan": "^1.10.0", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", "jest-fetch-mock": "^3.0.3" }, "files": [ diff --git a/plugins/sentry/README.md b/plugins/sentry/README.md index cce2228094..73cca29d34 100644 --- a/plugins/sentry/README.md +++ b/plugins/sentry/README.md @@ -10,7 +10,7 @@ Your plugin has been added to the example app in this repository, meaning you'll You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. Needs SENTRY_TOKEN set in the environment for the backend to startup diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 329a23c559..3a6cf54199 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -30,16 +30,16 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-sparklines": "^1.7.0", - "react-use": "^14.2.0", + "react-use": "^15.3.3", "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx index b0437ff4f7..d508ddf7c2 100644 --- a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx @@ -20,15 +20,26 @@ import mockFetch from 'jest-fetch-mock'; import SentryPluginPage from './SentryPluginPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; -import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; +import { + ApiProvider, + ApiRegistry, + errorApiRef, + configApiRef, +} from '@backstage/core'; const errorApi = { post: () => {} }; +const ConfigApi = { getString: () => 'test' }; describe('SentryPluginPage', () => { it('should render header and time switched', () => { mockFetch.mockResponse('{}'); const rendered = render( - + diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx index ae5f8bd35c..0473cb805c 100644 --- a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx +++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx @@ -30,6 +30,7 @@ import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; const SentryPluginPage: FC<{}> = () => { const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h'); const toggleStatsFor = () => setStatsFor(statsFor === '12h' ? '12h' : '24h'); + const sentryProjectId = 'sample-sentry-project-id'; return ( @@ -56,7 +57,7 @@ const SentryPluginPage: FC<{}> = () => { diff --git a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx index b101a9092a..8d522df4a7 100644 --- a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx +++ b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx @@ -21,18 +21,21 @@ import { InfoCard, Progress, useApi, + configApiRef, } from '@backstage/core'; import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable'; import { useAsync } from 'react-use'; import { sentryApiFactory } from '../../data/api-factory'; -const api = sentryApiFactory('spotify'); - export const SentryPluginWidget: FC<{ sentryProjectId: string; statsFor: '24h' | '12h'; }> = ({ sentryProjectId, statsFor }) => { const errorApi = useApi(errorApiRef); + const configApi = useApi(configApiRef); + const org = configApi.getString('sentry.organization'); + const backendBaseUrl = configApi.getString('backend.baseUrl'); + const api = sentryApiFactory(org, backendBaseUrl); const { loading, value, error } = useAsync( () => api.fetchIssues(sentryProjectId, statsFor), diff --git a/plugins/sentry/src/data/api-factory.ts b/plugins/sentry/src/data/api-factory.ts index 32ff4bde5c..88e1148b72 100644 --- a/plugins/sentry/src/data/api-factory.ts +++ b/plugins/sentry/src/data/api-factory.ts @@ -17,9 +17,12 @@ import { SentryApi } from './sentry-api'; import { MockSentryApi } from './mock-api'; import { ProductionSentryApi } from './production-api'; -export function sentryApiFactory(organization: string): SentryApi { +export function sentryApiFactory( + organization: string, + backendBaseUrl: string, +): SentryApi { if (process.env.NODE_ENV === 'production') { - return new ProductionSentryApi(organization); + return new ProductionSentryApi(organization, backendBaseUrl); } return new MockSentryApi(); } diff --git a/plugins/sentry/src/data/production-api.ts b/plugins/sentry/src/data/production-api.ts index ce4bab5aca..5d21e80604 100644 --- a/plugins/sentry/src/data/production-api.ts +++ b/plugins/sentry/src/data/production-api.ts @@ -16,20 +16,21 @@ import { SentryIssue } from './sentry-issue'; import { SentryApi } from './sentry-api'; -const API_HOST = process.env.API_HOST || 'http://localhost:7000'; -const API_BASE_URL = `${API_HOST}/sentry/api/0/projects/`; - export class ProductionSentryApi implements SentryApi { private organization: string; + private backendBaseUrl: string; - constructor(organization: string) { + constructor(organization: string, backendBaseUrl: string) { this.organization = organization; + this.backendBaseUrl = backendBaseUrl; } async fetchIssues(project: string, statsFor: string): Promise { try { + const apiBaseUrl = `${this.backendBaseUrl}/sentry/api/0/projects/`; + const response = await fetch( - `${API_BASE_URL}/${this.organization}/${project}/issues/?statsFor=${statsFor}`, + `${apiBaseUrl}/${this.organization}/${project}/issues/?statsFor=${statsFor}`, ); if (response.status >= 400 && response.status < 600) { diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 3141b021fb..b2a7be7341 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/test-utils-core": "^0.1.1-alpha.13", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/test-utils-core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,17 +32,17 @@ "prop-types": "^15.7.2", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", "@types/color": "^3.0.1", "@types/d3-force": "^1.2.1", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "@types/react": "^16.9", "jest-fetch-mock": "^3.0.3" diff --git a/plugins/techdocs-backend/.gitignore b/plugins/techdocs-backend/.gitignore new file mode 100644 index 0000000000..7b4d4ba2e6 --- /dev/null +++ b/plugins/techdocs-backend/.gitignore @@ -0,0 +1 @@ +static diff --git a/plugins/techdocs-backend/examples/documented-component/docs/index.md b/plugins/techdocs-backend/examples/documented-component/docs/index.md new file mode 100644 index 0000000000..bf0895710f --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/docs/index.md @@ -0,0 +1,3 @@ +# example docs + +This is a basic example of documentation. diff --git a/plugins/techdocs-backend/examples/documented-component/documented-component.yaml b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml new file mode 100644 index 0000000000..d5a1214b15 --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/documented-component.yaml @@ -0,0 +1,11 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: documented-component + description: A Service with TechDocs documentation + annotations: + backstage.io/techdocs-ref: 'dir:./' +spec: + type: service + lifecycle: experimental + owner: documented@example.com diff --git a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml new file mode 100644 index 0000000000..6a3f85d020 --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml @@ -0,0 +1,7 @@ +site_name: 'example-docs' + +nav: + - Home: index.md + +plugins: + - techdocs-core diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 3cd8f6f3ef..8a5a9fd10a 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -17,18 +17,26 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.18", + "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", + "dockerode": "^3.2.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", + "fs-extra": "^9.0.1", "knex": "^0.21.1", + "node-fetch": "^2.6.0", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@types/node-fetch": "^2.5.7", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs-backend/scripts/mock-data.sh b/plugins/techdocs-backend/scripts/mock-data.sh new file mode 100755 index 0000000000..0630490694 --- /dev/null +++ b/plugins/techdocs-backend/scripts/mock-data.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +for URL in \ + 'documented-component/documented-component.yaml' \ +; do \ + curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"file\", \"target\": \"$(pwd)/examples/${URL}\"}" + echo +done diff --git a/plugins/techdocs-backend/src/index.ts b/plugins/techdocs-backend/src/index.ts index 7612c392a2..8c65708017 100644 --- a/plugins/techdocs-backend/src/index.ts +++ b/plugins/techdocs-backend/src/index.ts @@ -15,3 +15,4 @@ */ export * from './service/router'; +export * from './techdocs'; diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index f4dbb706fd..b28010e059 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -15,6 +15,9 @@ */ import { getVoidLogger } from '@backstage/backend-common'; +import { Preparers, LocalPublish, Generators } from '../techdocs'; +import { ConfigReader } from '@backstage/config'; +import Docker from 'dockerode'; import express from 'express'; import request from 'supertest'; import { createRouter } from './router'; @@ -24,7 +27,12 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ + preparers: new Preparers(), + generators: new Generators(), + publisher: new LocalPublish(), logger: getVoidLogger(), + dockerClient: new Docker(), + config: ConfigReader.fromConfigs([]), }); app = express().use(router); }); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 7c91dd4ef7..874ab7c89c 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -17,18 +17,76 @@ import { Logger } from 'winston'; import Router from 'express-promise-router'; import express from 'express'; import Knex from 'knex'; +import fetch from 'node-fetch'; +import { Config } from '@backstage/config'; +import path from 'path'; +import Docker from 'dockerode'; +import { + GeneratorBuilder, + PreparerBuilder, + PublisherBase, + LocalPublish, +} from '../techdocs'; +import { Entity } from '@backstage/catalog-model'; type RouterOptions = { + preparers: PreparerBuilder; + generators: GeneratorBuilder; + publisher: PublisherBase; logger: Logger; database?: Knex; // TODO: Make database required when we're implementing database stuff. + config: Config; + dockerClient: Docker; }; -export async function createRouter({}: RouterOptions): Promise { +export async function createRouter({ + preparers, + generators, + publisher, + config, + dockerClient, +}: RouterOptions): Promise { const router = Router(); - router.get('/', (_, res) => { + router.get('/', async (_, res) => { res.status(200).send('Hello TechDocs Backend'); }); + // TODO: This route should not exist in the future + router.get('/buildall', async (_, res) => { + const baseUrl = config.getString('backend.baseUrl'); + const entitiesResponse = (await ( + await fetch(`${baseUrl}/catalog/entities`) + ).json()) as Entity[]; + + const entitiesWithDocs = entitiesResponse.filter( + entity => entity.metadata.annotations?.['backstage.io/techdocs-ref'], + ); + + entitiesWithDocs.forEach(async entity => { + const preparer = preparers.get(entity); + const generator = generators.get(entity); + + const { resultDir } = await generator.run({ + directory: await preparer.prepare(entity), + dockerClient, + }); + + publisher.publish({ + entity, + directory: resultDir, + }); + }); + + res.send('Successfully generated documentation'); + }); + + if (publisher instanceof LocalPublish) { + router.use( + '/static/docs/', + express.static(path.resolve(__dirname, `../../static/docs`)), + ); + } + return router; } diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index 09acfc9e27..76de973875 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -18,6 +18,15 @@ import { createServiceBuilder } from '@backstage/backend-common'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; +import Docker from 'dockerode'; +import { + Preparers, + DirectoryPreparer, + Generators, + TechdocsGenerator, + LocalPublish, +} from '../techdocs'; +import { ConfigReader } from '@backstage/config'; export interface ServerOptions { port: number; @@ -31,9 +40,27 @@ export async function startStandaloneServer( const logger = options.logger.child({ service: 'techdocs-backend' }); logger.debug('Creating application...'); + const preparers = new Preparers(); + const directoryPreparer = new DirectoryPreparer(); + preparers.register('dir', directoryPreparer); + + const generators = new Generators(); + const techdocsGenerator = new TechdocsGenerator(); + generators.register('techdocs', techdocsGenerator); + + const publisher = new LocalPublish(); + + const dockerClient = new Docker(); logger.debug('Starting application server...'); - const router = await createRouter({ logger }); + const router = await createRouter({ + preparers, + generators, + logger, + publisher, + dockerClient, + config: ConfigReader.fromConfigs([]), + }); const service = createServiceBuilder(module) .enableCors({ origin: 'http://localhost:3000' }) .addRouter('/techdocs', router); diff --git a/plugins/techdocs-backend/src/techdocs/index.ts b/plugins/techdocs-backend/src/techdocs/index.ts new file mode 100644 index 0000000000..7113525bb8 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './stages'; diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/generators.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/generators.ts new file mode 100644 index 0000000000..da9d3c418b --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/generators.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GeneratorBase, + SupportedGeneratorKey, + GeneratorBuilder, + } from './types'; + + import { Entity } from '@backstage/catalog-model'; + import { getGeneratorKey } from './helpers'; + + export class Generators implements GeneratorBuilder { + private generatorMap = new Map(); + + register(templaterKey: SupportedGeneratorKey, templater: GeneratorBase) { + this.generatorMap.set(templaterKey, templater); + } + + get(entity: Entity): GeneratorBase { + const generatorKey = getGeneratorKey(entity); + const generator = this.generatorMap.get(generatorKey); + + if (!generator) { + throw new Error( + `No generator registered for entity: "${generatorKey}"`, + ); + } + + return generator; + } + } + \ No newline at end of file diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts new file mode 100644 index 0000000000..7dc4403977 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { Writable, PassThrough } from 'stream'; +import Docker from 'dockerode'; +import { SupportedGeneratorKey } from './types'; + +// TODO: Implement proper support for more generators. +export function getGeneratorKey(entity: Entity): SupportedGeneratorKey { + if (!entity) { + throw new Error('No entity provided'); + } + + return 'techdocs'; +} + +type RunDockerContainerOptions = { + imageName: string; + args: string[]; + logStream?: Writable; + docsDir: string; + resultDir: string; + dockerClient: Docker; + createOptions?: Docker.ContainerCreateOptions; +}; + +export async function runDockerContainer({ + imageName, + args, + logStream = new PassThrough(), + docsDir, + resultDir, + dockerClient, + createOptions, +}: RunDockerContainerOptions) { + const [{ Error: error, StatusCode: statusCode }] = await dockerClient.run( + imageName, + args, + logStream, + { + Volumes: { + '/content': {}, + '/result': {}, + }, + WorkingDir: '/content', + HostConfig: { + Binds: [`${docsDir}:/content`, `${resultDir}:/result`], + }, + ...createOptions, + }, + ); + + if (error) { + throw new Error( + `Docker failed to run with the following error message: ${error}`, + ); + } + + if (statusCode !== 0) { + throw new Error( + `Docker container returned a non-zero exit code (${statusCode})`, + ); + } + + return { error, statusCode }; +} diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts new file mode 100644 index 0000000000..5c086a976a --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { TechdocsGenerator } from './techdocs'; +export { Generators } from './generators'; +export type { GeneratorBuilder } from './types'; diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts new file mode 100644 index 0000000000..d6650fb744 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + GeneratorBase, + GeneratorRunOptions, + GeneratorRunResult, +} from './types'; +import { runDockerContainer } from './helpers'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +export class TechdocsGenerator implements GeneratorBase { + public async run({ + directory, + logStream, + dockerClient, + }: GeneratorRunOptions): Promise { + const resultDir = fs.mkdtempSync(path.join(os.tmpdir(), `techdocs-tmp-`)); + + await runDockerContainer({ + imageName: 'spotify/techdocs', + args: ['build', '-d', '/result'], + logStream, + docsDir: directory, + resultDir, + dockerClient, + }); + + console.log( + `[TechDocs]: Successfully generated docs from ${directory} into ${resultDir}`, + ); + return { resultDir }; + } +} diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts new file mode 100644 index 0000000000..4292aa68f6 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { Writable } from 'stream'; +import Docker from 'dockerode'; +import { Entity } from '@backstage/catalog-model'; + +/** + * The returned directory from the generator which is ready + * to pass to the next stage of the TechDocs which is publishing + */ +export type GeneratorRunResult = { + resultDir: string; +}; + +/** + * The values that the generator will recieve. The directory of the + * uncompiled documentation, with the values from the frontend. A dedicated log stream and a docker + * client to run any generator on top of your directory. + */ +export type GeneratorRunOptions = { + directory: string; + logStream?: Writable; + dockerClient: Docker; +}; + +export type GeneratorBase = { + // runs the generator with the values and returns the directory to be published + run(opts: GeneratorRunOptions): Promise; +}; + +/** + * List of supported generator options + */ +export type SupportedGeneratorKey = 'techdocs' | string; + +/** + * The generator builder holds the generator ready for run time + */ +export type GeneratorBuilder = { + register(protocol: SupportedGeneratorKey, generator: GeneratorBase): void; + get(entity: Entity): GeneratorBase; +}; diff --git a/plugins/techdocs-backend/src/techdocs/stages/index.ts b/plugins/techdocs-backend/src/techdocs/stages/index.ts new file mode 100644 index 0000000000..40988483a3 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './generate'; +export * from './prepare'; +export * from './publish'; diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts new file mode 100644 index 0000000000..8a99a8b59a --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { DirectoryPreparer } from './dir'; + +const createMockEntity = (annotations: {}) => { + return { + apiVersion: 'version', + kind: 'TestKind', + metadata: { + name: 'testName', + annotations: { + ...annotations, + }, + }, + }; +}; + +describe('directory preparer', () => { + it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => { + const directoryPreparer = new DirectoryPreparer(); + + const mockEntity = createMockEntity({ + 'backstage.io/managed-by-location': + 'file:/directory/documented-component.yaml', + 'backstage.io/techdocs-ref': 'dir:./our-documentation', + }); + + expect(await directoryPreparer.prepare(mockEntity)).toEqual( + '/directory/our-documentation', + ); + }); + + it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => { + const directoryPreparer = new DirectoryPreparer(); + + const mockEntity = createMockEntity({ + 'backstage.io/managed-by-location': + 'file:/directory/documented-component.yaml', + 'backstage.io/techdocs-ref': 'dir:/our-documentation/techdocs', + }); + + expect(await directoryPreparer.prepare(mockEntity)).toEqual( + '/our-documentation/techdocs', + ); + }); +}); diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts new file mode 100644 index 0000000000..e61a3e48ac --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PreparerBase } from './types'; +import { Entity } from '@backstage/catalog-model'; +import path from 'path'; +import { parseReferenceAnnotation } from './helpers'; + +export class DirectoryPreparer implements PreparerBase { + prepare(entity: Entity): Promise { + const { location: managedByLocation } = parseReferenceAnnotation( + 'backstage.io/managed-by-location', + entity, + ); + const { location: techdocsLocation } = parseReferenceAnnotation( + 'backstage.io/techdocs-ref', + entity, + ); + + const managedByLocationDirectory = path.dirname(managedByLocation); + + return new Promise(resolve => { + resolve(path.resolve(managedByLocationDirectory, techdocsLocation)); + }); + } +} diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts new file mode 100644 index 0000000000..1fd86805ca --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entity } from '@backstage/catalog-model'; +import { InputError } from '@backstage/backend-common'; +import { RemoteProtocol } from './types'; + +export type ParsedLocationAnnotation = { + protocol: RemoteProtocol; + location: string; +}; + +export const parseReferenceAnnotation = ( + annotationName: string, + entity: Entity, +): ParsedLocationAnnotation => { + const annotation = entity.metadata.annotations?.[annotationName]; + + if (!annotation) { + throw new InputError( + `No location annotation provided in entity: ${entity.metadata.name}`, + ); + } + + // split on the first colon for the protocol and the rest after the first split + // is the location. + const [protocol, location] = annotation.split(/:(.+)/) as [ + RemoteProtocol?, + string?, + ]; + + if (!protocol || !location) { + throw new InputError( + `Failure to parse either protocol or location for entity: ${entity.metadata.name}`, + ); + } + + return { + protocol, + location, + }; +}; diff --git a/plugins/github-actions/src/components/BuildListTable/index.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts similarity index 81% rename from plugins/github-actions/src/components/BuildListTable/index.ts rename to plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts index 089d53db94..9f928e7413 100644 --- a/plugins/github-actions/src/components/BuildListTable/index.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { BuildListTable } from './BuildListTable'; -export type { Build } from './BuildListTable'; +export { DirectoryPreparer } from './dir'; +export { Preparers } from './preparers'; +export type { PreparerBuilder } from './types'; diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts new file mode 100644 index 0000000000..1d5b689d8b --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types'; +import { Entity } from '@backstage/catalog-model'; +import { parseReferenceAnnotation } from './helpers'; + +export class Preparers implements PreparerBuilder { + private preparerMap = new Map(); + + register(protocol: RemoteProtocol, preparer: PreparerBase) { + this.preparerMap.set(protocol, preparer); + } + + get(entity: Entity): PreparerBase { + const { protocol } = parseReferenceAnnotation('backstage.io/techdocs-ref', entity); + const preparer = this.preparerMap.get(protocol); + + if (!preparer) { + throw new Error(`No preparer registered for type: "${protocol}"`); + } + + return preparer; + } +} \ No newline at end of file diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts new file mode 100644 index 0000000000..8baf6347bb --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { Entity } from '@backstage/catalog-model'; +import { Logger } from 'winston'; + +export type PreparerBase = { + /** + * Given an Entity definition from the Service Catalog, go and prepare a directory + * with contents from the location in temporary storage and return the path + * @param entity The entity from the Service Catalog + */ + prepare(entity: Entity, opts?: { logger: Logger }): Promise; +}; + +export type PreparerBuilder = { + register(protocol: RemoteProtocol, preparer: PreparerBase): void; + get(entity: Entity): PreparerBase; +}; + +export type RemoteProtocol = 'dir' | string; diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/index.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/index.ts new file mode 100644 index 0000000000..0029ea5c4e --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { LocalPublish } from './local'; +export type { PublisherBase } from './types'; diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/local.test.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/local.test.ts new file mode 100644 index 0000000000..b5a96e4feb --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/local.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LocalPublish } from './local'; +import fs from 'fs-extra'; +import path from 'path'; + +const createMockEntity = (annotations = {}) => { + return { + apiVersion: 'version', + kind: 'TestKind', + metadata: { + name: 'test-component-name', + annotations: { + ...annotations, + }, + }, + }; +}; + +describe('local publisher', () => { + it('should publish generated documentation dir', async () => { + const publisher = new LocalPublish(); + + const mockEntity = createMockEntity(); + + const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`); + + expect(tempDir).toBeTruthy(); + + fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w')); + + await publisher.publish({ entity: mockEntity, directory: tempDir }); + const publishDir = path.resolve( + __dirname, + `../../../../static/docs/${mockEntity.metadata.name}`, + ); + + expect(fs.existsSync(publishDir)).toBeTruthy(); + expect(fs.existsSync(path.join(publishDir, '/mock-file'))).toBeTruthy(); + + fs.removeSync(publishDir); + fs.removeSync(tempDir); + }); +}); diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts new file mode 100644 index 0000000000..227ba445ae --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PublisherBase } from './types'; +import { Entity } from '@backstage/catalog-model'; +import path from 'path'; +import fs from 'fs-extra'; + +export class LocalPublish implements PublisherBase { + publish({ + entity, + directory, + }: { + entity: Entity; + directory: string; + }): + | Promise<{ + remoteUrl: string; + }> + | { remoteUrl: string } { + const publishDir = path.resolve( + __dirname, + `../../../../static/docs/${entity.metadata.name}`, + ); + + if (!fs.existsSync(publishDir)) { + fs.mkdirSync(publishDir, { recursive: true }); + } + + return new Promise((resolve, reject) => { + fs.copy(directory, publishDir, err => { + if (err) { + reject(err); + } + + resolve({ + remoteUrl: `http://localhost:7000/techdocs/static/docs/${entity.metadata.name}`, + }); + }); + }); + } +} diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/types.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/types.ts new file mode 100644 index 0000000000..ca85d9f56e --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/types.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entity } from '@backstage/catalog-model'; + +/** + * Publisher is in charge of taking a folder created by + * the builder, and pushing it to storage + */ +export type PublisherBase = { + /** + * + * @param opts object containing the entity from the service + * catalog, and the directory that has been generated + */ + publish(opts: { + entity: Entity; + directory: string; + }): Promise<{ remoteUrl: string }> | { remoteUrl: string }; +}; diff --git a/plugins/techdocs/README.md b/plugins/techdocs/README.md index b763679a1c..4a92c814a1 100644 --- a/plugins/techdocs/README.md +++ b/plugins/techdocs/README.md @@ -4,17 +4,13 @@ Welcome to the TechDocs plugin - Spotify's docs-like-code approach built directl **WIP: This plugin is a work in progress. It is not ready for use yet. Follow our progress on [the Backstage Discord](https://discord.gg/MUpMjP2) under #docs-like-code or on [our GitHub Milestone](https://github.com/spotify/backstage/milestone/15).** -## Sections - -- [MkDocs](./mkdocs/README.md) - ## Getting started Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/docs](http://localhost:3000/docs). You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](/dev) directory. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. ## Configuration diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7c730739ea..a1b7ddb589 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,16 +32,16 @@ "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0", + "react-use": "^15.3.3", "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/techdocs/src/reader/README.md b/plugins/techdocs/src/reader/README.md new file mode 100644 index 0000000000..464090415c --- /dev/null +++ b/plugins/techdocs/src/reader/README.md @@ -0,0 +1 @@ +# TODO diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index a6b23bd454..8e11df428e 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -91,6 +91,10 @@ export const Reader = () => { return; // Page isn't ready } + if (state.value instanceof Error) { + return; // Docs not found + } + // Pre-render const transformedElement = transformer(state.value as string, [ sanitizeDOM(), diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx index 7792164b14..7b0ec80619 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.test.tsx @@ -18,9 +18,12 @@ import React from 'react'; import { render } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; -describe('TechDocs Not Found', () => { - it('should render a Documentation not found page', async () => { - const { queryByText } = render(wrapInTestApp()); - expect(queryByText(/error: documentation not found/i)).toBeInTheDocument(); +describe('', () => { + it('should render with status code, status message and go back link', () => { + const rendered = render(wrapInTestApp()); + rendered.getByText(/Documentation not found/i); + rendered.getByText(/404/i); + rendered.getByText(/Looks like someone dropped the mic!/i); + expect(rendered.getByTestId('go-back-link')).toBeDefined(); }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx index 2a961b5bc0..c774000bd5 100644 --- a/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsNotFound.tsx @@ -15,20 +15,14 @@ */ import React from 'react'; -import { Typography, Button } from '@material-ui/core'; -import { TechDocsPageWrapper } from './TechDocsPageWrapper'; +import { ErrorPage } from '@backstage/core'; export const TechDocsNotFound = () => { return ( - - Error: Documentation not found - Path: {window.location.pathname} - - + ); }; diff --git a/plugins/techdocs/src/reader/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/reader/components/TechDocsPageWrapper.tsx index abb702535b..b511fde1e7 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageWrapper.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { Header, Content } from '@backstage/core'; +import { Header, Content, Page, pageTheme } from '@backstage/core'; type TechDocsPageWrapperProps = { title: string; @@ -29,9 +29,9 @@ export const TechDocsPageWrapper = ({ subtitle, }: TechDocsPageWrapperProps) => { return ( - <> +
{children} - + ); }; diff --git a/plugins/techdocs/src/reader/transformers/README.md b/plugins/techdocs/src/reader/transformers/README.md new file mode 100644 index 0000000000..464090415c --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/README.md @@ -0,0 +1 @@ +# TODO diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 5084495557..2c0c838de8 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.18", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -21,23 +21,23 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/core": "^0.1.1-alpha.18", + "@backstage/theme": "^0.1.1-alpha.18", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", - "react-use": "^14.2.0" + "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/dev-utils": "^0.1.1-alpha.18", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", - "@types/jest": "^25.2.2", + "@types/jest": "^26.0.7", "@types/node": "^12.0.0", "jest-fetch-mock": "^3.0.3" }, diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx index 272297ed45..3ceb0af58d 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.tsx @@ -113,7 +113,7 @@ const WelcomePage = () => { We suggest you either check out the documentation for{' '} - + creating a plugin {' '} or have a look in the code for the{' '} @@ -135,7 +135,7 @@ const WelcomePage = () => { backstage.io - + Create a plugin diff --git a/yarn.lock b/yarn.lock index 6c0ff99388..28814f9f85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,44 @@ # yarn lockfile v1 +"@apollo/protobufjs@^1.0.3": + version "1.0.4" + resolved "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz#cf01747a55359066341f31b5ce8db17df44244e0" + integrity sha512-EE3zx+/D/wur/JiLp6VCiw1iYdyy1lCJMf8CGPkLeDt5QJrN4N8tKFx33Ah4V30AUQzMk7Uz4IXKZ1LOj124gA== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.0" + "@types/node" "^10.1.0" + long "^4.0.0" + +"@apollographql/apollo-tools@^0.4.3": + version "0.4.8" + resolved "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.4.8.tgz#d81da89ee880c2345eb86bddb92b35291f6135ed" + integrity sha512-W2+HB8Y7ifowcf3YyPHgDI05izyRtOeZ4MqIr7LbTArtmJ0ZHULWpn84SGMW7NAvTV1tFExpHlveHhnXuJfuGA== + dependencies: + apollo-env "^0.6.5" + +"@apollographql/graphql-playground-html@1.6.26": + version "1.6.26" + resolved "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.26.tgz#2f7b610392e2a872722912fc342b43cf8d641cb3" + integrity sha512-XAwXOIab51QyhBxnxySdK3nuMEUohhDsHQ5Rbco/V1vjlP75zZ0ZLHD9dTpXTN8uxKxopb2lUvJTq+M4g2Q0HQ== + dependencies: + xss "^1.0.6" + +"@ardatan/aggregate-error@0.0.1": + version "0.0.1" + resolved "https://registry.npmjs.org/@ardatan/aggregate-error/-/aggregate-error-0.0.1.tgz#1403ac5de10d8ca689fc1f65844c27179ae1d44f" + integrity sha512-UQ9BequOTIavs0pTHLMwQwKQF8tTV1oezY/H2O9chA+JNPFZSua55xpU5dPSjAU9/jLJ1VwU+HJuTVN8u7S6Fg== + "@babel/code-frame@7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" @@ -23,12 +61,19 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/compat-data@^7.8.6", "@babel/compat-data@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz#04815556fc90b0c174abd2c0c1bb966faa036a6c" - integrity sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g== +"@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - browserslist "^4.9.1" + "@babel/highlight" "^7.10.4" + +"@babel/compat-data@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.10.5.tgz#d38425e67ea96b1480a3f50404d1bf85676301a6" + integrity sha512-mPVoWNzIpYJHbWje0if7Ck36bpbtTvIxOi9+6WSK9wjGEXearAqlwBoTQvVjsAY2VIwgcs8V940geY3okzRCEw== + dependencies: + browserslist "^4.12.0" invariant "^2.2.4" semver "^5.5.0" @@ -54,6 +99,15 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" + integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== + dependencies: + "@babel/types" "^7.10.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" @@ -64,6 +118,13 @@ lodash "^4.17.13" source-map "^0.5.0" +"@babel/helper-annotate-as-pure@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" + integrity sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-annotate-as-pure@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" @@ -71,51 +132,54 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" - integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" + integrity sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg== dependencies: - "@babel/helper-explode-assignable-expression" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-explode-assignable-expression" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-builder-react-jsx-experimental@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.9.0.tgz#066d80262ade488f9c1b1823ce5db88a4cedaa43" - integrity sha512-3xJEiyuYU4Q/Ar9BsHisgdxZsRlsShMe90URZ0e6przL26CCs8NJbDoxH94kKT17PcxlMhsCAwZd90evCo26VQ== +"@babel/helper-builder-react-jsx-experimental@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.5.tgz#f35e956a19955ff08c1258e44a515a6d6248646b" + integrity sha512-Buewnx6M4ttG+NLkKyt7baQn7ScC/Td+e99G914fRU8fGIUivDDgVIQeDHFa5e4CRSJQt58WpNHhsAZgtzVhsg== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-module-imports" "^7.8.3" - "@babel/types" "^7.9.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/types" "^7.10.5" -"@babel/helper-builder-react-jsx@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.9.0.tgz#16bf391990b57732700a3278d4d9a81231ea8d32" - integrity sha512-weiIo4gaoGgnhff54GQ3P5wsUQmnSwpkvU0r6ZHq6TzoSzKy4JxHEgnxNytaKbov2a9z/CVNyzliuCOUPEX3Jw== +"@babel/helper-builder-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz#8095cddbff858e6fa9c326daee54a2f2732c1d5d" + integrity sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/types" "^7.9.0" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-call-delegate@^7.8.7": - version "7.8.7" - resolved "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.7.tgz#28a279c2e6c622a6233da548127f980751324cab" - integrity sha512-doAA5LAKhsFCR0LAFIf+r2RSMmC+m8f/oQ+URnUET/rWeEzC0yTRmAGyWkD4sSu3xwbS7MYQ2u+xlt1V5R56KQ== +"@babel/helper-compilation-targets@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz#804ae8e3f04376607cc791b9d47d540276332bd2" + integrity sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.7" - -"@babel/helper-compilation-targets@^7.8.7": - version "7.8.7" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz#dac1eea159c0e4bd46e309b5a1b04a66b53c1dde" - integrity sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw== - dependencies: - "@babel/compat-data" "^7.8.6" - browserslist "^4.9.1" + "@babel/compat-data" "^7.10.4" + browserslist "^4.12.0" invariant "^2.2.4" levenary "^1.1.1" semver "^5.5.0" +"@babel/helper-create-class-features-plugin@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" + integrity sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A== + dependencies: + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-member-expression-to-functions" "^7.10.5" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/helper-create-class-features-plugin@^7.8.3": version "7.8.6" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.6.tgz#243a5b46e2f8f0f674dc1387631eb6b28b851de0" @@ -128,6 +192,15 @@ "@babel/helper-replace-supers" "^7.8.6" "@babel/helper-split-export-declaration" "^7.8.3" +"@babel/helper-create-regexp-features-plugin@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" + integrity sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-regex" "^7.10.4" + regexpu-core "^4.7.0" + "@babel/helper-create-regexp-features-plugin@^7.8.3", "@babel/helper-create-regexp-features-plugin@^7.8.8": version "7.8.8" resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.8.tgz#5d84180b588f560b7864efaeea89243e58312087" @@ -137,22 +210,31 @@ "@babel/helper-regex" "^7.8.3" regexpu-core "^4.7.0" -"@babel/helper-define-map@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" - integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== +"@babel/helper-define-map@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz#b53c10db78a640800152692b13393147acb9bb30" + integrity sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/types" "^7.8.3" - lodash "^4.17.13" + "@babel/helper-function-name" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" -"@babel/helper-explode-assignable-expression@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" - integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== +"@babel/helper-explode-assignable-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz#40a1cd917bff1288f699a94a75b37a1a2dbd8c7c" + integrity sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A== dependencies: - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" "@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5": version "7.9.5" @@ -163,6 +245,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.9.5" +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-get-function-arity@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" @@ -170,12 +259,19 @@ dependencies: "@babel/types" "^7.8.3" -"@babel/helper-hoist-variables@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" - integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== +"@babel/helper-hoist-variables@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" + integrity sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA== dependencies: - "@babel/types" "^7.8.3" + "@babel/types" "^7.10.4" + +"@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz#172f56e7a63e78112f3a04055f24365af702e7ee" + integrity sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA== + dependencies: + "@babel/types" "^7.10.5" "@babel/helper-member-expression-to-functions@^7.8.3": version "7.8.3" @@ -191,6 +287,26 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-module-imports@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" + integrity sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw== + dependencies: + "@babel/types" "^7.10.4" + +"@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" + integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + "@babel/helper-module-transforms@^7.9.0": version "7.9.0" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5" @@ -204,6 +320,13 @@ "@babel/types" "^7.9.0" lodash "^4.17.13" +"@babel/helper-optimise-call-expression@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" + integrity sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-optimise-call-expression@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" @@ -216,6 +339,18 @@ resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== +"@babel/helper-plugin-utils@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-regex@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" + integrity sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg== + dependencies: + lodash "^4.17.19" + "@babel/helper-regex@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" @@ -223,18 +358,28 @@ dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" - integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== +"@babel/helper-remap-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz#fce8bea4e9690bbe923056ded21e54b4e8b68ed5" + integrity sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-wrap-function" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-wrap-function" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" -"@babel/helper-replace-supers@^7.8.3", "@babel/helper-replace-supers@^7.8.6": +"@babel/helper-replace-supers@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz#d585cd9388ea06e6031e4cd44b6713cbead9e6cf" + integrity sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" + +"@babel/helper-replace-supers@^7.8.6": version "7.8.6" resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8" integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA== @@ -244,6 +389,14 @@ "@babel/traverse" "^7.8.6" "@babel/types" "^7.8.6" +"@babel/helper-simple-access@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" + integrity sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw== + dependencies: + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-simple-access@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" @@ -252,6 +405,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-split-export-declaration@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz#2c70576eaa3b5609b24cb99db2888cc3fc4251d1" + integrity sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-split-export-declaration@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" @@ -259,20 +419,25 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + "@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g== -"@babel/helper-wrap-function@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" - integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== +"@babel/helper-wrap-function@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" + integrity sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/template" "^7.8.3" - "@babel/traverse" "^7.8.3" - "@babel/types" "^7.8.3" + "@babel/helper-function-name" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.4" + "@babel/types" "^7.10.4" "@babel/helpers@^7.9.6": version "7.9.6" @@ -292,20 +457,42 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== -"@babel/plugin-proposal-async-generator-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" - integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== +"@babel/parser@^7.10.4", "@babel/parser@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== + +"@babel/plugin-proposal-async-generator-functions@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" + integrity sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" +"@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-class-properties@^7.7.0": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.8.3.tgz#5e06654af5cd04b608915aada9b2a6788004464e" @@ -314,39 +501,48 @@ "@babel/helper-create-class-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-proposal-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" - integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== +"@babel/plugin-proposal-dynamic-import@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" + integrity sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-dynamic-import" "^7.8.0" -"@babel/plugin-proposal-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" - integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== +"@babel/plugin-proposal-json-strings@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz#593e59c63528160233bd321b1aebe0820c2341db" + integrity sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" - integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz#02a7e961fc32e6d5b2db0649e01bf80ddee7e04a" + integrity sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-numeric-separator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.8.3.tgz#5d6769409699ec9b3b68684cd8116cedff93bad8" - integrity sha512-jWioO1s6R/R+wEHizfaScNsAx+xKgwTLNXSh7tTC4Usj3ItsPEhYkEpU4h+lpnBwq7NBVOJXfO6cRFYcX69JUQ== +"@babel/plugin-proposal-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz#ce1590ff0a65ad12970a609d78855e9a4c1aef06" + integrity sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.6.2", "@babel/plugin-proposal-object-rest-spread@^7.9.0": +"@babel/plugin-proposal-object-rest-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@^7.6.2": version "7.9.0" resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f" integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow== @@ -354,23 +550,39 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" -"@babel/plugin-proposal-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" - integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== +"@babel/plugin-proposal-optional-catch-binding@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" + integrity sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-optional-chaining@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58" - integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w== +"@babel/plugin-proposal-optional-chaining@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.10.4.tgz#750f1255e930a1f82d8cdde45031f81a0d0adff7" + integrity sha512-ZIhQIEeavTgouyMSdZRap4VPPHqJJ3NEs2cuHs5p0erH+iz6khB0qfgU8g7UuJkG88+fBMy23ZiU+nuHvekJeQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": +"@babel/plugin-proposal-private-methods@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz#b160d972b8fdba5c7d111a145fc8c421fc2a6909" + integrity sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz#4483cda53041ce3413b7fe2f00022665ddfaa75d" + integrity sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.8.8" resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.8.tgz#ee3a95e90cdc04fe8cd92ec3279fa017d68a0d1d" integrity sha512-EVhjVsMpbhLw9ZfHWSx2iy13Q8Z/eg8e8ccVWt23sWQK5l1UdkoLJPN5w69UA4uITGBnEZD2JOe4QOHycYKv8A== @@ -392,6 +604,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz#6644e6a0baa55a61f9e3231f6c9eeb6ee46c124c" + integrity sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-class-properties@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz#6cb933a8872c8d359bfde69bbeaae5162fd1e8f7" @@ -420,12 +639,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-jsx@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" - integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== +"@babel/plugin-syntax-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz#39abaae3cbf710c4373d8429484e6ba21340166c" + integrity sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.8.3" @@ -441,7 +660,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-numeric-separator@^7.8.0", "@babel/plugin-syntax-numeric-separator@^7.8.3": +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f" integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw== @@ -469,73 +695,80 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" - integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== +"@babel/plugin-syntax-top-level-await@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz#4bbeb8917b54fcf768364e0a81f560e33a3ef57d" + integrity sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-arrow-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" - integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== +"@babel/plugin-transform-arrow-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz#e22960d77e697c74f41c501d44d73dbf8a6a64cd" + integrity sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-async-to-generator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" - integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== +"@babel/plugin-transform-async-to-generator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz#41a5017e49eb6f3cda9392a51eef29405b245a37" + integrity sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ== dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-remap-async-to-generator" "^7.10.4" -"@babel/plugin-transform-block-scoped-functions@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" - integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== +"@babel/plugin-transform-block-scoped-functions@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz#1afa595744f75e43a91af73b0d998ecfe4ebc2e8" + integrity sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-block-scoping@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" - integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== +"@babel/plugin-transform-block-scoping@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d" + integrity sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - lodash "^4.17.13" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-classes@^7.9.0": - version "7.9.2" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d" - integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ== +"@babel/plugin-transform-classes@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz#405136af2b3e218bc4a1926228bc917ab1a0adc7" + integrity sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-define-map" "^7.8.3" - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-optimise-call-expression" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.6" - "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-define-map" "^7.10.4" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-optimise-call-expression" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" - integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== +"@babel/plugin-transform-computed-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz#9ded83a816e82ded28d52d4b4ecbdd810cdfc0eb" + integrity sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-destructuring@^7.8.3": - version "7.8.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b" - integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ== +"@babel/plugin-transform-destructuring@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz#70ddd2b3d1bea83d01509e9bb25ddb3a74fc85e5" + integrity sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": +"@babel/plugin-transform-dotall-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz#469c2062105c1eb6a040eaf4fac4b488078395ee" + integrity sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-dotall-regex@^7.4.4": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== @@ -543,20 +776,20 @@ "@babel/helper-create-regexp-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-duplicate-keys@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" - integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== +"@babel/plugin-transform-duplicate-keys@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz#697e50c9fee14380fe843d1f306b295617431e47" + integrity sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-exponentiation-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" - integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== +"@babel/plugin-transform-exponentiation-operator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz#5ae338c57f8cf4001bdb35607ae66b92d665af2e" + integrity sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-transform-flow-strip-types@^7.9.0": version "7.9.0" @@ -566,45 +799,55 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-flow" "^7.8.3" -"@babel/plugin-transform-for-of@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.9.0.tgz#0f260e27d3e29cd1bb3128da5e76c761aa6c108e" - integrity sha512-lTAnWOpMwOXpyDx06N+ywmF3jNbafZEqZ96CGYabxHrxNX8l5ny7dt4bK/rGwAh9utyP2b2Hv7PlZh1AAS54FQ== +"@babel/plugin-transform-for-of@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz#c08892e8819d3a5db29031b115af511dbbfebae9" + integrity sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-function-name@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" - integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== +"@babel/plugin-transform-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz#6a467880e0fc9638514ba369111811ddbe2644b7" + integrity sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg== dependencies: - "@babel/helper-function-name" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" - integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== +"@babel/plugin-transform-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz#9f42ba0841100a135f22712d0e391c462f571f3c" + integrity sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-member-expression-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" - integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== +"@babel/plugin-transform-member-expression-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz#b1ec44fcf195afcb8db2c62cd8e551c881baf8b7" + integrity sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-modules-amd@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz#19755ee721912cf5bb04c07d50280af3484efef4" - integrity sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q== +"@babel/plugin-transform-modules-amd@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz#1b9cddaf05d9e88b3aad339cb3e445c4f020a9b1" + integrity sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.9.0": +"@babel/plugin-transform-modules-commonjs@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz#66667c3eeda1ebf7896d41f1f16b17105a2fbca0" + integrity sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w== + dependencies: + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.4.4": version "7.9.6" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz#64b7474a4279ee588cacd1906695ca721687c277" integrity sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ== @@ -614,231 +857,249 @@ "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-systemjs@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz#e9fd46a296fc91e009b64e07ddaa86d6f0edeb90" - integrity sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ== +"@babel/plugin-transform-modules-systemjs@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz#6270099c854066681bae9e05f87e1b9cadbe8c85" + integrity sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw== dependencies: - "@babel/helper-hoist-variables" "^7.8.3" - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - babel-plugin-dynamic-import-node "^2.3.0" + "@babel/helper-hoist-variables" "^7.10.4" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helper-plugin-utils" "^7.10.4" + babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-umd@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.9.0.tgz#e909acae276fec280f9b821a5f38e1f08b480697" - integrity sha512-uTWkXkIVtg/JGRSIABdBoMsoIeoHQHPTL0Y2E7xf5Oj7sLqwVsNXOkNk0VJc7vF0IMBsPeikHxFjGe+qmwPtTQ== +"@babel/plugin-transform-modules-umd@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz#9a8481fe81b824654b3a0b65da3df89f3d21839e" + integrity sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA== dependencies: - "@babel/helper-module-transforms" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-module-transforms" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" - integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz#78b4d978810b6f3bcf03f9e318f2fc0ed41aecb6" + integrity sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" -"@babel/plugin-transform-new-target@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" - integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== +"@babel/plugin-transform-new-target@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz#9097d753cb7b024cb7381a3b2e52e9513a9c6888" + integrity sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-object-super@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" - integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== +"@babel/plugin-transform-object-super@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz#d7146c4d139433e7a6526f888c667e314a093894" + integrity sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" -"@babel/plugin-transform-parameters@^7.8.7": - version "7.8.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.8.tgz#0381de466c85d5404565243660c4496459525daf" - integrity sha512-hC4Ld/Ulpf1psQciWWwdnUspQoQco2bMzSrwU6TmzRlvoYQe4rQFy9vnCZDTlVeCQj0JPfL+1RX0V8hCJvkgBA== +"@babel/plugin-transform-parameters@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz#59d339d58d0b1950435f4043e74e2510005e2c4a" + integrity sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw== dependencies: - "@babel/helper-call-delegate" "^7.8.7" - "@babel/helper-get-function-arity" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-property-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" - integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== +"@babel/plugin-transform-property-literals@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz#f6fe54b6590352298785b83edd815d214c42e3c0" + integrity sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-constant-elements@^7.0.0", "@babel/plugin-transform-react-constant-elements@^7.2.0", "@babel/plugin-transform-react-constant-elements@^7.6.3": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.9.0.tgz#a75abc936a3819edec42d3386d9f1c93f28d9d9e" - integrity sha512-wXMXsToAUOxJuBBEHajqKLFWcCkOSLshTI2ChCFFj1zDd7od4IOxiwLCOObNUvOpkxLpjIuaIdBMmNt6ocCPAw== +"@babel/plugin-transform-react-constant-elements@^7.0.0", "@babel/plugin-transform-react-constant-elements@^7.2.0", "@babel/plugin-transform-react-constant-elements@^7.6.3", "@babel/plugin-transform-react-constant-elements@^7.7.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.10.4.tgz#0f485260bf1c29012bb973e7e404749eaac12c9e" + integrity sha512-cYmQBW1pXrqBte1raMkAulXmi7rjg3VI6ZLg9QIic8Hq7BtYXaWuZSxsr2siOMI6SWwpxjWfnwhTUrd7JlAV7g== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-display-name@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz#70ded987c91609f78353dd76d2fb2a0bb991e8e5" - integrity sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A== +"@babel/plugin-transform-react-display-name@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz#b5795f4e3e3140419c3611b7a2a3832b9aef328d" + integrity sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx-development@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.9.0.tgz#3c2a130727caf00c2a293f0aed24520825dbf754" - integrity sha512-tK8hWKrQncVvrhvtOiPpKrQjfNX3DtkNLSX4ObuGcpS9p0QrGetKmlySIGR07y48Zft8WVgPakqd/bk46JrMSw== +"@babel/plugin-transform-react-jsx-development@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz#6ec90f244394604623880e15ebc3c34c356258ba" + integrity sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ== dependencies: - "@babel/helper-builder-react-jsx-experimental" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" + "@babel/helper-builder-react-jsx-experimental" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx-self@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.9.0.tgz#f4f26a325820205239bb915bad8e06fcadabb49b" - integrity sha512-K2ObbWPKT7KUTAoyjCsFilOkEgMvFG+y0FqOl6Lezd0/13kMkkjHskVsZvblRPj1PHA44PrToaZANrryppzTvQ== +"@babel/plugin-transform-react-jsx-self@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz#cd301a5fed8988c182ed0b9d55e9bd6db0bd9369" + integrity sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx-source@^7.9.0": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.9.0.tgz#89ef93025240dd5d17d3122294a093e5e0183de0" - integrity sha512-K6m3LlSnTSfRkM6FcRk8saNEeaeyG5k7AVkBU2bZK3+1zdkSED3qNdsWrUgQBeTVD2Tp3VMmerxVO2yM5iITmw== +"@babel/plugin-transform-react-jsx-source@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz#34f1779117520a779c054f2cdd9680435b9222b4" + integrity sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.9.1": - version "7.9.1" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.9.1.tgz#d03af29396a6dc51bfa24eefd8005a9fd381152a" - integrity sha512-+xIZ6fPoix7h57CNO/ZeYADchg1tFyX9NDsnmNFFua8e1JNPln156mzS+8AQe1On2X2GLlANHJWHIXbMCqWDkQ== +"@babel/plugin-transform-react-jsx@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" + integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== dependencies: - "@babel/helper-builder-react-jsx" "^7.9.0" - "@babel/helper-builder-react-jsx-experimental" "^7.9.0" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-syntax-jsx" "^7.8.3" + "@babel/helper-builder-react-jsx" "^7.10.4" + "@babel/helper-builder-react-jsx-experimental" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-regenerator@^7.8.7": - version "7.8.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.7.tgz#5e46a0dca2bee1ad8285eb0527e6abc9c37672f8" - integrity sha512-TIg+gAl4Z0a3WmD3mbYSk+J9ZUH6n/Yc57rtKRnlA/7rcCvpekHXe0CMZHP1gYp7/KLe9GHTuIba0vXmls6drA== +"@babel/plugin-transform-react-pure-annotations@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz#3eefbb73db94afbc075f097523e445354a1c6501" + integrity sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-transform-regenerator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" + integrity sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw== dependencies: regenerator-transform "^0.14.2" -"@babel/plugin-transform-reserved-words@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" - integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== +"@babel/plugin-transform-reserved-words@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz#8f2682bcdcef9ed327e1b0861585d7013f8a54dd" + integrity sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-shorthand-properties@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" - integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== +"@babel/plugin-transform-shorthand-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" + integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" - integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== +"@babel/plugin-transform-spread@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" + integrity sha512-1e/51G/Ni+7uH5gktbWv+eCED9pP8ZpRhZB3jOaI3mmzfvJTWHkuyYTv0Z5PYtyM+Tr2Ccr9kUdQxn60fI5WuQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-sticky-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" - integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== +"@babel/plugin-transform-sticky-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz#8f3889ee8657581130a29d9cc91d7c73b7c4a28d" + integrity sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/helper-regex" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/helper-regex" "^7.10.4" -"@babel/plugin-transform-template-literals@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" - integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== +"@babel/plugin-transform-template-literals@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz#78bc5d626a6642db3312d9d0f001f5e7639fde8c" + integrity sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw== dependencies: - "@babel/helper-annotate-as-pure" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-typeof-symbol@^7.8.4": - version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" - integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== +"@babel/plugin-transform-typeof-symbol@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz#9509f1a7eec31c4edbffe137c16cc33ff0bc5bfc" + integrity sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-unicode-regex@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" - integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== +"@babel/plugin-transform-unicode-escapes@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz#feae523391c7651ddac115dae0a9d06857892007" + integrity sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-plugin-utils" "^7.10.4" -"@babel/preset-env@^7.4.5": - version "7.9.0" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.9.0.tgz#a5fc42480e950ae8f5d9f8f2bbc03f52722df3a8" - integrity sha512-712DeRXT6dyKAM/FMbQTV/FvRCms2hPCx+3weRjZ8iQVQWZejWWk1wwG6ViWMyqb/ouBbGOl5b6aCk0+j1NmsQ== +"@babel/plugin-transform-unicode-regex@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz#e56d71f9282fac6db09c82742055576d5e6d80a8" + integrity sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A== dependencies: - "@babel/compat-data" "^7.9.0" - "@babel/helper-compilation-targets" "^7.8.7" - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-proposal-async-generator-functions" "^7.8.3" - "@babel/plugin-proposal-dynamic-import" "^7.8.3" - "@babel/plugin-proposal-json-strings" "^7.8.3" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-proposal-numeric-separator" "^7.8.3" - "@babel/plugin-proposal-object-rest-spread" "^7.9.0" - "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" - "@babel/plugin-proposal-optional-chaining" "^7.9.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/helper-create-regexp-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/preset-env@^7.4.5", "@babel/preset-env@^7.9.5": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" + integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== + dependencies: + "@babel/compat-data" "^7.10.4" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.10.4" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" "@babel/plugin-syntax-dynamic-import" "^7.8.0" "@babel/plugin-syntax-json-strings" "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - "@babel/plugin-transform-arrow-functions" "^7.8.3" - "@babel/plugin-transform-async-to-generator" "^7.8.3" - "@babel/plugin-transform-block-scoped-functions" "^7.8.3" - "@babel/plugin-transform-block-scoping" "^7.8.3" - "@babel/plugin-transform-classes" "^7.9.0" - "@babel/plugin-transform-computed-properties" "^7.8.3" - "@babel/plugin-transform-destructuring" "^7.8.3" - "@babel/plugin-transform-dotall-regex" "^7.8.3" - "@babel/plugin-transform-duplicate-keys" "^7.8.3" - "@babel/plugin-transform-exponentiation-operator" "^7.8.3" - "@babel/plugin-transform-for-of" "^7.9.0" - "@babel/plugin-transform-function-name" "^7.8.3" - "@babel/plugin-transform-literals" "^7.8.3" - "@babel/plugin-transform-member-expression-literals" "^7.8.3" - "@babel/plugin-transform-modules-amd" "^7.9.0" - "@babel/plugin-transform-modules-commonjs" "^7.9.0" - "@babel/plugin-transform-modules-systemjs" "^7.9.0" - "@babel/plugin-transform-modules-umd" "^7.9.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" - "@babel/plugin-transform-new-target" "^7.8.3" - "@babel/plugin-transform-object-super" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.8.7" - "@babel/plugin-transform-property-literals" "^7.8.3" - "@babel/plugin-transform-regenerator" "^7.8.7" - "@babel/plugin-transform-reserved-words" "^7.8.3" - "@babel/plugin-transform-shorthand-properties" "^7.8.3" - "@babel/plugin-transform-spread" "^7.8.3" - "@babel/plugin-transform-sticky-regex" "^7.8.3" - "@babel/plugin-transform-template-literals" "^7.8.3" - "@babel/plugin-transform-typeof-symbol" "^7.8.4" - "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.10.4" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.9.0" - browserslist "^4.9.1" + "@babel/types" "^7.10.4" + browserslist "^4.12.0" core-js-compat "^3.6.2" invariant "^2.2.2" levenary "^1.1.1" @@ -863,17 +1124,18 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-react@^7.0.0": - version "7.9.1" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.9.1.tgz#b346403c36d58c3bb544148272a0cefd9c28677a" - integrity sha512-aJBYF23MPj0RNdp/4bHnAP0NVqqZRr9kl0NAOP4nJCex6OYVio59+dnQzsAWFuogdLyeaKA1hmfUIVZkY5J+TQ== +"@babel/preset-react@^7.0.0", "@babel/preset-react@^7.9.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz#92e8a66d816f9911d11d4cc935be67adfc82dbcf" + integrity sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw== dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - "@babel/plugin-transform-react-display-name" "^7.8.3" - "@babel/plugin-transform-react-jsx" "^7.9.1" - "@babel/plugin-transform-react-jsx-development" "^7.9.0" - "@babel/plugin-transform-react-jsx-self" "^7.9.0" - "@babel/plugin-transform-react-jsx-source" "^7.9.0" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.10.4" + "@babel/plugin-transform-react-jsx" "^7.10.4" + "@babel/plugin-transform-react-jsx-development" "^7.10.4" + "@babel/plugin-transform-react-jsx-self" "^7.10.4" + "@babel/plugin-transform-react-jsx-source" "^7.10.4" + "@babel/plugin-transform-react-pure-annotations" "^7.10.4" "@babel/runtime-corejs2@^7.8.7": version "7.10.3" @@ -899,13 +1161,22 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": - version "7.10.3" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz#670d002655a7c366540c67f6fd3342cd09500364" - integrity sha512-RzGO0RLSdokm9Ipe/YD+7ww8X2Ro79qiXZF3HU9ljrM+qnJmH1Vqth+hbiQZy761LnMJTMitHDuKVYTk3k4dLw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2", "@babel/runtime@^7.9.6": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" @@ -915,7 +1186,7 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.6": +"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== @@ -930,7 +1201,22 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.8.7", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": +"@babel/traverse@^7.10.4": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6": version "7.9.6" resolved "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" integrity sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA== @@ -939,6 +1225,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.10.4", "@babel/types@^7.10.5": + version "7.10.5" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1184,43 +1479,148 @@ unique-filename "^1.1.1" which "^1.3.1" -"@hapi/address@^2.1.2": - version "2.1.4" - resolved "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" - integrity sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ== - -"@hapi/formula@^1.2.0": - version "1.2.0" - resolved "https://registry.npmjs.org/@hapi/formula/-/formula-1.2.0.tgz#994649c7fea1a90b91a0a1e6d983523f680e10cd" - integrity sha512-UFbtbGPjstz0eWHb+ga/GM3Z9EzqKXFWIbSOFURU0A/Gku0Bky4bCk9/h//K2Xr3IrCfjFNhMm4jyZ5dbCewGA== - -"@hapi/hoek@^8.2.4", "@hapi/hoek@^8.3.0": - version "8.5.1" - resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-8.5.1.tgz#fde96064ca446dec8c55a8c2f130957b070c6e06" - integrity sha512-yN7kbciD87WzLGc5539Tn0sApjyiGHAJgKvG9W8C7O+6c7qmoQMfVs0W4bX17eqz6C78QJqqFrtgdK5EWf6Qow== - -"@hapi/joi@^16.1.8": - version "16.1.8" - resolved "https://registry.npmjs.org/@hapi/joi/-/joi-16.1.8.tgz#84c1f126269489871ad4e2decc786e0adef06839" - integrity sha512-wAsVvTPe+FwSrsAurNt5vkg3zo+TblvC5Bb1zMVK6SJzZqw9UrJnexxR+76cpePmtUZKHAPxcQ2Bf7oVHyahhg== +"@graphql-tools/delegate@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-6.0.15.tgz#9e060bfc31fe7735bd5b2b401e98dea3fa5d3b25" + integrity sha512-GG/zp29PMfG6eXpfe1M5C3U1EI1f3tJu2glFN8t0RIfp4FEgZs/PRvZuuep5orFge8dvX/LQpJY8Vl2JmU4WMg== dependencies: - "@hapi/address" "^2.1.2" - "@hapi/formula" "^1.2.0" - "@hapi/hoek" "^8.2.4" - "@hapi/pinpoint" "^1.0.2" - "@hapi/topo" "^3.1.3" + "@ardatan/aggregate-error" "0.0.1" + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" -"@hapi/pinpoint@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-1.0.2.tgz#025b7a36dbbf4d35bf1acd071c26b20ef41e0d13" - integrity sha512-dtXC/WkZBfC5vxscazuiJ6iq4j9oNx1SHknmIr8hofarpKUZKmlUVYVIhNVzIEgK5Wrc4GMHL5lZtt1uS2flmQ== - -"@hapi/topo@^3.1.3": - version "3.1.6" - resolved "https://registry.npmjs.org/@hapi/topo/-/topo-3.1.6.tgz#68d935fa3eae7fdd5ab0d7f953f3205d8b2bfc29" - integrity sha512-tAag0jEcjwH+P2quUfipd7liWCNX2F8NvYjQp2wtInsZxnMlypdw0FtAOLxtvvkO+GSRRbmNi8m/5y42PQJYCQ== +"@graphql-tools/graphql-file-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.0.15.tgz#e186b8147397bd7510e1b3318f5d3f7d365fc4e1" + integrity sha512-QbCf731A2A2hrHP+cMSAKvY3D7IauFNqp5bAGdbLwSHRqaxUIfKi7Q76/9pZ3rN/e6yu/zVz+t1rkf7lT2/8OA== dependencies: - "@hapi/hoek" "^8.3.0" + "@graphql-tools/import" "6.0.15" + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + tslib "~2.0.0" + +"@graphql-tools/import@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/import/-/import-6.0.15.tgz#d6a9d3b6199a1e07d80d8021635d317b2c190c6f" + integrity sha512-YaQizD031nlrObiAJj+DO+0Wf2ompR2G5OFNQZIOgUlm1+kfH3GPIFoE5Ww74YH6vy9s4UyYYeZJz6APxPdMzg== + dependencies: + fs-extra "9.0.1" + resolve-from "5.0.0" + +"@graphql-tools/json-file-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/json-file-loader/-/json-file-loader-6.0.15.tgz#e341a5fed1ef3af8f8bfde17ed9a14e1a6df36c0" + integrity sha512-SQO7w+KPxW6Q3snE3G4eNOA8CcBBDYHpk8JILj93oe4BassuPY5NCUOeZ+2PYczwZQbTNDQXeW1oQou44U1aBg== + dependencies: + "@graphql-tools/utils" "6.0.15" + fs-extra "9.0.1" + tslib "~2.0.0" + +"@graphql-tools/load@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/load/-/load-6.0.15.tgz#f2012b8938b3a535dbafefbb719997684028f30f" + integrity sha512-STH3ZjbViRqDyCw+f7PZrnDs6yhP7m2l4x5lJBMyMeLaLwuO1z+WhgtqYZNpCYlQY2jNSLXWCa0nWmpYvdLnlA== + dependencies: + "@graphql-tools/merge" "6.0.15" + "@graphql-tools/utils" "6.0.15" + globby "11.0.1" + import-from "3.0.0" + is-glob "4.0.1" + p-limit "3.0.2" + tslib "~2.0.0" + unixify "1.0.0" + valid-url "1.0.9" + +"@graphql-tools/merge@6.0.15", "@graphql-tools/merge@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/merge/-/merge-6.0.15.tgz#09c84bd08971edfb3e1035d71432b47687820bdc" + integrity sha512-qusTLzkf6GtxS6LRQnEAWIwA1BeJj5SkZ2pnE4/wVe9gs0grpEsOKYxvGpBi8IZR7r8UeNpkdgk2HP0jlq/WWA== + dependencies: + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@graphql-tools/schema@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/schema/-/schema-6.0.15.tgz#b016f9f36820342982887a291baa7e7d11b039ae" + integrity sha512-Wo+d6/OPjeXjwB1pcqsWmqLdweGH+BVhvKe/YPQA/uiWr8ikgShvNLNiuF03gc/1AMR487A09XcPEyabRKJLew== + dependencies: + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@graphql-tools/url-loader@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/url-loader/-/url-loader-6.0.15.tgz#3ad621ed75c03fbc0ea08d2560f379a053c4035f" + integrity sha512-/iGuK7J9yCECYMYQJqKNWnz4ytPHppkxh4YS5Ud9QPDNl488e+eInyNbkdiWcFGyZ4KHqEnXSDdRFg3mFNrMnw== + dependencies: + "@graphql-tools/delegate" "6.0.15" + "@graphql-tools/utils" "6.0.15" + "@graphql-tools/wrap" "6.0.15" + "@types/websocket" "1.0.1" + cross-fetch "3.0.5" + subscriptions-transport-ws "0.9.17" + tslib "~2.0.0" + valid-url "1.0.9" + websocket "1.0.31" + +"@graphql-tools/utils@6.0.15", "@graphql-tools/utils@^6.0.0": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-6.0.15.tgz#6d54d383285bea3c22797531933b62a408e78e49" + integrity sha512-VG5cMLPgh9RDLGHamGpXVnBrNw7bZGT46LrxK7IIqDZI9H0GPsRCo8+p+CfDkw0IlDiEECb624WVCpm9IYNecA== + dependencies: + "@ardatan/aggregate-error" "0.0.1" + camel-case "4.1.1" + +"@graphql-tools/wrap@6.0.15": + version "6.0.15" + resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.0.15.tgz#1b4a43d406f3894e4cbc931f9d4e0d282ab0076e" + integrity sha512-yWiDBrbzml6PRl4aeJBLNGPw385LFtszMfkfYwjLSWvNyVILDCMa/XWHThw4FMaZ1nPL0GuLggW2bVkUBi3TYA== + dependencies: + "@graphql-tools/delegate" "6.0.15" + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + aggregate-error "3.0.1" + tslib "~2.0.0" + +"@hapi/address@^4.0.1": + version "4.1.0" + resolved "https://registry.npmjs.org/@hapi/address/-/address-4.1.0.tgz#d60c5c0d930e77456fdcde2598e77302e2955e1d" + integrity sha512-SkszZf13HVgGmChdHo/PxchnSaCJ6cetVqLzyciudzZRT0jcOouIF/Q93mgjw8cce+D+4F4C1Z/WrfFN+O3VHQ== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@hapi/formula@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz#edade0619ed58c8e4f164f233cda70211e787128" + integrity sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A== + +"@hapi/hoek@^9.0.0": + version "9.0.4" + resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz#e80ad4e8e8d2adc6c77d985f698447e8628b6010" + integrity sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw== + +"@hapi/joi@^17.1.1": + version "17.1.1" + resolved "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz#9cc8d7e2c2213d1e46708c6260184b447c661350" + integrity sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg== + dependencies: + "@hapi/address" "^4.0.1" + "@hapi/formula" "^2.0.0" + "@hapi/hoek" "^9.0.0" + "@hapi/pinpoint" "^2.0.0" + "@hapi/topo" "^5.0.0" + +"@hapi/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz#805b40d4dbec04fc116a73089494e00f073de8df" + integrity sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw== + +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" "@hot-loader/react-dom@^16.13.0": version "16.13.0" @@ -2444,6 +2844,59 @@ resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6" integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + "@reach/router@^1.2.1": version "1.3.3" resolved "https://registry.npmjs.org/@reach/router/-/router-1.3.3.tgz#58162860dce6c9449d49be86b0561b5ef46d80db" @@ -2455,9 +2908,9 @@ react-lifecycles-compat "^3.0.4" "@rjsf/core@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.1.0.tgz#00130c89959850cc90224fd14c82feaecc2b9dc8" - integrity sha512-2A65RZ3I/RVVNfJUTYUkFs4snX02GHzql6o/KcLBBoG8G8+gr9ZeIi3+JEbe2mOEN02rIPnyJtBkl6C7QajyAw== + version "2.2.2" + resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.2.2.tgz#1ebb6fe47448998f3b54e2dea8d58de8a46014dc" + integrity sha512-4d6DHIiTJEkUq5vyl4LIxLGIYYKKnHcprf94oVchUtGQvRFjNUDFxeFQoyr90oaxcBMs2WDDcCgjcFaKVyfErg== dependencies: "@babel/runtime-corejs2" "^7.8.7" "@types/json-schema" "^7.0.4" @@ -2497,19 +2950,28 @@ "@rollup/pluginutils" "^3.0.8" "@rollup/plugin-node-resolve@^8.1.0": - version "8.1.0" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.1.0.tgz#1da5f3d0ccabc8f66f5e3c74462aad76cfd96c47" - integrity sha512-ovq7ZM3JJYUUmEjjO+H8tnUdmQmdQudJB7xruX8LFZ1W2q8jXdPUS6SsIYip8ByOApu4RR7729Am9WhCeCMiHA== + version "8.4.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz#261d79a680e9dc3d86761c14462f24126ba83575" + integrity sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ== dependencies: - "@rollup/pluginutils" "^3.0.8" - "@types/resolve" "0.0.8" + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" builtin-modules "^3.1.0" deep-freeze "^0.0.1" deepmerge "^4.2.2" is-module "^1.0.0" - resolve "^1.14.2" + resolve "^1.17.0" -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": +"@rollup/plugin-yaml@^2.1.1": + version "2.1.1" + resolved "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-2.1.1.tgz#6c864adee22004eb325d53bc4ee5553828e056d8" + integrity sha512-CnGD3dbDhP+JeKFX7kJuaBOa7jVzXpl3G8lXp1hasB87cDYOz7qNWd4cFPmYbxMsT/8OCVsceIpRtQ+cj9uxBw== + dependencies: + "@rollup/pluginutils" "^3.0.1" + js-yaml "^3.13.1" + tosource "^1.0.0" + +"@rollup/pluginutils@^3.0.1", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -3102,41 +3564,81 @@ resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" integrity sha512-j7KnilGyZzYr/jhcrSYS3FGWMZVaqyCG0vzMCwzvei0coIkczuYMcniK07nI0aHJINciujjH11T72ICW5eL5Ig== +"@svgr/babel-plugin-add-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" + integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== + "@svgr/babel-plugin-remove-jsx-attribute@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-4.2.0.tgz#297550b9a8c0c7337bea12bdfc8a80bb66f85abc" integrity sha512-3XHLtJ+HbRCH4n28S7y/yZoEQnRpl0tvTZQsHqvaeNXPra+6vE5tbRliH3ox1yZYPCxrlqaJT/Mg+75GpDKlvQ== +"@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" + integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== + "@svgr/babel-plugin-remove-jsx-empty-expression@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-4.2.0.tgz#c196302f3e68eab6a05e98af9ca8570bc13131c7" integrity sha512-yTr2iLdf6oEuUE9MsRdvt0NmdpMBAkgK8Bjhl6epb+eQWk6abBaX3d65UZ3E3FWaOwePyUgNyNCMVG61gGCQ7w== +"@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" + integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== + "@svgr/babel-plugin-replace-jsx-attribute-value@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-4.2.0.tgz#310ec0775de808a6a2e4fd4268c245fd734c1165" integrity sha512-U9m870Kqm0ko8beHawRXLGLvSi/ZMrl89gJ5BNcT452fAjtF2p4uRzXkdzvGJJJYBgx7BmqlDjBN/eCp5AAX2w== +"@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" + integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== + "@svgr/babel-plugin-svg-dynamic-title@^4.3.3": version "4.3.3" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-4.3.3.tgz#2cdedd747e5b1b29ed4c241e46256aac8110dd93" integrity sha512-w3Be6xUNdwgParsvxkkeZb545VhXEwjGMwExMVBIdPQJeyMQHqm9Msnb2a1teHBqUYL66qtwfhNkbj1iarCG7w== +"@svgr/babel-plugin-svg-dynamic-title@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" + integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== + "@svgr/babel-plugin-svg-em-dimensions@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-4.2.0.tgz#9a94791c9a288108d20a9d2cc64cac820f141391" integrity sha512-C0Uy+BHolCHGOZ8Dnr1zXy/KgpBOkEUYY9kI/HseHVPeMbluaX3CijJr7D4C5uR8zrc1T64nnq/k63ydQuGt4w== +"@svgr/babel-plugin-svg-em-dimensions@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" + integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== + "@svgr/babel-plugin-transform-react-native-svg@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-4.2.0.tgz#151487322843359a1ca86b21a3815fd21a88b717" integrity sha512-7YvynOpZDpCOUoIVlaaOUU87J4Z6RdD6spYN4eUb5tfPoKGSF9OG2NuhgYnq4jSkAxcpMaXWPf1cePkzmqTPNw== +"@svgr/babel-plugin-transform-react-native-svg@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" + integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== + "@svgr/babel-plugin-transform-svg-component@^4.2.0": version "4.2.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-4.2.0.tgz#5f1e2f886b2c85c67e76da42f0f6be1b1767b697" integrity sha512-hYfYuZhQPCBVotABsXKSCfel2slf/yvJY8heTVX1PCTaq/IgASq1IyxPPKJ0chWREEKewIU/JMSsIGBtK1KKxw== +"@svgr/babel-plugin-transform-svg-component@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.4.0.tgz#a2212b4d018e6075a058bb7e220a66959ef7a03c" + integrity sha512-zLl4Fl3NvKxxjWNkqEcpdSOpQ3LGVH2BNFQ6vjaK6sFo2IrSznrhURIPI0HAphKiiIwNYjAfE0TNoQDSZv0U9A== + "@svgr/babel-preset@^4.3.3": version "4.3.3" resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-4.3.3.tgz#a75d8c2f202ac0e5774e6bfc165d028b39a1316c" @@ -3151,6 +3653,20 @@ "@svgr/babel-plugin-transform-react-native-svg" "^4.2.0" "@svgr/babel-plugin-transform-svg-component" "^4.2.0" +"@svgr/babel-preset@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.4.0.tgz#da21854643e1c4ad2279239baa7d5a8b128c1f15" + integrity sha512-Gyx7cCxua04DBtyILTYdQxeO/pwfTBev6+eXTbVbxe4HTGhOUW6yo7PSbG2p6eJMl44j6XSequ0ZDP7bl0nu9A== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-attribute" "^5.4.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "^5.0.1" + "@svgr/babel-plugin-replace-jsx-attribute-value" "^5.0.1" + "@svgr/babel-plugin-svg-dynamic-title" "^5.4.0" + "@svgr/babel-plugin-svg-em-dimensions" "^5.4.0" + "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" + "@svgr/babel-plugin-transform-svg-component" "^5.4.0" + "@svgr/core@^4.3.3": version "4.3.3" resolved "https://registry.npmjs.org/@svgr/core/-/core-4.3.3.tgz#b37b89d5b757dc66e8c74156d00c368338d24293" @@ -3160,6 +3676,15 @@ camelcase "^5.3.1" cosmiconfig "^5.2.1" +"@svgr/core@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/core/-/core-5.4.0.tgz#655378ee43679eb94fee3d4e1976e38252dff8e7" + integrity sha512-hWGm1DCCvd4IEn7VgDUHYiC597lUYhFau2lwJBYpQWDirYLkX4OsXu9IslPgJ9UpP7wsw3n2Ffv9sW7SXJVfqQ== + dependencies: + "@svgr/plugin-jsx" "^5.4.0" + camelcase "^6.0.0" + cosmiconfig "^6.0.0" + "@svgr/hast-util-to-babel-ast@^4.3.2": version "4.3.2" resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-4.3.2.tgz#1d5a082f7b929ef8f1f578950238f630e14532b8" @@ -3167,6 +3692,13 @@ dependencies: "@babel/types" "^7.4.4" +"@svgr/hast-util-to-babel-ast@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.4.0.tgz#bb5d002e428f510aa5b53ec0a02377a95b367715" + integrity sha512-+U0TZZpPsP2V1WvVhqAOSTk+N+CjYHdZx+x9UBa1eeeZDXwH8pt0CrQf2+SvRl/h2CAPRFkm+Ey96+jKP8Bsgg== + dependencies: + "@babel/types" "^7.9.5" + "@svgr/plugin-jsx@4.3.x", "@svgr/plugin-jsx@^4.3.3": version "4.3.3" resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-4.3.3.tgz#e2ba913dbdfbe85252a34db101abc7ebd50992fa" @@ -3177,6 +3709,16 @@ "@svgr/hast-util-to-babel-ast" "^4.3.2" svg-parser "^2.0.0" +"@svgr/plugin-jsx@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.4.0.tgz#ab47504c55615833c6db70fca2d7e489f509787c" + integrity sha512-SGzO4JZQ2HvGRKDzRga9YFSqOqaNrgLlQVaGvpZ2Iht2gwRp/tq+18Pvv9kS9ZqOMYgyix2LLxZMY1LOe9NPqw== + dependencies: + "@babel/core" "^7.7.5" + "@svgr/babel-preset" "^5.4.0" + "@svgr/hast-util-to-babel-ast" "^5.4.0" + svg-parser "^2.0.2" + "@svgr/plugin-svgo@4.3.x", "@svgr/plugin-svgo@^4.3.1": version "4.3.1" resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-4.3.1.tgz#daac0a3d872e3f55935c6588dd370336865e9e32" @@ -3186,19 +3728,28 @@ merge-deep "^3.0.2" svgo "^1.2.2" -"@svgr/rollup@4.3.x": - version "4.3.3" - resolved "https://registry.npmjs.org/@svgr/rollup/-/rollup-4.3.3.tgz#db8bc2746ae0930c14cba2409f417a6ac6aadb38" - integrity sha512-YwgnXN8xPRYFhkfoTUiZktjkjolthaK/lz0okzU09VcBvjx08R7yK1IEwXH3c98sMn8ORdNdiy4Qox78CMjljg== +"@svgr/plugin-svgo@^5.4.0": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.4.0.tgz#45d9800b7099a6f7b4d85ebac89ab9abe8592f64" + integrity sha512-3Cgv3aYi1l6SHyzArV9C36yo4kgwVdF3zPQUC6/aCDUeXAofDYwE5kk3e3oT5ZO2a0N3lB+lLGvipBG6lnG8EA== dependencies: - "@babel/core" "^7.4.5" - "@babel/plugin-transform-react-constant-elements" "^7.0.0" - "@babel/preset-env" "^7.4.5" - "@babel/preset-react" "^7.0.0" - "@svgr/core" "^4.3.3" - "@svgr/plugin-jsx" "^4.3.3" - "@svgr/plugin-svgo" "^4.3.1" - rollup-pluginutils "^2.8.1" + cosmiconfig "^6.0.0" + merge-deep "^3.0.2" + svgo "^1.2.2" + +"@svgr/rollup@5.4.x": + version "5.4.0" + resolved "https://registry.npmjs.org/@svgr/rollup/-/rollup-5.4.0.tgz#b1946c8d2c13870397af014a105d40945b7371e5" + integrity sha512-hwYjrTddW6mFU9vwqRr1TULNvxiIxGdIbqrD5J7vtoATSfWazq/2JSnT4BmiH+/4kFXLEtjKuSKoDUotkOIAkg== + dependencies: + "@babel/core" "^7.7.5" + "@babel/plugin-transform-react-constant-elements" "^7.7.4" + "@babel/preset-env" "^7.9.5" + "@babel/preset-react" "^7.9.4" + "@svgr/core" "^5.4.0" + "@svgr/plugin-jsx" "^5.4.0" + "@svgr/plugin-svgo" "^5.4.0" + rollup-pluginutils "^2.8.2" "@svgr/webpack@4.3.x", "@svgr/webpack@^4.0.3": version "4.3.3" @@ -3256,12 +3807,12 @@ redent "^3.0.0" "@testing-library/react-hooks@^3.3.0": - version "3.3.0" - resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.3.0.tgz#dc217bfce8e7c34a99c811d73d23feef957b7c1d" - integrity sha512-rE9geI1+HJ6jqXkzzJ6abREbeud6bLF8OmF+Vyc7gBoPwZAEVBYjbC1up5nNoVfYBhO5HUwdD4u9mTehAUeiyw== + version "3.4.1" + resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.4.1.tgz#1f8ccd21208086ec228d9743fe40b69d0efcd7e5" + integrity sha512-LbzvE7oKsVzuW1cxA/aOeNgeVvmHWG2p/WSzalIGyWuqZT3jVcNDT5KPEwy36sUYWde0Qsh32xqIUFXukeywXg== dependencies: "@babel/runtime" "^7.5.4" - "@types/testing-library__react-hooks" "^3.0.0" + "@types/testing-library__react-hooks" "^3.3.0" "@testing-library/react@^10.4.1": version "10.4.3" @@ -3333,6 +3884,13 @@ "@theme-ui/core" "^0.3.1" "@theme-ui/mdx" "^0.3.0" +"@types/accepts@*", "@types/accepts@^1.3.5": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" + integrity sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ== + dependencies: + "@types/node" "*" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -3371,7 +3929,7 @@ dependencies: "@babel/types" "^7.3.0" -"@types/body-parser@*", "@types/body-parser@^1.19.0": +"@types/body-parser@*", "@types/body-parser@1.19.0", "@types/body-parser@^1.19.0": version "1.19.0" resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== @@ -3439,6 +3997,11 @@ dependencies: "@types/node" "*" +"@types/content-disposition@*": + version "0.5.3" + resolved "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.3.tgz#0aa116701955c2faa0717fc69cd1596095e49d96" + integrity sha512-P1bffQfhD3O4LW0ioENXUhZ9OIa0Zn+P7M+pWgkCKaT53wVLSq0mrKksCID/FGHpFhRSxRGhgrQmfhRuzwtKdg== + "@types/cookie-parser@^1.4.2": version "1.4.2" resolved "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz#e4d5c5ffda82b80672a88a4281aaceefb1bd9df5" @@ -3456,7 +4019,17 @@ resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== -"@types/cors@^2.8.6": +"@types/cookies@*": + version "0.7.4" + resolved "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz#26dedf791701abc0e36b5b79a5722f40e455f87b" + integrity sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw== + dependencies: + "@types/connect" "*" + "@types/express" "*" + "@types/keygrip" "*" + "@types/node" "*" + +"@types/cors@^2.8.4", "@types/cors@^2.8.6": version "2.8.6" resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02" integrity sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg== @@ -3487,6 +4060,13 @@ dependencies: "@types/node" "*" +"@types/dockerode@^2.5.34": + version "2.5.34" + resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-2.5.34.tgz#9adb884f7cc6c012a6eb4b2ad794cc5d01439959" + integrity sha512-LcbLGcvcBwBAvjH9UrUI+4qotY+A5WCer5r43DR5XHv2ZIEByNXFdPLo1XxR+v/BjkGjlggW8qUiXuVEhqfkpA== + dependencies: + "@types/node" "*" + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -3516,23 +4096,41 @@ integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": - version "4.17.5" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.5.tgz#a00ac7dadd746ae82477443e4d480a6a93ea083c" - integrity sha512-578YH5Lt88AKoADy0b2jQGwJtrBxezXtVe/MBqWXKZpqx91SnC0pVkVCcxcytz3lWW+cHBYDi3Ysh0WXc+rAYw== + version "4.17.9" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" + integrity sha512-DG0BYg6yO+ePW+XoDENYz8zhNGC3jDDEpComMYn7WJc4mY1Us8Rw9ax2YhJXxpyk2SF47PQAoQ0YyVT1a0bEkA== dependencies: "@types/node" "*" + "@types/qs" "*" "@types/range-parser" "*" "@types/express@*", "@types/express@^4.17.6": - version "4.17.6" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e" - integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w== + version "4.17.7" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.7.tgz#42045be6475636d9801369cd4418ef65cdb0dd59" + integrity sha512-dCOT5lcmV/uC2J9k0rPafATeeyz+99xTt54ReX11/LObZgfzJqZNcW27zGhYyX+9iSEGXGt5qLPwRSvBZcLvtQ== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "*" "@types/qs" "*" "@types/serve-static" "*" +"@types/express@4.17.4": + version "4.17.4" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz#e78bf09f3f530889575f4da8a94cd45384520aac" + integrity sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "*" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/fs-capacitor@*": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e" + integrity sha512-FKVPOCFbhCvZxpVAMhdBdTfVfXUpsh15wFHgqOKxh9N9vzWZVuWCSijZ5T4U34XYNnuj2oduh6xcs1i+LPI+BQ== + dependencies: + "@types/node" "*" + "@types/fs-extra@^9.0.1": version "9.0.1" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" @@ -3545,6 +4143,11 @@ resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.0.tgz#aac1315a44fa4ed5a52c3820f6c3c2fb79cbd12d" integrity sha512-kA2RxBT/r/ZuDDKwMl+vFWn1Z0lfm1/Ik6Qb91wnSzyzCDa/fkM8gIOq6ruB7xfr37n6Mj5dyivileUVKsidlg== +"@types/github-slugger@^1.3.0": + version "1.3.0" + resolved "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz#16ab393b30d8ae2a111ac748a015ac05a1fc5524" + integrity sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g== + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -3566,6 +4169,16 @@ dependencies: "@types/node" "*" +"@types/graphql-upload@^8.0.0": + version "8.0.3" + resolved "https://registry.npmjs.org/@types/graphql-upload/-/graphql-upload-8.0.3.tgz#b371edb5f305a2a1f7b7843a890a2a7adc55c3ec" + integrity sha512-hmLg9pCU/GmxBscg8GCr1vmSoEmbItNNxdD5YH2TJkXm//8atjwuprB+xJBK714JG1dkxbbhp5RHX+Pz1KsCMA== + dependencies: + "@types/express" "*" + "@types/fs-capacitor" "*" + "@types/koa" "*" + graphql "^14.5.3" + "@types/helmet@^0.0.47": version "0.0.47" resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.47.tgz#ec5161541b649142205b7c558bca14801c5ce129" @@ -3601,10 +4214,15 @@ "@types/tapable" "*" "@types/webpack" "*" +"@types/http-assert@*": + version "1.5.1" + resolved "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz#d775e93630c2469c2f980fc27e3143240335db3b" + integrity sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ== + "@types/http-errors@^1.6.3": - version "1.6.3" - resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.6.3.tgz#619a55768eab98299e8f76747339f3373f134e69" - integrity sha512-4KCE/agIcoQ9bIfa4sBxbZdnORzRjIw8JNQPLfqoNv7wQl/8f8mRbW68Q8wBsQFoJkPUHGlQYZ9sqi5WpfGSEQ== + version "1.8.0" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz#682477dbbbd07cd032731cb3b0e7eaee3d026b69" + integrity sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA== "@types/http-proxy-middleware@*": version "0.19.3" @@ -3655,18 +4273,18 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@*", "@types/jest@^25.2.2": - version "25.2.3" - resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" - integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== +"@types/jest@*", "@types/jest@^26.0.7": + version "26.0.9" + resolved "https://registry.npmjs.org/@types/jest/-/jest-26.0.9.tgz#0543b57da5f0cd949c5f423a00c56c492289c989" + integrity sha512-k4qFfJ5AUKrWok5KYXp2EPm89b0P/KZpl7Vg4XuOTVVQEhLDBDBU3iBFrjjdgd8fLw96aAtmnwhXHl63bWeBQQ== dependencies: jest-diff "^25.2.1" pretty-format "^25.2.1" "@types/jquery@^3.3.34": - version "3.3.38" - resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.38.tgz#6385f1e1b30bd2bff55ae8ee75ea42a999cc3608" - integrity sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA== + version "3.5.1" + resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz#cebb057acf5071c40e439f30e840c57a30d406c3" + integrity sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg== dependencies: "@types/sizzle" "*" @@ -3675,12 +4293,7 @@ resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== -"@types/json-schema@*", "@types/json-schema@^7.0.3": - version "7.0.4" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" - integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== - -"@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5": +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5": version "7.0.5" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== @@ -3695,10 +4308,40 @@ resolved "https://registry.npmjs.org/@types/jwt-decode/-/jwt-decode-2.2.1.tgz#afdf5c527fcfccbd4009b5fd02d1e18241f2d2f2" integrity sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A== +"@types/keygrip@*": + version "1.0.2" + resolved "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" + integrity sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw== + +"@types/koa-compose@*": + version "3.2.5" + resolved "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz#85eb2e80ac50be95f37ccf8c407c09bbe3468e9d" + integrity sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ== + dependencies: + "@types/koa" "*" + +"@types/koa@*": + version "2.11.3" + resolved "https://registry.npmjs.org/@types/koa/-/koa-2.11.3.tgz#540ece376581b12beadf9a417dd1731bc31c16ce" + integrity sha512-ABxVkrNWa4O/Jp24EYI/hRNqEVRlhB9g09p48neQp4m3xL1TJtdWk2NyNQSMCU45ejeELMQZBYyfstyVvO2H3Q== + dependencies: + "@types/accepts" "*" + "@types/content-disposition" "*" + "@types/cookies" "*" + "@types/http-assert" "*" + "@types/keygrip" "*" + "@types/koa-compose" "*" + "@types/node" "*" + "@types/lodash@^4.14.151": - version "4.14.155" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.155.tgz#e2b4514f46a261fd11542e47519c20ebce7bc23a" - integrity sha512-vEcX7S7aPhsBCivxMwAANQburHBtfN9RdyXFk84IJmu2Z4Hkg1tOFgaslRiEqqvoLtbCBi6ika1EMspE+NZ9Lg== + version "4.14.159" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.159.tgz#61089719dc6fdd9c5cb46efc827f2571d1517065" + integrity sha512-gF7A72f7WQN33DpqOWw9geApQPh4M3PxluMtaHxWHXEGSN12/WbcEk/eNSqWNQcQhF66VSZ06vCF94CrHwXJDg== + +"@types/long@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== "@types/mime@*": version "2.0.1" @@ -3731,7 +4374,7 @@ dependencies: "@types/node" "*" -"@types/node-fetch@^2.5.7": +"@types/node-fetch@2.5.7", "@types/node-fetch@^2.5.7": version "2.5.7" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== @@ -3739,20 +4382,30 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@^13.7.2": - version "13.9.2" - resolved "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" - integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== +"@types/node@*", "@types/node@>= 8": + version "14.0.26" + resolved "https://registry.npmjs.org/@types/node/-/node-14.0.26.tgz#22a3b8a46510da8944b67bfc27df02c34a35331c" + integrity sha512-W+fpe5s91FBGE0pEa0lnqGLL4USgpLgs4nokw16SrBBco/gQxuua7KnArSEOd5iaMqbbSHV10vUDkJYJJqpXKA== + +"@types/node@^10.1.0": + version "10.17.28" + resolved "https://registry.npmjs.org/@types/node/-/node-10.17.28.tgz#0e36d718a29355ee51cec83b42d921299200f6d9" + integrity sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ== "@types/node@^12.0.0": - version "12.12.30" - resolved "https://registry.npmjs.org/@types/node/-/node-12.12.30.tgz#3501e6f09b954de9c404671cefdbcc5d9d7c45f6" - integrity sha512-sz9MF/zk6qVr3pAnM0BSQvYIBK44tS75QC5N+VbWSE4DjCV/pJ+UzCW/F+vVnl7TkOPcuwQureKNtSSwjBTaMg== + version "12.12.53" + resolved "https://registry.npmjs.org/@types/node/-/node-12.12.53.tgz#be0d375933c3d15ef2380dafb3b0350ea7021129" + integrity sha512-51MYTDTyCziHb70wtGNFRwB4l+5JNvdqzFSkbDvpbftEgVUBEE+T5f7pROhWMp/fxp07oNIEQZd5bbfAH22ohQ== -"@types/nodegit@0.26.5": - version "0.26.5" - resolved "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.5.tgz#f13032617da3894620b6132828f0136a65043e14" - integrity sha512-Dszf6yKBPLSPDC18QtY5LFRaRoE682/BHEhPQqwPtSBh0pG9uTfrhh1fnt61OSXiF8s4lBJunXbBpfwxvhQElQ== +"@types/node@^13.7.2": + version "13.13.15" + resolved "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz#fe1cc3aa465a3ea6858b793fd380b66c39919766" + integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw== + +"@types/nodegit@0.26.7": + version "0.26.7" + resolved "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.26.7.tgz#3fc8f5108dcabddd6238509748c0e812ba02b9b7" + integrity sha512-qVwQupq4To/wkRtUnaiwbNhuRiVJShOG9CYIWiGdKQRkvWijtHmEnrC1KJHOVrzK04sMdHRClyecrfmN17VMFQ== dependencies: "@types/node" "*" @@ -3843,9 +4496,9 @@ integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== "@types/qs@*": - version "6.9.1" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz#937fab3194766256ee09fcd40b781740758617e7" - integrity sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw== + version "6.9.4" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" + integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== "@types/range-parser@*": version "1.2.3" @@ -3948,10 +4601,10 @@ resolved "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz#6bda7db8653fa62643f5ee69e9f69c11a392e3a6" integrity sha1-a9p9uGU/piZD9e5p6facEaOS46Y= -"@types/resolve@0.0.8": - version "0.0.8" - resolved "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" - integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== +"@types/resolve@1.17.1": + version "1.17.1" + resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" + integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== dependencies: "@types/node" "*" @@ -4085,12 +4738,11 @@ dependencies: "@types/jest" "*" -"@types/testing-library__react-hooks@^3.0.0": - version "3.2.0" - resolved "https://registry.npmjs.org/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.2.0.tgz#52f3a109bef06080e3b1e3ae7ea1c014ce859897" - integrity sha512-dE8iMTuR5lzB+MqnxlzORlXzXyCL0EKfzH0w/lau20OpkHD37EaWjZDz0iNG8b71iEtxT4XKGmSKAGVEqk46mw== +"@types/testing-library__react-hooks@^3.3.0": + version "3.4.0" + resolved "https://registry.npmjs.org/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.0.tgz#be148b7fa7d19cd3349c4ef9d9534486bc582fcc" + integrity sha512-QYLZipqt1hpwYsBU63Ssa557v5wWbncqL36No59LI7W3nCMYKrLWTnYGn2griZ6v/3n5nKXNYkTeYpqPHY7Ukg== dependencies: - "@types/react" "*" "@types/react-test-renderer" "*" "@types/through@*": @@ -4113,9 +4765,9 @@ integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== "@types/webpack-dev-server@*", "@types/webpack-dev-server@^3.10.0": - version "3.10.1" - resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.10.1.tgz#93b7133cc9dab1ca1b76659f5ef8b763ad54c28a" - integrity sha512-2nwwQ/qHRghUirvG/gEDkOQDa+d881UTJM7EG9ok5KNaYCjYVvy7fdaO528Lcym9OQDn75SvruPYVVvMJxqO0g== + version "3.11.0" + resolved "https://registry.npmjs.org/@types/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#bcc3b85e7dc6ac2db25330610513f2228c2fcfb2" + integrity sha512-3+86AgSzl18n5P1iUP9/lz3G3GMztCp+wxdDvVuNhx1sr1jE79GpYfKHL8k+Vht3N74K2n98CuAEw4YPJCYtDA== dependencies: "@types/connect-history-api-fallback" "*" "@types/express" "*" @@ -4123,12 +4775,7 @@ "@types/serve-static" "*" "@types/webpack" "*" -"@types/webpack-env@^1.15.0": - version "1.15.1" - resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.1.tgz#c8e84705e08eed430b5e15b39c65b0944e4d1422" - integrity sha512-eWN5ElDTeBc5lRDh95SqA8x18D0ll2pWudU3uWiyfsRmIZcmUXpEsxPU+7+BsdCrO2vfLRC629u/MmjbmF+2tA== - -"@types/webpack-env@^1.15.2": +"@types/webpack-env@^1.15.0", "@types/webpack-env@^1.15.2": version "1.15.2" resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.15.2.tgz#927997342bb9f4a5185a86e6579a0a18afc33b0a" integrity sha512-67ZgZpAlhIICIdfQrB5fnDvaKFcDxpKibxznfYRVAT4mQE41Dido/3Ty+E3xGBmTogc5+0Qb8tWhna+5B8z1iQ== @@ -4161,11 +4808,25 @@ "@types/webpack-sources" "*" source-map "^0.6.0" +"@types/websocket@1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138" + integrity sha512-f5WLMpezwVxCLm1xQe/kdPpQIOmL0TXYx2O15VYfYzc7hTIdxiOoOvez+McSIw3b7z/1zGovew9YSL7+h4h7/Q== + dependencies: + "@types/node" "*" + "@types/whatwg-streams@^0.0.7": version "0.0.7" resolved "https://registry.npmjs.org/@types/whatwg-streams/-/whatwg-streams-0.0.7.tgz#28bfe73dc850562296367249c4b32a50db81e9d3" integrity sha512-6sDiSEP6DWcY2ZolsJ2s39ZmsoGQ7KVwBDI3sESQsEm9P2dHTcqnDIHRZFRNtLCzWp7hCFGqYbw5GyfpQnJ01A== +"@types/ws@^7.0.0": + version "7.2.6" + resolved "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz#516cbfb818310f87b43940460e065eb912a4178d" + integrity sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ== + dependencies: + "@types/node" "*" + "@types/yaml@^1.9.7": version "1.9.7" resolved "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz#2331f36e0aac91311a63d33eb026c21687729679" @@ -4390,6 +5051,13 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@wry/equality@^0.1.2": + version "0.1.11" + resolved "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790" + integrity sha512-mwEVBDUVODlsQQ5dfuLUS5/Tf7jqUKyhKYHmVi4fPB6bDMOfWvUPJmKgS1Z7Za/sOI3vzWt4+O7yCiL/70MogA== + dependencies: + tslib "^1.9.3" + "@xobotyi/scrollbar-width@1.9.5": version "1.9.5" resolved "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" @@ -4422,6 +5090,11 @@ JSONStream@^1.0.4, JSONStream@^1.3.4: jsonparse "^1.2.0" through ">=2.2.7 <3" +abab@^1.0.0: + version "1.0.4" + resolved "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4= + abab@^2.0.0, abab@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a" @@ -4432,7 +5105,7 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -4440,6 +5113,13 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + integrity sha1-VbtemGkVB7dFedBRNBMhfDgMVM8= + dependencies: + acorn "^2.1.0" + acorn-globals@^4.1.0: version "4.3.4" resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7" @@ -4471,6 +5151,11 @@ acorn-walk@^7.1.1: resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz#345f0dffad5c735e7373d2fec9a1023e6a44b83e" integrity sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ== +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + integrity sha1-q259nYhqrKiwhbwzEreaGYQz8Oc= + acorn@^5.5.3: version "5.7.4" resolved "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" @@ -4517,7 +5202,7 @@ agentkeepalive@^3.4.1: dependencies: humanize-ms "^1.2.1" -aggregate-error@^3.0.0: +aggregate-error@3.0.1, aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== @@ -4675,6 +5360,187 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +apollo-cache-control@^0.11.1: + version "0.11.1" + resolved "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.11.1.tgz#3bce0924ae7322a8b9f7ca1e2fb036d1fc9f1df5" + integrity sha512-6iHa8TkcKt4rx5SKRzDNjUIpCQX+7/FlZwD7vRh9JDnM4VH8SWhpj8fUR3CiEY8Kuc4ChXnOY8bCcMju5KPnIQ== + dependencies: + apollo-server-env "^2.4.5" + apollo-server-plugin-base "^0.9.1" + +apollo-datasource@^0.7.2: + version "0.7.2" + resolved "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.7.2.tgz#1662ee93453a9b89af6f73ce561bde46b41ebf31" + integrity sha512-ibnW+s4BMp4K2AgzLEtvzkjg7dJgCaw9M5b5N0YKNmeRZRnl/I/qBTQae648FsRKgMwTbRQIvBhQ0URUFAqFOw== + dependencies: + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + +apollo-engine-reporting-protobuf@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.5.2.tgz#b01812508a1c583328a8dc603769bc63b8895e7e" + integrity sha512-4wm9FR3B7UvJxcK/69rOiS5CAJPEYKufeRWb257ZLfX7NGFTMqvbc1hu4q8Ch7swB26rTpkzfsftLED9DqH9qg== + dependencies: + "@apollo/protobufjs" "^1.0.3" + +apollo-engine-reporting@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-2.3.0.tgz#3bb59f81aaf6b967ed098896a4a60a053b4eed5a" + integrity sha512-SbcPLFuUZcRqDEZ6mSs8uHM9Ftr8yyt2IEu0JA8c3LNBmYXSLM7MHqFe80SVcosYSTBgtMz8mLJO8orhYoSYZw== + dependencies: + apollo-engine-reporting-protobuf "^0.5.2" + apollo-graphql "^0.5.0" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + apollo-server-errors "^2.4.2" + apollo-server-plugin-base "^0.9.1" + apollo-server-types "^0.5.1" + async-retry "^1.2.1" + uuid "^8.0.0" + +apollo-env@^0.6.5: + version "0.6.5" + resolved "https://registry.npmjs.org/apollo-env/-/apollo-env-0.6.5.tgz#5a36e699d39e2356381f7203493187260fded9f3" + integrity sha512-jeBUVsGymeTHYWp3me0R2CZRZrFeuSZeICZHCeRflHTfnQtlmbSXdy5E0pOyRM9CU4JfQkKDC98S1YglQj7Bzg== + dependencies: + "@types/node-fetch" "2.5.7" + core-js "^3.0.1" + node-fetch "^2.2.0" + sha.js "^2.4.11" + +apollo-graphql@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/apollo-graphql/-/apollo-graphql-0.5.0.tgz#7e9152093211b58352aa6504d8d39ec7241d6872" + integrity sha512-YSdF/BKPbsnQpxWpmCE53pBJX44aaoif31Y22I/qKpB6ZSGzYijV5YBoCL5Q15H2oA/v/02Oazh9lbp4ek3eig== + dependencies: + apollo-env "^0.6.5" + lodash.sortby "^4.7.0" + +apollo-link@^1.2.14: + version "1.2.14" + resolved "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" + integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== + dependencies: + apollo-utilities "^1.3.0" + ts-invariant "^0.4.0" + tslib "^1.9.3" + zen-observable-ts "^0.8.21" + +apollo-server-caching@^0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.5.2.tgz#bef5d5e0d48473a454927a66b7bb947a0b6eb13e" + integrity sha512-HUcP3TlgRsuGgeTOn8QMbkdx0hLPXyEJehZIPrcof0ATz7j7aTPA4at7gaiFHCo8gk07DaWYGB3PFgjboXRcWQ== + dependencies: + lru-cache "^5.0.0" + +apollo-server-core@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.16.0.tgz#56b367db49f97b7da03b29cef89b63d9ed14ee0c" + integrity sha512-mnvg2cPvsQtjFXIqIhEAbPqGyiSXDSbiBgNQ8rY8g7r2eRMhHKZePqGF03gP1/w87yVaSDRAZBDk6o+jiBXjVQ== + dependencies: + "@apollographql/apollo-tools" "^0.4.3" + "@apollographql/graphql-playground-html" "1.6.26" + "@types/graphql-upload" "^8.0.0" + "@types/ws" "^7.0.0" + apollo-cache-control "^0.11.1" + apollo-datasource "^0.7.2" + apollo-engine-reporting "^2.3.0" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + apollo-server-errors "^2.4.2" + apollo-server-plugin-base "^0.9.1" + apollo-server-types "^0.5.1" + apollo-tracing "^0.11.1" + fast-json-stable-stringify "^2.0.0" + graphql-extensions "^0.12.4" + graphql-tag "^2.9.2" + graphql-tools "^4.0.0" + graphql-upload "^8.0.2" + loglevel "^1.6.7" + sha.js "^2.4.11" + subscriptions-transport-ws "^0.9.11" + ws "^6.0.0" + +apollo-server-env@^2.4.5: + version "2.4.5" + resolved "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.4.5.tgz#73730b4f0439094a2272a9d0caa4079d4b661d5f" + integrity sha512-nfNhmGPzbq3xCEWT8eRpoHXIPNcNy3QcEoBlzVMjeglrBGryLG2LXwBSPnVmTRRrzUYugX0ULBtgE3rBFNoUgA== + dependencies: + node-fetch "^2.1.2" + util.promisify "^1.0.0" + +apollo-server-errors@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.4.2.tgz#1128738a1d14da989f58420896d70524784eabe5" + integrity sha512-FeGxW3Batn6sUtX3OVVUm7o56EgjxDlmgpTLNyWcLb0j6P8mw9oLNyAm3B+deHA4KNdNHO5BmHS2g1SJYjqPCQ== + +apollo-server-express@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.16.0.tgz#3474a7f7eb868a2a847a364839147f8d7f26454a" + integrity sha512-mBIvKcF8gApj7wbmqe0A4Tsy+Pw66mI6cmtD912bG59KhUBveSCZ21dDlRSvnXUyK+GOo2ItwcUEtmks+Z2Pqw== + dependencies: + "@apollographql/graphql-playground-html" "1.6.26" + "@types/accepts" "^1.3.5" + "@types/body-parser" "1.19.0" + "@types/cors" "^2.8.4" + "@types/express" "4.17.4" + accepts "^1.3.5" + apollo-server-core "^2.16.0" + apollo-server-types "^0.5.1" + body-parser "^1.18.3" + cors "^2.8.4" + express "^4.17.1" + graphql-subscriptions "^1.0.0" + graphql-tools "^4.0.0" + parseurl "^1.3.2" + subscriptions-transport-ws "^0.9.16" + type-is "^1.6.16" + +apollo-server-plugin-base@^0.9.1: + version "0.9.1" + resolved "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-0.9.1.tgz#a62ae9ab4e89790fd4cc5d123bb616da34e8e5fb" + integrity sha512-kvrX4Z3FdpjrZdHkyl5iY2A1Wvp4b6KQp00DeZqss7GyyKNUBKr80/7RQgBLEw7EWM7WB19j459xM/TjvW0FKQ== + dependencies: + apollo-server-types "^0.5.1" + +apollo-server-types@^0.5.1: + version "0.5.1" + resolved "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-0.5.1.tgz#091c09652894d6532db9ba873574443adabf85b9" + integrity sha512-my2cPw+DAb2qVnIuBcsRKGyS28uIc2vjFxa1NpRoJZe9gK0BWUBk7wzXnIzWy3HZ5Er11e/40MPTUesNfMYNVA== + dependencies: + apollo-engine-reporting-protobuf "^0.5.2" + apollo-server-caching "^0.5.2" + apollo-server-env "^2.4.5" + +apollo-server@^2.16.0: + version "2.16.0" + resolved "https://registry.npmjs.org/apollo-server/-/apollo-server-2.16.0.tgz#fa30e29b78e8cb70b2c81d0f7b96953beb3d4baf" + integrity sha512-zbEe0FSqatqE6bmIfq/o1/Hsqc596ZOwZM/L8Ttwa4ucQ1ybqf1ZejSYu6ehFEj1G6rOBY1ttVKkIllcErK4GQ== + dependencies: + apollo-server-core "^2.16.0" + apollo-server-express "^2.16.0" + express "^4.0.0" + graphql-subscriptions "^1.0.0" + graphql-tools "^4.0.0" + +apollo-tracing@^0.11.1: + version "0.11.1" + resolved "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.11.1.tgz#3e3a4ce4b21e57dcc57b10bbd539db243b752606" + integrity sha512-l7g+uILw7v32GA46IRXIx5XXbZhFI96BhSqrGK9yyvfq+NMcvVZrj3kIhRImPGhAjMdV+5biA/jztabElAbDjg== + dependencies: + apollo-server-env "^2.4.5" + apollo-server-plugin-base "^0.9.1" + +apollo-utilities@^1.0.1, apollo-utilities@^1.3.0: + version "1.3.4" + resolved "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz#6129e438e8be201b6c55b0f13ce49d2c7175c9cf" + integrity sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig== + dependencies: + "@wry/equality" "^0.1.2" + fast-json-stable-stringify "^2.0.0" + ts-invariant "^0.4.0" + tslib "^1.10.0" + app-root-dir@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" @@ -4923,6 +5789,13 @@ async-limiter@~1.0.0: resolved "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async-retry@^1.2.1: + version "1.3.1" + resolved "https://registry.npmjs.org/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" + integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA== + dependencies: + retry "0.12.0" + async@^2.6.1, async@^2.6.2: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -5053,7 +5926,7 @@ babel-plugin-add-react-displayname@^0.0.5: resolved "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz#339d4cddb7b65fd62d1df9db9fe04de134122bd5" integrity sha1-M51M3be2X9YtHfnbn+BN4TQSK9U= -babel-plugin-dynamic-import-node@^2.3.0, babel-plugin-dynamic-import-node@^2.3.3: +babel-plugin-dynamic-import-node@^2.3.3: version "2.3.3" resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== @@ -5320,6 +6193,11 @@ babel-runtime@6.26.0, babel-runtime@^6.26.0: core-js "^2.4.0" regenerator-runtime "^0.11.0" +backo2@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= + bail@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" @@ -5330,6 +6208,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-arraybuffer@^0.1.5: + version "0.1.5" + resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= + base64-js@^1.0.2: version "1.3.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -5441,7 +6324,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== -body-parser@1.19.0, body-parser@^1.19.0: +body-parser@1.19.0, body-parser@^1.18.3, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -5474,11 +6357,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bowser@2.9.0: - version "2.9.0" - resolved "https://registry.npmjs.org/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9" - integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA== - bowser@^1.7.3: version "1.9.4" resolved "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" @@ -5598,7 +6476,7 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@4.10.0, browserslist@^4.0.0, browserslist@^4.8.3, browserslist@^4.9.1: +browserslist@4.10.0, browserslist@^4.0.0, browserslist@^4.8.3: version "4.10.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.10.0.tgz#f179737913eaf0d2b98e4926ac1ca6a15cbcc6a9" integrity sha512-TpfK0TDgv71dzuTsEAlQiHeWQ/tiPqgNZVdv046fvNtBZrjbv2O3TsWCDU0AWGJJKCF/KsjNdLzR9hXOsh/CfA== @@ -5617,6 +6495,16 @@ browserslist@4.7.0: electron-to-chromium "^1.3.247" node-releases "^1.1.29" +browserslist@^4.12.0: + version "4.13.0" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d" + integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ== + dependencies: + caniuse-lite "^1.0.30001093" + electron-to-chromium "^1.3.488" + escalade "^3.0.1" + node-releases "^1.1.58" + bs-logger@0.x: version "0.2.6" resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" @@ -5669,6 +6557,16 @@ 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" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -5706,6 +6604,13 @@ builtins@^1.0.3: resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= +busboy@^0.3.1: + version "0.3.1" + resolved "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" + integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== + dependencies: + dicer "0.3.0" + byline@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -5833,7 +6738,7 @@ callsites@^3.0.0: resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@^4.1.1: +camel-case@4.1.1, camel-case@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== @@ -5872,6 +6777,11 @@ camelcase@^2.0.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + camelcase@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -5887,11 +6797,6 @@ camelcase@^6.0.0: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== -camelize@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" - integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= - can-use-dom@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a" @@ -5912,6 +6817,21 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000989, caniuse-lite@^1.0.30001020, can resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001035.tgz#2bb53b8aa4716b2ed08e088d4dc816a5fe089a1e" integrity sha512-C1ZxgkuA4/bUEdMbU5WrGY4+UhMFFiXrgNAfxiMIqWgFTWfv/xsZCS2xEHT2LMq7xAZfuAnu6mcqyDl0ZR6wLQ== +caniuse-lite@^1.0.30001093: + version "1.0.30001107" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a" + integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ== + +canvg@1.5.3: + version "1.5.3" + resolved "https://registry.npmjs.org/canvg/-/canvg-1.5.3.tgz#aad17915f33368bf8eb80b25d129e3ae922ddc5f" + integrity sha512-7Gn2IuQzvUQWPIuZuFHrzsTM0gkPz2RRT9OcbdmA03jeKk8kltrD8gqUzNX15ghY/4PV5bbe5lmD6yDLDY6Ybg== + dependencies: + jsdom "^8.1.0" + rgbcolor "^1.0.1" + stackblur-canvas "^1.4.1" + xmldom "^0.1.22" + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -6020,9 +6940,9 @@ chokidar@^2.0.4, chokidar@^2.1.8: fsevents "^1.2.7" chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1: - version "3.4.0" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== + version "3.4.1" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1" + integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -6169,6 +7089,15 @@ clipboard@^2.0.0: select "^1.1.2" tiny-emitter "^2.0.0" +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -6370,7 +7299,7 @@ commander@4.1.1, commander@^4.0.0, commander@^4.0.1, commander@^4.1.1: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@~2.20.3: +commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@~2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -6479,6 +7408,21 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" +concurrently@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b" + integrity sha512-8MhqOB6PWlBfA2vJ8a0bSFKATOdWlHiQlk11IfmQBPaHVP8oP2gsh2MObE6UR3hqDHqvaIvLTyceNW6obVuFHQ== + dependencies: + chalk "^2.4.2" + date-fns "^2.0.1" + lodash "^4.17.15" + read-pkg "^4.0.1" + rxjs "^6.5.2" + spawn-command "^0.0.2-1" + supports-color "^6.1.0" + tree-kill "^1.2.2" + yargs "^13.3.0" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -6536,11 +7480,6 @@ content-disposition@0.5.3: dependencies: safe-buffer "5.1.2" -content-security-policy-builder@2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/content-security-policy-builder/-/content-security-policy-builder-2.1.0.tgz#0a2364d769a3d7014eec79ff7699804deb8cfcbb" - integrity sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ== - content-type@~1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -6706,12 +7645,7 @@ core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.5: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== -core-js@^3.0.1, core-js@^3.0.4: - version "3.6.4" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647" - integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw== - -core-js@^3.5.0: +core-js@^3.0.1, core-js@^3.0.4, core-js@^3.5.0: version "3.6.5" resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== @@ -6729,7 +7663,7 @@ corejs-upgrade-webpack-plugin@^2.2.0: resolve-from "^5.0.0" webpack "^4.38.0" -cors@^2.8.5: +cors@^2.8.4, cors@^2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -6737,6 +7671,17 @@ cors@^2.8.5: object-assign "^4" vary "^1" +cosmiconfig@6.0.0, cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -6747,17 +7692,6 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1: js-yaml "^3.13.1" parse-json "^4.0.0" -cosmiconfig@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" - integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.1.0" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.7.2" - create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -6804,6 +7738,13 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" +cross-fetch@3.0.5: + version "3.0.5" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" + integrity sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew== + dependencies: + node-fetch "2.6.0" + cross-fetch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" @@ -6891,6 +7832,13 @@ css-in-js-utils@^2.0.0: hyphenate-style-name "^1.0.2" isobject "^3.0.1" +css-line-break@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-1.0.1.tgz#19f2063a33e95fb2831b86446c0b80c188af450a" + integrity sha1-GfIGOjPpX7KDG4ZEbAuAwYivRQo= + dependencies: + base64-arraybuffer "^0.1.5" + css-loader@^3.0.0, css-loader@^3.5.3: version "3.6.0" resolved "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" @@ -7010,6 +7958,11 @@ cssesc@^3.0.0: resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfilter@0.0.10: + version "0.0.10" + resolved "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" + integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4= + cssnano-preset-default@^4.0.7: version "4.0.7" resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" @@ -7085,7 +8038,7 @@ csso@^4.0.2: dependencies: css-tree "1.0.0-alpha.37" -cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", "cssom@>= 0.3.2 < 0.4.0", cssom@~0.3.6: version "0.3.8" resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== @@ -7095,6 +8048,13 @@ cssom@^0.4.4: resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== +"cssstyle@>= 0.2.34 < 0.3.0": + version "0.2.37" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ= + dependencies: + cssom "0.3.x" + cssstyle@^1.0.0: version "1.4.0" resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz#9d31328229d3c565c61e586b02041a28fccdccf1" @@ -7193,6 +8153,14 @@ d3-timer@1: resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + damerau-levenshtein@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" @@ -7212,11 +8180,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dasherize@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" - integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= - dashify@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz#fff270ca2868ca427fee571de35691d6e437a648" @@ -7245,10 +8208,10 @@ date-fns@^1.27.2: resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -date-fns@^2.0.0-alpha.27: - version "2.12.0" - resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz#01754c8a2f3368fc1119cf4625c3dad8c1845ee6" - integrity sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw== +date-fns@^2.0.0-alpha.27, date-fns@^2.0.1: + version "2.15.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.15.0.tgz#424de6b3778e4e69d3ff27046ec136af58ae5d5f" + integrity sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ== dateformat@^3.0.0: version "3.0.3" @@ -7301,7 +8264,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -7456,16 +8419,21 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +depd@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +deprecated-decorator@^0.1.6: + version "0.1.6" + resolved "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" + integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= + deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" @@ -7542,6 +8510,13 @@ diagnostics@^1.1.1: enabled "1.0.x" kuler "1.0.x" +dicer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" + integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== + dependencies: + streamsearch "0.1.2" + diff-sequences@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" @@ -7627,6 +8602,14 @@ dockerode@^3.2.0: docker-modem "^2.1.0" tar-fs "~2.0.1" +dockerode@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/dockerode/-/dockerode-3.2.1.tgz#4a2222e3e1df536bf595e78e76d3cfbf6d4d93b9" + integrity sha512-XsSVB5Wu5HWMg1aelV5hFSqFJaKS5x1aiV/+sT7YOzOq1IRl49I/UwV8Pe4x6t0iF9kiGkWu5jwfvbkcFVupBw== + dependencies: + docker-modem "^2.1.0" + tar-fs "~2.0.1" + doctrine@1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" @@ -7758,11 +8741,6 @@ domutils@^2.0.0: domelementtype "^2.0.1" domhandler "^3.0.0" -dont-sniff-mimetype@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/dont-sniff-mimetype/-/dont-sniff-mimetype-1.1.0.tgz#c7d0427f8bcb095762751252af59d148b0a623b2" - integrity sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug== - dot-case@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz#21d3b52efaaba2ea5fda875bb1aa8124521cf4aa" @@ -7864,6 +8842,11 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.380.tgz#1e1f07091b42b54bccd0ad6d3a14f2b73b60dc9d" integrity sha512-2jhQxJKcjcSpVOQm0NAfuLq8o+130blrcawoumdXT6411xG/xIAOyZodO/y7WTaYlz/NHe3sCCAe/cJLnDsqTw== +electron-to-chromium@^1.3.488: + version "1.3.509" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.509.tgz#830fcb89cd66dc2984d18d794973b99e3f00584c" + integrity sha512-cN4lkjNRuTG8rtAqTOVgwpecEC2kbKA04PG6YijcKGHK/kD0xLjiqExcAOmLUwtXZRF8cBeam2I0VZcih919Ug== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -7877,9 +8860,9 @@ element-resize-detector@^1.2.1: batch-processor "1.0.0" elliptic@^6.0.0: - version "6.5.2" - resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" - integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== + version "6.5.3" + resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -7889,6 +8872,11 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +"emoji-regex@>=6.0.0 <=6.1.1": + version "6.1.1" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" + integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= + emoji-regex@^7.0.1, emoji-regex@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -8055,11 +9043,29 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + es5-shim@^4.5.13: version "4.5.13" resolved "https://registry.npmjs.org/es5-shim/-/es5-shim-4.5.13.tgz#5d88062de049f8969f83783f4a4884395f21d28b" integrity sha512-xi6hh6gsvDE0MaW4Vp1lgNEBpVcCXRWfPXj5egDvtgLz4L9MEvNwYEMdJH+JJinWkwa8c3c3o5HduV7dB/e1Hw== +es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + es6-promise@^4.0.3: version "4.2.8" resolved "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" @@ -8077,11 +9083,24 @@ es6-shim@^0.35.5: resolved "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.5.tgz#46f59dc0a84a1c5029e8ff1166ca0a902077a9ab" integrity sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg== +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + esbuild@0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.6.3.tgz#a957e22f2503c745793514388110f9b55b3a6f83" integrity sha512-4lHgz/EvGLRQnDYzzrvW+eilaPHim5pCLz4mP0k0QIalD/n8Ji2NwQwoIa1uX+yKkbn9R/FAZvaEbodjx55rDg== +escalade@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" + integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -8114,6 +9133,18 @@ escodegen@^1.14.1, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" +escodegen@^1.6.1: + version "1.14.3" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-prettier@^6.0.0: version "6.10.0" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz#7b15e303bf9c956875c948f6b21500e48ded6a7f" @@ -8155,6 +9186,16 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-graphql@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" + integrity sha512-d5tQm24YkVvCEk29ZR5ScsgXqAGCjKlMS8lx3mS7FS/EKsWbkvXQImpvic03EpMIvNTBW5e+2xnHzXB/VHNZJw== + dependencies: + "@babel/runtime" "^7.10.0" + graphql-config "^3.0.2" + lodash.flatten "^4.4.0" + lodash.without "^4.4.0" + eslint-plugin-import@^2.20.2, eslint-plugin-import@^2.22.0: version "2.22.0" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" @@ -8515,11 +9556,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect-ct@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz#3a54741b6ed34cc7a93305c605f63cd268a54a62" - integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g== - expect@^26.0.1: version "26.0.1" resolved "https://registry.npmjs.org/expect/-/expect-26.0.1.tgz#18697b9611a7e2725e20ba3ceadda49bc9865421" @@ -8541,7 +9577,7 @@ express-promise-router@^3.0.3: lodash.flattendeep "^4.0.0" methods "^1.0.0" -express@^4.17.0, express@^4.17.1: +express@^4.0.0, express@^4.17.0, express@^4.17.1: version "4.17.1" resolved "https://registry.npmjs.org/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -8577,6 +9613,13 @@ express@^4.17.0, express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -8645,10 +9688,10 @@ fast-deep-equal@2.0.1, fast-deep-equal@^2.0.1: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= -fast-deep-equal@^3.0.0, fast-deep-equal@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" - integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-deep-equal@^3.0.0, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^2.0.2, fast-glob@^2.2.6: version "2.2.7" @@ -8718,7 +9761,7 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fault@^1.0.2: +fault@^1.0.0, fault@^1.0.2: version "1.0.4" resolved "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== @@ -8753,11 +9796,6 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -feature-policy@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" - integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ== - fecha@^2.3.3: version "2.3.3" resolved "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" @@ -8810,6 +9848,11 @@ file-loader@^4.2.0: loader-utils "^1.2.3" schema-utils "^2.5.0" +"file-saver@github:eligrey/FileSaver.js#1.3.8": + version "1.3.8" + uid e865e37af9f9947ddcced76b549e27dc45c1cb2e + resolved "https://codeload.github.com/eligrey/FileSaver.js/tar.gz/e865e37af9f9947ddcced76b549e27dc45c1cb2e" + file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -9120,11 +10163,6 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -frameguard@3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/frameguard/-/frameguard-3.1.0.tgz#bd1442cca1d67dc346a6751559b6d04502103a22" - integrity sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g== - fresh@0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -9143,6 +10181,11 @@ from@~0: resolved "https://registry.npmjs.org/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= +fs-capacitor@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/fs-capacitor/-/fs-capacitor-2.0.4.tgz#5a22e72d40ae5078b4fe64fe4d08c0d3fc88ad3c" + integrity sha512-8S4f4WsCryNw2mJJchi46YgB6CR5Ze+4L1h8ewl9tEpL4SJ3ZO+c/bS4BWhB8bK+O3TMqhuZarTitd0S0eh2pA== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -9157,6 +10200,16 @@ fs-extra@8.1.0, fs-extra@^8.0.1, fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@9.0.1, fs-extra@^9.0.0, fs-extra@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^0.30.0: version "0.30.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz#f233ffcc08d4da7d432daa449776989db1df93f0" @@ -9177,26 +10230,6 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3" - integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - -fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -9289,6 +10322,11 @@ 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= + genfun@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537" @@ -9299,6 +10337,11 @@ gensync@^1.0.0-beta.1: resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== +get-caller-file@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" + integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== + get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -9435,6 +10478,13 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-slugger@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" + glob-base@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" @@ -9552,6 +10602,18 @@ globalthis@^1.0.0: dependencies: define-properties "^1.1.3" +globby@11.0.1, globby@^11.0.0: + version "11.0.1" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.1.1" + ignore "^5.1.4" + merge2 "^1.3.0" + slash "^3.0.0" + globby@8.0.2: version "8.0.2" resolved "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz#5697619ccd95c5275dbb2d6faa42087c1a941d8d" @@ -9579,18 +10641,6 @@ globby@^10.0.1: merge2 "^1.2.3" slash "^3.0.0" -globby@^11.0.0: - version "11.0.1" - resolved "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" - integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - globby@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -9652,12 +10702,7 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2: - version "4.2.3" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - -graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.4: version "4.2.4" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -9678,6 +10723,31 @@ graphiql@^1.0.0-alpha.10: regenerator-runtime "^0.13.5" theme-ui "^0.3.1" +graphql-config@^3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/graphql-config/-/graphql-config-3.0.3.tgz#58907c65ed7d6e04132321450b60e57863ea9a5f" + integrity sha512-MBY0wEjvcgJtZUyoqpPvOE1e5qPI0hJaa1gKTqjonSFiCsNHX2lykNjpOPcodmAgH1V06ELxhGnm9kcVzqvi/g== + dependencies: + "@graphql-tools/graphql-file-loader" "^6.0.0" + "@graphql-tools/json-file-loader" "^6.0.0" + "@graphql-tools/load" "^6.0.0" + "@graphql-tools/merge" "^6.0.0" + "@graphql-tools/url-loader" "^6.0.0" + "@graphql-tools/utils" "^6.0.0" + cosmiconfig "6.0.0" + minimatch "3.0.4" + string-env-interpolation "1.0.1" + tslib "^2.0.0" + +graphql-extensions@^0.12.4: + version "0.12.4" + resolved "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.12.4.tgz#c0aa49a20f983a2da641526d1e505996bd2b4188" + integrity sha512-GnR4LiWk3s2bGOqIh6V1JgnSXw2RCH4NOgbCFEWvB6JqWHXTlXnLZ8bRSkCiD4pltv7RHUPWqN/sGh8R6Ae/ag== + dependencies: + "@apollographql/apollo-tools" "^0.4.3" + apollo-server-env "^2.4.5" + apollo-server-types "^0.5.1" + graphql-language-service-interface@^2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/graphql-language-service-interface/-/graphql-language-service-interface-2.4.0.tgz#4e2e63242c76197c4f56c61122db68187ea566b3" @@ -9713,11 +10783,56 @@ graphql-language-service@^3.0.0: graphql-language-service-interface "^2.4.0" graphql-language-service-types "^1.6.0" +graphql-subscriptions@^1.0.0: + version "1.1.0" + resolved "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.1.0.tgz#5f2fa4233eda44cf7570526adfcf3c16937aef11" + integrity sha512-6WzlBFC0lWmXJbIVE8OgFgXIP4RJi3OQgTPa0DVMsDXdpRDjTsM1K9wfl5HSYX7R87QAGlvcv2Y4BIZa/ItonA== + dependencies: + iterall "^1.2.1" + +graphql-tag@^2.9.2: + version "2.10.4" + resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.4.tgz#2f301a98219be8b178a6453bb7e33b79b66d8f83" + integrity sha512-O7vG5BT3w6Sotc26ybcvLKNTdfr4GfsIVMD+LdYqXCeJIYPRyp8BIsDOUtxw7S1PYvRw5vH3278J2EDezR6mfA== + +graphql-tools@^4.0.0: + version "4.0.8" + resolved "https://registry.npmjs.org/graphql-tools/-/graphql-tools-4.0.8.tgz#e7fb9f0d43408fb0878ba66b522ce871bafe9d30" + integrity sha512-MW+ioleBrwhRjalKjYaLQbr+920pHBgy9vM/n47sswtns8+96sRn5M/G+J1eu7IMeKWiN/9p6tmwCHU7552VJg== + dependencies: + apollo-link "^1.2.14" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-upload@^8.0.2: + version "8.1.0" + resolved "https://registry.npmjs.org/graphql-upload/-/graphql-upload-8.1.0.tgz#6d0ab662db5677a68bfb1f2c870ab2544c14939a" + integrity sha512-U2OiDI5VxYmzRKw0Z2dmfk0zkqMRaecH9Smh1U277gVgVe9Qn+18xqf4skwr4YJszGIh7iQDZ57+5ygOK9sM/Q== + dependencies: + busboy "^0.3.1" + fs-capacitor "^2.0.4" + http-errors "^1.7.3" + object-path "^0.11.4" + graphql@15.1.0, graphql@^15.0.0: version "15.1.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.1.0.tgz#b93e28de805294ec08e1630d901db550cb8960a1" integrity sha512-0TVyfOlCGhv/DBczQkJmwXOK6fjWkjzY3Pt7wY8i0gcYXq8aogG3weCsg48m72lywKSeOqedEHvVPOvZvSD51Q== +graphql@^14.5.3: + version "14.7.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== + dependencies: + iterall "^1.2.2" + +graphql@^15.3.0: + version "15.3.0" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" + integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== + growly@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -9887,60 +11002,26 @@ headers-utils@^1.1.9, headers-utils@^1.2.0: resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== -helmet-crossdomain@0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/helmet-crossdomain/-/helmet-crossdomain-0.4.0.tgz#5f1fe5a836d0325f1da0a78eaa5fd8429078894e" - integrity sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA== - -helmet-csp@2.10.0: - version "2.10.0" - resolved "https://registry.npmjs.org/helmet-csp/-/helmet-csp-2.10.0.tgz#685dde1747bc16c5e28ad9d91e229a69f0a85e84" - integrity sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w== - dependencies: - bowser "2.9.0" - camelize "1.0.0" - content-security-policy-builder "2.1.0" - dasherize "2.0.0" - -helmet@^3.22.0: - version "3.23.2" - resolved "https://registry.npmjs.org/helmet/-/helmet-3.23.2.tgz#390bb6f3f5e593f90f441bdd91000912c543a898" - integrity sha512-pe0UiHw3aHbP8Lon9McCq4AN2XLUMSbhwxJnUY6U2t8wTda7F1SsYg0/pBa1BPugaRqAtx9e1/FyF6E9PsUU5A== - dependencies: - depd "2.0.0" - dont-sniff-mimetype "1.1.0" - expect-ct "0.2.0" - feature-policy "0.3.0" - frameguard "3.1.0" - helmet-crossdomain "0.4.0" - helmet-csp "2.10.0" - hide-powered-by "1.1.0" - hpkp "2.0.0" - hsts "2.2.0" - nocache "2.1.0" - referrer-policy "1.2.0" - x-xss-protection "1.3.0" +helmet@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/helmet/-/helmet-4.0.0.tgz#34c187894ed001834f997c688f2b2df19846b193" + integrity sha512-HyoRKKHhWhO6+EBfgRLkuZR4/+NXc1nJB7x0bWwW89i9eoPciK0qUqyZNOA/zowpgrW9C4+J5toqMkZrpBOlkg== hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -hide-powered-by@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/hide-powered-by/-/hide-powered-by-1.1.0.tgz#be3ea9cab4bdb16f8744be873755ca663383fa7a" - integrity sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg== +highlight.js@^10.1.1, highlight.js@~10.1.0: + version "10.1.2" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c" + integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA== highlight.js@~9.13.0: version "9.13.1" resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e" integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A== -highlight.js@~9.15.0, highlight.js@~9.15.1: - version "9.15.10" - resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz#7b18ed75c90348c045eef9ed08ca1319a2219ad2" - integrity sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw== - history@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/history/-/history-5.0.0.tgz#0cabbb6c4bbf835addb874f8259f6d25101efd08" @@ -9991,11 +11072,6 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -hpkp@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/hpkp/-/hpkp-2.0.0.tgz#10e142264e76215a5d30c44ec43de64dee6d1672" - integrity sha1-EOFCJk52IVpdMMROxD3mTe5tFnI= - hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -10006,13 +11082,6 @@ hsla-regex@^1.0.0: resolved "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -hsts@2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/hsts/-/hsts-2.2.0.tgz#09119d42f7a8587035d027dda4522366fe75d964" - integrity sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ== - dependencies: - depd "2.0.0" - html-comment-regex@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz#97d4688aeb5c81886a364faa0cad1dda14d433a7" @@ -10080,6 +11149,13 @@ html-webpack-plugin@^4.0.0-beta.2, html-webpack-plugin@^4.3.0: tapable "^1.1.3" util.promisify "1.0.0" +html2canvas@1.0.0-alpha.12: + version "1.0.0-alpha.12" + resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz#3b1992e3c9b3f56063c35fd620494f37eba88513" + integrity sha1-OxmS48mz9WBjw1/WIElPN+uohRM= + dependencies: + css-line-break "1.0.1" + htmlparser2@^3.3.0: version "3.10.1" resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" @@ -10128,14 +11204,14 @@ http-errors@1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" -http-errors@^1.7.3, http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== +http-errors@^1.7.3: + version "1.8.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" + integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== dependencies: depd "~1.1.2" inherits "2.0.4" - setprototypeof "1.1.1" + setprototypeof "1.2.0" statuses ">= 1.5.0 < 2" toidentifier "1.0.0" @@ -10149,6 +11225,17 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + "http-parser-js@>=0.4.0 <0.4.11": version "0.4.10" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" @@ -10255,7 +11342,7 @@ hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3: resolved "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -iconv-lite@0.4.24, iconv-lite@^0.4.21, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.21, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -10358,6 +11445,13 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-from@3.0.0, import-from@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" + integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== + dependencies: + resolve-from "^5.0.0" + import-from@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" @@ -10365,13 +11459,6 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" -import-from@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" - integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== - dependencies: - resolve-from "^5.0.0" - import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -10585,6 +11672,11 @@ invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -10817,6 +11909,13 @@ is-generator-fn@^2.0.0: resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-glob@4.0.1, is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -10831,13 +11930,6 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" @@ -11180,6 +12272,11 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterall@^1.1.3, iterall@^1.2.1, iterall@^1.2.2: + version "1.3.0" + resolved "https://registry.npmjs.org/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" + integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== + iterate-iterator@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" @@ -11626,6 +12723,11 @@ js-cookie@^2.2.1: resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ== +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= + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -11713,6 +12815,29 @@ jsdom@^16.2.2: ws "^7.2.3" xml-name-validator "^3.0.0" +jsdom@^8.1.0: + version "8.5.0" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-8.5.0.tgz#d4d8f5dbf2768635b62a62823b947cf7071ebc98" + integrity sha1-1Nj12/J2hjW2KmKCO5R89wcevJg= + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + array-equal "^1.0.0" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.34 < 0.3.0" + escodegen "^1.6.1" + iconv-lite "^0.4.13" + nwmatcher ">= 1.3.7 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.2.0" + webidl-conversions "^3.0.1" + whatwg-url "^2.0.1" + xml-name-validator ">= 2.0.1 < 3.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -11826,6 +12951,23 @@ jsonpointer@^4.0.1: resolved "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= +jspdf-autotable@3.5.3: + version "3.5.3" + resolved "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.5.3.tgz#2f73adb07f340e7dbf22950e3e6c8bf853991479" + integrity sha512-K+cNWW3x6w0R/1B5m6PYOm6v8CTTDXy/g32lZouc7SuC6zhvzMN2dauhk6dDYxPD0pky0oyPIJFwSJ/tV8PAeg== + +jspdf@1.5.3: + version "1.5.3" + resolved "https://registry.npmjs.org/jspdf/-/jspdf-1.5.3.tgz#5a12c011479defabef5735de55c913060ed219f2" + integrity sha512-J9X76xnncMw+wIqb15HeWfPMqPwYxSpPY8yWPJ7rAZN/ZDzFkjCSZObryCyUe8zbrVRNiuCnIeQteCzMn7GnWw== + dependencies: + canvg "1.5.3" + file-saver "github:eligrey/FileSaver.js#1.3.8" + html2canvas "1.0.0-alpha.12" + omggif "1.0.7" + promise-polyfill "8.1.0" + stackblur-canvas "2.2.0" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -12034,6 +13176,13 @@ lazy-universal-dotenv@^3.0.1: dotenv "^8.0.0" dotenv-expand "^5.1.0" +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + left-pad@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -12303,6 +13452,11 @@ lodash._reinterpolate@^3.0.0: resolved "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= +lodash.assign@^4.1.0, lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= + lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -12318,6 +13472,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash.flattendeep@^4.0.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" @@ -12378,12 +13537,17 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash.without@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" + integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw= + lodash@4.17.15: version "4.17.15" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: +lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.4, lodash@^4.2.1: version "4.17.19" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== @@ -12439,11 +13603,16 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -loglevel@^1.6.8: +loglevel@^1.6.7, loglevel@^1.6.8: version "1.6.8" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171" integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA== +long@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -12476,13 +13645,13 @@ lowercase-keys@^2.0.0: resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lowlight@1.12.1: - version "1.12.1" - resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz#014acf8dd73a370e02ff1cc61debcde3bb1681eb" - integrity sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w== +lowlight@^1.14.0: + version "1.14.0" + resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.14.0.tgz#83ebc143fec0f9e6c0d3deffe01be129ce56b108" + integrity sha512-N2E7zTM7r1CwbzwspPxJvmjAbxljCPThTFawEX2Z7+P3NGrrvY54u8kyU16IY4qWfoVIxY8SYCS8jTkuG7TqYA== dependencies: - fault "^1.0.2" - highlight.js "~9.15.0" + fault "^1.0.0" + highlight.js "~10.1.0" lowlight@~1.11.0: version "1.11.0" @@ -12492,7 +13661,7 @@ lowlight@~1.11.0: fault "^1.0.2" highlight.js "~9.13.0" -lru-cache@^5.1.1: +lru-cache@^5.0.0, lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== @@ -12630,10 +13799,10 @@ markdown-to-jsx@^6.11.4: prop-types "^15.6.2" unquote "^1.1.0" -material-table@1.62.x: - version "1.62.0" - resolved "https://registry.npmjs.org/material-table/-/material-table-1.62.0.tgz#117793ebf16ab0fccbb6f8a670d849a7be0b5995" - integrity sha512-+3tnk32lXtkXeKM7k/hZ82jpSzlXU5CsWXqJHq4Tl0Un7ycjK2Kef6EMPqeE3i58vKNqbIvbrFf/ESH0D/Qwig== +material-table@1.68.x: + version "1.68.0" + resolved "https://registry.npmjs.org/material-table/-/material-table-1.68.0.tgz#275c3d9a885c40ae4bc5a7461c00e877f92397b9" + integrity sha512-dyJJaVsS3m+i6sn71AvYcVdA1P9X1XiUOM2PekfvEeeMtkdQb66oChGkk77ndYi3Ja6j4DovGVNrgeVLwXLZiw== dependencies: "@date-io/date-fns" "^1.1.0" "@material-ui/pickers" "^3.2.2" @@ -12642,6 +13811,8 @@ material-table@1.62.x: debounce "^1.2.0" fast-deep-equal "2.0.1" filefy "0.1.10" + jspdf "1.5.3" + jspdf-autotable "3.5.3" prop-types "^15.6.2" react-beautiful-dnd "^13.0.0" react-double-scrollbar "0.0.15" @@ -13075,6 +14246,11 @@ moment@2.26.0, moment@^2.25.3, moment@^2.26.0: resolved "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz#5e1f82c6bafca6e83e808b30c8705eed0dcbd39a" integrity sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw== +moment@^2.27.0: + version "2.27.0" + resolved "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" + integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== + monaco-editor@^0.20.0: version "0.20.0" resolved "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.20.0.tgz#5d5009343a550124426cb4d965a4d27a348b4dea" @@ -13127,7 +14303,7 @@ ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -msw@^0.19.0: +msw@^0.19.0, msw@^0.19.5: version "0.19.5" resolved "https://registry.npmjs.org/msw/-/msw-0.19.5.tgz#7d2a1a852ccf1644d3db6735d69fff6777aac33f" integrity sha512-J5eQ++gDVZoHPC8gVXtWcakLjgmPipvFj/sEnlRV/WViXuiq2CamSqO3Wbh6H8bAmj+k2vUWCfcVT1HjMdKB2Q== @@ -13255,6 +14431,11 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -13268,11 +14449,6 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -nocache@2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" - integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q== - node-dir@^0.1.10: version "0.1.17" resolved "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -13289,7 +14465,7 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" -node-fetch@2.6.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: +node-fetch@2.6.0, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.5.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== @@ -13433,6 +14609,11 @@ node-releases@^1.1.29, node-releases@^1.1.52: dependencies: semver "^6.3.0" +node-releases@^1.1.58: + version "1.1.60" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== + node-request-interceptor@^0.2.5: version "0.2.6" resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.6.tgz#541278d7033bb6a8befb5dd793f83428cf6446a2" @@ -13644,6 +14825,11 @@ number-is-nan@^1.0.0: resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +"nwmatcher@>= 1.3.7 < 2.0.0": + version "1.4.4" + resolved "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + nwsapi@^2.0.7, nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -13659,6 +14845,11 @@ 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" @@ -13688,6 +14879,11 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== +object-path@^0.11.4: + version "0.11.4" + resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz#370ae752fbf37de3ea70a861c23bba8915691949" + integrity sha1-NwrnUvvzfePqcKhhwju6iRVpGUk= + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" @@ -13778,6 +14974,11 @@ octokit-pagination-methods@^1.1.0: resolved "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" integrity sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ== +omggif@1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/omggif/-/omggif-1.0.7.tgz#59d2eecb0263de84635b3feb887c0c9973f1e49d" + integrity sha1-WdLuywJj3oRjWz/riHwMmXPx5J0= + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -13903,6 +15104,13 @@ os-homedir@^1.0.0: resolved "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + os-name@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" @@ -13949,6 +15157,13 @@ p-finally@^2.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== +p-limit@3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== + dependencies: + p-try "^2.0.0" + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -14076,6 +15291,16 @@ 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" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + pako@~1.0.5: version "1.0.11" resolved "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -14129,6 +15354,18 @@ parse-entities@^1.1.0, parse-entities@^1.1.2: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -14206,7 +15443,12 @@ parse5@5.1.1: resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parseurl@~1.3.2, parseurl@~1.3.3: +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha1-m387DeMr543CQBsXVzzK8Pb1nZQ= + +parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -14444,11 +15686,111 @@ 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.2.0: version "2.2.0" resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz#caab4d38a9de4fdc29c9317acceed752897de41c" integrity sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A== +pg-connection-string@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.3.0.tgz#c13fcb84c298d0bfa9ba12b40dd6c23d946f55d6" + integrity sha512-ukMTJXLI7/hZIwTW7hGMZJ0Lj0S2XQBCJ4Shv4y1zgQ/vqVea+FLhzywvPj0ujSuofu+yA4MYHGZPTsgjBgJ+w== + +pg-int8@1.0.1: + version "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.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.1.tgz#5f4afc0f58063659aeefa952d36af49fa28b30e0" + integrity sha512-BQDPWUeKenVrMMDN9opfns/kZo4lxmSWhIqo+cSAF7+lfi9ZclQbr9vfnlNaPr8wYF3UYjm5X0yPAhbcgqNOdA== + +pg-protocol@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.5.tgz#28a1492cde11646ff2d2d06bdee42a3ba05f126c" + integrity sha512-1uYCckkuTfzz/FCefvavRywkowa6M5FohNMF5OjKrqo9PSR8gYc8poVmwwYQaBxhmQdBjhtP514eXy9/Us2xKg== + +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" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.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.3.0" + resolved "https://registry.npmjs.org/pg/-/pg-8.3.0.tgz#941383300d38eef51ecb88a0188cec441ab64d81" + integrity sha512-jQPKWHWxbI09s/Z9aUvoTbvGgoj98AU7FDCcQ7kdejupn/TcNpx56v2gaOTzXkzOajmOEJEdi9eTh9cA2RVAjQ== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.3.0" + pg-pool "^3.2.1" + pg-protocol "^1.2.5" + pg-types "^2.1.0" + pgpass "1.x" + semver "4.3.2" + +pgpass@1.*, pgpass@1.x: + version "1.0.2" + resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz#2a7bb41b6065b67907e91da1b07c1847c877b306" + integrity sha1-Knu0G2BltnkH6R2hsHwYR8h3swY= + dependencies: + 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== + dependencies: + bluebird "^3.3.5" + pg "^6.1.0" + pg-connection-string "^0.1.3" + yargs "^5.0.0" + picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" @@ -14980,6 +16322,33 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^5.4.0" +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" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "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: + version "1.0.5" + resolved "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz#710b27de5f27d550f6e80b5d34f7ba189213c2ee" + integrity sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -15058,10 +16427,10 @@ pretty-hrtime@^1.0.3: resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= -prismjs@^1.8.4: - version "1.19.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz#713afbd45c3baca4b321569f2df39e17e729d4dc" - integrity sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw== +prismjs@^1.20.0, prismjs@^1.8.4, prismjs@~1.20.0: + version "1.20.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz#9b685fc480a3514ee7198eac6a3bf5024319ff03" + integrity sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ== optionalDependencies: clipboard "^2.0.0" @@ -15097,6 +16466,11 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@8.1.0: + version "8.1.0" + resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz#30059da54d1358ce905ac581f287e184aedf995d" + integrity sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA== + promise-polyfill@^8.1.3: version "8.1.3" resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz#8c99b3cf53f3a91c68226ffde7bde81d7f904116" @@ -15666,9 +17040,9 @@ react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-i integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-lazylog@^4.5.2: - version "4.5.2" - resolved "https://registry.npmjs.org/react-lazylog/-/react-lazylog-4.5.2.tgz#9b66a0997348690f56286f6afcda97ebd0c62b7c" - integrity sha512-XvAjlzs8tzbjmqyEdj8HwW8Kg5nfqZAzIGeeG1incZuhuXQkekIs6nYb3W/GQNxDpA1CowgNUR4UfP+7/C2Ang== + version "4.5.3" + resolved "https://registry.npmjs.org/react-lazylog/-/react-lazylog-4.5.3.tgz#289e24995b5599e75943556ac63f5e2c04d0001e" + integrity sha512-lyov32A/4BqihgXgtNXTHCajXSXkYHPlIEmV8RbYjHIMxCFSnmtdg4kDCI3vATz7dURtiFTvrw5yonHnrS+NNg== dependencies: "@mattiasbuelens/web-streams-polyfill" "^0.2.0" fetch-readablestream "^0.2.0" @@ -15786,16 +17160,16 @@ react-syntax-highlighter@^11.0.2: prismjs "^1.8.4" refractor "^2.4.1" -react-syntax-highlighter@^12.2.1: - version "12.2.1" - resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz#14d78352da1c1c3f93c6698b70ec7c706b83493e" - integrity sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA== +react-syntax-highlighter@^13.2.1: + version "13.2.1" + resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-13.2.1.tgz#3d5a3b655cd85ce06b9508e0365d01ef9b8560bb" + integrity sha512-O/AF/ll4I3Dp+2WuKbnF5yKG4b1BUncqcpTW6MIBDJPr2eGKqlNGS3Nv+lyCFtAIHx8N+/ktti8qH14Q5VjjcA== dependencies: "@babel/runtime" "^7.3.1" - highlight.js "~9.15.1" - lowlight "1.12.1" - prismjs "^1.8.4" - refractor "^2.4.1" + highlight.js "^10.1.1" + lowlight "^1.14.0" + prismjs "^1.20.0" + refractor "^3.0.0" react-test-renderer@^16.13.1: version "16.13.1" @@ -15825,24 +17199,30 @@ react-transition-group@^4.0.0, react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react-use@^14.2.0: - version "14.2.0" - resolved "https://registry.npmjs.org/react-use/-/react-use-14.2.0.tgz#abac033fae5e358599b7e38084ff11b02e5d4868" - integrity sha512-vwC7jsBsiDENLrXGPqIH3W4mMS2j24h5jp4ol3jiiUQzZhCaG+ihumrShJxBI59hXso1pLHAePRQAg/fJjDcaQ== +react-universal-interface@^0.6.2: + version "0.6.2" + resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" + integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== + +react-use@^15.3.3: + version "15.3.3" + resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.3.tgz#f16de7a16286c446388e8bd99680952fc3dc9a95" + integrity sha512-nYb94JbmDCaLZg3sOXmFW8HN+lXWxnl0caspXoYfZG1CON8JfLN9jMOyxRDUpm7dUq7WZ5mIept/ByqBQKJ0wQ== dependencies: "@types/js-cookie" "2.2.6" "@xobotyi/scrollbar-width" "1.9.5" copy-to-clipboard "^3.2.0" - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" fast-shallow-equal "^1.0.0" js-cookie "^2.2.1" nano-css "^5.2.1" + react-universal-interface "^0.6.2" resize-observer-polyfill "^1.5.1" screenfull "^5.0.0" set-harmonic-interval "^1.0.1" throttle-debounce "^2.1.0" ts-easing "^0.2.0" - tslib "^1.10.0" + tslib "^2.0.0" react-virtualized@^9.21.0: version "9.21.2" @@ -15953,6 +17333,15 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + integrity sha1-ljYlN48+HE1IyFhytabsfV0JMjc= + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + read-pkg@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" @@ -16074,11 +17463,6 @@ redux@^4.0.4: loose-envify "^1.4.0" symbol-observable "^1.2.0" -referrer-policy@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e" - integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA== - refractor@^2.4.1: version "2.10.1" resolved "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz#166c32f114ed16fd96190ad21d5193d3afc7d34e" @@ -16088,6 +17472,15 @@ refractor@^2.4.1: parse-entities "^1.1.2" prismjs "~1.17.0" +refractor@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/refractor/-/refractor-3.0.0.tgz#7c8072eaf49dbc1b333e7acc64fb52a1c9b17c75" + integrity sha512-eCGK/oP4VuyW/ERqjMZRZHxl2QsztbkedkYy/SxqE/+Gh1gLaAF17tWIOcVJDiyGhar1NZy/0B9dFef7J0+FDw== + dependencies: + hastscript "^5.0.0" + parse-entities "^2.0.0" + prismjs "~1.20.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -16242,9 +17635,9 @@ replace-ext@1.0.0: integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs= replace-in-file@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.0.0.tgz#a583be911c4af3ecbf709ca97c48f3f9e7f2b626" - integrity sha512-vMmJekpRgju0GA0UvRxauDbQ645wGXxasVZfw5l5HKk58OlAI1tMmXNcV+YAUKrS/XFdBPla5qgvjI5Tqvshvg== + version "6.1.0" + resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.1.0.tgz#9f9ddd7bb70d6ad231d2ad692e1b646e73d06647" + integrity sha512-URzjyF3nucvejuY13HFd7O+Q6tFJRLKGHLYVvSh+LiZj3gFXzSYGnIkQflnJJulCAI2/RTZaZkpOtdVdW0EhQA== dependencies: chalk "^4.0.0" glob "^7.1.6" @@ -16273,7 +17666,7 @@ request-promise-native@^1.0.5, request-promise-native@^1.0.8: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.55.0, request@^2.85.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.npmjs.org/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -16304,6 +17697,11 @@ require-directory@^2.1.1: resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -16341,6 +17739,11 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" +resolve-from@5.0.0, resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -16351,11 +17754,6 @@ resolve-from@^4.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -16368,7 +17766,7 @@ resolve@1.15.1: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -16411,16 +17809,16 @@ ret@~0.1.10: resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +retry@0.12.0, retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= + retry@^0.10.0: version "0.10.1" resolved "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -16436,6 +17834,11 @@ rgba-regex@^1.0.0: resolved "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM= +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha1-1lBezbMEplldom+ktDMHMGd1lF0= + rifm@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be" @@ -16473,11 +17876,11 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: inherits "^2.0.1" rollup-plugin-dts@^1.4.6: - version "1.4.7" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.7.tgz#6255147ac777314c0725a1efcb42df10fe282243" - integrity sha512-QkunbJ96yUNkW95k/Vd6SdTjCbWSG0rMVUtpHSCwfg078Z7vbDaBnfz/gkSqR5h8WFMxoccBT4aodHm6387Jvg== + version "1.4.10" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.10.tgz#0373c4284a2ba4d2d72df69c289271a816bc2736" + integrity sha512-bL6MBXc8lK7D5b/tYbHaglxs4ZxMQTQilGA6Xm9KQBEj4h9ZwIDlAsvDooGjJ/cOw23r3POTRtSCEyTHxtzHJg== optionalDependencies: - "@babel/code-frame" "^7.8.3" + "@babel/code-frame" "^7.10.4" rollup-plugin-esbuild@^2.0.0: version "2.3.0" @@ -16495,9 +17898,9 @@ rollup-plugin-image-files@^1.4.2: rollup-pluginutils "2.4.1" rollup-plugin-peer-deps-external@^2.2.2: - version "2.2.2" - resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.2.tgz#506cef67fb68f41cca3ec08ca6ff7936b190f568" - integrity sha512-XcHH4UW9exRTAf73d8rk2dw2UgF//cWbilhRI4Ni/n+t0zA1eBtohKyJROn0fxa4/+WZ5R3onAyIDiwRQL+59A== + version "2.2.3" + resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.3.tgz#059a8aec1eefb48a475e9fcedc3b9e3deb521213" + integrity sha512-W6IePXTExGXVDAlfZbNUUrx3GxUOZP248u5n4a4ID1XZMrbQ+uGeNiEfapvdzwx0qZi5DNH/hDLiPUP+pzFIxg== rollup-plugin-postcss@^3.1.1: version "3.1.1" @@ -16538,7 +17941,7 @@ rollup-pluginutils@2.4.1: estree-walker "^0.6.0" micromatch "^3.1.10" -rollup-pluginutils@2.8.2, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2: +rollup-pluginutils@2.8.2, rollup-pluginutils@^2.8.2: version "2.8.2" resolved "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e" integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ== @@ -16553,10 +17956,10 @@ rollup@0.64.1: "@types/estree" "0.0.39" "@types/node" "*" -rollup@2.10.x: - version "2.10.9" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.10.9.tgz#17dcc6753c619efcc1be2cf61d73a87827eebdf9" - integrity sha512-dY/EbjiWC17ZCUSyk14hkxATAMAShkMsD43XmZGWjLrgFj15M3Dw2kEkA9ns64BiLFm9PKN6vTQw8neHwK74eg== +rollup@2.23.x: + version "2.23.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" + integrity sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg== optionalDependencies: fsevents "~2.1.2" @@ -16599,10 +18002,10 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3, rxjs@^6.5.4, rxjs@^6.5.5: - version "6.5.5" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== +rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.3, rxjs@^6.5.5: + version "6.6.0" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.0.tgz#af2901eedf02e3a83ffa7f886240ff9018bbec84" + integrity sha512-3HMA8z/Oz61DUHe+SdOiQyzIf4tOx5oQHmMir7IZEu6TMqCLHT4LRcmNaUS0NwOz8VLvmmBduMsoaUvMaIiqzg== dependencies: tslib "^1.9.0" @@ -16665,7 +18068,7 @@ sanitize-html@^1.27.0: srcset "^2.0.1" xtend "^4.0.1" -sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4: +sax@>=0.6.0, sax@^1.1.4, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -16747,6 +18150,11 @@ semver-regex@^2.0.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" @@ -16874,7 +18282,12 @@ setprototypeof@1.1.1: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== -sha.js@^2.4.0, sha.js@^2.4.8: +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.0, sha.js@^2.4.11, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -17201,6 +18614,11 @@ space-separated-tokens@^1.0.0: resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= + spdx-correct@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4" @@ -17371,6 +18789,16 @@ stack-utils@^2.0.2: dependencies: escape-string-regexp "^2.0.0" +stackblur-canvas@2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.2.0.tgz#cacc5924a0744b3e183eb2e6c1d8559c1a17c26e" + integrity sha512-5Gf8dtlf8k6NbLzuly2NkGrkS/Ahh+I5VUjO7TnFizdJtgpfpLLEdQlLe9umbcnZlitU84kfYjXE67xlSXfhfQ== + +stackblur-canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz#849aa6f94b272ff26f6471fa4130ed1f7e47955b" + integrity sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs= + stackframe@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71" @@ -17394,9 +18822,9 @@ stacktrace-js@^2.0.0: stacktrace-gps "^3.0.4" start-server-and-test@^1.10.11: - version "1.11.0" - resolved "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.11.0.tgz#1b1a83d062b0028ee6e296bb4e0231f2d8b2f4af" - integrity sha512-FhkJFYL/lvbd0tKWvbxWNWjtFtq3Zpa09QDjA8EUH88AsgNL4hkAAKYNmbac+fFM8/GIZoJ1Mj4mm3SMI0X1bA== + version "1.11.2" + resolved "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-1.11.2.tgz#9144b7b6f25197148f159f261ae80119afbb17d5" + integrity sha512-rk1zS5WQvdbc8slE5hPtzfji1dFSnBAfm+vSjToZNrBvozHJvuAG80xE5u8N4tQjg3Ej1Crjc19J++r28HGJgg== dependencies: bluebird "3.7.2" check-more-types "2.24.0" @@ -17404,7 +18832,7 @@ start-server-and-test@^1.10.11: execa "3.4.0" lazy-ass "1.6.0" ps-tree "1.2.0" - wait-on "4.0.0" + wait-on "5.1.0" start-server-webpack-plugin@^2.2.5: version "2.2.5" @@ -17496,7 +18924,7 @@ stream-shift@^1.0.0: resolved "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== -streamsearch@~0.1.2: +streamsearch@0.1.2, streamsearch@~0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= @@ -17511,6 +18939,11 @@ string-argv@0.3.1: resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== +string-env-interpolation@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" + integrity sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg== + string-hash@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" @@ -17524,7 +18957,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -string-width@^1.0.1: +string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -17773,6 +19206,17 @@ stylis@3.5.0: resolved "https://registry.npmjs.org/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1" integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw== +subscriptions-transport-ws@0.9.17, subscriptions-transport-ws@^0.9.11, subscriptions-transport-ws@^0.9.16: + version "0.9.17" + resolved "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz#e30e40f0caae0d2781903c01a8cb51b6e2682098" + integrity sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA== + dependencies: + backo2 "^1.0.2" + eventemitter3 "^3.1.0" + iterall "^1.2.1" + symbol-observable "^1.0.4" + ws "^5.2.0" + sucrase@^3.14.1: version "3.15.0" resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.15.0.tgz#78596a78be7264a65b52ed8d873883413ef0220c" @@ -17850,7 +19294,7 @@ supports-hyperlinks@^2.0.0: has-flag "^4.0.0" supports-color "^7.0.0" -svg-parser@^2.0.0: +svg-parser@^2.0.0, svg-parser@^2.0.2: version "2.0.4" resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== @@ -17881,12 +19325,12 @@ swr@^0.2.2: dependencies: fast-deep-equal "2.0.1" -symbol-observable@^1.1.0, symbol-observable@^1.2.0: +symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -symbol-tree@^3.2.2, symbol-tree@^3.2.4: +"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== @@ -18294,6 +19738,11 @@ toposort@^2.0.2: resolved "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= +tosource@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/tosource/-/tosource-1.0.0.tgz#42d88dd116618bcf00d6106dd5446f3427902ff1" + integrity sha1-QtiN0RZhi88A1hBt1URvNCeQL/E= + touch@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -18301,7 +19750,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: +tough-cookie@^2.2.0, tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== @@ -18332,6 +19781,11 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -18392,6 +19846,13 @@ ts-interface-checker@^0.1.9: resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.10.tgz#b68a49e37e90a05797e590f08494dd528bf383cf" integrity sha512-UJYuKET7ez7ry0CnvfY6fPIUIZDw+UI3qvTUQeS2MyI4TgEeWAUBqy185LeaHcdJ9zG2dgFpPJU/AecXU0Afug== +ts-invariant@^0.4.0: + version "0.4.4" + resolved "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" + integrity sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA== + dependencies: + tslib "^1.9.3" + ts-jest@^26.0.0: version "26.1.1" resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz#b98569b8a4d4025d966b3d40c81986dd1c510f8d" @@ -18455,6 +19916,11 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^2.0.0, tslib@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" + integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -18518,7 +19984,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -18526,6 +19992,16 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.npmjs.org/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" + integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== + typed-styles@^0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" @@ -18737,6 +20213,13 @@ universalify@^1.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +unixify@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unixify/-/unixify-1.0.0.tgz#3a641c8c2ffbce4da683a5c70f03a462940c2090" + integrity sha1-OmQcjC/7zk2mg6XHDwOkYpQMIJA= + dependencies: + normalize-path "^2.1.1" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -18880,7 +20363,7 @@ util.promisify@1.0.0: define-properties "^1.1.2" object.getownpropertydescriptors "^2.0.3" -util.promisify@~1.0.0: +util.promisify@^1.0.0, util.promisify@~1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== @@ -18914,7 +20397,7 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: +uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -18955,6 +20438,11 @@ v8flags@^3.1.3: dependencies: homedir-polyfill "^1.0.1" +valid-url@1.0.9: + version "1.0.9" + resolved "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= + validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.3: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -19065,17 +20553,16 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" -wait-on@4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/wait-on/-/wait-on-4.0.0.tgz#4d7e4485ca759968897fd3b0cc50720c0b4ca959" - integrity sha512-QrW3J8LzS5ADPfD9Rx5S6KJck66xkqyiFKQs9jmUTkIhiEOmkzU7WRZc+MjsnmkrgjitS2xQ4bb13hnlQnKBUQ== +wait-on@5.1.0: + version "5.1.0" + resolved "https://registry.npmjs.org/wait-on/-/wait-on-5.1.0.tgz#b697f21c6fea0908b9c7ad6ed56ace4736768b66" + integrity sha512-JM0kgaE+V0nCDvSl72iM05W8NDt2E2M56WC5mzR7M+T+k6xjt2yYpyom+xA8RasSunFGzbxIpAXbVzXqtweAnA== dependencies: - "@hapi/joi" "^16.1.8" - lodash "^4.17.15" - minimist "^1.2.0" - request "^2.88.0" - request-promise-native "^1.0.8" - rxjs "^6.5.4" + "@hapi/joi" "^17.1.1" + axios "^0.19.2" + lodash "^4.17.19" + minimist "^1.2.5" + rxjs "^6.5.5" walker@^1.0.7, walker@~1.0.5: version "1.0.7" @@ -19114,6 +20601,11 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" @@ -19267,6 +20759,17 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +websocket@1.0.31: + version "1.0.31" + resolved "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz#e5d0f16c3340ed87670e489ecae6144c79358730" + integrity sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ== + dependencies: + debug "^2.2.0" + es5-ext "^0.10.50" + nan "^2.14.0" + typedarray-to-buffer "^3.1.5" + yaeti "^0.0.6" + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -19279,7 +20782,7 @@ whatwg-fetch@3.0.0, whatwg-fetch@^3.0.0: resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-fetch@^2.0.4: +whatwg-fetch@^2.0.0, whatwg-fetch@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== @@ -19289,6 +20792,14 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659" + integrity sha1-U5ayBD8CDub3BNnEXqhRnnJN5lk= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^6.4.1: version "6.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8" @@ -19316,6 +20827,11 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^5.0.0" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + which-module@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -19354,6 +20870,11 @@ widest-line@^3.1.0: dependencies: string-width "^4.0.0" +window-size@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" + integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= + windows-release@^3.1.0: version "3.2.0" resolved "https://registry.npmjs.org/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" @@ -19408,6 +20929,14 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -19504,7 +21033,7 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.1.2, ws@^6.2.1: +ws@^6.0.0, ws@^6.1.2, ws@^6.2.1: version "6.2.1" resolved "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== @@ -19521,11 +21050,6 @@ x-is-string@^0.1.0: resolved "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= -x-xss-protection@1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/x-xss-protection/-/x-xss-protection-1.3.0.tgz#3e3a8dd638da80421b0e9fff11a2dbe168f6d52c" - integrity sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg== - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -19549,6 +21073,11 @@ xml-encryption@^1.0.0: xmldom "~0.1.15" xpath "0.0.27" +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha1-TYuPHszTQZqjYgYb7O9RXh5VljU= + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -19577,7 +21106,7 @@ xmldom@0.1.27: resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= -xmldom@0.1.x, xmldom@~0.1.15: +xmldom@0.1.x, xmldom@^0.1.22, xmldom@~0.1.15: version "0.1.31" resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== @@ -19594,16 +21123,34 @@ xregexp@^4.3.0: dependencies: "@babel/runtime-corejs3" "^7.8.3" +xss@^1.0.6: + version "1.0.7" + resolved "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz#a554cbd5e909324bd6893fb47fff441ad54e2a95" + integrity sha512-A9v7tblGvxu8TWXQC9rlpW96a+LN1lyw6wyhpTmmGW+FwRMactchBR3ROKSi33UPCUcUHSu8s9YP6F+K3Mw//w== + dependencies: + commander "^2.20.3" + cssfilter "0.0.10" + xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + y18n@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -19650,7 +21197,15 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs@^13.3.2: +yargs-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-3.2.0.tgz#5081355d19d9d0c8c5d81ada908cb4e6d186664f" + integrity sha1-UIE1XRnZ0MjF2BrakIy05tGGZk8= + dependencies: + camelcase "^3.0.0" + lodash.assign "^4.1.0" + +yargs@^13.3.0, yargs@^13.3.2: version "13.3.2" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== @@ -19700,6 +21255,26 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.1" +yargs@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-5.0.0.tgz#3355144977d05757dbb86d6e38ec056123b3a66e" + integrity sha1-M1UUSXfQV1fbuG1uOOwFYSOzpm4= + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + lodash.assign "^4.2.0" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + window-size "^0.2.0" + y18n "^3.2.1" + yargs-parser "^3.2.0" + yauzl@2.10.0, yauzl@^2.10.0: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" @@ -19739,7 +21314,15 @@ yup@^0.29.1: synchronous-promise "^2.0.10" toposort "^2.0.2" -zen-observable@^0.8.15: +zen-observable-ts@^0.8.21: + version "0.8.21" + resolved "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" + integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg== + dependencies: + tslib "^1.9.3" + zen-observable "^0.8.0" + +zen-observable@^0.8.0, zen-observable@^0.8.15: version "0.8.15" resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==