diff --git a/.github/workflows/chromatic-storybook-test.yml b/.github/workflows/chromatic-storybook-test.yml index e38b494a73..0bcaf400d0 100644 --- a/.github/workflows/chromatic-storybook-test.yml +++ b/.github/workflows/chromatic-storybook-test.yml @@ -14,7 +14,37 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # Required to retrieve git history - - run: yarn install && yarn build-storybook + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml + - name: use node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: yarn install + run: yarn install --frozen-lockfile + # End of yarn setup + + - run: yarn build-storybook + - uses: chromaui/action@v1 with: token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/frontend.yml b/.github/workflows/ci.yml similarity index 63% rename from .github/workflows/frontend.yml rename to .github/workflows/ci.yml index dc65b987a8..492b008431 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,11 @@ -name: Frontend CI +name: CI on: pull_request: paths-ignore: - 'microsite/**' jobs: - build: + verify: runs-on: ubuntu-latest strategy: @@ -20,34 +20,52 @@ jobs: - uses: actions/checkout@v2 - name: fetch branch master run: git fetch origin master - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: cache node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + + # Beginning of yarn setup, keep in sync between all workflows. + # TODO(Rugvip): move this to composite action once all features we use are supported - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + + # Cache every node_modules folder inside the monorepo + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + # We use both yarn.lock and package.json as cache keys to ensure that + # changes to local monorepo packages bust the cache. + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + + # If we get a cache hit for node_modules, there's no need to bring in the global + # yarn cache or run yarn install, as all dependencies will be installed already. + + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: yarn install + if: steps.cache-modules.outputs.cache-hit != 'true' + run: yarn install --frozen-lockfile + # End of yarn setup - name: check for yarn.lock changes id: yarn-lock run: git diff --quiet origin/master HEAD -- yarn.lock continue-on-error: true - - name: yarn install - run: yarn install --frozen-lockfile - - name: verify doc links run: node docs/verify-links.js @@ -59,8 +77,7 @@ jobs: - name: build changed packages if: ${{ steps.yarn-lock.outcome == 'success' }} - # Need to build all dependencies as well to be able to run tests later - run: yarn lerna -- run build --since origin/master --include-dependencies + run: yarn lerna -- run build --since origin/master - name: build all packages if: ${{ steps.yarn-lock.outcome == 'failure' }} @@ -79,6 +96,3 @@ jobs: - name: verify plugin template run: yarn lerna -- run diff -- --check - - - name: verify storybook - run: yarn workspace storybook build-storybook diff --git a/.github/workflows/cli-win.yml b/.github/workflows/e2e-win.yml similarity index 54% rename from .github/workflows/cli-win.yml rename to .github/workflows/e2e-win.yml index 4e7a186282..37cb13ffbe 100644 --- a/.github/workflows/cli-win.yml +++ b/.github/workflows/e2e-win.yml @@ -1,12 +1,13 @@ -name: CLI Test Windows +name: E2E Test Windows -# Building on windows is really slow, so this workflow is separate from cli.yml and only builds on changes +# Building on windows is really slow, so this workflow is separate from e2e.yml and only builds on changes # to the cli itself. They're more likely to introduce issues on windows, compared to changes to core and yarn.lock. on: pull_request: paths: - - '.github/workflows/cli-win.yml' + - '.github/workflows/e2e-win.yml' - 'packages/cli/**' + - 'packages/e2e/**' - 'packages/create-app/**' jobs: @@ -25,23 +26,37 @@ jobs: name: Node ${{ matrix.node-version }} on ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile + # End of yarn setup + - run: yarn tsc - - run: yarn build - - name: verify app and plugin creation - run: node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js + - name: yarn build + run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli --ignore backstage-microsite + - name: run E2E test + run: yarn workspace e2e-test start diff --git a/.github/workflows/cli.yml b/.github/workflows/e2e.yml similarity index 65% rename from .github/workflows/cli.yml rename to .github/workflows/e2e.yml index 158310b5ea..401fe7297a 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/e2e.yml @@ -1,11 +1,10 @@ -name: CLI Test +name: E2E Test Linux on: pull_request: paths-ignore: - 'microsite/**' - jobs: build: runs-on: ${{ matrix.os }} @@ -34,30 +33,44 @@ jobs: name: Node ${{ matrix.node-version }} on ${{ matrix.os }} steps: - uses: actions/checkout@v2 - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile + # End of yarn setup + - run: yarn tsc - - run: yarn build - - name: verify app and plugin creation + - name: yarn build + run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli --ignore backstage-microsite + - name: run E2E test + run: | + sudo sysctl fs.inotify.max_user_watches=524288 + yarn workspace e2e-test start env: POSTGRES_HOST: localhost POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres - run: | - sudo sysctl fs.inotify.max_user_watches=524288 - node ${{ github.workspace }}/packages/cli/e2e-test/cli-e2e-test.js diff --git a/.github/workflows/master-win.yml b/.github/workflows/master-win.yml new file mode 100644 index 0000000000..b5a042a236 --- /dev/null +++ b/.github/workflows/master-win.yml @@ -0,0 +1,53 @@ +name: Master Build Windows + +on: + push: + branches: [master] + +jobs: + build: + runs-on: windows-latest + + strategy: + matrix: + node-version: [12.x] + + env: + CI: true + NODE_OPTIONS: --max-old-space-size=4096 + + steps: + - uses: actions/checkout@v2 + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml + - name: use node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: yarn install + run: yarn install --frozen-lockfile + # End of yarn setup + + +# Tests are broken on Windows, disabled for now + # - name: test + # run: yarn lerna -- run test diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 3ef0c1fee3..04b50c3e60 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,4 +1,4 @@ -name: Master Build +name: Main Master Build on: push: @@ -18,29 +18,34 @@ jobs: steps: - uses: actions/checkout@v2 - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: cache node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile + # End of yarn setup - name: lint run: yarn lerna -- run lint diff --git a/.github/workflows/microsite-build-check.yml b/.github/workflows/microsite-build-check.yml index 41f43399ca..dc86ca2630 100644 --- a/.github/workflows/microsite-build-check.yml +++ b/.github/workflows/microsite-build-check.yml @@ -21,32 +21,34 @@ jobs: steps: - uses: actions/checkout@v2 - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: cache node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile + # End of yarn setup - name: build microsite run: yarn workspace backstage-microsite build diff --git a/.github/workflows/microsite-with-storybook-deploy.yml b/.github/workflows/microsite-with-storybook-deploy.yml index 84ac6f9490..2c8b8af40c 100644 --- a/.github/workflows/microsite-with-storybook-deploy.yml +++ b/.github/workflows/microsite-with-storybook-deploy.yml @@ -25,32 +25,34 @@ jobs: steps: - uses: actions/checkout@v2 - - name: find location of global yarn cache - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - name: cache global yarn cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - - name: cache node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }} + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- - name: yarn install run: yarn install --frozen-lockfile + # End of yarn setup - name: build microsite run: yarn workspace backstage-microsite build diff --git a/ADOPTERS.md b/ADOPTERS.md index 7a96198529..dc02ad3b85 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,13 +1,14 @@ -| 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 | +| 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 | +| [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f03e3fa5..d295dc38e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re > Collect changes for the next release below +## v0.1.1-alpha.19 + ### @backstage/create-app - Many plugins have been added to the catalog and will for now be required to be added to separate apps as well. This will be solved as [#1536](https://github.com/spotify/backstage/issues/1536) gets sorted out, but for now you may need to install some plugins just to get pages to work. diff --git a/README.md b/README.md index ee9e85fd7f..b5e8704548 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,6 @@ For more information go to [backstage.io](https://backstage.io) or join our [Dis A detailed project roadmap, including already delivered milestones, is available [here](https://backstage.io/docs/overview/roadmap). -## Overview - -The Backstage platform consists of a number of different components: - -- **app** - Main web application that users interact with. It's built up by a number of different _Plugins_. This repo contains an example implementation of an app (located in `packages/app`) and you can easily get started with your own app by [creating one](docs/getting-started/create-an-app.md). -- [**plugins**](https://github.com/spotify/backstage/tree/master/plugins) - Each plugin is treated as a self-contained web app and can include almost any type of content. Plugins all use a common set of platform API's and reusable UI components. Plugins can fetch data either from the _backend_ or through any RESTful API exposed through the _proxy_. -- [**service catalog**](https://github.com/spotify/backstage/tree/master/packages/backend) - Service that holds the model of your software ecosystem, including organisational information and what team owns what software. The backend also has a Plugin model for extending its graph. -- [**proxy**](https://github.com/spotify/backstage/tree/master/plugins/proxy-backend) - Terminates HTTPS and exposes any RESTful API to Plugins. -- **identity** - A backend service that holds your organisation's metadata. - ## Getting Started There are two different ways to get started with Backstage, either by creating a standalone app, or by cloning this repo. Which method you use depends on what you're planning to do. diff --git a/catalog-info.yaml b/catalog-info.yaml index 86b67f95cd..4221ee6dc6 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -7,6 +7,7 @@ metadata: annotations: github.com/project-slug: spotify/backstage backstage.io/github-actions-id: spotify/backstage + backstage.io/techdocs-ref: github:https://github.com/spotify/backstage.git spec: type: library owner: Spotify diff --git a/docs/README.md b/docs/README.md index b9e79a9e0a..55e178b691 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,6 +12,7 @@ better yet, a pull request. - [Architecture and terminology](overview/architecture-terminology.md) - [Roadmap](overview/roadmap.md) - [Vision](overview/vision.md) + - [Strategies for adopting](overview/adopting.md) - Getting started - [Running Backstage locally](getting-started/index.md) - [Installation](getting-started/installation.md) diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index ff8c91e178..b4c2fd161e 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -71,18 +71,23 @@ import { AlertApiForwarder, ErrorApiForwarder, ErrorAlerter, + ConfigApi } from '@backstage/core'; -const builder = ApiRegistry.builder(); -// The alert API is a self-contained implementation that shows alerts to the user. -const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); +const apis = (config: ConfigApi) => { + const builder = ApiRegistry.builder(); -// The error API uses the alert API to send error notifications to the user. -builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder())); + // The alert API is a self-contained implementation that shows alerts to the user. + const alertApi = builder.add(alertApiRef, new AlertApiForwarder()); + + // The error API uses the alert API to send error notifications to the user. + builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder())); + return builder.build(); +} const app = createApp({ - apis: apiBuilder.build(), + apis, // ... other config }); ``` diff --git a/docs/assets/dls/designheader-updated.png b/docs/assets/dls/designheader-updated.png new file mode 100644 index 0000000000..56c5a56abb Binary files /dev/null and b/docs/assets/dls/designheader-updated.png differ diff --git a/docs/assets/pop.png b/docs/assets/pop.png new file mode 100644 index 0000000000..441126f33c Binary files /dev/null and b/docs/assets/pop.png differ diff --git a/docs/assets/techdocs/documentation-template.png b/docs/assets/techdocs/documentation-template.png new file mode 100644 index 0000000000..1f44ad27c6 Binary files /dev/null and b/docs/assets/techdocs/documentation-template.png differ diff --git a/docs/dls/design.md b/docs/dls/design.md index c8a94fad4d..23ba84024a 100644 --- a/docs/dls/design.md +++ b/docs/dls/design.md @@ -3,7 +3,7 @@ id: design title: Design --- -![header](../assets/dls/designheader.png) +![header](../assets/dls/designheader-updated.png) Much like Backstage Open Source, this is a _living_ document! We'll keep this updated as we evolve our practices! @@ -116,7 +116,7 @@ components. If you’d like to help build up our design system, you can also add components we’ve designed to the Storybook as well. **[Figma](https://www.figma.com/@backstage)** - we're stoked to be using Figma -Community to share our design assets. You can duplicate our component library +Community to share our design assets. You can duplicate our UI Kit and design your own plugin for Backstage. **[Discord](https://discord.gg/EBHEGzX)** - all design questions should be diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index a7d57890dc..78dfcf365c 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -370,8 +370,8 @@ metadata: description: Next.js application skeleton for creating isomorphic web applications. tags: - - Recommended - - React + - recommended + - react spec: owner: web@example.com templater: cookiecutter diff --git a/docs/features/software-catalog/software-model-core-entities.png b/docs/features/software-catalog/software-model-core-entities.png new file mode 100644 index 0000000000..b718b7527c Binary files /dev/null and b/docs/features/software-catalog/software-model-core-entities.png differ diff --git a/docs/features/software-catalog/system-model.md b/docs/features/software-catalog/system-model.md index 4b2884e097..435fe05b48 100644 --- a/docs/features/software-catalog/system-model.md +++ b/docs/features/software-catalog/system-model.md @@ -3,55 +3,24 @@ id: system-model title: System Model --- -We believe that a strong shared understanding and terminology around systems, -software and resources leads to a better Backstage experience. +We believe that a strong shared understanding and terminology around software +and resources leads to a better Backstage experience. _This description originates from [this RFC](https://github.com/spotify/backstage/issues/390). Note that some of the concepts are not yet supported in Backstage._ -## Concepts +## Core Entities -We model our technology using these five concepts (further explained below): +We model software in the Backstage catalogue using these three core entities +(further explained below): -- **Domains** are a high-level grouping of systems -- **Systems** encapsulate the implementation of APIs -- **APIs** are the boundaries between different components and systems -- **Components** are pieces of software +- **Components** are individual pieces of software +- **APIs** are the boundaries between different components - **Resources** are physical or virtual infrastructure needed to operate a - system + component -![Software Ecosystem Model_ Public Github version](https://user-images.githubusercontent.com/24575/77633084-39bcde80-6f4f-11ea-8251-f8df561a3652.png) - -### Domain - -While systems are the basic level of encapsulation for resources, components and -APIs, it is often useful to group a collection of systems that share -terminology, domain models, business purpose, or documentation, i.e. they form a -bounded context. - -For example, it would make sense if the different systems in the “Payments” -domain would come with some documentation on how to accept payments for a new -product or use-case, share the same entity types in their APIs, and integrate -well with each other. - -### System - -With increasing complexity in software, we believe that systems form an -important abstraction level to help us reason about software ecosystems. Systems -are a useful concept in that they allow us to ignore the implementation details -of a certain functionality for consumers, while allowing the owning team to make -changes as they see fit (leading to low coupling). - -A system, in this sense, is a collection of resources and components that -exposes one or several APIs. Components and resources in a system are typically -owned by the same team and are expected to co-evolve. As such, systems usually -consist of at most a handful of components. - -For example, a playlist management system might encapsulate a backend service to -update playlists, a backend service to query them, and a database to store them. -It could expose an RPC API, a daily snapshots dataset, and an event stream of -playlist updates. +![](system-model-core-entities.png) ### Component @@ -60,34 +29,78 @@ backend service or data pipeline (list not exhaustive). A component can be tracked in source control, or use some existing open source or commercial software. -A component can implement APIs for other components to consume. It might depend -on the resources of the system it belongs to, and APIs from other components or -other systems. All other aspects of the component, e.g. any code dependencies, -must be encapsulated. +A component can implement APIs for other components to consume. In turn it +might depend on APIs implemented by other components, or resources that are +attached to it at runtime. ### API -We believe APIs form an important (maybe the most important) abstraction that -allows large software ecosystems to scale. Thus, APIs are a first class citizen -in the Backstage model and the primary way to discover existing functionality in +APIs form an important (maybe the most important) abstraction that allows large +software ecosystems to scale. Thus, APIs are a first class citizen in the +Backstage model and the primary way to discover existing functionality in the ecosystem. -APIs are implemented by components and form boundaries between components and -systems. They might be defined using an RPC IDL (eg Protobuf, GraphQL, ...), a -data schema (eg Avro, TFRecord, ...), or as code interfaces. In any case, APIs -exposed by components need to be in a known machine-readable format so we can +APIs are implemented by components and form boundaries between components. They +might be defined using an RPC IDL (eg Protobuf, GraphQL, ...), a data schema +(eg Avro, TFRecord, ...), or as code interfaces. In any case, APIs exposed by +components need to be in a known machine-readable format so we can build further tooling and analysis on top. -Some APIs might be exposed by the system, making them available for any other -Spotify component to consume. Those public APIs must be documented and humanly -discoverable in Backstage. +APIs have a visibility: they are either public (making them available for any +other component to consume), restricted (only available to a whitelisted set of +consumers), or private (only available within their system). As public APIs are +going to be the primary way interaction between components, Backstage supports +documenting, indexing and searching all APIs so we can browse them as +developers. ### Resource -Resources are the infrastructure a system needs to operate, like BigTable -databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with -components and systems will better allow us to visualize resource footprint, and -create tooling around them. +Resources are the infrastructure a component needs to operate at runtime, like +BigTable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together +with components and systems will better allow us to visualize resource +footprint, and create tooling around them. + +## Ecosystem Modeling + +A large catalogue of components, APIs and resources can be highly granular +and hard to understand as a whole. It might thus be convenient to further +categorize these entities using the following (optional) concepts: +* **Systems** are a collection of entities that cooperate to perform some + function +* **Domains** relate entities and systems to part of the business + +### System + +With increasing complexity in software, systems form an important abstraction +level to help us reason about software ecosystems. Systems are a useful concept +in that they allow us to ignore the implementation details of a certain +functionality for consumers, while allowing the owning team to make changes as +they see fit (leading to low coupling). + +A system, in this sense, is a collection of resources and components that +exposes one or several public APIs. The main benefit of modelling a system is +that it hides its resources and private APIs between the components for any +consumers. This means that as the owner, you can evolve the implementation, in +terms of components and resources, without your consumers being able to notice. +Typically, a system will consist of at most a handful of components (see +Domain for a grouping of systems). + +For example, a playlist management system might encapsulate a backend service +to update playlists, a backend service to query them, and a database to store +them. It could expose an RPC API, a daily snapshots dataset, and an event +stream of playlist updates. + +### Domain +While systems are the basic level of encapsulation for related entities, it is +often useful to group a collection of systems that share terminology, domain +models, metrics, KPIs, business purpose, or documentation, i.e. they form a +bounded context. + +For example, it would make sense if the different systems in the “Payments” +domain would come with some documentation on how to accept payments for a new +product or use-case, share the same entity types in their APIs, and integrate +well with each other. Other domains could be “Content Ingestion”, “Ads” or +“Search”. ## Current status diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 2ec4bdd6f3..67e9c23605 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -22,8 +22,8 @@ metadata: Next.js application skeleton for creating isomorphic web applications. # some tags to display in the frontend tags: - - Recommended - - React + - recommended + - react spec: # which templater key to use in the templaters builder templater: cookiecutter @@ -54,7 +54,7 @@ contains more information about the required fields. Once we have a `template.yaml` ready, we can then add it to the service catalog for use by the scaffolder. -Currently the catalog supports loading definitions from Github + Local Files. To +Currently the catalog supports loading definitions from GitHub + Local Files. To load from other places, not only will there need to be another preparer, but the support to load the location will also need to be added to the Catalog. diff --git a/docs/features/software-templates/extending/create-your-own-preparer.md b/docs/features/software-templates/extending/create-your-own-preparer.md index 739c516658..0b3fb31323 100644 --- a/docs/features/software-templates/extending/create-your-own-preparer.md +++ b/docs/features/software-templates/extending/create-your-own-preparer.md @@ -54,7 +54,7 @@ The `protocol` is set on the when added to the service catalog. You can see more about this `PreparerKey` here in [Register your own template](../adding-templates.md) -**note:** Currently the catalog supports loading definitions from Github + Local +**note:** Currently the catalog supports loading definitions from GitHub + Local Files, which translate into the two `PreparerKeys` `file` and `github`. To load from other places, not only will there need to be another preparer, but the support to load the location will also need to be added to the Catalog. diff --git a/docs/features/software-templates/extending/create-your-own-templater.md b/docs/features/software-templates/extending/create-your-own-templater.md index ac05eed3b0..a39f87924b 100644 --- a/docs/features/software-templates/extending/create-your-own-templater.md +++ b/docs/features/software-templates/extending/create-your-own-templater.md @@ -116,8 +116,8 @@ metadata: description: Next.js application skeleton for creating isomorphic web applications. tags: - - Recommended - - React + - recommended + - react spec: owner: web@example.com templater: handlebars diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index d9a86ee7aa..503fbb7a3d 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -34,7 +34,7 @@ internally. After filling in these variables, you'll get some more fields to fill out which are required for backstage usage. The owner, which is a `user` in the backstage -system, and the `storePath` which right now must be a Github Organisation and a +system, and the `storePath` which right now must be a GitHub Organisation and a non-existing github repository name in the format `organisation/reponame`. ![Enter backstage vars](../../assets/software-templates/template-picked-2.png) diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 0508d1f3f7..73b4f20359 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -109,7 +109,7 @@ export default async function createPlugin({ logger }: PluginEnvironment) { preparers.register('file', filePreparer); preparers.register('github', githubPreparer); - // Create Github client with your access token from environment variables + // Create GitHub client with your access token from environment variables const githubClient = new Octokit({ auth: process.env.GITHUB_ACCESS_TOKEN }); const publisher = new GithubPublisher({ client: githubClient }); diff --git a/docs/features/techdocs/concepts.md b/docs/features/techdocs/concepts.md index fd87543994..7d5b5325cf 100644 --- a/docs/features/techdocs/concepts.md +++ b/docs/features/techdocs/concepts.md @@ -42,7 +42,7 @@ 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)) +([coming soon V.3](./README.md#project-roadmap)) [TechDocs Reader](https://github.com/spotify/backstage/blob/master/plugins/techdocs/src/reader/README.md) diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index 342b846dcc..abb56b348a 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -6,26 +6,38 @@ sidebar_label: Creating and Publishing Documentation This section will guide you through: -- Creating a basic setup for your documentation -- Writing and previewing your documentation in a local Backstage environment -- Creating a build ready for publication -- Publishing your documentation and making your Backstage instance read your - published docs. +- [Create a basic documentation setup](#create-a-basic-documentation-setup) + - [Use the documentation template](#use-the-documentation-template) + - [Manually add documentation setup to already existing repository](#manually-add-documentation-setup-to-already-existing-repository) +- [Writing and previewing your documentation](#writing-and-previewing-your-documentation) ## Prerequisities -- [Docker](https://docs.docker.com/get-docker/) -- Static file hosting - A working Backstage instance with TechDocs installed (see [TechDocs getting started](getting-started.md)) ## Create a basic documentation setup -In your home directory (also known as `~`), create a directory that contains -your documentation (for example, `hello-docs`). Inside this directory, create a -file called `mkdocs.yml`. Below is a basic example of how it could look. +### Use the documentation template -The `~/hello-docs/mkdocs.yml` file should have the following content: +Your working Backstage instance should by default have a documentation template +added. If not, follow these +[instructions](../software-templates/installation.md#adding-templates) to add +the documentation template. + +![Documentation Template](../../assets/techdocs/documentation-template.png) + +Create an entity from the documentation template and you will get the needed +setup for free. + +### Manually add documentation setup to already existing repository + +Prerequisities: + +- `catalog-info.yml` file registered to Backstage. + +Create a `mkdocs.yml` file in the root of the repository with the following +content: ```yaml site_name: 'example-docs' @@ -37,7 +49,20 @@ plugins: - techdocs-core ``` -The `~/hello-docs/docs/index.md` should have the following content: +Update your `catalog-info.yaml` file in the root of the repository with the +following content: + +```yaml +metadata: + annotations: + backstage.io/techdocs-ref: dir:./ +``` + +Create a `/docs` folder in the root of the project with at least a `index.md` +file. _(If you add more markdown files, make sure to update the nav in the +mkdocs.yml file to get a proper navigation for your documentation.)_ + +The `docs/index.md` can for example have the following content: ```md # example docs @@ -45,6 +70,9 @@ The `~/hello-docs/docs/index.md` should have the following content: This is a basic example of documentation. ``` +Commit your changes, open a pull request and merge. You will now get your +updated documentation next time you run Backstage! + ## Writing and previewing your documentation Using the `techdocs-cli` you can preview your docs inside a local Backstage @@ -54,83 +82,6 @@ want to write your documentation. To do this you can run: ```bash -cd ~/hello-docs/ +cd ~// 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/ -docker run -it -w /content -v $(pwd):/content spotify/techdocs build -``` - -You should now have a folder called `~/hello-docs/site/`. - -## Deploy to a file server - -In order to serve documentation to TechDocs, our Backstage plugin needs to -download the HTML rendered from the previous step. This will likely exist on an -external file server, or a storage solution such as Google Cloud Storage. - -When deploying documentation, it should be deployed on that file server/storage -solution with the following convention: `{id}/{file}`. For example, if you want -to upload the `getting-started/index.html` file for the `backstage` -documentation site, we would upload it to our file server as -`backstage/getting-started/index.html`. - -To explain further what this would look like for multiple documentation sites, -take a look at this example file tree that would be represented on your file -server: - -```md -/backstage/index.html /backstage/getting-started/index.html -/backstage/contributing/index.html /mkdocs/index.html -/mkdocs/plugin-development/index.html -/mkdocs/plugin-development/debugging/index.html -``` - -In this file tree, we have two documentation sites available: `backstage` and -`mkdocs`. Each of them expose several pages. Let's say both of these are hosted -on `http://example.com` as the server URL. - -When you configure the TechDocs plugin in Backstage to use `http://example.com` -as the file server/storage solution, it will translate the following URLs to the -file server: - -| Backstage URL | File Server URL | -| --------------------------------------------------------- | ------------------------------------------------------- | -| https://demo.backstage.io/docs/backstage/ | http://example.com/backstage/index.html | -| https://demo.backstage.io/docs/mkdocs/plugin-development/ | http://example.com/mkdocs/plugin-development/index.html | - -Then deploying new sites is easy: simply copy over the `site/` folder produced -in the [Create documentation](#build-production-ready-documentation) step above -to the file server/storage solution under the ID of the documentation site. It -will then become immediately available in Backstage under the same ID as you can -see in the table above. - -So, if the URL to your file server is `http://example.com/`, your -`~/hello-docs/site` folder containing the documentation should be accessible at -`http://example.com/hello-docs/`. - -## Configure TechDocs to read from file server - -In order for Backstage to show your documentation, it needs to know where you -uploaded it. - -Make sure you have Backstage set up using -[TechDocs getting started](getting-started.md). - -To point Backstage to your docs storage, add or change the following lines in -your Backstage `app-config.yaml`: - -```yaml -techdocs: - storageUrl: http://example.com -``` - -You can now start Backstage using `yarn start` and open up your browser at -`http://localhost:3000/docs/hello-docs` to view your docs. diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 094f9a8378..414b3baae8 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -3,16 +3,6 @@ id: getting-started title: Getting Started --- -> TechDocs is not yet feature complete - currently you can't set up a complete -> end-to-end working TechDocs plugin without customizing the plugin itself. - -> What you can expect from TechDocs V.0 is a demonstration of how to integrate -> docs into Backstage. TechDocs can create docs using -> [mkdocs](https://www.mkdocs.org/), as well as read published docs. If you -> publish generated docs and pass in a `storageUrl` in your `app-config.yaml`, -> you can view them in Backstage by going to -> `http://localhost:3000/docs/`. - TechDocs functions as a plugin to Backstage, so you will need to use Backstage to use TechDocs. @@ -48,8 +38,8 @@ containing your new Backstage application. ## Installing TechDocs -TechDocs is not provided with the Backstage application by default, so you will -now need to set up TechDocs manually. It should take less than a minute. +TechDocs is provided with the Backstage application by default. If you want to +set up TechDocs manually, keep follow the instructions below. ### Adding the package @@ -84,19 +74,29 @@ export { plugin as TechDocs } from '@backstage/plugin-techdocs'; ### Setting the configuration TechDocs allows for configuration of the docs storage URL through your -`app-config` file. The URL provided here is for demo docs to use for testing -purposes. +`app-config` file. -To use the demo docs, add the following lines to `app-config.yaml`: +The default storage URL: ```yaml techdocs: - storageUrl: https://techdocs-mock-sites.storage.googleapis.com + storageUrl: http://localhost:7000/techdocs/static/docs ``` +If you want to configure this to point to another storage URL, change the value +of `storageUrl`. + ## Run Backstage locally -Change folder to your Backstage application root and run the following command: +Change folder to `/packages/backend` and run the +following command: + +```bash +yarn start +``` + +Open a new command line window. Change directory to your Backstage application +root and run the following command: ```bash yarn start diff --git a/docs/overview/adopting.md b/docs/overview/adopting.md new file mode 100644 index 0000000000..52c0118a6a --- /dev/null +++ b/docs/overview/adopting.md @@ -0,0 +1,144 @@ +--- +id: adopting +title: Strategies for adopting +--- + +This document outlines some general best practices that have been key to +Backstage's success inside Spotify. Every organization is different and some of +these learnings will therefore not be applicable for your company. We are hoping +that this can become a living document, and strongly encourage you to contribute +back whatever learnings you gather while adopting Backstage inside your company. + +## Organizational setup + +The true value of Backstage is unlocked when it becomes _THE_ developer portal +at your company. As such it is important to recognize that you will need a +central team that owns your Backstage deployment and treats it like a product. + +This team will have **four** primary objectives: + +1. Maintain and operate your deployment of Backstage. This includes customer + support, infrastructure, CI/CD and, as your Backstage product grows, on-call + support. + +2. Drive adoption of customers (developers at your company). + +3. Work with senior tech leadership and architects to ensure your organizations + best practices for software development are encoded into a set of + [Software Templates](../features/software-templates/index.md). + +4. Evangelize Backstage as a central platform towards other + infrastructure/platform teams. + +## Internal evangelization + +The last objective deserves more attention, since it is the least obvious, but +also the most critical to successfully creating a consolidated platform. When +done right, Backstage acts as a "platform of platforms" or marketplace between +infra/platform teams and end-users: + +![pop](../assets/pop.png) + +While anyone at your company can contribute to the platform, the vast majority +of work will be done by teams that also has internal engineers as their +customers. The central team should treat these _contributing teams_ as customers +of the platform as well. + +These teams should be able to autonomously deliver value directly to their +customers. This is done primarily by building [plugins](../plugins/index.md). +Contributing teams should themselves treat their plugins as, or part of, the +products they maintain. + +> Case study: Inside Spotify we have a team that owns our CI platform. They +> don't only maintain the pipelines and build servers, but also expose their +> product in Backstage through a plugin. Since they also +> [maintain their own API](../plugins/call-existing-api.md), they can improve +> their product by iterating on API and UI in lockstep. Because the plugin +> follows our [platform design guidelines](../dls/design.md) their customers get +> a CI experience that is consistent with other tools on the platform (and users +> don't have to become experts in Jenkins). + +### Tactics + +Example of tactics we have used to evangelize Backstage internally: + +- Arrange "Lunch & Learns" and seminars. Frequently offer teams interested in + Backstage development to come to a seminar where you show, for example, how to + build a plugin from scratch. + +- Embedding. As contributing teams start development of their first plugin it is + often very appreciated to have one person from the central team come over and + "embed" for a Sprint or two. + +- Hack days. Backstage-focused Hackathons or hack days is a fun way to get + people into plugin development. + +- Show & tell meetings. In order to build an internal community around Backstage + we have quarterly meetings where anyone working on Backstage is invited to + present their work. This is a not only a great way to get early feedback, but + also helps coordination between teams that are building overlapping + experiences. + +- Provide metrics. Add instrumentation to your Backstage deployment and make + metrics available to contributing teams. At Spotify we have even gone so far + as sending out weekly digest email showing how usage metrics have changed for + individual plugins. + +- Pro-actively identify new plugins. Reach out to teams that own internal UIs or + platforms that you think would make sense to consolidate into Backstage. + +## Metrics + +These are some of the metrics that you can use to verify if Backstage has a +successful impact on your software development process: + +- **Onboarding time** Time until new engineers are productive. At Spotify we + measure this as the time until the employee has merged their 10th PR (this + metric was down 55% two years after deploying Backstage). + +- **Number of merges per developer/day** Less time spent jumping between + different tools and looking for information means more time to focus on + shipping code. A second level of bottlenecks can be identified if you + categorize contributions by domain (services, web, data, etc). + +- **Deploys to production** Cousin to the metric above: How many times does an + engineer push changes into production. + +- **MTTR** With clear ownership of all the pieces in your micro services + ecosystem and all tools integrated into one place, Backstage makes it quicker + for teams to find the root cause of failures, and fix them. + +- **Context switching** Reducing context switching can help engineers stay in + the "zone". We measure the number of different tools an engineer have to + interact with in order to get a certain job done (e.g. push a change, follow + it into production and validate it did not break anything). + +- **T-shapedness** A + [T-shaped](https://medium.com/@jchyip/why-t-shaped-people-e8706198e437) + engineer is someone that is able to contribute to different domains of + engineering. Teams with T-shaped people have fewer bottlenecks and can + therefore deliver more consistently. Backstage makes it easier to be T-shaped + since tools and infrastructure is consistent between domains, and information + is available centrally. + +- **eNPS** Surveys asking about how productive people feel, how easy it is to + find information and overall satisfaction with internal tools. + +- **Fragmentation** _(Experimental)_ Backstage + [Software Templates](../features/software-templates/index.md) helps drive + standardization in your software ecosystem. By measuring the variance in + technology between different software components it is possible to get a sense + of the overall fragmentation in your ecosystem. Examples could include: + framework versions, languages, deployment methods and various code quality + measurements. + +Additionally, these proxy metrics can be used to validate the success of +Backstage as _the_ platform: + +- Nr of teams that have contributed at least one plugin (currently 63 inside + Spotify) + +- Nr of total plugins (currently 135 inside Spotify) + +- % of contributions coming from outside the central Backstage team (currently + 85% inside Spotify) diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md index 075d4bdeb6..f6c9990b90 100644 --- a/docs/plugins/call-existing-api.md +++ b/docs/plugins/call-existing-api.md @@ -1,6 +1,177 @@ --- id: call-existing-api -title: Call existing API +title: Call Existing API --- -## TODO +This article describes the various options that Backstage frontend plugins have, +in communicating with service APIs that already exist. Each section below +describes a possible choice, and the circumstances under which it fits. + +In these examples, we will be ultimately requesting data from the fictional +FrobsCo API. + +## Issuing Requests Directly + +The most basic choice available is to issue requests directly from the plugin +frontend code to the FrobsCo API, using for example `fetch` or a support library +such as `axios`. + +Example: + +```ts +// Inside your component +fetch('https://api.frobsco.com/v1/list') + .then(response => response.json()) + .then(payload => setFrobs(payload as Frob[])); +``` + +Internally at Spotify, this has not been a very common choice. Third party APIs +are sometimes accessed like this. Just a handful of internal APIs also went +through the trouble of exposing themselves in a way that is useful directly from +a browser, but even then, often not from the public internet but only supporting +users that are already on the company VPN. + +This can be used when: + +- The API already does/exposes exactly what you need. +- The request/response patterns of the API match real world usage needs in + Backstage frontend plugins. For example, if the end use case is to show a + small summary in Backstage, but the only available API endpoint gives a 30 + megabyte blob with large amounts of redundant information, it would hurt the + end user experience. Particularly on mobile. The same goes for cases where you + want to show many individual pieces of information: if a common use case is to + show large tables where one API request per cell is necessary, the browser + will quickly become swamped and you may want to consider performing + aggregation elsewhere instead. +- The API can maintain interactive request/response times at your required peak + request rates. The end user experience will be degraded if they spend a lot of + time waiting for the data to arrive. +- The API endpoint is highly available. The browser does not have builtin + facilities for load balancing, service discovery, retries, health checks, + circuit breaking and similar. If the endpoint is occasionally down even for + short periods of time (e.g. during deploys), end users will quickly notice. +- The API is exposed over HTTPS (not just HTTP), and properly handles + [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). These are + requirements that the user's browser will impose for security reasons, and the + requests will be rejected otherwise. +- The API endpoint is easily reachable, in terms of network conditions, by end + users. This may be particularly relevant if your end users are outside of your + perimeter. +- The requests do not require secrets to be passed. This limitation does not + apply to OAuth tokens, which the frontend can negotiate and make proper use + of. + +## Using The Backstage Proxy + +Backstage has an optional proxy plugin for the backend, that can be used to +easily add proxy routes to downstream APIs. + +Example: + +```yaml +# In app-config.yaml +proxy: + '/frobs': + target: 'http://api.frobsco.com/v1' + changeOrigin: true + pathRewrite: + '^/proxy/frobs/': '/' +``` + +```ts +// Inside your component +const backendUrl = config.getString('backend.baseUrl'); +fetch(`${backendUrl}/proxy/frobs/list`) + .then(response => response.json()) + .then(payload => setFrobs(payload as Frob[])); +``` + +The proxy is powered by the `http-proxy-middleware` package, and supports all of +its +[configuration options](https://github.com/chimurai/http-proxy-middleware#options). + +Internally at Spotify, the proxy option has been the overwhelmingly most popular +choice for plugin makers. Since we have DNS based service discovery in place and +a microservices framework that made it trivial to expose plain HTTP, it has been +a matter of just adding a few lines of Backstage config to get the benefit of +being easily and robustly reachable from users' web browsers as well. + +This may be used instead of direct requests, when: + +- You need to perform HTTPS termination and/or CORS handling, because the API + itself is not supplying those. +- You need to inject a simple static secret into the requests, e.g. an + Authorization header that gets added to the request headers. +- You want to make use of other proxy facilities, such as retries, failover, + health checks, routing, request logging, rewrites, etc. +- You already have the Backstage backend itself exposed through your perimeter + and find it practical to have only one entry point to deal with, governing + ingress with just the Backstage config. + +## Creating a Backstage Backend Plugin + +Much like the Backstage frontend, the Backstage backend also has a plugin +system. The above mentioned proxy is actually one such plugin. If you were in +need of a more involved integration than just direct access to the FrobsCo API, +or if you needed to hold state, you may want to make such a plugin. + +Example: + +```ts +// Inside your component +const backendUrl = config.getString('backend.baseUrl'); +fetch(`${backendUrl}/frobs-aggregator/summary`) + .then(response => response.json()) + .then(payload => setSummary(payload as FrobSummary)); +``` + +```ts +// Inside a new frobs-aggregator backend plugin +router.use('/summary', async (req, res) => { + const agg = await Promise.all([ + fetch('https://api.frobsco.com/v1/list'), + fetch('http://flerps.partnercompany.com:8080/flerp-batch'), + database.currentThunk(), + ]).then(async ([frobs, flerps, thunk]) => { + return computeAggregate(await frobs.json(), await flerps.json(), thunk); + }); + res.status(200).send(agg); +}); +``` + +For a more detailed example, see +[the lighthouse plugin](https://github.com/spotify/backstage/tree/master/plugins/lighthouse) +that stores some state in a database and adds new capabilities to the underlying +API. + +Internally at Spotify, this has been a fairly popular choice for different +reasons. Commonly, the backend has been used as a caching and data massaging +layer for slow APIs or APIs whose request/response shapes or speeds were not +acceptable for direct use by frontends. For example, this has made it possible +to issue efficient batch queries from the frontend, e.g. in big lists or tables +that want to resolve a lot of sparse data from the larger list that an +underlying service supplies. + +This may be used instead of the above, when: + +- You need to perform complex model conversion, or protocol translation beyond + what the proxy handles. +- You want to perform aggregations or summaries on the backend instead of on the + frontend. +- You want to enable batching or caching of slower or more unreliable APIs. +- You need to maintain state for your plugin, perhaps using the builtin database + support in the backend. +- You need to inject secrets or in other ways negotiate with other parts of the + API or other services in order to perform your work. +- You want to enforce end user authentication / authorization for operations on + behalf of the API, have session handling, or similar. + +There is a balance to strike regarding when to make an entirely separate backend +for a purpose, and when to make a Backstage backend plugin that adapts something +that already exists. General advice is not easy to give, but contact us on +Discord if you have any questions, and we may be able to offer guidance. + +## Extending the GraphQL Model + +The extensible GraphQL backend layer is not built yet. This section will be +expanded when that happens. Stay tuned! diff --git a/docs/plugins/index.md b/docs/plugins/index.md index bb8951f959..c5b67bcec4 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -1,6 +1,6 @@ --- id: index -title: Intro +title: Intro to plugins --- Backstage is a single-page application composed of a set of plugins. diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md index fbfbbc475e..521628fd33 100644 --- a/docs/reference/utility-apis/README.md +++ b/docs/reference/utility-apis/README.md @@ -55,7 +55,7 @@ ApiRef: ## githubAuth -Provides authentication towards Github APIs +Provides authentication towards GitHub APIs Implemented types: [OAuthApi](./OAuthApi.md), [ProfileInfoApi](./ProfileInfoApi.md), @@ -67,7 +67,7 @@ ApiRef: ## gitlabAuth -Provides authentication towards Gitlab APIs +Provides authentication towards GitLab APIs Implemented types: [OAuthApi](./OAuthApi.md), [ProfileInfoApi](./ProfileInfoApi.md), diff --git a/microsite/blog/2020-03-18-what-is-backstage.md b/microsite/blog/2020-03-18-what-is-backstage.md index 942adc607f..8b0a0fcbe6 100644 --- a/microsite/blog/2020-03-18-what-is-backstage.md +++ b/microsite/blog/2020-03-18-what-is-backstage.md @@ -13,7 +13,7 @@ Two days ago, we released the open source version of [Backstage](https://backsta ## What’s the big infrastructure problem? -As companies grow, their infrastructure systems get messier. Consider a team that wants to deploy something to the cloud. While Spotify has many awesome engineers, not every engineer is well-versed in our chosen cloud-provider tooling. Yet everyone is required to know and understand Terraform, GCP/AWS/Azure CLIs, Gitlab CI, Prometheus, Kubernetes, Docker, various monitoring and alerting tools, and much, much more. Once other resources come into play (databases, queueing, etc.), each engineer requires even more tools and domain-specific knowledge (or “disciplines”), from backend to machine learning, to mobile and data. +As companies grow, their infrastructure systems get messier. Consider a team that wants to deploy something to the cloud. While Spotify has many awesome engineers, not every engineer is well-versed in our chosen cloud-provider tooling. Yet everyone is required to know and understand Terraform, GCP/AWS/Azure CLIs, GitLab CI, Prometheus, Kubernetes, Docker, various monitoring and alerting tools, and much, much more. Once other resources come into play (databases, queueing, etc.), each engineer requires even more tools and domain-specific knowledge (or “disciplines”), from backend to machine learning, to mobile and data. ## What’s the fix? diff --git a/microsite/i18n/en.json b/microsite/i18n/en.json index d2593a13ce..96c874f496 100644 --- a/microsite/i18n/en.json +++ b/microsite/i18n/en.json @@ -177,6 +177,9 @@ "journey": { "title": "journey" }, + "overview/adopting": { + "title": "Strategies for adopting" + }, "overview/architecture-overview": { "title": "Architecture overview" }, @@ -199,7 +202,7 @@ "title": "Backend plugin" }, "plugins/call-existing-api": { - "title": "Call existing API" + "title": "Call Existing API" }, "plugins/create-a-plugin": { "title": "Create a Backstage Plugin" @@ -208,7 +211,7 @@ "title": "Existing plugins" }, "plugins/index": { - "title": "Intro" + "title": "Intro to plugins" }, "plugins/plugin-development": { "title": "Plugin Development in Backstage" diff --git a/microsite/package.json b/microsite/package.json index dd8957245a..bcf28d7fe5 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -1,5 +1,5 @@ { - "version": "1.0.0", + "version": "0.1.1-alpha.19", "name": "backstage-microsite", "license": "Apache-2.0", "private": true, diff --git a/microsite/pages/en/background.js b/microsite/pages/en/background.js index 70a15421ab..c5bf9bee94 100644 --- a/microsite/pages/en/background.js +++ b/microsite/pages/en/background.js @@ -112,7 +112,7 @@ const Background = props => { left: 0, right: 0, bottom: 0, - backgroundImage: `linear-gradient( to bottom, rgb(18, 18, 18), rgba(0, 0, 0, 0) ), url(../img/dot.svg);`, + backgroundImage: `linear-gradient( to bottom, rgb(18, 18, 18), rgba(0, 0, 0, 0) ), url(../img/dot.svg)`, }} /> diff --git a/microsite/pages/en/docs.js b/microsite/pages/en/docs.js new file mode 100644 index 0000000000..a8bda18f88 --- /dev/null +++ b/microsite/pages/en/docs.js @@ -0,0 +1,12 @@ +const React = require('react'); +const Redirect = require('../../core/Redirect.js'); + +const siteConfig = require(process.cwd() + '/siteConfig.js'); + +function Docs() { + return ( + + ); +} + +module.exports = Docs; diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index d140a2036b..986db4b732 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -353,10 +353,20 @@ class Index extends React.Component { + + + + } /> diff --git a/microsite/sidebars.json b/microsite/sidebars.json index b07f326086..efb76397aa 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -5,7 +5,8 @@ "overview/architecture-overview", "overview/architecture-terminology", "overview/roadmap", - "overview/vision" + "overview/vision", + "overview/adopting" ], "Getting Started": [ "getting-started/index", diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index f0c8ce5fa5..64e1af4d86 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -35,11 +35,12 @@ const siteConfig = { // For no header links in the top nav bar -> headerLinks: [], headerLinks: [ { - href: 'https://github.com/spotify/backstage#backstage', + href: 'https://github.com/spotify/backstage', label: 'GitHub', }, { doc: 'overview/what-is-backstage', + href: '/docs', label: 'Docs', }, { diff --git a/microsite/static/img/techdocs2.gif b/microsite/static/img/techdocs2.gif new file mode 100644 index 0000000000..ba937581b3 Binary files /dev/null and b/microsite/static/img/techdocs2.gif differ diff --git a/mkdocs.yml b/mkdocs.yml index 085d83b7cc..74f9815483 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,7 @@ nav: - Architecture and terminology: 'overview/architecture-terminology.md' - Roadmap: 'overview/roadmap.md' - Vision: 'overview/vision.md' + - Strategies for adopting: 'overview/adopting.md' - Getting started: - Running Backstage locally: 'getting-started/index.md' - Installation: 'getting-started/installation.md' @@ -41,11 +42,7 @@ nav: - Overview: 'features/techdocs/README.md' - Getting Started: 'features/techdocs/getting-started.md' - Concepts: 'features/techdocs/concepts.md' - - Reading Documentation: 'features/techdocs/reading-documentation.md' - - Writing Documentation: 'features/techdocs/writing-documentation.md' - - Publishing Documentation: 'features/techdocs/publishing-documentation.md' - - Contributing: 'features/techdocs/contributing.md' - - Debugging: 'features/techdocs/debugging.md' + - Creating and Publishing Documentation: 'features/techdocs/creating-and-publishing.md' - FAQ: 'features/techdocs/FAQ.md' - Plugins: - Overview: 'plugins/index.md' diff --git a/packages/app/package.json b/packages/app/package.json index 126913b32a..d6598b9954 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,34 +1,34 @@ { "name": "example-app", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "private": true, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/plugin-api-docs": "^0.1.1-alpha.18", - "@backstage/plugin-catalog": "^0.1.1-alpha.18", - "@backstage/plugin-circleci": "^0.1.1-alpha.18", - "@backstage/plugin-explore": "^0.1.1-alpha.18", - "@backstage/plugin-github-actions": "^0.1.1-alpha.18", - "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.18", - "@backstage/plugin-graphiql": "^0.1.1-alpha.18", - "@backstage/plugin-jenkins": "^0.1.1-alpha.18", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.18", - "@backstage/plugin-newrelic": "^0.1.1-alpha.18", - "@backstage/plugin-register-component": "^0.1.1-alpha.18", - "@backstage/plugin-rollbar": "^0.1.1-alpha.18", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.18", - "@backstage/plugin-sentry": "^0.1.1-alpha.18", - "@backstage/plugin-tech-radar": "^0.1.1-alpha.18", - "@backstage/plugin-techdocs": "^0.1.1-alpha.18", - "@backstage/plugin-welcome": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/plugin-api-docs": "^0.1.1-alpha.19", + "@backstage/plugin-catalog": "^0.1.1-alpha.19", + "@backstage/plugin-circleci": "^0.1.1-alpha.19", + "@backstage/plugin-explore": "^0.1.1-alpha.19", + "@backstage/plugin-github-actions": "^0.1.1-alpha.19", + "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.19", + "@backstage/plugin-graphiql": "^0.1.1-alpha.19", + "@backstage/plugin-jenkins": "^0.1.1-alpha.19", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.19", + "@backstage/plugin-newrelic": "^0.1.1-alpha.19", + "@backstage/plugin-register-component": "^0.1.1-alpha.19", + "@backstage/plugin-rollbar": "^0.1.1-alpha.19", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.19", + "@backstage/plugin-sentry": "^0.1.1-alpha.19", + "@backstage/plugin-tech-radar": "^0.1.1-alpha.19", + "@backstage/plugin-techdocs": "^0.1.1-alpha.19", + "@backstage/plugin-welcome": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", "@roadiehq/backstage-plugin-github-pull-requests": "0.3.0", - "@roadiehq/backstage-plugin-travis-ci": "^0.1.3", + "@roadiehq/backstage-plugin-travis-ci": "^0.1.4", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", diff --git a/packages/app/src/identityProviders.ts b/packages/app/src/identityProviders.ts index 0ae98cf971..fab0e92577 100644 --- a/packages/app/src/identityProviders.ts +++ b/packages/app/src/identityProviders.ts @@ -37,14 +37,14 @@ export const providers = [ }, { id: 'gitlab-auth-provider', - title: 'Gitlab', - message: 'Sign In using Gitlab', + title: 'GitLab', + message: 'Sign In using GitLab', apiRef: gitlabAuthApiRef, }, { id: 'github-auth-provider', - title: 'Github', - message: 'Sign In using Github', + title: 'GitHub', + message: 'Sign In using GitHub', apiRef: githubAuthApiRef, }, { diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 5cf2ee95e5..72ff1c3b3e 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli-common": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", - "@backstage/config-loader": "^0.1.1-alpha.18", + "@backstage/cli-common": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", + "@backstage/config-loader": "^0.1.1-alpha.19", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -43,7 +43,8 @@ "lodash": "^4.17.15", "morgan": "^1.10.0", "stoppable": "^1.1.0", - "winston": "^3.2.1" + "winston": "^3.2.1", + "selfsigned": "^1.10.7" }, "peerDependencies": { "pg-connection-string": "^2.3.0" @@ -54,7 +55,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/compression": "^1.7.0", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index cd8e445dd8..4669c00e68 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -19,7 +19,7 @@ import compression from 'compression'; import cors from 'cors'; import express, { Router } from 'express'; import helmet from 'helmet'; -import { Server } from 'http'; +import * as http from 'http'; import stoppable from 'stoppable'; import { Logger } from 'winston'; import { useHotCleanup } from '../../hot'; @@ -30,7 +30,13 @@ import { requestLoggingHandler, } from '../../middleware'; import { ServiceBuilder } from '../types'; -import { readBaseOptions, readCorsOptions } from './config'; +import { + readBaseOptions, + readCorsOptions, + readHttpsSettings, + HttpsSettings, +} from './config'; +import { createHttpServer, createHttpsServer } from './hostFactory'; const DEFAULT_PORT = 7000; // '' is express default, which listens to all interfaces @@ -41,6 +47,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; + private httpsSettings: HttpsSettings | undefined; private routers: [string, Router][]; // Reference to the module where builder is created - needed for hot module // reloading @@ -70,6 +77,11 @@ export class ServiceBuilderImpl implements ServiceBuilder { this.corsOptions = corsOptions; } + const httpsSettings = readHttpsSettings(backendConfig); + if (httpsSettings) { + this.httpsSettings = httpsSettings; + } + return this; } @@ -88,6 +100,11 @@ export class ServiceBuilderImpl implements ServiceBuilder { return this; } + setHttpsSettings(settings: HttpsSettings): ServiceBuilder { + this.httpsSettings = settings; + return this; + } + enableCors(options: cors.CorsOptions): ServiceBuilder { this.corsOptions = options; return this; @@ -98,9 +115,15 @@ export class ServiceBuilderImpl implements ServiceBuilder { return this; } - start(): Promise { + start(): Promise { const app = express(); - const { port, host, logger, corsOptions } = this.getOptions(); + const { + port, + host, + logger, + corsOptions, + httpsSettings, + } = this.getOptions(); app.use(helmet()); if (corsOptions) { @@ -121,20 +144,24 @@ export class ServiceBuilderImpl implements ServiceBuilder { reject(e); }); - const server = stoppable( - app.listen(port, host, () => { + const server: http.Server = httpsSettings + ? createHttpsServer(app, httpsSettings, logger) + : createHttpServer(app, logger); + + const stoppableServer = stoppable( + server.listen(port, host, () => { logger.info(`Listening on ${host}:${port}`); }), 0, ); useHotCleanup(this.module, () => - server.stop((e: any) => { + stoppableServer.stop((e: any) => { if (e) console.error(e); }), ); - resolve(server); + resolve(stoppableServer); }); } @@ -143,12 +170,14 @@ export class ServiceBuilderImpl implements ServiceBuilder { host: string; logger: Logger; corsOptions?: cors.CorsOptions; + httpsSettings?: HttpsSettings; } { return { port: this.port ?? DEFAULT_PORT, host: this.host ?? DEFAULT_HOST, logger: this.logger ?? getRootLogger(), corsOptions: this.corsOptions, + httpsSettings: this.httpsSettings, }; } } diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts index 49f8dc4f04..e257bd4111 100644 --- a/packages/backend-common/src/service/lib/config.ts +++ b/packages/backend-common/src/service/lib/config.ts @@ -22,6 +22,41 @@ export type BaseOptions = { listenHost?: string; }; +export type CertificateOptions = { + key?: CertificateKeyOptions; + attributes?: CertificateAttributeOptions; +}; + +export type CertificateKeyOptions = { + size?: number; + algorithm?: string; + days?: number; +}; + +export type CertificateAttributeOptions = { + commonName?: string; +}; + +export type HttpsSettings = { + certificate: CertificateSigningOptions | CertificateReferenceOptions; +}; + +export type CertificateReferenceOptions = { + key: string; + cert: string; +}; + +export type CertificateSigningOptions = { + algorithm: string; + size?: number; + days?: number; + attributes?: CertificateAttributes; +}; + +export type CertificateAttributes = { + commonName?: string; +}; + /** * Reads some base options out of a config object. * @@ -40,6 +75,7 @@ export function readBaseOptions(config: ConfigReader): BaseOptions { if (typeof config.get('listen') === 'string') { // TODO(freben): Expand this to support more addresses and perhaps optional const { host, port } = parseListenAddress(config.getString('listen')); + return removeUnknown({ listenPort: port, listenHost: host, @@ -49,6 +85,7 @@ export function readBaseOptions(config: ConfigReader): BaseOptions { return removeUnknown({ listenPort: config.getOptionalNumber('listen.port'), listenHost: config.getOptionalString('listen.host'), + baseUrl: config.getOptionalString('baseUrl'), }); } @@ -86,6 +123,39 @@ export function readCorsOptions(config: ConfigReader): CorsOptions | undefined { }); } +/** + * Attempts to read a https settings object from the root of a config object. + * + * @param config The root of a backend config object + * @returns A https settings object, or undefined if not specified + * + * @example + * ```json + * { + * https: { + * certificate: ... + * } + * } + * ``` + */ +export function readHttpsSettings( + config: ConfigReader, +): HttpsSettings | undefined { + const cc = config.getOptionalConfig('https'); + + if (!cc) { + return undefined; + } + + const certificateConfig = cc.get('certificate'); + + const cfg = { + certificate: certificateConfig, + }; + + return removeUnknown(cfg as HttpsSettings); +} + function getOptionalStringOrStrings( config: ConfigReader, key: string, diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts new file mode 100644 index 0000000000..8fab2fd4ab --- /dev/null +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -0,0 +1,92 @@ +/* + * 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 express from 'express'; +import * as http from 'http'; +import * as https from 'https'; +import { Logger } from 'winston'; +import { HttpsSettings } from './config'; + +/** + * Creates a Http server instance based on an Express application. + * + * @param app The Express application object + * @param logger Optional Winston logger object + * @returns A Http server instance + * + */ +export function createHttpServer( + app: express.Express, + logger?: Logger, +): http.Server { + logger?.info('Initializing http server'); + + return http.createServer(app); +} + +/** + * Creates a Https server instance based on an Express application. + * + * @param app The Express application object + * @param httpsSettings HttpsSettings for self-signed certificate generation + * @param logger Optional Winston logger object + * @returns A Https server instance + * + */ +export function createHttpsServer( + app: express.Express, + httpsSettings: HttpsSettings, + logger?: Logger, +): http.Server { + logger?.info('Initializing https server'); + + const credentials: { key: string; cert: string } = { + key: '', + cert: '', + }; + + const signingOptions: any = httpsSettings?.certificate; + + if (signingOptions?.algorithm !== undefined) { + logger?.info('Generating self-signed certificate with attributes'); + + const certificateAttributes: Array = Object.entries( + signingOptions.attributes, + ).map(([name, value]) => ({ name, value })); + + // TODO: Create a type def for selfsigned. + const signatures = require('selfsigned').generate(certificateAttributes, { + algorithm: signingOptions?.algorithm, + keySize: signingOptions?.size || 2048, + days: signingOptions?.days || 30, + }); + + logger?.info('Bootstrapping self-signed certificate'); + + credentials.key = signatures.private; + credentials.cert = signatures.cert; + } else { + logger?.info('Bootstrapping cert from config'); + + credentials.key = signingOptions?.key; + credentials.cert = signingOptions?.cert; + } + + if (credentials.key === '' || credentials.cert === '') { + throw new Error('Invalid credentials'); + } + + return https.createServer(credentials, app) as http.Server; +} diff --git a/packages/backend-common/src/service/types.ts b/packages/backend-common/src/service/types.ts index 070794969f..d389f4b5ff 100644 --- a/packages/backend-common/src/service/types.ts +++ b/packages/backend-common/src/service/types.ts @@ -19,6 +19,7 @@ import cors from 'cors'; import { Router, RequestHandler } from 'express'; import { Server } from 'http'; import { Logger } from 'winston'; +import { HttpsSettings } from './lib/config'; export type ServiceBuilder = { /** @@ -39,6 +40,15 @@ export type ServiceBuilder = { */ setPort(port: number): ServiceBuilder; + /** + * Sets the host to listen on. + * + * '' is express default, which listens to all interfaces. + * + * @param host The host to listen on + */ + setHost(host: string): ServiceBuilder; + /** * Sets the logger to use for service-specific logging. * @@ -58,6 +68,15 @@ export type ServiceBuilder = { */ enableCors(options: cors.CorsOptions): ServiceBuilder; + /** + * Configure self-signed certificate generation options. + * + * If this method is not called, the resulting service will use sensible defaults + * + * @param options Standard certificate options + */ + setHttpsSettings(settings: HttpsSettings): ServiceBuilder; + /** * Adds a router (similar to the express .use call) to the service. * diff --git a/packages/backend/package.json b/packages/backend/package.json index c80fbc7c38..99f0cf3ece 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,18 +18,18 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", - "@backstage/plugin-auth-backend": "^0.1.1-alpha.18", - "@backstage/plugin-catalog-backend": "^0.1.1-alpha.18", - "@backstage/plugin-graphql-backend": "^0.1.1-alpha.18", - "@backstage/plugin-identity-backend": "^0.1.1-alpha.18", - "@backstage/plugin-proxy-backend": "^0.1.1-alpha.18", - "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.18", - "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.18", - "@backstage/plugin-sentry-backend": "^0.1.1-alpha.18", - "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", + "@backstage/plugin-auth-backend": "^0.1.1-alpha.19", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.19", + "@backstage/plugin-graphql-backend": "^0.1.1-alpha.19", + "@backstage/plugin-identity-backend": "^0.1.1-alpha.19", + "@backstage/plugin-proxy-backend": "^0.1.1-alpha.19", + "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.19", + "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.19", + "@backstage/plugin-sentry-backend": "^0.1.1-alpha.19", + "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.19", "@octokit/rest": "^18.0.0", "dockerode": "^3.2.0", "express": "^4.17.1", @@ -40,7 +40,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/backend/src/plugins/techdocs.ts b/packages/backend/src/plugins/techdocs.ts index 812a56cd9f..7364cc4d83 100644 --- a/packages/backend/src/plugins/techdocs.ts +++ b/packages/backend/src/plugins/techdocs.ts @@ -20,22 +20,27 @@ import { Preparers, Generators, LocalPublish, - TechdocsGenerator + TechdocsGenerator, + GithubPreparer, } from '@backstage/plugin-techdocs-backend'; import { PluginEnvironment } from '../types'; import Docker from 'dockerode'; -export default async function createPlugin({ logger, config }: PluginEnvironment) { +export default async function createPlugin({ + logger, + config, +}: PluginEnvironment) { const generators = new Generators(); - const techdocsGenerator = new TechdocsGenerator(); + const techdocsGenerator = new TechdocsGenerator(logger); generators.register('techdocs', techdocsGenerator); - const directoryPreparer = new DirectoryPreparer(); const preparers = new Preparers(); - + const githubPreparer = new GithubPreparer(logger); + const directoryPreparer = new DirectoryPreparer(logger); preparers.register('dir', directoryPreparer); + preparers.register('github', githubPreparer); - const publisher = new LocalPublish(); + const publisher = new LocalPublish(logger); const dockerClient = new Docker(); diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 248f2765e4..54b3d3a621 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.18", + "version": "0.1.1-alpha.19", "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.18", + "@backstage/config": "^0.1.1-alpha.19", "@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.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index 092a2a5167..5e050210da 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-common", "description": "Common functionality used by cli, backend, and create-app", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "private": false, "main": "src/index.ts", "types": "src/index.ts", @@ -29,7 +29,6 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", "@types/jest": "^26.0.7", "@types/node": "^12.0.0" }, diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 77d3a84e82..cc7d45c139 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -37,8 +37,11 @@ async function getConfig() { // TODO: jest is working on module support, it's possible that we can remove this in the future transform: { '\\.esm\\.js$': require.resolve('jest-esm-transformer'), - '\\.(js|jsx|ts|tsx)': require.resolve('ts-jest'), - '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)': require.resolve( + '\\.(js|jsx|ts|tsx)$': [ + require.resolve('ts-jest'), + { isolatedModules: true }, + ], + '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)$': require.resolve( './jestFileTransform.js', ), }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 3c9f85ac30..b57ee8cb67 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public" @@ -21,7 +21,6 @@ "build": "backstage-cli build --outputs cjs", "lint": "backstage-cli lint", "test": "backstage-cli test", - "test:e2e": "node e2e-test/cli-e2e-test.js", "clean": "backstage-cli clean", "start": "nodemon --" }, @@ -29,9 +28,9 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/cli-common": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", - "@backstage/config-loader": "^0.1.1-alpha.18", + "@backstage/cli-common": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", + "@backstage/config-loader": "^0.1.1-alpha.19", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", @@ -69,9 +68,7 @@ "jest-css-modules": "^2.1.0", "jest-esm-transformer": "^1.0.0", "mini-css-extract-plugin": "^0.9.0", - "node-fetch": "^2.6.0", "ora": "^4.0.3", - "pgtools": "^0.3.0", "raw-loader": "^4.0.1", "react": "^16.0.0", "react-dev-utils": "^10.2.1", @@ -79,7 +76,7 @@ "recursive-readdir": "^2.2.2", "replace-in-file": "^6.0.0", "rollup": "2.23.x", - "rollup-plugin-dts": "^1.4.6", + "rollup-plugin-dts": "1.4.11", "rollup-plugin-esbuild": "^2.0.0", "rollup-plugin-image-files": "^1.4.2", "rollup-plugin-peer-deps-external": "^2.2.2", @@ -118,9 +115,7 @@ "@types/webpack-dev-server": "^3.10.0", "del": "^5.1.0", "nodemon": "^2.0.2", - "tree-kill": "^1.2.2", - "ts-node": "^8.6.2", - "zombie": "^6.1.4" + "ts-node": "^8.6.2" }, "files": [ "asset-types", diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index 91c08af201..a0752b94ed 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -25,9 +25,11 @@ export default async (cmd: Command) => { env: 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); + const waitForExit = await serveBackend({ entry: 'src/index', checksEnabled: cmd.check, + inspectEnabled: cmd.inspect, config: ConfigReader.fromConfigs(appConfigs), appConfigs, }); diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/commands/lint.ts index b355939e55..7b4532ffa7 100644 --- a/packages/cli/src/commands/lint.ts +++ b/packages/cli/src/commands/lint.ts @@ -22,7 +22,7 @@ export default async (cmd: Command, cmdArgs: string[]) => { const args = [ '--ext=js,jsx,ts,tsx', '--max-warnings=0', - '--format=eslint-formatter-friendly', + `--format=${cmd.format}`, ...(cmdArgs ?? [paths.targetDir]), ]; if (cmd.fix) { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 4b9dc4bab7..9fb909592b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -52,6 +52,7 @@ const main = (argv: string[]) => { .command('backend:dev') .description('Start local development server with HMR for the backend') .option('--check', 'Enable type checking and linting') + .option('--inspect', 'Enable debugger') .action(lazyAction(() => import('./commands/backend/dev'), 'default')); program @@ -113,6 +114,11 @@ const main = (argv: string[]) => { program .command('lint') + .option( + '--format ', + 'Lint report output format', + 'eslint-formatter-friendly', + ) .option('--fix', 'Attempt to automatically fix violations') .description('Lint a package') .action(lazyAction(() => import('./commands/lint'), 'default')); diff --git a/packages/cli/src/lib/bundler/backend.ts b/packages/cli/src/lib/bundler/backend.ts index ce821a585b..f5f233854f 100644 --- a/packages/cli/src/lib/bundler/backend.ts +++ b/packages/cli/src/lib/bundler/backend.ts @@ -19,7 +19,11 @@ import { createBackendConfig } from './config'; import { resolveBundlingPaths } from './paths'; import { ServeOptions } from './types'; -export async function serveBackend(options: ServeOptions) { +export async function serveBackend( + options: ServeOptions & { + inspectEnabled: boolean; + }, +) { const paths = resolveBundlingPaths(options); const config = createBackendConfig(paths, { ...options, diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 8049ce436d..d432d6b64e 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -198,9 +198,17 @@ export function createBackendConfig( chunkFilename: isDev ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js', + ...(isDev + ? { + devtoolModuleFilenameTemplate: 'file:///[absolute-resource-path]', + } + : {}), }, plugins: [ - new StartServerPlugin('main.js'), + new StartServerPlugin({ + name: 'main.js', + nodeArgs: options.inspectEnabled ? ['--inspect'] : undefined, + }), new webpack.HotModuleReplacementPlugin(), ...(checksEnabled ? [ diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 5d0ba66a51..03d68d9bb8 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -25,7 +25,9 @@ export type BundlingOptions = { baseUrl: URL; }; -export type BackendBundlingOptions = Omit; +export type BackendBundlingOptions = Omit & { + inspectEnabled: boolean; +}; export type ServeOptions = BundlingPathsOptions & { checksEnabled: boolean; diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index a2d7bbbc59..f5873c3c9d 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.19", "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 9c8c81d31b..acbc690625 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config/src/types.ts b/packages/config/src/types.ts index 3e46874c1d..840cc36173 100644 --- a/packages/config/src/types.ts +++ b/packages/config/src/types.ts @@ -30,10 +30,12 @@ export type AppConfig = { }; export type Config = { + has(key: string): boolean; + keys(): string[]; - get(key: string): JsonValue; - getOptional(key: string): JsonValue | undefined; + get(key?: string): JsonValue; + getOptional(key?: string): JsonValue | undefined; getConfig(key: string): Config; getOptionalConfig(key: string): Config | undefined; diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 9ce1290a73..7b2087630e 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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.18", - "@backstage/test-utils-core": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/test-utils-core": "^0.1.1-alpha.19", "@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-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts index b26b23ce08..469414d3dc 100644 --- a/packages/core-api/src/apis/definitions/auth.ts +++ b/packages/core-api/src/apis/definitions/auth.ts @@ -222,7 +222,7 @@ export const googleAuthApiRef = createApiRef< }); /** - * Provides authentication towards Github APIs. + * Provides authentication towards GitHub APIs. * * See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ * for a full list of supported scopes. @@ -231,7 +231,7 @@ export const githubAuthApiRef = createApiRef< OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi >({ id: 'core.auth.github', - description: 'Provides authentication towards Github APIs', + description: 'Provides authentication towards GitHub APIs', }); /** @@ -252,7 +252,7 @@ export const oktaAuthApiRef = createApiRef< }); /** - * Provides authentication towards Gitlab APIs. + * Provides authentication towards GitLab APIs. * * See https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token * for a full list of supported scopes. @@ -261,7 +261,7 @@ export const gitlabAuthApiRef = createApiRef< OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionStateApi >({ id: 'core.auth.gitlab', - description: 'Provides authentication towards Gitlab APIs', + description: 'Provides authentication towards GitLab APIs', }); /** diff --git a/packages/core/package.json b/packages/core/package.json index 7333e0ce2a..5b59ee3dff 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.18", - "@backstage/core-api": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/config": "^0.1.1-alpha.19", + "@backstage/core-api": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -50,12 +50,12 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-sparklines": "^1.7.0", - "react-syntax-highlighter": "^13.2.1", + "react-syntax-highlighter": "^13.5.1", "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@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/Tabs/Tabs.tsx b/packages/core/src/components/Tabs/Tabs.tsx index 54369de473..336ed2b93b 100644 --- a/packages/core/src/components/Tabs/Tabs.tsx +++ b/packages/core/src/components/Tabs/Tabs.tsx @@ -48,7 +48,7 @@ export interface TabsProps { tabs: TabProps[]; } -const useStyles = makeStyles((theme: BackstageTheme) => ({ +const useStyles = makeStyles(theme => ({ root: { flexGrow: 1, width: '100%', @@ -66,7 +66,7 @@ const useStyles = makeStyles((theme: BackstageTheme) => ({ export const Tabs: FC = ({ tabs }) => { const classes = useStyles(); - const [value, setValue] = useState([0, 0]); // [selectedChunckedNavIndex, selectedIndex] + const [value, setValue] = useState([0, 0]); // [selectedChunkedNavIndex, selectedIndex] const [navIndex, setNavIndex] = useState(0); const [numberOfChunkedElement, setNumberOfChunkedElement] = useState(0); const [chunkedTabs, setChunkedTabs] = useState([[]]); @@ -89,7 +89,7 @@ export const Tabs: FC = ({ tabs }) => { const hasNextNavIndex = () => navIndex + 1 < chunkedTabs.length; useEffect(() => { - // Each time the window is resized we calculate how many tabs wwe can render given the window width + // Each time the window is resized we calculate how many tabs we can render given the window width const padding = 20; // The AppBar padding const numberOfTabIcons = navIndex === 0 ? 1 : 2; @@ -99,7 +99,7 @@ export const Tabs: FC = ({ tabs }) => { const newChunkedElementSize = Math.floor(wrapperWidth / 170); setNumberOfChunkedElement(newChunkedElementSize); - setChunkedTabs(chunkArray([...tabs], newChunkedElementSize)); + setChunkedTabs(chunkArray(tabs, newChunkedElementSize)); setValue([ Math.floor(flattenIndex / newChunkedElementSize), flattenIndex % newChunkedElementSize, diff --git a/packages/core/src/components/Tabs/utils.ts b/packages/core/src/components/Tabs/utils.ts index 3e0ab6f2c3..8d6a3be5f3 100644 --- a/packages/core/src/components/Tabs/utils.ts +++ b/packages/core/src/components/Tabs/utils.ts @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TabProps } from './Tabs'; -export const chunkArray = ( - myArray: TabProps[], - chunkSize: number, -): TabProps[][] => { - const results = []; - while (myArray.length) { - results.push(myArray.splice(0, chunkSize)); +export function chunkArray(array: T[], chunkSize: number): T[][] { + if (chunkSize <= 0) { + return [array]; } - return results; -}; + + const result: T[][] = []; + for (let i = 0; i < array.length; i += chunkSize) { + result.push(array.slice(i, i + chunkSize)); + } + + return result; +} diff --git a/packages/core/src/layout/Sidebar/UserSettings.tsx b/packages/core/src/layout/Sidebar/UserSettings.tsx index fbfdea9085..5ea7d2b359 100644 --- a/packages/core/src/layout/Sidebar/UserSettings.tsx +++ b/packages/core/src/layout/Sidebar/UserSettings.tsx @@ -70,14 +70,14 @@ export function SidebarUserSettings() { )} {providers.includes('github') && ( )} {providers.includes('gitlab') && ( diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 6a1a3b60b5..ae62bc518a 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public" @@ -27,7 +27,7 @@ "start": "nodemon --" }, "dependencies": { - "@backstage/cli-common": "^0.1.1-alpha.18", + "@backstage/cli-common": "^0.1.1-alpha.19", "chalk": "^4.0.0", "commander": "^4.1.1", "fs-extra": "^9.0.0", diff --git a/packages/create-app/templates/default-app/app-config.yaml b/packages/create-app/templates/default-app/app-config.yaml index 49c3dc3691..0c3ec3ed80 100644 --- a/packages/create-app/templates/default-app/app-config.yaml +++ b/packages/create-app/templates/default-app/app-config.yaml @@ -54,3 +54,5 @@ catalog: target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml - type: github target: https://github.com/spotify/cookiecutter-golang/blob/master/template.yaml + - type: github + target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts index 8c5144f50c..a7b713fcb6 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/techdocs.ts @@ -14,15 +14,15 @@ export default async function createPlugin({ config, }: PluginEnvironment) { const generators = new Generators(); - const techdocsGenerator = new TechdocsGenerator(); + const techdocsGenerator = new TechdocsGenerator(logger); generators.register('techdocs', techdocsGenerator); - const directoryPreparer = new DirectoryPreparer(); + const directoryPreparer = new DirectoryPreparer(logger); const preparers = new Preparers(); preparers.register('dir', directoryPreparer); - const publisher = new LocalPublish(); + const publisher = new LocalPublish(logger); const dockerClient = new Docker(); diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 7eab350990..ba82447ea4 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 4b832afdfd..446f57f3dc 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,7 +1,7 @@ { "name": "docgen", "description": "Tool for generating API Documentation for itself", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "private": true, "homepage": "https://backstage.io", "repository": { diff --git a/packages/docgen/src/docgen/ApiDocGenerator.ts b/packages/docgen/src/docgen/ApiDocGenerator.ts index bfe449155b..3edcfedb9c 100644 --- a/packages/docgen/src/docgen/ApiDocGenerator.ts +++ b/packages/docgen/src/docgen/ApiDocGenerator.ts @@ -15,7 +15,11 @@ */ import ts from 'typescript'; -import { relative } from 'path'; +import { + relative as relativePath, + sep as pathSep, + posix as posixPath, +} from 'path'; import { ExportedInstance, ApiDoc, @@ -25,6 +29,12 @@ import { TypeLink, } from './types'; +// Always use unix path separators for the relative file +// paths, since we'll be using those for HTTP URLs. +function relativeLink(basePath: string, filePath: string) { + return relativePath(basePath, filePath).split(pathSep).join(posixPath.sep); +} + /** * The ApiDocGenerator uses the typescript compiler API to build the data structure that * describes a Backstage API and all of it's related types. @@ -58,7 +68,7 @@ export default class ApiDocGenerator { const id = this.getObjectPropertyLiteral(info, 'id'); const description = this.getObjectPropertyLiteral(info, 'description'); - const file = relative(this.basePath, source.fileName); + const file = relativeLink(this.basePath, source.fileName); const { line } = source.getLineAndCharacterOfPosition( apiInstance.node.getStart(), ); @@ -111,7 +121,7 @@ export default class ApiDocGenerator { const name = (type.aliasSymbol || type.symbol).name; const [declaration] = (type.aliasSymbol || type.symbol).declarations; const sourceFile = declaration.getSourceFile(); - const file = relative(this.basePath, sourceFile.fileName); + const file = relativeLink(this.basePath, sourceFile.fileName); const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); @@ -234,7 +244,7 @@ export default class ApiDocGenerator { const { line } = sourceFile.getLineAndCharacterOfPosition( declaration.getStart(), ); - const file = relative(this.basePath, sourceFile.fileName); + const file = relativeLink(this.basePath, sourceFile.fileName); const typeInfo = { id: (symbol as any).id, name: symbol.name, diff --git a/packages/e2e-test/.eslintrc.js b/packages/e2e-test/.eslintrc.js new file mode 100644 index 0000000000..ce2592e03b --- /dev/null +++ b/packages/e2e-test/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], + ignorePatterns: ['templates/**'], + rules: { + 'no-console': 0, + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + optionalDependencies: false, + peerDependencies: false, + bundledDependencies: false, + }, + ], + }, +}; diff --git a/packages/e2e-test/README.md b/packages/e2e-test/README.md new file mode 100644 index 0000000000..6cf50283c5 --- /dev/null +++ b/packages/e2e-test/README.md @@ -0,0 +1,24 @@ +# e2e-test + +End-to-end test for verifying Backstage packages. + +## Usage + +This package is only meant for usage within the Backstage monorepo. + +All packages need to be installed and built before running the test. In a fresh clone of this repo you first need to run the following from the repo root: + +```sh +yarn install +yarn tsc +yarn build +``` + +Once those tasks have completed, you can now run the test using `yarn start` inside this package. + +If you make changes to other packages you will need to rerun `yarn tsc && yarn build`. Changes to this package do not require a rebuild. + +## Documentation + +- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md) diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json new file mode 100644 index 0000000000..8e6c730431 --- /dev/null +++ b/packages/e2e-test/package.json @@ -0,0 +1,35 @@ +{ + "name": "e2e-test", + "description": "E2E test for verifying Backstage packages", + "version": "0.0.0", + "private": true, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/spotify/backstage", + "directory": "packages/e2e-test" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "main": "src/index.js", + "scripts": { + "start": "node .", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "test:e2e": "yarn start" + }, + "devDependencies": { + "@backstage/cli-common": "^0.1.1-alpha.19", + "@types/fs-extra": "^9.0.1", + "@types/node": "^13.7.2", + "fs-extra": "^9.0.0", + "handlebars": "^4.7.3", + "node-fetch": "^2.6.0", + "pgtools": "^0.3.0", + "tree-kill": "^1.2.2", + "ts-node": "^8.6.2", + "zombie": "^6.1.4" + } +} diff --git a/packages/cli/e2e-test/cli-e2e-test.js b/packages/e2e-test/src/e2e-test.ts similarity index 76% rename from packages/cli/e2e-test/cli-e2e-test.js rename to packages/e2e-test/src/e2e-test.ts index c4004f7c46..7377655497 100644 --- a/packages/cli/e2e-test/cli-e2e-test.js +++ b/packages/e2e-test/src/e2e-test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -const os = require('os'); -const fs = require('fs-extra'); -const fetch = require('node-fetch'); -const handlebars = require('handlebars'); -const killTree = require('tree-kill'); -const { resolve: resolvePath, join: joinPath } = require('path'); -const Browser = require('zombie'); -const { +import os from 'os'; +import fs from 'fs-extra'; +import fetch from 'node-fetch'; +import handlebars from 'handlebars'; +import killTree from 'tree-kill'; +import { resolve as resolvePath, join as joinPath } from 'path'; +import Browser from 'zombie'; +import { spawnPiped, runPlain, handleError, @@ -29,8 +29,17 @@ const { waitFor, waitForExit, print, -} = require('./helpers'); -const pgtools = require('pgtools'); +} from './helpers'; +import pgtools from 'pgtools'; +import { findPaths } from '@backstage/cli-common'; + +const paths = findPaths(__dirname); + +const templatePackagePaths = [ + 'packages/cli/templates/default-plugin/package.json.hbs', + 'packages/create-app/templates/default-app/packages/app/package.json.hbs', + 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', +]; async function main() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); @@ -59,38 +68,24 @@ async function main() { /** * Builds a dist workspace that contains the cli and core packages */ -async function buildDistWorkspace(workspaceName, rootDir) { +async function buildDistWorkspace(workspaceName: string, rootDir: string) { const workspaceDir = resolvePath(rootDir, workspaceName); await fs.ensureDir(workspaceDir); - // We grab the needed dependencies from the template packages - const appPkgTemplate = await fs.readFile( - resolvePath( - __dirname, - '../../create-app/templates/default-app/packages/app/package.json.hbs', - ), - 'utf8', - ); - const appPkg = JSON.parse( - handlebars.compile(appPkgTemplate)({ version: '0.0.0' }), - ); - const appDeps = Object.keys(appPkg.dependencies).filter(name => - name.startsWith('@backstage/'), - ); + // We grab the needed dependencies from the create app template + const createAppDeps = new Set(); + for (const pkgJsonPath of templatePackagePaths) { + const path = paths.resolveOwnRoot(pkgJsonPath); + const pkgTemplate = await fs.readFile(path, 'utf8'); + const { dependencies = {}, devDependencies = {} } = JSON.parse( + handlebars.compile(pkgTemplate)({ version: '0.0.0' }), + ); - const backendPkgTemplate = await fs.readFile( - resolvePath( - __dirname, - '../../create-app/templates/default-app/packages/backend/package.json.hbs', - ), - 'utf8', - ); - const backendPkg = JSON.parse( - handlebars.compile(backendPkgTemplate)({ version: '0.0.0' }), - ); - const backendDeps = Object.keys(backendPkg.dependencies).filter(name => - name.startsWith('@backstage/'), - ); + Array() + .concat(Object.keys(dependencies), Object.keys(devDependencies)) + .filter(name => name.startsWith('@backstage/')) + .forEach(dep => createAppDeps.add(dep)); + } print(`Preparing workspace`); await runPlain([ @@ -98,13 +93,8 @@ async function buildDistWorkspace(workspaceName, rootDir) { 'backstage-cli', 'build-workspace', workspaceDir, - '@backstage/cli', '@backstage/create-app', - '@backstage/core', - '@backstage/dev-utils', - '@backstage/test-utils', - ...appDeps, - ...backendDeps, + ...createAppDeps, ]); print('Pinning yarn version in workspace'); @@ -121,14 +111,19 @@ async function buildDistWorkspace(workspaceName, rootDir) { /** * Pin the yarn version in a directory to the one we're using in the Backstage repo */ -async function pinYarnVersion(dir) { - const repoRoot = resolvePath(__dirname, '../../..'); - - const yarnRc = await fs.readFile(resolvePath(repoRoot, '.yarnrc'), 'utf8'); +async function pinYarnVersion(dir: string) { + const yarnRc = await fs.readFile(paths.resolveOwnRoot('.yarnrc'), 'utf8'); const yarnRcLines = yarnRc.split('\n'); const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarn-path')); - const [, localYarnPath] = yarnPathLine.match(/"(.*)"/); - const yarnPath = resolvePath(repoRoot, localYarnPath); + if (!yarnPathLine) { + throw new Error(`Unable to find 'yarn-path' in ${yarnRc}`); + } + const match = yarnPathLine.match(/"(.*)"/); + if (!match) { + throw new Error(`Invalid 'yarn-path' in ${yarnRc}`); + } + const [, localYarnPath] = match; + const yarnPath = paths.resolveOwnRoot(localYarnPath); await fs.writeFile(resolvePath(dir, '.yarnrc'), `yarn-path "${yarnPath}"\n`); } @@ -136,7 +131,12 @@ async function pinYarnVersion(dir) { /** * Creates a new app inside rootDir called test-app, using packages from the workspaceDir */ -async function createApp(appName, isPostgres, workspaceDir, rootDir) { +async function createApp( + appName: string, + isPostgres: boolean, + workspaceDir: string, + rootDir: string, +) { const child = spawnPiped( [ 'node', @@ -150,20 +150,20 @@ async function createApp(appName, isPostgres, workspaceDir, rootDir) { try { let stdout = ''; - child.stdout.on('data', data => { + child.stdout?.on('data', data => { stdout = stdout + data.toString('utf8'); }); await waitFor(() => stdout.includes('Enter a name for the app')); - child.stdin.write(`${appName}\n`); + child.stdin?.write(`${appName}\n`); await waitFor(() => stdout.includes('Select database for the backend')); if (!isPostgres) { // Simulate down arrow press - child.stdin.write(`\u001B\u005B\u0042`); + child.stdin?.write(`\u001B\u005B\u0042`); } - child.stdin.write(`\n`); + child.stdin?.write(`\n`); print('Waiting for app create script to be done'); await waitForExit(child); @@ -205,7 +205,7 @@ async function createApp(appName, isPostgres, workspaceDir, rootDir) { /** * This points dependency resolutions into the workspace for each package that is present there */ -async function overrideModuleResolutions(appDir, workspaceDir) { +async function overrideModuleResolutions(appDir: string, workspaceDir: string) { const pkgJsonPath = resolvePath(appDir, 'package.json'); const pkgJson = await fs.readJson(pkgJsonPath); @@ -231,19 +231,19 @@ async function overrideModuleResolutions(appDir, workspaceDir) { /** * Uses create-plugin command to create a new plugin in the app */ -async function createPlugin(pluginName, appDir) { +async function createPlugin(pluginName: string, appDir: string) { const child = spawnPiped(['yarn', 'create-plugin'], { cwd: appDir, }); try { let stdout = ''; - child.stdout.on('data', data => { + child.stdout?.on('data', (data: Buffer) => { stdout = stdout + data.toString('utf8'); }); await waitFor(() => stdout.includes('Enter an ID for the plugin')); - child.stdin.write(`${pluginName}\n`); + child.stdin?.write(`${pluginName}\n`); // await waitFor(() => stdout.includes('Enter the owner(s) of the plugin')); // child.stdin.write('@someuser\n'); @@ -266,7 +266,7 @@ async function createPlugin(pluginName, appDir) { /** * Start serving the newly created app and make sure that the create plugin is rendering correctly */ -async function testAppServe(pluginName, appDir) { +async function testAppServe(pluginName: string, appDir: string) { const startApp = spawnPiped(['yarn', 'start'], { cwd: appDir, }); @@ -302,7 +302,7 @@ async function testAppServe(pluginName, appDir) { } /** Creates PG databases (drops if exists before) */ -async function createDB(database) { +async function createDB(database: string) { const config = { host: process.env.POSTGRES_HOST, port: process.env.POSTGRES_PORT, @@ -321,7 +321,7 @@ async function createDB(database) { /** * Start serving the newly created backend and make sure that all db migrations works correctly */ -async function testBackendStart(appDir, isPostgres) { +async function testBackendStart(appDir: string, isPostgres: boolean) { if (isPostgres) { print('Creating DBs'); await Promise.all( @@ -343,10 +343,10 @@ async function testBackendStart(appDir, isPostgres) { let stdout = ''; let stderr = ''; - child.stdout.on('data', data => { + child.stdout?.on('data', (data: Buffer) => { stdout = stdout + data.toString('utf8'); }); - child.stderr.on('data', data => { + child.stderr?.on('data', (data: Buffer) => { stderr = stderr + data.toString('utf8'); }); let successful = false; @@ -384,4 +384,4 @@ async function testBackendStart(appDir, isPostgres) { } process.on('unhandledRejection', handleError); -main(process.argv.slice(2)).catch(handleError); +main().catch(handleError); diff --git a/packages/e2e-test/src/helpers.test.ts b/packages/e2e-test/src/helpers.test.ts new file mode 100644 index 0000000000..196241f9e0 --- /dev/null +++ b/packages/e2e-test/src/helpers.test.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 { waitFor } from './helpers'; + +describe('waitFor', () => { + it('should wait for true', async () => { + const fn = jest.fn().mockReturnValue(true); + await waitFor(fn); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('should time out', async () => { + const fn = jest.fn().mockReturnValue(false); + await expect(waitFor(fn, 1)).rejects.toThrow( + 'Timed out while waiting for condition', + ); + expect(fn).toHaveBeenCalled(); + }); +}); diff --git a/packages/cli/e2e-test/helpers.js b/packages/e2e-test/src/helpers.ts similarity index 80% rename from packages/cli/e2e-test/helpers.js rename to packages/e2e-test/src/helpers.ts index 580f53cbe8..b59de75860 100644 --- a/packages/cli/e2e-test/helpers.js +++ b/packages/e2e-test/src/helpers.ts @@ -14,16 +14,21 @@ * limitations under the License. */ -const { spawn, execFile: execFileCb } = require('child_process'); -const { promisify } = require('util'); +import { + spawn, + execFile as execFileCb, + SpawnOptions, + ChildProcess, +} from 'child_process'; +import { promisify } from 'util'; const execFile = promisify(execFileCb); const EXPECTED_LOAD_ERRORS = /ECONNREFUSED|ECONNRESET|did not get to load all resources/; -function spawnPiped(cmd, options) { - function pipeWithPrefix(stream, prefix = '') { - return data => { +export function spawnPiped(cmd: string[], options?: SpawnOptions) { + function pipeWithPrefix(stream: NodeJS.WriteStream, prefix = '') { + return (data: Buffer) => { const prefixedMsg = data .toString('utf8') .trimRight() @@ -40,11 +45,11 @@ function spawnPiped(cmd, options) { child.on('error', handleError); const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' '); - child.stdout.on( + child.stdout?.on( 'data', pipeWithPrefix(process.stdout, `[${logPrefix}].out: `), ); - child.stderr.on( + child.stderr?.on( 'data', pipeWithPrefix(process.stderr, `[${logPrefix}].err: `), ); @@ -52,7 +57,7 @@ function spawnPiped(cmd, options) { return child; } -async function runPlain(cmd, options) { +export async function runPlain(cmd: string[], options?: SpawnOptions) { try { const { stdout } = await execFile(cmd[0], cmd.slice(1), { ...options, @@ -70,8 +75,9 @@ async function runPlain(cmd, options) { } } -function handleError(err) { +export function handleError(err: Error & { code?: unknown }) { process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); + if (typeof err.code === 'number') { process.exit(err.code); } else { @@ -85,9 +91,14 @@ function handleError(err) { * .cancel() is available * @returns {Promise} Promise of resolution */ -function waitFor(fn) { - return new Promise(resolve => { +export function waitFor(fn: () => boolean, maxSeconds: number = 120) { + let count = 0; + return new Promise((resolve, reject) => { const handle = setInterval(() => { + if (count++ > maxSeconds * 10) { + reject(new Error('Timed out while waiting for condition')); + return; + } if (fn()) { clearInterval(handle); resolve(); @@ -97,7 +108,7 @@ function waitFor(fn) { }); } -async function waitForExit(child) { +export async function waitForExit(child: ChildProcess) { if (child.exitCode !== null) { throw new Error(`Child already exited with code ${child.exitCode}`); } @@ -113,10 +124,10 @@ async function waitForExit(child) { ); } -async function waitForPageWithText( - browser, - path, - text, +export async function waitForPageWithText( + browser: any, + path: string, + text: string, { intervalMs = 1000, maxLoadAttempts = 240, maxFindTextAttempts = 3 } = {}, ) { let loadAttempts = 0; @@ -163,16 +174,6 @@ async function waitForPageWithText( } } -function print(msg) { +export function print(msg: string) { return process.stdout.write(`${msg}\n`); } - -module.exports = { - spawnPiped, - runPlain, - handleError, - waitFor, - waitForExit, - waitForPageWithText, - print, -}; diff --git a/packages/cli/e2e-test/.eslintrc.js b/packages/e2e-test/src/index.js similarity index 70% rename from packages/cli/e2e-test/.eslintrc.js rename to packages/e2e-test/src/index.js index 274c7426b8..90f8030a0e 100644 --- a/packages/cli/e2e-test/.eslintrc.js +++ b/packages/e2e-test/src/index.js @@ -14,16 +14,12 @@ * limitations under the License. */ -module.exports = { - rules: { - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: true, - peerDependencies: true, - bundledDependencies: true, - }, - ], +require('ts-node').register({ + transpileOnly: true, + project: require('path').resolve(__dirname, '../../../tsconfig.json'), + compilerOptions: { + module: 'CommonJS', }, -}; +}); + +require('./e2e-test'); diff --git a/packages/e2e-test/src/types.d.ts b/packages/e2e-test/src/types.d.ts new file mode 100644 index 0000000000..0ae86490ee --- /dev/null +++ b/packages/e2e-test/src/types.d.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. + */ + +declare module 'zombie'; +declare module 'pgtools'; diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 3a928b044e..8660c0e8d8 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "description": "Storybook build for core package", "private": true, "scripts": { @@ -14,7 +14,7 @@ ] }, "dependencies": { - "@backstage/theme": "^0.1.1-alpha.18" + "@backstage/theme": "^0.1.1-alpha.19" }, "devDependencies": { "@storybook/addon-actions": "^5.3.17", diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 2e5d1e3958..a5951fd770 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public" @@ -44,7 +44,7 @@ "ext": "ts" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "commander": "^5.1.0", "fs-extra": "^9.0.1", "http-proxy": "^1.18.1", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 638513af5c..821a94b1aa 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 35bbb7d0b0..e336835592 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/core-api": "^0.1.1-alpha.18", - "@backstage/test-utils-core": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/core-api": "^0.1.1-alpha.19", + "@backstage/test-utils-core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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 f4eafa58ff..f98a2cb762 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.9.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18" + "@backstage/cli": "^0.1.1-alpha.19" }, "files": [ "dist" diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index c8a6b5732d..342d0aa8ca 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/plugin-catalog": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/plugin-catalog": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@kyma-project/asyncapi-react": "^0.11.0", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.9.1", @@ -37,9 +37,9 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx b/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx index e30583caed..99848637b6 100644 --- a/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx +++ b/plugins/api-docs/src/components/ApiCatalogTable/ApiCatalogTable.tsx @@ -37,6 +37,7 @@ const columns: TableColumn[] = [ .filter(Boolean) .join(':'), kind: entity.kind, + selectedTabId: 'overview', })} > {entity.metadata.name} diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index e9412a0c4d..966a1fbb98 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "body-parser": "^1.19.0", "compression": "^1.7.4", @@ -50,7 +50,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@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/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index e6ba56d2c3..b63acdfb20 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -74,7 +74,7 @@ export class GithubAuthProvider implements OAuthProviderHandlers { idToken: params.id_token, }; - // Github provides an id numeric value (123) + // GitHub provides an id numeric value (123) // as a fallback const id = passportProfile!.id; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 7dedbe62cd..5692ff992e 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,9 +22,9 @@ "mock-data:local": "./scripts/mock-data-local.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -41,7 +41,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/lodash": "^4.14.151", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts index cd46d97d13..c43c68591e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.test.ts @@ -50,7 +50,7 @@ describe('GitlabApiReaderProcessor', () => { 'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/', url: null, err: - 'Incorrect url: https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/, Error: Gitlab url does not end in .ya?ml', + 'Incorrect url: https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/, Error: GitLab url does not end in .ya?ml', }, ]; diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts index 6964640eb4..3e585bf2b3 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabApiReaderProcessor.ts @@ -77,7 +77,7 @@ export class GitlabApiReaderProcessor implements LocationProcessor { const branchAndfilePath = url.pathname.split('/-/blob/')[1]; if (!branchAndfilePath.match(/\.ya?ml$/)) { - throw new Error('Gitlab url does not end in .ya?ml'); + throw new Error('GitLab url does not end in .ya?ml'); } const [branch, ...filePath] = branchAndfilePath.split('/'); @@ -127,7 +127,7 @@ export class GitlabApiReaderProcessor implements LocationProcessor { return projectID; } catch (e) { - throw new Error(`Could not get Gitlab ProjectID for: ${target}, ${e}`); + throw new Error(`Could not get GitLab ProjectID for: ${target}, ${e}`); } } } diff --git a/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts index 211f325afe..9f308ee184 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitlabReaderProcessor.ts @@ -77,7 +77,7 @@ export class GitlabReaderProcessor implements LocationProcessor { blobKeyword !== 'blob' || !restOfPath.join('/').match(/\.yaml$/) ) { - throw new Error('Wrong Gitlab URL'); + throw new Error('Wrong GitLab URL'); } // Replace 'blob' with 'raw' diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 44b3a7b7ee..1e10e91478 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,15 +21,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/plugin-api-docs": "^0.1.1-alpha.18", - "@backstage/plugin-github-actions": "^0.1.1-alpha.18", - "@backstage/plugin-jenkins": "^0.1.1-alpha.18", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.18", - "@backstage/plugin-sentry": "^0.1.1-alpha.18", - "@backstage/plugin-techdocs": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/plugin-api-docs": "^0.1.1-alpha.19", + "@backstage/plugin-github-actions": "^0.1.1-alpha.19", + "@backstage/plugin-jenkins": "^0.1.1-alpha.19", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.19", + "@backstage/plugin-sentry": "^0.1.1-alpha.19", + "@backstage/plugin-techdocs": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -42,9 +42,9 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 4fb7a28124..b70230364f 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@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 e7783675ae..9b729d7a82 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@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 8fcc4591d2..db35022fae 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/core-api": "^0.1.1-alpha.18", - "@backstage/plugin-catalog": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/core-api": "^0.1.1-alpha.19", + "@backstage/plugin-catalog": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -39,8 +39,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@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/scripts/sample.yaml b/plugins/github-actions/scripts/sample.yaml index d582bf9c35..7d9de50463 100644 --- a/plugins/github-actions/scripts/sample.yaml +++ b/plugins/github-actions/scripts/sample.yaml @@ -5,6 +5,7 @@ metadata: description: backstage.io annotations: github.com/project-slug: 'spotify/backstage' + backstage.io/techdocs-ref: github:https://github.com/spotify/backstage.git spec: type: website lifecycle: production diff --git a/plugins/github-actions/src/api/GithubActionsApi.ts b/plugins/github-actions/src/api/GithubActionsApi.ts index acdfb90cf3..9a95f21860 100644 --- a/plugins/github-actions/src/api/GithubActionsApi.ts +++ b/plugins/github-actions/src/api/GithubActionsApi.ts @@ -23,7 +23,7 @@ import { export const githubActionsApiRef = createApiRef({ id: 'plugin.githubactions.service', - description: 'Used by the Github Actions plugin to make requests', + description: 'Used by the GitHub Actions plugin to make requests', }); export type GithubActionsApi = { diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 5815386014..a2a7900d52 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.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@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 27437d0318..94a755bb17 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.18", + "version": "0.1.1-alpha.19", "private": false, "publishConfig": { "access": "public", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@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/src/lib/api/GraphQLEndpoints.ts b/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts index ee0b4bfd4e..23a9e9e697 100644 --- a/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts +++ b/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts @@ -33,7 +33,7 @@ export type GithubEndpointConfig = { id: string; title: string; /** - * Github GraphQL API url, defaults to https://api.github.com/graphql + * GitHub GraphQL API url, defaults to https://api.github.com/graphql */ url?: string; /** diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 02dc9d3608..f41a4540ec 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-graphql-backend", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "apollo-server": "^2.16.0", "apollo-server-express": "^2.16.0", @@ -32,7 +32,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.20.5", diff --git a/plugins/identity-backend/package.json b/plugins/identity-backend/package.json index c5c3bffc9b..8b16d85827 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.18", + "version": "0.1.1-alpha.19", "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.18", + "@backstage/backend-common": "^0.1.1-alpha.19", "@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.18", + "@backstage/cli": "^0.1.1-alpha.19", "jest-fetch-mock": "^3.0.3" }, "files": [ diff --git a/plugins/jenkins/README.md b/plugins/jenkins/README.md index b8a844f5d8..a911704dc4 100644 --- a/plugins/jenkins/README.md +++ b/plugins/jenkins/README.md @@ -4,7 +4,7 @@ Website: [https://jenkins.io/](https://jenkins.io/) Last master build Folder results -Build detials +Build details ## Setup diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index dce577801e..2e8b89bea3 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index cbf4a77f7e..bcd34d32de 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 5b1b5d54ce..b6e066bbc0 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -31,8 +31,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@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 023fc81cf2..4a37ab8a45 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,8 +19,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -34,7 +34,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@types/node-fetch": "^2.5.7", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 02fd3fe0ed..c13121de66 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.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/plugin-catalog": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/plugin-catalog": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", "@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 4b4a45b9dd..a52abf91b9 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.18", + "version": "0.1.1-alpha.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", "@types/express": "^4.17.6", "axios": "^0.19.2", "camelcase-keys": "^6.2.2", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@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 1ca02cb528..5eeeacc505 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@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 fc956c9832..7112d0f49d 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.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/catalog-model": "^0.1.1-alpha.18", - "@backstage/config": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.19", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/config": "^0.1.1-alpha.19", "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", @@ -43,7 +43,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", "@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 index 958882e9fd..a377d60ccc 100644 --- a/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/create-react-app/template.yaml @@ -5,9 +5,9 @@ metadata: title: Create React App Template description: Create a new CRA website project tags: - - Experimental - - React - - CRA + - experimental + - react + - cra spec: owner: web@example.com templater: cra diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml new file mode 100644 index 0000000000..da2152280d --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml @@ -0,0 +1,29 @@ +apiVersion: backstage.io/v1alpha1 +kind: Template +metadata: + name: docs-template + title: Documentation Template + description: Create a new standalone documentation project + tags: + - experimental + - techdocs + - mkdocs +spec: + owner: spotify/techdocs-core + templater: cookiecutter + type: documentation + path: '.' + + schema: + required: + - component_id + properties: + component_id: + title: Name + type: string + description: Unique name of the component + description: + title: Description + type: string + description: Description of the component + diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/component-info.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/component-info.yaml new file mode 100644 index 0000000000..854b692fcf --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/component-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{cookiecutter.component_id}} + description: {{cookiecutter.description}} + annotations: + github.com/project-slug: {{cookiecutter.storePath}} + backstage.io/techdocs-ref: github:https://github.com/{{cookiecutter.storePath}} +spec: + type: documentation + lifecycle: experimental + owner: {{cookiecutter.owner}} diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/docs/index.md b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/docs/index.md new file mode 100644 index 0000000000..5352ef7801 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/docs/index.md @@ -0,0 +1,28 @@ +## {{ cookiecutter.component_id }} + +{{ cookiecutter.description }} + +## Getting started + +Start write your documentation by adding more markdown (.md) files to this folder (/docs) or replace the content in this file. + +## Table of Contents + +The Table of Contents on the right is generated automatically based on the hierarchy +of headings. Only use one H1 (`#` in Markdown) per file. + +## Site navigation + +For new pages to appear in the left hand navigation you need edit the `mkdocs.yml` +file in root of your repo. The navigation can also link out to other sites. + +Alternatively, if there is no `nav` section in `mkdocs.yml`, a navigation section +will be created for you. However, you will not be able to use alternate titles for +pages, or include links to other sites. + +Note that MkDocs uses `mkdocs.yml`, not `mkdocs.yaml`, although both appear to work. +See also . + +## Support + +That's it. If you need support, reach out in [#docs-like-code](https://discord.com/channels/687207715902193673/714754240933003266) on Discord. diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/mkdocs.yml b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/mkdocs.yml new file mode 100644 index 0000000000..2126971502 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/docs-template/{{cookiecutter.component_id}}/mkdocs.yml @@ -0,0 +1,8 @@ +site_name: {{cookiecutter.component_id}} +site_description: {{cookiecutter.description}} + +nav: + - Introduction: index.md + +plugins: + - techdocs-core diff --git a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml index 5594a3a919..ee051de6e0 100644 --- a/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/react-ssr-template/template.yaml @@ -5,8 +5,8 @@ metadata: title: React SSR Template description: Create a website powered with Next.js tags: - - Recommended - - React + - recommended + - react spec: owner: web@example.com templater: cookiecutter diff --git a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml index 520d0a62ac..c6189fe7c8 100644 --- a/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/springboot-grpc-template/template.yaml @@ -5,8 +5,8 @@ metadata: title: Spring Boot GRPC Service description: Create a simple microservice using gRPC and Spring Boot Java tags: - - Recommended - - Java + - recommended + - java spec: owner: service@example.com templater: cookiecutter diff --git a/plugins/scaffolder-backend/scripts/mock-data.sh b/plugins/scaffolder-backend/scripts/mock-data.sh index f18d4b4838..9a9b0b186b 100755 --- a/plugins/scaffolder-backend/scripts/mock-data.sh +++ b/plugins/scaffolder-backend/scripts/mock-data.sh @@ -4,6 +4,7 @@ for URL in \ 'react-ssr-template' \ 'springboot-grpc-template' \ 'create-react-app' \ + 'docs-template' \ ; do \ curl \ --location \ diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts index 164ec316df..93493ab2cc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/publish/github.test.ts @@ -45,7 +45,7 @@ const { mockRemote: jest.Mocked; }; -describe('Github Publisher', () => { +describe('GitHub Publisher', () => { const publisher = new GithubPublisher({ client: new Octokit() }); beforeEach(() => { diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index c0376a3594..8dec795c3d 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.19", "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.18", - "@backstage/core": "^0.1.1-alpha.18", - "@backstage/plugin-catalog": "^0.1.1-alpha.18", - "@backstage/theme": "^0.1.1-alpha.18", + "@backstage/catalog-model": "^0.1.1-alpha.19", + "@backstage/core": "^0.1.1-alpha.19", + "@backstage/plugin-catalog": "^0.1.1-alpha.19", + "@backstage/theme": "^0.1.1-alpha.19", "@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.3.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", - "@backstage/dev-utils": "^0.1.1-alpha.18", - "@backstage/test-utils": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.19", + "@backstage/dev-utils": "^0.1.1-alpha.19", + "@backstage/test-utils": "^0.1.1-alpha.19", "@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/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index c6ed55c92c..f5a592b7b7 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -26,7 +26,7 @@ import { useJobPolling } from './useJobPolling'; import { Job } from '../../types'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Button } from '@backstage/core'; -import { entityRoute } from '@backstage/plugin-catalog'; +import { entityRouteDefault } from '@backstage/plugin-catalog'; import { generatePath } from 'react-router-dom'; type Props = { @@ -72,7 +72,7 @@ export const JobStatusModal = ({ {entity && (