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/frontend.yml b/.github/workflows/frontend.yml index f154c586fa..0137e4b9dc 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -76,8 +76,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/ADOPTERS.md b/ADOPTERS.md index 5551a91c41..3af423df9e 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,8 +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. | -| [H-E-B](https://www.heb.com) | [@german-j-rodriguez](https://github.com/german-j-rodriguez) | Initial work on Engineering Portal service platform. | +| 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/app-config.yaml b/app-config.yaml index cd8494613a..9a4a14b923 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -11,14 +11,17 @@ backend: credentials: true proxy: - '/circleci/api': - target: 'https://circleci.com/api/v1.1' + "/circleci/api": + 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 + +sentry: + organization: spotify diff --git a/docker/run.sh b/docker/run.sh index 469fdd08cf..9cecd88f63 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -22,7 +22,7 @@ function inject_config() { >&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..6763554afb 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 diff --git a/docs/dls/figma.md b/docs/dls/figma.md index e69de29bb2..8fe6ea8d0f 100644 --- a/docs/dls/figma.md +++ b/docs/dls/figma.md @@ -0,0 +1 @@ +We have a [Figma component library](https://www.figma.com/@backstage) that you can use to build your own plugins for Backstage. diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 4514b4c106..ee9a40c983 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -1 +1,29 @@ -# FAQ +# TechDocs FAQ + +This page answer frequently asked questions about [TechDocs]. + +#### 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 +doucmentation 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. + +_Add a question that you think others might be interested in? Edit the file +[here](https://github.com/spotify/backstage/edit/master/docs/features/techdocs/FAQ.md)._ + +[techdocs]: README.md diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index d5d6db1240..b6a4d82b92 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -36,11 +36,7 @@ Spotify’s developer experience offering with 2,400+ documentation sites and - [Getting Started] - [Concepts] -- [Reading Documentation] -- [Writing Documentation] -- [Publishing Documentation] -- [Contributing] -- [Debugging] +- [Creating and Publishing Documentation] - [FAQ] ## Tech Stack @@ -54,11 +50,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..d0e3c3c0ea 100644 --- a/docs/features/techdocs/concepts.md +++ b/docs/features/techdocs/concepts.md @@ -1 +1,53 @@ # Concepts + +This page describes concepts that has been 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 a 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 is different pieces of functionality used inside the TechDocs +Reader. The reason to why transformers were introduced is 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 80% rename from docs/features/techdocs/publishing-documentation.md rename to docs/features/techdocs/creating-and-publishing.md index 73ac74b458..0ac4f054d7 100644 --- a/docs/features/techdocs/publishing-documentation.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -1,11 +1,21 @@ -# 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 from + your published docs. ## Prerequisities - [Docker](https://docs.docker.com/get-docker/) - Static file hosting +- A working Backstage instance with TechDocs installed + [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 @@ -32,7 +42,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/ 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/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/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/lerna.json b/lerna.json index 4b4e7d0298..1a316638fb 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.16" } diff --git a/mkdocs.yml b/mkdocs.yml index 613c9b7216..168ebb4b37 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -4,6 +4,7 @@ site_description: 'Main documentation for Backstage features and platform APIs' nav: - 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: diff --git a/package.json b/package.json index 9d702c4755..500b688990 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ }, "scripts": { "start": "yarn workspace example-app start", - "bundle": "yarn workspace example-app bundle", "build": "lerna run build", "tsc": "tsc", "clean": "backstage-cli clean && lerna run clean", @@ -16,7 +15,7 @@ "lint": "lerna run lint --since origin/master --", "lint:all": "lerna run lint --", "lint:type-deps": "node scripts/check-type-dependencies.js", - "docker-build": "yarn bundle && docker build . -t spotify/backstage", + "docker-build": "yarn workspace example-app build && docker build . -t spotify/backstage", "docker-build:all": "yarn tsc && yarn build && yarn docker-build && yarn workspace example-backend build-image", "create-plugin": "backstage-cli create-plugin", "remove-plugin": "backstage-cli remove-plugin", diff --git a/packages/app/package.json b/packages/app/package.json index 63f17e3ad1..ea4c308380 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,28 +1,29 @@ { "name": "example-app", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": true, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/plugin-catalog": "^0.1.1-alpha.14", - "@backstage/plugin-circleci": "^0.1.1-alpha.14", - "@backstage/plugin-explore": "^0.1.1-alpha.14", - "@backstage/plugin-github-actions": "^0.1.1-alpha.14", - "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.14", - "@backstage/plugin-graphiql": "^0.1.1-alpha.14", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.14", - "@backstage/plugin-register-component": "^0.1.1-alpha.14", - "@backstage/plugin-rollbar": "^0.1.1-alpha.14", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.14", - "@backstage/plugin-sentry": "^0.1.1-alpha.14", - "@backstage/plugin-tech-radar": "^0.1.1-alpha.14", - "@backstage/plugin-techdocs": "^0.1.1-alpha.14", - "@backstage/plugin-welcome": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/plugin-catalog": "^0.1.1-alpha.16", + "@backstage/plugin-circleci": "^0.1.1-alpha.16", + "@backstage/plugin-explore": "^0.1.1-alpha.16", + "@backstage/plugin-github-actions": "^0.1.1-alpha.16", + "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.16", + "@backstage/plugin-graphiql": "^0.1.1-alpha.16", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.16", + "@backstage/plugin-register-component": "^0.1.1-alpha.16", + "@backstage/plugin-rollbar": "^0.1.1-alpha.16", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.16", + "@backstage/plugin-sentry": "^0.1.1-alpha.16", + "@backstage/plugin-tech-radar": "^0.1.1-alpha.16", + "@backstage/plugin-techdocs": "^0.1.1-alpha.16", + "@backstage/plugin-welcome": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", + "@octokit/rest": "^18.0.0", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -50,7 +51,7 @@ }, "scripts": { "start": "backstage-cli app:serve", - "bundle": "backstage-cli app:build", + "build": "backstage-cli app:build", "clean": "backstage-cli clean", "test": "backstage-cli test", "test:e2e": "start-server-and-test start http://localhost:3000 cy:dev", diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index 011c549619..cac3d6e171 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -82,7 +82,9 @@ export const apis = (config: ConfigApi) => { circleCIApiRef, new CircleCIApi(`${backendUrl}/proxy/circleci/api`), ); + builder.add(githubActionsApiRef, new GithubActionsClient()); + builder.add(featureFlagsApiRef, new FeatureFlags()); builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003')); diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 2f8009d555..3c944846d1 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -41,7 +41,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@types/compression": "^1.7.0", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", diff --git a/packages/backend-common/src/middleware/errorHandler.test.ts b/packages/backend-common/src/middleware/errorHandler.test.ts index e8a5a7f47a..a7a3d64bd1 100644 --- a/packages/backend-common/src/middleware/errorHandler.test.ts +++ b/packages/backend-common/src/middleware/errorHandler.test.ts @@ -96,4 +96,38 @@ describe('errorHandler', () => { expect((await r.get('/NotFoundError')).status).toBe(404); expect((await r.get('/ConflictError')).status).toBe(409); }); + + it('logs all 500 errors', async () => { + const app = express(); + + const mockLogger = { child: jest.fn(), error: jest.fn() }; + mockLogger.child.mockImplementation(() => mockLogger as any); + + const thrownError = new Error('some error'); + + app.use('/breaks', () => { + throw thrownError; + }); + app.use(errorHandler({ logger: mockLogger as any })); + + await request(app).get('/breaks'); + + expect(mockLogger.error).toHaveBeenCalledWith(thrownError); + }); + + it('does not log 400 errors', async () => { + const app = express(); + + const mockLogger = { child: jest.fn(), error: jest.fn() }; + mockLogger.child.mockImplementation(() => mockLogger as any); + + app.use('/NotFound', () => { + throw new errors.NotFoundError(); + }); + app.use(errorHandler({ logger: mockLogger as any })); + + await request(app).get('/NotFound'); + + expect(mockLogger.error).not.toHaveBeenCalled(); + }); }); diff --git a/packages/backend-common/src/middleware/errorHandler.ts b/packages/backend-common/src/middleware/errorHandler.ts index ad8f170dcd..798d173d38 100644 --- a/packages/backend-common/src/middleware/errorHandler.ts +++ b/packages/backend-common/src/middleware/errorHandler.ts @@ -15,7 +15,9 @@ */ import { ErrorRequestHandler, NextFunction, Request, Response } from 'express'; +import { Logger } from 'winston'; import * as errors from '../errors'; +import { getRootLogger } from '../logging'; export type ErrorHandlerOptions = { /** @@ -24,6 +26,13 @@ export type ErrorHandlerOptions = { * If not specified, by default shows stack traces only in development mode. */ showStackTraces?: boolean; + + /** + * Logger + * + * If not specified, by default shows stack traces only in development mode. + */ + logger?: Logger; }; /** @@ -39,12 +48,17 @@ export type ErrorHandlerOptions = { * * @returns An Express error request handler */ + export function errorHandler( options: ErrorHandlerOptions = {}, ): ErrorRequestHandler { const showStackTraces = options.showStackTraces ?? process.env.NODE_ENV === 'development'; + const logger = (options.logger || getRootLogger()).child({ + type: 'errorHandler', + }); + /* eslint-disable @typescript-eslint/no-unused-vars */ return ( error: Error, @@ -61,6 +75,11 @@ export function errorHandler( const status = getStatusCode(error); const message = showStackTraces ? error.stack : error.message; + + if (logger && status >= 500) { + logger.error(error); + } + response.status(status).send(message); }; } diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 19418b2671..4a51fb195c 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -45,9 +45,9 @@ export class ServiceBuilderImpl implements ServiceBuilder { // reloading private module: NodeModule; - constructor(module: NodeModule) { + constructor(moduleRef: NodeModule) { this.routers = []; - this.module = module; + this.module = moduleRef; } loadConfig(config: ConfigReader): ServiceBuilder { diff --git a/packages/backend/README.md b/packages/backend/README.md index 47574e12af..90c70912f0 100644 --- a/packages/backend/README.md +++ b/packages/backend/README.md @@ -29,6 +29,7 @@ AUTH_GOOGLE_CLIENT_ID=x AUTH_GOOGLE_CLIENT_SECRET=x \ AUTH_GITHUB_CLIENT_ID=x AUTH_GITHUB_CLIENT_SECRET=x \ AUTH_OAUTH2_CLIENT_ID=x AUTH_OAUTH2_CLIENT_SECRET=x \ AUTH_OAUTH2_AUTH_URL=x AUTH_OAUTH2_TOKEN_URL=x \ +ROLLBAR_ACCOUNT_TOKEN=x \ SENTRY_TOKEN=x \ LOG_LEVEL=debug \ yarn start diff --git a/packages/backend/package.json b/packages/backend/package.json index 0a169ee22c..75a7af44a2 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,18 +18,19 @@ "migrate:create": "knex migrate:make -x ts" }, "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/config-loader": "^0.1.1-alpha.13", - "@backstage/plugin-auth-backend": "^0.1.1-alpha.14", - "@backstage/plugin-catalog-backend": "^0.1.1-alpha.14", - "@backstage/plugin-identity-backend": "^0.1.1-alpha.14", - "@backstage/plugin-proxy-backend": "^0.1.1-alpha.14", - "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.14", - "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.14", - "@backstage/plugin-sentry-backend": "^0.1.1-alpha.14", - "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.14", + "@backstage/backend-common": "^0.1.1-alpha.16", + "@backstage/catalog-model": "^0.1.1-alpha.16", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", + "@backstage/plugin-auth-backend": "^0.1.1-alpha.16", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.16", + "@backstage/plugin-identity-backend": "^0.1.1-alpha.16", + "@backstage/plugin-proxy-backend": "^0.1.1-alpha.16", + "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.16", + "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.16", + "@backstage/plugin-sentry-backend": "^0.1.1-alpha.16", + "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.16", + "@backstage/plugin-graphql-backend": "^0.1.1-alpha.16", "@octokit/rest": "^18.0.0", "dockerode": "^3.2.0", "express": "^4.17.1", @@ -38,7 +39,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index d060791f98..7c98140270 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -38,6 +38,7 @@ import scaffolder from './plugins/scaffolder'; import sentry from './plugins/sentry'; import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; +import graphql from './plugins/graphql'; import { PluginEnvironment } from './types'; function makeCreateEnv(loadedConfigs: AppConfig[]) { @@ -70,6 +71,7 @@ async function main() { const rollbarEnv = useHotMemoize(module, () => createEnv('rollbar')); const sentryEnv = useHotMemoize(module, () => createEnv('sentry')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); + const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); const service = createServiceBuilder(module) .loadConfig(configReader) @@ -80,7 +82,8 @@ async function main() { .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) - .addRouter('/proxy', await proxy(proxyEnv)); + .addRouter('/proxy', await proxy(proxyEnv)) + .addRouter('/graphql', await graphql(graphqlEnv)); await service.start().catch(err => { console.log(err); diff --git a/packages/backend/src/plugins/graphql.ts b/packages/backend/src/plugins/graphql.ts new file mode 100644 index 0000000000..0f9b167d19 --- /dev/null +++ b/packages/backend/src/plugins/graphql.ts @@ -0,0 +1,39 @@ +/* + * 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 { createRouter } from '@backstage/plugin-graphql-backend'; +import type { PluginEnvironment } from '../types'; + +export default async function createPlugin({ logger }: PluginEnvironment) { + return await createRouter({ + logger, + }); +} diff --git a/packages/backend/src/plugins/scaffolder.ts b/packages/backend/src/plugins/scaffolder.ts index 70f3d88f3e..a9e5f185ec 100644 --- a/packages/backend/src/plugins/scaffolder.ts +++ b/packages/backend/src/plugins/scaffolder.ts @@ -21,6 +21,7 @@ import { GithubPreparer, Preparers, GithubPublisher, + CreateReactAppTemplater, Templaters, } from '@backstage/plugin-scaffolder-backend'; import { Octokit } from '@octokit/rest'; @@ -29,8 +30,10 @@ import Docker from 'dockerode'; export default async function createPlugin({ logger }: PluginEnvironment) { const cookiecutterTemplater = new CookieCutter(); + const craTemplater = new CreateReactAppTemplater(); const templaters = new Templaters(); templaters.register('cookiecutter', cookiecutterTemplater); + templaters.register('cra', craTemplater); const filePreparer = new FilePreparer(); const githubPreparer = new GithubPreparer(); diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index f871bdfe39..43ac830327 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "@types/json-schema": "^7.0.5", "@types/yup": "^0.28.2", "json-schema": "^0.2.5", @@ -29,7 +29,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "@types/jest": "^25.2.2", "@types/lodash": "^4.14.151", diff --git a/packages/cli/asset-types/asset-types.d.ts b/packages/cli/asset-types/asset-types.d.ts index 1763d256f5..28c1fde9e3 100644 --- a/packages/cli/asset-types/asset-types.d.ts +++ b/packages/cli/asset-types/asset-types.d.ts @@ -50,6 +50,11 @@ declare module '*.webp' { export default src; } +declare module '*.yaml' { + const src: string; + export default src; +} + declare module '*.icon.svg' { import { ComponentType } from 'react'; import { SvgIconProps } from '@material-ui/core'; diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 23312bd308..da9b3ec3e6 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -38,7 +38,7 @@ async function getConfig() { transform: { '\\.esm\\.js$': require.resolve('jest-esm-transformer'), '\\.(js|jsx|ts|tsx)': require.resolve('ts-jest'), - '\\.(bmp|gif|jpe|png|frag|xml|svg)': require.resolve( + '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)': require.resolve( './jestFileTransform.js', ), }, @@ -49,7 +49,7 @@ async function getConfig() { // Default behaviour is to not apply transforms for node_modules, but we still want // to apply the esm-transformer to .esm.js files, since that's what we use in backstage packages. transformIgnorePatterns: [ - '/node_modules/(?!.*\\.(?:esm\\.js|bmp|gif|jpe|png|frag|xml|svg)$)', + '/node_modules/(?!.*\\.(?:esm\\.js|bmp|gif|jpg|jpeg|png|frag|xml|svg)$)', ], }; diff --git a/packages/cli/package.json b/packages/cli/package.json index 55462d1ebf..6aa3f6458d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public" @@ -29,14 +29,15 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/config-loader": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", "@rollup/plugin-commonjs": "^13.0.0", "@rollup/plugin-json": "^4.0.2", "@rollup/plugin-node-resolve": "^8.1.0", + "@rollup/plugin-yaml": "^2.1.1", "@spotify/eslint-config": "^7.0.1", "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "4.3.x", @@ -53,6 +54,7 @@ "css-loader": "^3.5.3", "dashify": "^2.0.0", "diff": "^4.0.2", + "esbuild": "0.6.3", "eslint": "^7.1.0", "eslint-formatter-friendly": "^7.0.0", "eslint-plugin-import": "^2.20.2", diff --git a/packages/cli/src/commands/backend/buildImage.ts b/packages/cli/src/commands/backend/buildImage.ts index a847f1ef54..258ef102d7 100644 --- a/packages/cli/src/commands/backend/buildImage.ts +++ b/packages/cli/src/commands/backend/buildImage.ts @@ -19,8 +19,12 @@ import { createDistWorkspace } from '../../lib/packager'; import { paths } from '../../lib/paths'; import { run } from '../../lib/run'; +const PKG_PATH = 'package.json'; + export default async (imageTag: string) => { - const tempDistWorkspace = await createDistWorkspace(['example-backend'], { + const pkgPath = paths.resolveTarget(PKG_PATH); + const pkg = await fs.readJson(pkgPath); + const tempDistWorkspace = await createDistWorkspace([pkg.name], { files: [ 'package.json', 'yarn.lock', @@ -28,7 +32,6 @@ export default async (imageTag: string) => { { src: paths.resolveTarget('Dockerfile'), dest: 'Dockerfile' }, ], }); - console.log(`Dist workspace ready at ${tempDistWorkspace}`); await run('docker', ['build', '.', '-t', imageTag], { diff --git a/packages/cli/src/lib/builder/config.ts b/packages/cli/src/lib/builder/config.ts index 5987290604..e62e1fc6cd 100644 --- a/packages/cli/src/lib/builder/config.ts +++ b/packages/cli/src/lib/builder/config.ts @@ -26,6 +26,7 @@ import imageFiles from 'rollup-plugin-image-files'; import svgr from '@svgr/rollup'; import dts from 'rollup-plugin-dts'; import json from '@rollup/plugin-json'; +import yaml from '@rollup/plugin-yaml'; import { RollupOptions, OutputOptions } from 'rollup'; import { BuildOptions, Output } from './types'; @@ -93,6 +94,7 @@ export const makeConfigs = async ( postcss(), imageFiles({ exclude: '**/*.icon.svg' }), json(), + yaml(), svgr({ include: '**/*.icon.svg', template: svgrTemplate, diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 0eaac9ad2d..56150ad968 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -25,11 +25,6 @@ import { Config } from '@backstage/config'; import { BundlingPaths } from './paths'; import { transforms } from './transforms'; import { BundlingOptions, BackendBundlingOptions } from './types'; -// import checkRequiredFiles from 'react-dev-utils/checkRequiredFiles'; -// import ModuleNotFoundPlugin from 'react-dev-utils/ModuleNotFoundPlugin'; -// import errorOverlayMiddleware from 'react-dev-utils/errorOverlayMiddleware'; -// import evalSourceMapMiddleware from 'react-dev-utils/evalSourceMapMiddleware'; -// import WatchMissingNodeModulesPlugin from 'react-dev-utils/WatchMissingNodeModulesPlugin'; export function resolveBaseUrl(config: Config): URL { const baseUrl = config.getString('app.baseUrl'); @@ -126,10 +121,10 @@ export function createConfig( output: { path: paths.targetDist, publicPath: validBaseUrl.pathname, - filename: isDev ? '[name].js' : '[name].[hash:8].js', + filename: isDev ? '[name].js' : 'static/[name].[hash:8].js', chunkFilename: isDev ? '[name].chunk.js' - : '[name].[chunkhash:8].chunk.js', + : 'static/[name].[chunkhash:8].chunk.js', }, plugins, }; diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index 03168b376d..acd6a937d9 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -46,7 +46,7 @@ export const optimization = ( }, filename: isDev ? 'module-[name].js' - : 'module-[name].[chunkhash:8].js', + : 'static/module-[name].[chunkhash:8].js', priority: 10, minSize: 100000, minChunks: 1, diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index b498482f8a..d8186dac66 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -79,7 +79,7 @@ export const transforms = ( loader: require.resolve('url-loader'), options: { limit: 10000, - name: 'static/media/[name].[hash:8].[ext]', + name: 'static/[name].[hash:8].[ext]', }, }, { @@ -111,8 +111,8 @@ export const transforms = ( } else { plugins.push( new MiniCssExtractPlugin({ - filename: '[name].[contenthash:8].css', - chunkFilename: '[name].[id].[contenthash:8].css', + filename: 'static/[name].[contenthash:8].css', + chunkFilename: 'static/[name].[id].[contenthash:8].css', }), ); } diff --git a/packages/cli/src/lib/paths.ts b/packages/cli/src/lib/paths.ts index be3f3dcee8..ab3578ca1c 100644 --- a/packages/cli/src/lib/paths.ts +++ b/packages/cli/src/lib/paths.ts @@ -47,7 +47,7 @@ export type Paths = { resolveTargetRoot: ResolveFunc; }; -// Looks for a package.json that has name: "root" to identify the root of the monorepo +// Looks for a package.json with a workspace config to identify the root of the monorepo export function findRootPath(topPath: string): string { let path = topPath; @@ -58,7 +58,7 @@ export function findRootPath(topPath: string): string { if (exists) { try { const data = fs.readJsonSync(packagePath); - if (data.name === 'root' || data.name.includes('backstage-e2e')) { + if (data.workspaces?.packages) { return path; } } catch (error) { @@ -70,9 +70,7 @@ export function findRootPath(topPath: string): string { const newPath = dirname(path); if (newPath === path) { - throw new Error( - `No package.json with name "root" found as a parent of ${topPath}`, - ); + return topPath; // We didn't find any root package.json, assume we're not in a monorepo } path = newPath; } diff --git a/packages/cli/src/types.d.ts b/packages/cli/src/types.d.ts index 8cb5a1e7df..94136f1dbc 100644 --- a/packages/cli/src/types.d.ts +++ b/packages/cli/src/types.d.ts @@ -27,3 +27,5 @@ declare module 'rollup-plugin-image-files' { declare module '@svgr/rollup' { export default function svgr(options?: any): any; } + +declare module '@rollup/plugin-yaml'; diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index 1414ef341a..b59ca63fa3 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -7,7 +7,6 @@ }, "scripts": { "start": "yarn workspace app start", - "bundle": "yarn workspace app bundle", "build": "lerna run build", "tsc": "tsc", "clean": "backstage-cli clean && lerna run clean", @@ -32,7 +31,7 @@ "prettier": "^1.19.1" }, "resolutions": { - "**/esbuild": "0.5.3" + "esbuild": "0.6.3" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 26cce2c5e6..a34d25f216 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -32,7 +32,7 @@ }, "scripts": { "start": "backstage-cli app:serve", - "bundle": "backstage-cli app:build", + "build": "backstage-cli app:build", "test": "backstage-cli test", "lint": "backstage-cli lint", "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 25512529b8..72bfc0378b 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", + "@backstage/config": "^0.1.1-alpha.16", "fs-extra": "^9.0.0", "yaml": "^1.9.2", "yup": "^0.29.1" diff --git a/packages/config/package.json b/packages/config/package.json index aac52ab023..a401bc9af5 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 519e9d0894..38abdf9487 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -41,8 +41,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/test-utils-core": "^0.1.1-alpha.13", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/test-utils-core": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/core/package.json b/packages/core/package.json index 8e5df280ce..4b29f22ba3 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.13", - "@backstage/core-api": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/core-api": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -54,8 +54,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx b/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx index 4a404a840f..62760f72fb 100644 --- a/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx +++ b/packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx @@ -89,7 +89,7 @@ export const WithLink = () => ( message={ This is a dismissable banner with a link:{' '} - + example.com diff --git a/packages/core/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core/src/components/DismissableBanner/DismissableBanner.tsx index eebb884806..747ed06b37 100644 --- a/packages/core/src/components/DismissableBanner/DismissableBanner.tsx +++ b/packages/core/src/components/DismissableBanner/DismissableBanner.tsx @@ -18,13 +18,14 @@ import React, { FC, ReactNode, useState, useEffect } from 'react'; import { useApi, storageApiRef } from '@backstage/core-api'; import { useObservable } from 'react-use'; import classNames from 'classnames'; -import { makeStyles, Theme } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; import Snackbar from '@material-ui/core/Snackbar'; import SnackbarContent from '@material-ui/core/SnackbarContent'; import IconButton from '@material-ui/core/IconButton'; import Close from '@material-ui/icons/Close'; -const useStyles = makeStyles((theme: Theme) => ({ +const useStyles = makeStyles((theme: BackstageTheme) => ({ root: { position: 'relative', padding: theme.spacing(0), @@ -46,10 +47,10 @@ const useStyles = makeStyles((theme: Theme) => ({ alignItems: 'center', }, info: { - backgroundColor: theme.palette.primary.main, + backgroundColor: theme.palette.banner.info, }, error: { - backgroundColor: theme.palette.error.dark, + backgroundColor: theme.palette.banner.error, }, })); diff --git a/packages/core/src/layout/Page/PageThemeProvider.ts b/packages/core/src/layout/Page/PageThemeProvider.ts index 3df2a61aa8..36430021bb 100644 --- a/packages/core/src/layout/Page/PageThemeProvider.ts +++ b/packages/core/src/layout/Page/PageThemeProvider.ts @@ -26,7 +26,7 @@ export type PageTheme = { export const gradients: Record = { darkGrey: { - colors: ['#181818', '#404040'], + colors: ['#171717', '#383838'], waveColor: '#757575', opacity: ['1.0', '0.0'], }, diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 9c33bfdaac..24e54a5cbb 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/core": "^0.1.1-alpha.14", - "@backstage/test-utils": "^0.1.1-alpha.14", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 3f958b203c..dafd592bb0 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "description": "Storybook build for core package", "private": true, "scripts": { @@ -14,7 +14,7 @@ ] }, "dependencies": { - "@backstage/theme": "^0.1.1-alpha.14" + "@backstage/theme": "^0.1.1-alpha.16" }, "devDependencies": { "@storybook/addon-actions": "^5.3.17", diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md index 960574db37..5d8a3869c7 100644 --- a/packages/techdocs-cli/README.md +++ b/packages/techdocs-cli/README.md @@ -4,44 +4,45 @@ Check out the [TechDocs README](https://github.com/spotify/backstage/blob/master **WIP: This cli 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).** -## Commands +## Prerequisities + +Run the following command from the project root: ```bash -# Serve localhost:3000 (and localhost:8000) -yarn serve - -# Serve localhost:8000 containing your Mkdocs documentation. -yarn serve:mkdocs +yarn install ``` -## Getting Started +## Run TechDocs CLI -You'll need Docker installed and running to use this. You will also need to build the container located at `/packages/techdocs-container` under the tag `mkdocs:local-dev`, as you can see in the commands from below: - -```bash -docker build packages/techdocs-container -t mkdocs:local-dev -``` - -From that point, you can invoke the CLI from any project with a docs folder. Try out our example! +You'll need Docker installed and running to use this. ```bash cd packages/techdocs-container/mock-docs -npx @techdocs/cli serve + +# To get a view of your docs in Backstage, use: +npx techdocs-cli serve + +# To view the raw mkdocs site (without Backstage), use: +npx techdocs-cli serve:mkdocs ``` -## Local Development +If you run `npx techdocs-cli serve` you should have a `localhost:3000` serving TechDocs in Backstage, as well as `localhost:8000` serving Mkdocs (which won't open up and be exposed to the user). -You'll need Docker installed and running to use this. You will also need to build the container located at `packages/techdocs-container` under the tag `mkdocs:local-dev` (for now until we deploy the container to a centralized Docker registry), as you can see in the commands from below: - -```bash -docker build packages/techdocs-container -t mkdocs:local-dev -``` - -```bash -cd packages/techdocs-container/mock-docs -npx techdocs serve -``` - -You should have a `localhost:3000` serving TechDocs in Backstage, as well as `localhost:8000` serving Mkdocs (which won't open up and be exposed to the user). +If running `npx techdocs-cli serve:mkdocs` you will have `localhost:8000` exposed, serving Mkdocs. Happy hacking! + +## Deploying a new version + +Deploying the Node packages to NPM happens automatically on merge to `master` through GitHub Actions. The deployment happens through Lerna which determines which packages throughout the Backstage project have changed. In our case, the package is called `techdocs-cli` in the repository but `@techdocs/cli` in the NPM registry. + +> Note: Once a package is published under a version, any subsequent changes will not override that version. You will need to bump up the version across the entire Backstage repository, which can be done through Lerna (see the command below). + +In order to bump up all packages, go to the root of the Backstage repository. To see the current version see the `lerna.json` under the `version` key. To then update all the versions (locally on your machine), run the following: + +```bash +git checkout -b bump-up-version +yarn lerna version --no-push --allow-branch --yes +``` + +Upon being merged to master, Lerna will then automatically publish these packages as configured by the Backstage core team. diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index c50ef6be56..a097284f82 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "CLI for running TechDocs locally.", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public" @@ -25,7 +25,7 @@ "start": "nodemon --" }, "bin": { - "techdocs": "bin/techdocs-cli" + "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { "@spotify/eslint-config": "^7.0.0", @@ -44,7 +44,7 @@ "ext": "ts" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "commander": "^5.1.0", "fs-extra": "^9.0.1", "http-proxy": "^1.18.1", diff --git a/packages/techdocs-cli/src/index.ts b/packages/techdocs-cli/src/index.ts index 4d9f9525d2..1469089d52 100644 --- a/packages/techdocs-cli/src/index.ts +++ b/packages/techdocs-cli/src/index.ts @@ -68,7 +68,7 @@ const runMkdocsServer = (options?: { '$(pwd):/content', '-p', '8000:8000', - 'mkdocs:local-dev', + 'spotify/techdocs', 'serve', '-a', devAddr, diff --git a/packages/techdocs-container/Dockerfile b/packages/techdocs-container/Dockerfile index f2a92287e4..cfda62c51d 100644 --- a/packages/techdocs-container/Dockerfile +++ b/packages/techdocs-container/Dockerfile @@ -15,10 +15,6 @@ FROM python:3.7.7-alpine3.12 RUN apk update && apk --no-cache add gcc musl-dev -RUN pip install --upgrade pip && pip install mkdocs==1.1.2 mkdocs-material==5.3.2 mkdocs-monorepo-plugin==0.4.5 pymdown-extensions==7.1 - -ADD ./techdocs-core /techdocs-core -RUN pip install --no-index /techdocs-core +RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==0.0.2 mkdocs==1.1.2 mkdocs-material==5.3.2 mkdocs-monorepo-plugin==0.4.5 pymdown-extensions==7.1 ENTRYPOINT [ "mkdocs" ] - diff --git a/packages/techdocs-container/README.md b/packages/techdocs-container/README.md index 57bdefe894..2bfa9a1d26 100644 --- a/packages/techdocs-container/README.md +++ b/packages/techdocs-container/README.md @@ -9,7 +9,7 @@ This is the Docker container that powers the creation of static documentation si Using the TechDocs CLI, we can invoke the latest version of `techdocs-container` via Docker Hub: ```bash -npx @techdocs/cli serve +npx techdocs-cli serve ``` ## Local Development diff --git a/packages/techdocs-container/techdocs-core/README.md b/packages/techdocs-container/techdocs-core/README.md index 12b3acb1e2..9608780bd2 100644 --- a/packages/techdocs-container/techdocs-core/README.md +++ b/packages/techdocs-container/techdocs-core/README.md @@ -2,9 +2,13 @@ This is the base [Mkdocs](https://mkdocs.org) plugin used when using Mkdocs with Spotify's TechDocs. It is written in Python and packages all of our Mkdocs defaults, such as theming, plugins, etc in a single plugin. +[Python Package](https://pypi.org/project/mkdocs-techdocs-core/) + ## Usage -**Installation instructions TBD.** We haven't published it to a Python registry yet. +```bash +$ pip install mkdocs-techdocs-core +``` Once you have installed the `mkdocs-techdocs-core` plugin, you'll need to add it to your `mkdocs.yml`. diff --git a/packages/techdocs-container/techdocs-core/setup.py b/packages/techdocs-container/techdocs-core/setup.py index a259ae0615..9429409319 100644 --- a/packages/techdocs-container/techdocs-core/setup.py +++ b/packages/techdocs-container/techdocs-core/setup.py @@ -18,13 +18,13 @@ from setuptools import setup, find_packages setup( name='mkdocs-techdocs-core', - version='0.0.1', + version='0.0.2', description='A Mkdocs package that contains TechDocs defaults', long_description='', keywords='mkdocs', url='https://github.com/spotify/backstage', - author='Spotify', - author_email='fossboard@spotify.com', + author='TechDocs Core', + author_email='pulp-fiction@spotify.com', license='Apache-2.0', python_requires='>=3.7', install_requires=[ diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 10e5b0ea6b..390aad87d6 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1-alpha.13", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 183c4ac9c6..ed3ee31540 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/core-api": "^0.1.1-alpha.14", - "@backstage/test-utils-core": "^0.1.1-alpha.13", - "@backstage/theme": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/core-api": "^0.1.1-alpha.16", + "@backstage/test-utils-core": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/theme/package.json b/packages/theme/package.json index 826ef8e110..144712cfe6 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.9.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14" + "@backstage/cli": "^0.1.1-alpha.16" }, "files": [ "dist" diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts index c6bff73ef4..e5ebd419a3 100644 --- a/packages/theme/src/themes.ts +++ b/packages/theme/src/themes.ts @@ -41,6 +41,10 @@ export const lightTheme = createTheme({ primary: { main: blue[500], }, + banner: { + info: '#2E77D0', + error: '#E22134', + }, border: '#E6E6E6', textContrast: '#000000', textVerySubtle: '#DDD', @@ -90,6 +94,10 @@ export const darkTheme = createTheme({ primary: { main: blue[500], }, + banner: { + info: '#2E77D0', + error: '#E22134', + }, border: '#E6E6E6', textContrast: '#FFFFFF', textVerySubtle: '#DDD', diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index fdb9e09869..557af23cd0 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -58,6 +58,10 @@ type PaletteAdditions = { icon: string; background: string; }; + banner: { + info: string; + error: string; + }; }; export type BackstagePalette = Palette & PaletteAdditions; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index ee0309d17c..b16c1e5a4f 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.1.1-alpha.14", + "version": "0.1.1-alpha.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "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.16", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "body-parser": "^1.19.0", "compression": "^1.7.4", @@ -49,7 +49,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/jwt-decode": "2.2.1", diff --git a/plugins/auth-backend/src/lib/OAuthProvider.test.ts b/plugins/auth-backend/src/lib/OAuthProvider.test.ts index f67a91dfcd..3d631bd4ac 100644 --- a/plugins/auth-backend/src/lib/OAuthProvider.test.ts +++ b/plugins/auth-backend/src/lib/OAuthProvider.test.ts @@ -51,7 +51,7 @@ describe('OAuthProvider Utils', () => { } as unknown) as express.Request; expect(() => { verifyNonce(mockRequest, 'providera'); - }).toThrowError('Missing nonce'); + }).toThrowError('Auth response is missing cookie nonce'); }); it('should throw error if state nonce missing', () => { @@ -63,7 +63,7 @@ describe('OAuthProvider Utils', () => { } as unknown) as express.Request; expect(() => { verifyNonce(mockRequest, 'providera'); - }).toThrowError('Missing nonce'); + }).toThrowError('Auth response is missing state nonce'); }); it('should throw error if nonce mismatch', () => { diff --git a/plugins/auth-backend/src/lib/OAuthProvider.ts b/plugins/auth-backend/src/lib/OAuthProvider.ts index 5434b6a4ba..c4996c31a2 100644 --- a/plugins/auth-backend/src/lib/OAuthProvider.ts +++ b/plugins/auth-backend/src/lib/OAuthProvider.ts @@ -43,10 +43,12 @@ export const verifyNonce = (req: express.Request, providerId: string) => { const cookieNonce = req.cookies[`${providerId}-nonce`]; const stateNonce = req.query.state; - if (!cookieNonce || !stateNonce) { - throw new Error('Missing nonce'); + if (!cookieNonce) { + throw new Error('Auth response is missing cookie nonce'); + } + if (!stateNonce) { + throw new Error('Auth response is missing state nonce'); } - if (cookieNonce !== stateNonce) { throw new Error('Invalid nonce'); } @@ -151,9 +153,8 @@ export class OAuthProvider implements AuthProviderRouteHandlers { } if (!this.options.disableRefresh) { - // throw error if missing refresh token if (!refreshToken) { - throw new Error('Missing refresh token'); + throw new InputError('Missing refresh token'); } // set new refresh token 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/package.json b/plugins/catalog-backend/package.json index 004d459b92..b84267c854 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.16", "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.16", + "@backstage/catalog-model": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -39,7 +39,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@types/lodash": "^4.14.151", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", diff --git a/plugins/catalog-backend/scripts/mock-data.sh b/plugins/catalog-backend/scripts/mock-data.sh index 76f97ccf3a..58e2efbe21 100755 --- a/plugins/catalog-backend/scripts/mock-data.sh +++ b/plugins/catalog-backend/scripts/mock-data.sh @@ -17,3 +17,11 @@ for URL in \ --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/${URL}\"}" echo done + +curl \ + --location \ + --request POST 'localhost:7000/catalog/locations' \ + --header 'Content-Type: application/json' \ + --data-raw "{\"type\": \"github\", \"target\": \"https://github.com/benjdlambert/cookiecutter-golang/blob/master/template.yaml\"}" +echo + 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/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/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/package.json b/plugins/catalog/package.json index 12a2faf1e1..cc96d18d92 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.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "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.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.16", + "@backstage/plugin-sentry": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -38,9 +38,9 @@ "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", @@ -50,7 +50,7 @@ "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/circleci/package.json b/plugins/circleci/package.json index e5b88929d8..b5dfcb3211 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -36,8 +36,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index bdb5a28153..4dc8542a12 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,9 +32,9 @@ "react-use": "^14.2.0" }, "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index b06f447a5a..ce0068c35a 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.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,21 +21,23 @@ "clean": "backstage-cli clean" }, "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.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/core-api": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@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", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index dd75f34bf7..eedf9686f3 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -15,7 +15,11 @@ */ import { createApiRef } from '@backstage/core'; -import { Build, BuildDetails } from './types'; +import { + ActionsListWorkflowRunsForRepoResponseData, + ActionsGetWorkflowResponseData, + ActionsGetWorkflowRunResponseData, +} from '@octokit/types'; export const githubActionsApiRef = createApiRef({ id: 'plugin.githubactions.service', @@ -23,14 +27,50 @@ export const githubActionsApiRef = createApiRef({ }); export type GithubActionsApi = { - listBuilds: ({ + listWorkflowRuns: ({ + token, owner, repo, - token, + pageSize, + page, }: { + token: string; owner: string; repo: string; + pageSize?: number; + page?: number; + }) => Promise; + getWorkflow: ({ + token, + owner, + repo, + id, + }: { token: string; - }) => Promise; - getBuild: (buildUri: string, token: Promise) => Promise; + owner: string; + repo: string; + id: number; + }) => Promise; + getWorkflowRun: ({ + token, + owner, + repo, + id, + }: { + token: string; + owner: string; + repo: string; + id: number; + }) => Promise; + reRunWorkflow: ({ + token, + owner, + repo, + runId, + }: { + token: string; + owner: string; + repo: string; + runId: number; + }) => void; }; diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index 58d7280c1f..1606c268f4 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -15,117 +15,88 @@ */ import { GithubActionsApi } from './GithubActionsApi'; -import { Build, BuildDetails, BuildStatus, WorkflowRun } from './types'; - -const statusToBuildStatus: { [status: string]: BuildStatus } = { - success: BuildStatus.Success, - failure: BuildStatus.Failure, - pending: BuildStatus.Pending, - running: BuildStatus.Running, - in_progress: BuildStatus.Running, - completed: BuildStatus.Success, -}; - -const conclusionToStatus = (conslusion: string): BuildStatus => - statusToBuildStatus[conslusion] ?? BuildStatus.Null; +import { Octokit } from '@octokit/rest'; +import { + ActionsListWorkflowRunsForRepoResponseData, + ActionsGetWorkflowResponseData, + ActionsGetWorkflowRunResponseData, +} from '@octokit/types'; export class GithubActionsClient implements GithubActionsApi { - async listBuilds({ + reRunWorkflow({ + token, owner, repo, - token, + runId, }: { + token: string; owner: string; repo: string; - token: string; - }): Promise { - const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs`; - - const response = await fetch(url, { - headers: new Headers({ - Authorization: `Bearer ${token}`, - }), + runId: number; + }) { + new Octokit({ auth: token }).actions.reRunWorkflow({ + owner, + repo, + run_id: runId, }); - - if (!response.ok) { - return [ - { - commitId: 'Error', - message: 'Response status is not OK', - branch: 'Error', - status: BuildStatus.Failure, - uri: 'Error', - }, - ]; - } - - const data = await response.json(); - - const newData: WorkflowRun[] = data.workflow_runs; - - const endData: Build[] = []; - - newData.forEach((element, index) => { - const transData: Build = { - commitId: '', - message: '', - branch: '', - status: BuildStatus.Null, - uri: '', - }; - transData.commitId = String(element.head_commit.id); - transData.branch = element.head_branch; - transData.status = conclusionToStatus(element.conclusion); - transData.message = element.head_commit.message; - transData.uri = element.url; - endData[index] = transData; - }); - - return endData; } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getBuild( - buildUri: string, - token: Promise, - ): Promise { - const response = await fetch(buildUri, { - headers: new Headers({ - Authorization: `Bearer ${await token}`, - }), + async listWorkflowRuns({ + token, + owner, + repo, + pageSize = 100, + page = 0, + }: { + token: string; + owner: string; + repo: string; + pageSize?: number; + page?: number; + }): Promise { + const workflowRuns = await new Octokit({ + auth: token, + }).actions.listWorkflowRunsForRepo({ + owner, + repo, + per_page: pageSize, + page, }); - const buildBlank: Build = { - commitId: '', - message: '', - branch: '', - status: BuildStatus.Null, - uri: '', - }; - - const dataBlank: BuildDetails = { - build: buildBlank, - author: '', - logUrl: '', - overviewUrl: '', - }; - - if (!response.ok) { - return dataBlank; - } - - const data = await response.json(); - - const newData: WorkflowRun = data; - - dataBlank.author = newData.head_commit.author.name; - dataBlank.build.branch = newData.head_branch; - dataBlank.build.commitId = newData.head_commit.id; - dataBlank.build.message = newData.head_commit.message; - dataBlank.build.status = conclusionToStatus(newData.status); - dataBlank.build.uri = newData.url; - dataBlank.logUrl = newData.logs_url; - dataBlank.overviewUrl = newData.html_url; - - return dataBlank; + return workflowRuns.data; + } + async getWorkflow({ + token, + owner, + repo, + id, + }: { + token: string; + owner: string; + repo: string; + id: number; + }): Promise { + const workflow = await new Octokit({ auth: token }).actions.getWorkflow({ + owner, + repo, + workflow_id: id, + }); + return workflow.data; + } + async getWorkflowRun({ + token, + owner, + repo, + id, + }: { + token: string; + owner: string; + repo: string; + id: number; + }): Promise { + const run = await new Octokit({ auth: token }).actions.getWorkflowRun({ + owner, + repo, + run_id: id, + }); + return run.data; } } diff --git a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx index 707c670f40..55c568106c 100644 --- a/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx +++ b/plugins/github-actions/src/components/BuildDetailsPage/BuildDetailsPage.tsx @@ -16,7 +16,6 @@ import { Button, - ButtonGroup, LinearProgress, makeStyles, Paper, @@ -29,9 +28,8 @@ import { Typography, } from '@material-ui/core'; import React from 'react'; -import { useLocation } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { useAsync } from 'react-use'; -import { BuildStatusIndicator } from '../BuildStatusIndicator'; import { Link, useApi, githubAuthApiRef } from '@backstage/core'; import { githubActionsApiRef } from '../../api'; @@ -49,17 +47,26 @@ const useStyles = makeStyles(theme => ({ })); export const BuildDetailsPage = () => { + const repo = 'try-ssr'; + const owner = 'CircleCITest3'; const api = useApi(githubActionsApiRef); - const githubApi = useApi(githubAuthApiRef); - const token = githubApi.getAccessToken('repo'); + const auth = useApi(githubAuthApiRef); const classes = useStyles(); - const location = useLocation(); - const status = useAsync( - () => - api.getBuild(decodeURIComponent(location.search.split('uri=')[1]), token), - [location.search], - ); + 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 ; @@ -90,55 +97,42 @@ export const BuildDetailsPage = () => { Branch - {details?.build.branch} + {details?.head_branch} Message - {details?.build.message} + {details?.head_commit.message} Commit ID - {details?.build.commitId} + {details?.head_commit.id} Status - - - + {details?.status} Author - {details?.author} + {`${details?.head_commit.author.name} (${details?.head_commit.author.email})`} Links - - {details?.overviewUrl && ( - - )} - {details?.logUrl && ( - - )} - + {details?.html_url && ( + + )} diff --git a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx index cbbc1da339..acc1b496ec 100644 --- a/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx +++ b/plugins/github-actions/src/components/BuildInfoCard/BuildInfoCard.tsx @@ -27,7 +27,7 @@ import { import React from 'react'; import { useAsync } from 'react-use'; import { BuildStatusIndicator } from '../BuildStatusIndicator'; -import { githubActionsApiRef } from '../../api'; +import { githubActionsApiRef, BuildStatus } from '../../api'; import { Link, useApi, githubAuthApiRef } from '@backstage/core'; const useStyles = makeStyles(theme => ({ @@ -41,11 +41,11 @@ const useStyles = makeStyles(theme => ({ const BuildInfoCardContent = () => { const api = useApi(githubActionsApiRef); - const githubApi = useApi(githubAuthApiRef); + const auth = useApi(githubAuthApiRef); const status = useAsync(async () => { - const token = await githubApi.getAccessToken('repo'); - return api.listBuilds({ owner: 'spotify', repo: 'backstage', token }); + const token = await auth.getAccessToken(['repo', 'user']); + return api.listWorkflowRuns({ token, owner: 'spotify', repo: 'backstage' }); }); if (status.loading) { @@ -58,8 +58,8 @@ const BuildInfoCardContent = () => { ); } - const [build] = - status.value?.filter(({ branch }) => branch === 'master') ?? []; + // const [build] = + // status.value?.filter(({ branch }) => branch === 'master') ?? []; return ( @@ -69,8 +69,8 @@ const BuildInfoCardContent = () => { Message - - {build?.message} + + build message @@ -78,14 +78,14 @@ const BuildInfoCardContent = () => { Commit ID - {build?.commitId} + build commit id Status - + diff --git a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx b/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx index 9f6807d8bd..172b3611bf 100644 --- a/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx +++ b/plugins/github-actions/src/components/BuildListPage/BuildListPage.tsx @@ -47,7 +47,7 @@ export const BuildListPage = () => { - + diff --git a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx b/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx index dc593afdbc..b5fd4636f5 100644 --- a/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx +++ b/plugins/github-actions/src/components/BuildListTable/BuildListTable.tsx @@ -74,7 +74,7 @@ const generatedColumns: TableColumn[] = [ field: 'buildName', highlight: true, render: (row: Partial) => ( - + {row.buildName} ), @@ -161,16 +161,23 @@ const BuildListTableView: FC = ({ ); }; -const noop = () => {}; - -export const BuildListTable = () => { - const [tableProps] = useBuilds(); +export const BuildListTable = ({ + repo, + owner, +}: { + repo: string; + owner: string; +}) => { + const [tableProps, { retry, setPage, setPageSize }] = useBuilds({ + repo, + owner, + }); return ( ); }; diff --git a/plugins/github-actions/src/components/BuildListTable/useBuilds.ts b/plugins/github-actions/src/components/BuildListTable/useBuilds.ts index 2ac5ef98d6..7b93e178d6 100644 --- a/plugins/github-actions/src/components/BuildListTable/useBuilds.ts +++ b/plugins/github-actions/src/components/BuildListTable/useBuilds.ts @@ -13,301 +13,62 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useState } from 'react'; +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 { ActionsListWorkflowRunsForRepoResponseData } from '@octokit/types'; -// TODO(shmidt-i): use real APIs -const useEntityGHSettings = () => ({ repo: 'test', owner: 'shmidt-i-test' }); -const buildsMock = { - total_count: 1, - workflow_runs: [ - { - id: 30433642, - node_id: 'MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ==', - head_branch: 'master', - head_sha: 'acb5820ced9479c074f688cc328bf03f341a511d', - run_number: 562, - event: 'push', - status: 'queued', - conclusion: null, - workflow_id: 159038, - url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642', - html_url: 'https://github.com/octo-org/octo-repo/actions/runs/30433642', - pull_requests: [], - created_at: '2020-01-22T19:33:08Z', - updated_at: '2020-01-22T19:33:08Z', - jobs_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs', - logs_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs', - check_suite_url: - 'https://api.github.com/repos/octo-org/octo-repo/check-suites/414944374', - artifacts_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts', - cancel_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel', - rerun_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun', - workflow_url: - 'https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038', - head_commit: { - id: 'acb5820ced9479c074f688cc328bf03f341a511d', - tree_id: 'd23f6eedb1e1b9610bbc754ddb5197bfe7271223', - message: 'Create linter.yml', - timestamp: '2020-01-22T19:33:05Z', - author: { - name: 'Octo Cat', - email: 'octocat@github.com', - }, - committer: { - name: 'GitHub', - email: 'noreply@github.com', - }, - }, - repository: { - id: 1296269, - node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5', - name: 'Hello-World', - full_name: 'octocat/Hello-World', - owner: { - login: 'octocat', - id: 1, - node_id: 'MDQ6VXNlcjE=', - avatar_url: 'https://github.com/images/error/octocat_happy.gif', - gravatar_id: '', - url: 'https://api.github.com/users/octocat', - html_url: 'https://github.com/octocat', - followers_url: 'https://api.github.com/users/octocat/followers', - following_url: - 'https://api.github.com/users/octocat/following{/other_user}', - gists_url: 'https://api.github.com/users/octocat/gists{/gist_id}', - starred_url: - 'https://api.github.com/users/octocat/starred{/owner}{/repo}', - subscriptions_url: - 'https://api.github.com/users/octocat/subscriptions', - organizations_url: 'https://api.github.com/users/octocat/orgs', - repos_url: 'https://api.github.com/users/octocat/repos', - events_url: 'https://api.github.com/users/octocat/events{/privacy}', - received_events_url: - 'https://api.github.com/users/octocat/received_events', - type: 'User', - site_admin: false, - }, - private: false, - html_url: 'https://github.com/octocat/Hello-World', - description: 'This your first repo!', - fork: false, - url: 'https://api.github.com/repos/octocat/Hello-World', - archive_url: - 'http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}', - assignees_url: - 'http://api.github.com/repos/octocat/Hello-World/assignees{/user}', - blobs_url: - 'http://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}', - branches_url: - 'http://api.github.com/repos/octocat/Hello-World/branches{/branch}', - collaborators_url: - 'http://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}', - comments_url: - 'http://api.github.com/repos/octocat/Hello-World/comments{/number}', - commits_url: - 'http://api.github.com/repos/octocat/Hello-World/commits{/sha}', - compare_url: - 'http://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}', - contents_url: - 'http://api.github.com/repos/octocat/Hello-World/contents/{+path}', - contributors_url: - 'http://api.github.com/repos/octocat/Hello-World/contributors', - deployments_url: - 'http://api.github.com/repos/octocat/Hello-World/deployments', - downloads_url: - 'http://api.github.com/repos/octocat/Hello-World/downloads', - events_url: 'http://api.github.com/repos/octocat/Hello-World/events', - forks_url: 'http://api.github.com/repos/octocat/Hello-World/forks', - git_commits_url: - 'http://api.github.com/repos/octocat/Hello-World/git/commits{/sha}', - git_refs_url: - 'http://api.github.com/repos/octocat/Hello-World/git/refs{/sha}', - git_tags_url: - 'http://api.github.com/repos/octocat/Hello-World/git/tags{/sha}', - git_url: 'git:github.com/octocat/Hello-World.git', - issue_comment_url: - 'http://api.github.com/repos/octocat/Hello-World/issues/comments{/number}', - issue_events_url: - 'http://api.github.com/repos/octocat/Hello-World/issues/events{/number}', - issues_url: - 'http://api.github.com/repos/octocat/Hello-World/issues{/number}', - keys_url: - 'http://api.github.com/repos/octocat/Hello-World/keys{/key_id}', - labels_url: - 'http://api.github.com/repos/octocat/Hello-World/labels{/name}', - languages_url: - 'http://api.github.com/repos/octocat/Hello-World/languages', - merges_url: 'http://api.github.com/repos/octocat/Hello-World/merges', - milestones_url: - 'http://api.github.com/repos/octocat/Hello-World/milestones{/number}', - notifications_url: - 'http://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}', - pulls_url: - 'http://api.github.com/repos/octocat/Hello-World/pulls{/number}', - releases_url: - 'http://api.github.com/repos/octocat/Hello-World/releases{/id}', - ssh_url: 'git@github.com:octocat/Hello-World.git', - stargazers_url: - 'http://api.github.com/repos/octocat/Hello-World/stargazers', - statuses_url: - 'http://api.github.com/repos/octocat/Hello-World/statuses/{sha}', - subscribers_url: - 'http://api.github.com/repos/octocat/Hello-World/subscribers', - subscription_url: - 'http://api.github.com/repos/octocat/Hello-World/subscription', - tags_url: 'http://api.github.com/repos/octocat/Hello-World/tags', - teams_url: 'http://api.github.com/repos/octocat/Hello-World/teams', - trees_url: - 'http://api.github.com/repos/octocat/Hello-World/git/trees{/sha}', - }, - head_repository: { - id: 217723378, - node_id: 'MDEwOlJlcG9zaXRvcnkyMTc3MjMzNzg=', - name: 'octo-repo', - full_name: 'octo-org/octo-repo', - private: true, - owner: { - login: 'octocat', - id: 1, - node_id: 'MDQ6VXNlcjE=', - avatar_url: 'https://github.com/images/error/octocat_happy.gif', - gravatar_id: '', - url: 'https://api.github.com/users/octocat', - html_url: 'https://github.com/octocat', - followers_url: 'https://api.github.com/users/octocat/followers', - following_url: - 'https://api.github.com/users/octocat/following{/other_user}', - gists_url: 'https://api.github.com/users/octocat/gists{/gist_id}', - starred_url: - 'https://api.github.com/users/octocat/starred{/owner}{/repo}', - subscriptions_url: - 'https://api.github.com/users/octocat/subscriptions', - organizations_url: 'https://api.github.com/users/octocat/orgs', - repos_url: 'https://api.github.com/users/octocat/repos', - events_url: 'https://api.github.com/users/octocat/events{/privacy}', - received_events_url: - 'https://api.github.com/users/octocat/received_events', - type: 'User', - site_admin: false, - }, - html_url: 'https://github.com/octo-org/octo-repo', - description: null, - fork: false, - url: 'https://api.github.com/repos/octo-org/octo-repo', - forks_url: 'https://api.github.com/repos/octo-org/octo-repo/forks', - keys_url: - 'https://api.github.com/repos/octo-org/octo-repo/keys{/key_id}', - collaborators_url: - 'https://api.github.com/repos/octo-org/octo-repo/collaborators{/collaborator}', - teams_url: 'https://api.github.com/repos/octo-org/octo-repo/teams', - hooks_url: 'https://api.github.com/repos/octo-org/octo-repo/hooks', - issue_events_url: - 'https://api.github.com/repos/octo-org/octo-repo/issues/events{/number}', - events_url: 'https://api.github.com/repos/octo-org/octo-repo/events', - assignees_url: - 'https://api.github.com/repos/octo-org/octo-repo/assignees{/user}', - branches_url: - 'https://api.github.com/repos/octo-org/octo-repo/branches{/branch}', - tags_url: 'https://api.github.com/repos/octo-org/octo-repo/tags', - blobs_url: - 'https://api.github.com/repos/octo-org/octo-repo/git/blobs{/sha}', - git_tags_url: - 'https://api.github.com/repos/octo-org/octo-repo/git/tags{/sha}', - git_refs_url: - 'https://api.github.com/repos/octo-org/octo-repo/git/refs{/sha}', - trees_url: - 'https://api.github.com/repos/octo-org/octo-repo/git/trees{/sha}', - statuses_url: - 'https://api.github.com/repos/octo-org/octo-repo/statuses/{sha}', - languages_url: - 'https://api.github.com/repos/octo-org/octo-repo/languages', - stargazers_url: - 'https://api.github.com/repos/octo-org/octo-repo/stargazers', - contributors_url: - 'https://api.github.com/repos/octo-org/octo-repo/contributors', - subscribers_url: - 'https://api.github.com/repos/octo-org/octo-repo/subscribers', - subscription_url: - 'https://api.github.com/repos/octo-org/octo-repo/subscription', - commits_url: - 'https://api.github.com/repos/octo-org/octo-repo/commits{/sha}', - git_commits_url: - 'https://api.github.com/repos/octo-org/octo-repo/git/commits{/sha}', - comments_url: - 'https://api.github.com/repos/octo-org/octo-repo/comments{/number}', - issue_comment_url: - 'https://api.github.com/repos/octo-org/octo-repo/issues/comments{/number}', - contents_url: - 'https://api.github.com/repos/octo-org/octo-repo/contents/{+path}', - compare_url: - 'https://api.github.com/repos/octo-org/octo-repo/compare/{base}...{head}', - merges_url: 'https://api.github.com/repos/octo-org/octo-repo/merges', - archive_url: - 'https://api.github.com/repos/octo-org/octo-repo/{archive_format}{/ref}', - downloads_url: - 'https://api.github.com/repos/octo-org/octo-repo/downloads', - issues_url: - 'https://api.github.com/repos/octo-org/octo-repo/issues{/number}', - pulls_url: - 'https://api.github.com/repos/octo-org/octo-repo/pulls{/number}', - milestones_url: - 'https://api.github.com/repos/octo-org/octo-repo/milestones{/number}', - notifications_url: - 'https://api.github.com/repos/octo-org/octo-repo/notifications{?since,all,participating}', - labels_url: - 'https://api.github.com/repos/octo-org/octo-repo/labels{/name}', - releases_url: - 'https://api.github.com/repos/octo-org/octo-repo/releases{/id}', - deployments_url: - 'https://api.github.com/repos/octo-org/octo-repo/deployments', - }, - }, - ], -}; - -export function useBuilds() { - const { repo, owner } = useEntityGHSettings(); +export function useBuilds({ repo, owner }: { repo: string; owner: string }) { + const api = useApi(githubActionsApiRef); + const auth = useApi(githubAuthApiRef); const [total, setTotal] = useState(0); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(5); - const getBuilds = useCallback(async () => buildsMock, []); const restartBuild = async () => {}; - const { loading, value: builds, retry } = useAsyncRetry( - () => - getBuilds().then((allBuilds): Build[] => { - setTotal(allBuilds.total_count); - // Transformation here - return allBuilds.workflow_runs.map(run => ({ - buildName: run.head_commit.message, - id: `${run.id}`, - onRestartClick: () => {}, - source: { - branchName: run.head_branch, - commit: { - hash: run.head_commit.id, - url: run.head_repository.branches_url.replace( - '{/branch}', - run.head_branch, - ), - }, + const { loading, value: builds, retry } = useAsyncRetry(async () => { + const token = await auth.getAccessToken(['repo', 'user']); + + return ( + api + // GitHub API pagination count starts from 1 + .listWorkflowRuns({ token, owner, repo, pageSize, page: page + 1 }) + .then( + (allBuilds: ActionsListWorkflowRunsForRepoResponseData): Build[] => { + setTotal(allBuilds.total_count); + // Transformation here + return allBuilds.workflow_runs.map(run => ({ + buildName: run.head_commit.message, + id: `${run.id}`, + onRestartClick: () => { + api.reRunWorkflow({ + token, + owner, + repo, + runId: run.id, + }); + }, + source: { + branchName: run.head_branch, + commit: { + hash: run.head_commit.id, + url: run.head_repository.branches_url.replace( + '{/branch}', + run.head_branch, + ), + }, + }, + status: run.status, + buildUrl: run.url, + })); }, - status: run.status, - buildUrl: run.url, - })); - }), - [page, pageSize, getBuilds], - ); + ) + ); + }, [page, pageSize]); const projectName = `${owner}/${repo}`; return [ @@ -320,7 +81,7 @@ export function useBuilds() { total, }, { - getBuilds, + builds, setPage, setPageSize, restartBuild, diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index 8897fe53db..b728296cbc 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -24,7 +24,7 @@ export const rootRouteRef = createRouteRef({ title: 'GitHub Actions', }); export const buildRouteRef = createRouteRef({ - path: '/github-actions/builds', + path: '/github-actions/build/:id', title: 'GitHub Actions Build', }); diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 33e7c7c60c..80063c6295 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,8 +32,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index e2c5caca22..2d5f727f4e 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -43,9 +43,9 @@ "react-use": "^14.2.0" }, "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", 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..4a251363ce --- /dev/null +++ b/plugins/graphql/README.md @@ -0,0 +1,14 @@ +# graphql + +Welcome to the graphql backend plugin! + +_This plugin was created through the Backstage CLI_ + +## 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 [/graphql](http://localhost:3000/graphql). + +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. diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json new file mode 100644 index 0000000000..42f3dc9046 --- /dev/null +++ b/plugins/graphql/package.json @@ -0,0 +1,45 @@ +{ + "name": "@backstage/plugin-graphql-backend", + "version": "0.1.1-alpha.16", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.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.16", + "@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.16", + "@types/supertest": "^2.0.8", + "eslint-plugin-graphql": "^4.0.0", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist" + ] +} 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/schema.gql b/plugins/graphql/src/schema.gql new file mode 100644 index 0000000000..5568182f7f --- /dev/null +++ b/plugins/graphql/src/schema.gql @@ -0,0 +1,11 @@ +type CatalogEntity { + id: String +} + +type CatalogQuery { + list: [CatalogEntity!]! +} + +type Query { + catalog: CatalogQuery! +} 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..09ab5270b7 --- /dev/null +++ b/plugins/graphql/src/service/router.ts @@ -0,0 +1,50 @@ +/* + * 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'; + +export interface RouterOptions { + logger: Logger; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const typeDefs = await fs.promises.readFile( + path.resolve(__dirname, '..', 'schema.gql'), + '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/graphql/src/setupTests.ts b/plugins/graphql/src/setupTests.ts new file mode 100644 index 0000000000..ac41ef995a --- /dev/null +++ b/plugins/graphql/src/setupTests.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. + */ +require('whatwg-fetch'); + +export {}; diff --git a/plugins/identity-backend/package.json b/plugins/identity-backend/package.json index 2467663e48..2e049b78df 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.16", "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.16", "@types/express": "^4.17.6", "compression": "^1.7.4", "cors": "^2.8.5", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "jest-fetch-mock": "^3.0.3" }, "files": [ diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 5f1ca9b170..f9fd64ee69 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -33,9 +33,9 @@ "react-use": "^14.2.0" }, "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 26fca94376..e80bf0e00f 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.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,9 +19,9 @@ "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.16", + "@backstage/config": "^0.1.1-alpha.16", + "@backstage/config-loader": "^0.1.1-alpha.16", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -35,7 +35,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@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.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/register-component/package.json b/plugins/register-component/package.json index 71590cf3be..1dcbb86a9c 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.16", "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.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/plugin-catalog": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -36,8 +36,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 72c09faf6b..2b2577fa73 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.16", "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.16", "@types/express": "^4.17.6", "axios": "^0.19.2", "camelcase-keys": "^6.2.2", @@ -36,7 +36,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "@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..2055cc9525 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.16", "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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -35,9 +35,9 @@ "react-use": "^14.2.0" }, "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c416dd6828..9dee285320 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.16", "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.16", + "@backstage/catalog-model": "^0.1.1-alpha.16", + "@backstage/config": "^0.1.1-alpha.16", "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", @@ -39,10 +39,11 @@ "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.16", "@octokit/types": "^5.0.1", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.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..4d730fb702 --- /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: A template that scaffolds an applications using the Create React App (CRA) templater. + 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/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 74% 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..b1bc273d88 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: Standard Spring Boot (Java) microservice with recommended configuration for GRPC 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}}/.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..f796df8820 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/{{cookiecutter.component_id}}/component-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{cookiecutter.component_id}} + description: {{cookiecutter.description}} +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..68d011b977 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 \ diff --git a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts index 2e88dd34fe..a35be88832 100644 --- a/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/jobs/logger.ts @@ -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..e04ba48324 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({ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index 0267d5eeab..11bbcd6005 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,17 @@ 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 +85,7 @@ export const runDockerContainer = async ({ `${await fs.promises.realpath(templateDir)}:/template`, ], }, + ...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..a52acfed43 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.16", "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.16", + "@backstage/core": "^0.1.1-alpha.16", + "@backstage/plugin-catalog": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", + "@backstage/test-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", 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 a0957a6544..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'; @@ -64,7 +64,7 @@ export const ScaffolderPage: React.FC<{}> = () => { }, [error, errorApi]); return ( - +
= () => { {!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/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 2f36e12511..7d38c6f223 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Button } from '@backstage/core'; +import { Button, pageTheme } from '@backstage/core'; import { Card, Chip, makeStyles, Typography } from '@material-ui/core'; import React from 'react'; import { generatePath } from 'react-router-dom'; @@ -23,8 +23,8 @@ const useStyles = makeStyles(theme => ({ header: { color: theme.palette.common.white, padding: theme.spacing(2, 2, 6), - backgroundImage: - 'linear-gradient(-137deg, rgb(25, 230, 140) 0%, rgb(29, 127, 110) 100%)', + backgroundImage: (props: { gradientStart: string; gradientStop: string }) => + `linear-gradient(-137deg, ${props.gradientStart} 0%, ${props.gradientStop} 100%)`, }, content: { padding: theme.spacing(2), @@ -55,7 +55,9 @@ export const TemplateCard = ({ type, name, }: TemplateCardProps) => { - const classes = useStyles(); + const theme = pageTheme[type] ?? pageTheme.other; + const [gradientStart, gradientStop] = theme.gradient.colors; + const classes = useStyles({ gradientStart, gradientStop }); const href = generatePath(templateRoute.path, { templateName: name }); return ( @@ -72,7 +74,7 @@ export const TemplateCard = ({ {description}
-
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 5c949e55d9..05cc249e41 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -22,6 +22,7 @@ import { Lifecycle, Page, useApi, + pageTheme, } from '@backstage/core'; import { catalogApiRef } from '@backstage/plugin-catalog'; import { LinearProgress } from '@material-ui/core'; @@ -135,7 +136,7 @@ export const TemplatePage = () => { } return ( - +
{} }; +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..1bcc13cd55 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.16", "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.16", + "@backstage/test-utils-core": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -35,8 +35,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 3cd8f6f3ef..528f130e4b 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.16", "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.16", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -28,7 +28,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7c730739ea..dfd8393b5f 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.16", "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.16", + "@backstage/test-utils": "^0.1.1-alpha.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -36,8 +36,8 @@ "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.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", 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/welcome/package.json b/plugins/welcome/package.json index 5084495557..71a19d59bc 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.16", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -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.16", + "@backstage/theme": "^0.1.1-alpha.16", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -32,8 +32,8 @@ "react-use": "^14.2.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.14", - "@backstage/dev-utils": "^0.1.1-alpha.14", + "@backstage/cli": "^0.1.1-alpha.16", + "@backstage/dev-utils": "^0.1.1-alpha.16", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/yarn.lock b/yarn.lock index 3b883964f8..1f6c4c56b4 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" @@ -906,6 +944,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.0": + 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.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" @@ -1184,6 +1229,109 @@ unique-filename "^1.1.1" which "^1.3.1" +"@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: + "@ardatan/aggregate-error" "0.0.1" + "@graphql-tools/schema" "6.0.15" + "@graphql-tools/utils" "6.0.15" + tslib "~2.0.0" + +"@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: + "@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@^2.1.2": version "2.1.4" resolved "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -2444,6 +2592,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" @@ -2509,7 +2710,16 @@ is-module "^1.0.0" resolve "^1.14.2" -"@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== @@ -3333,6 +3543,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 +3588,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 +3656,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 +3678,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== @@ -3533,6 +3765,23 @@ "@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" @@ -3566,6 +3815,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,6 +3860,11 @@ "@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" @@ -3695,11 +3959,41 @@ 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== +"@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" resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -3731,7 +4025,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== @@ -3744,6 +4038,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== +"@types/node@^10.1.0": + version "10.17.27" + resolved "https://registry.npmjs.org/@types/node/-/node-10.17.27.tgz#391cb391c75646c8ad2a7b6ed3bbcee52d1bdf19" + integrity sha512-J0oqm9ZfAXaPdwNXMMgAhylw5fhmXkToJd06vuDUSAgEDZ/n/69/69UmyBZbc+zT34UnShuDSBqvim3SPnozJg== + "@types/node@^12.0.0": version "12.12.30" resolved "https://registry.npmjs.org/@types/node/-/node-12.12.30.tgz#3501e6f09b954de9c404671cefdbcc5d9d7c45f6" @@ -4161,11 +4460,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 +4703,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" @@ -4432,7 +4752,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== @@ -4517,7 +4837,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 +4995,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 +5424,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" @@ -5320,6 +5828,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" @@ -5441,7 +5954,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== @@ -5706,6 +6219,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 +6353,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== @@ -6370,7 +6890,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== @@ -6729,7 +7249,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 +7257,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 +7278,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 +7324,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" @@ -7010,6 +7537,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" @@ -7193,6 +7725,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" @@ -7466,6 +8006,11 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= +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 +8087,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" @@ -8055,11 +8607,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,10 +8647,18 @@ es6-shim@^0.35.5: resolved "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.5.tgz#46f59dc0a84a1c5029e8ff1166ca0a902077a9ab" integrity sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg== -esbuild@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.5.3.tgz#18f5bdb618220c6f14bcb1cf5af528d02d4734c9" - integrity sha512-RVzTK62svYjnh+agJRh+NWfZX74iKwFNUX52cF7Mo4QPS6bKxP1o+8GacPUMND2QnodVp2D3nKJs8gLspSfZzA== +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== escape-goat@^2.0.0: version "2.1.1" @@ -8155,6 +8733,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" @@ -8541,7 +9129,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 +9165,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" @@ -9143,6 +9738,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 +9757,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.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" @@ -9187,16 +9797,6 @@ fs-extra@^9.0.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" @@ -9552,6 +10152,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 +10191,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" @@ -9678,6 +10278,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 +10338,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" @@ -10358,6 +11028,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 +11042,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" @@ -10817,6 +11487,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 +11508,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 +11850,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" @@ -12318,6 +12993,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,6 +13058,11 @@ 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" @@ -12439,11 +13124,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" @@ -12492,7 +13182,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== @@ -13127,7 +13817,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 +13945,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" @@ -13289,7 +13984,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== @@ -13688,6 +14383,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" @@ -13949,6 +14649,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" @@ -14206,7 +14913,7 @@ 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: +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== @@ -16341,6 +17048,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 +17063,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" @@ -16411,16 +17118,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" @@ -16480,12 +17187,11 @@ rollup-plugin-dts@^1.4.6: "@babel/code-frame" "^7.8.3" rollup-plugin-esbuild@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.1.0.tgz#8e12337c63a5b1144e0c5e8adf2f1568ad4d7d69" - integrity sha512-XYqmwk4X0SPEExgilARbre/PplhLtE3q6wiZtfgIbwxJOVGXWec1Bkcux7TFTHGX3TQozzqEASTsRJCt7py/5Q== + version "2.3.0" + resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" + integrity sha512-lNbWDRaClwXauaIybuaiOBRWgxcp3GkZu0UUix9yH/OHFgVohKmzlU0NtNbvfbxAcfEooyuBRAv6YCaJl1Mbpw== dependencies: "@rollup/pluginutils" "^3.1.0" - esbuild "^0.5.3" rollup-plugin-image-files@^1.4.2: version "1.4.2" @@ -16875,7 +17581,7 @@ 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: +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== @@ -17497,7 +18203,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= @@ -17512,6 +18218,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" @@ -17774,6 +18485,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" @@ -17882,7 +18604,7 @@ 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== @@ -18295,6 +19017,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" @@ -18393,6 +19120,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" @@ -18456,6 +19190,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" @@ -18519,7 +19258,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== @@ -18527,6 +19266,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" @@ -18738,6 +19487,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" @@ -18881,7 +19637,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== @@ -18915,7 +19671,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== @@ -18956,6 +19712,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" @@ -19268,6 +20029,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" @@ -19280,7 +20052,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== @@ -19505,7 +20277,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== @@ -19595,6 +20367,14 @@ 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" @@ -19605,6 +20385,11 @@ y18n@^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" @@ -19740,7 +20525,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==