From 9414db5ee7206aa5072c430e4497ae0fc53da22f Mon Sep 17 00:00:00 2001 From: Paul Pacheco Date: Fri, 21 Aug 2020 15:38:12 -0500 Subject: [PATCH 01/24] show how to use config when setting up api (#2075) Most API will require some configuration. So it would be more useful to show an example where the config is available when the API's are being wired up --- docs/api/utility-apis.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 }); ``` From d224f9bba7c96a652e767043af3beea79fc8181d Mon Sep 17 00:00:00 2001 From: Mustansar Anwar ul Samad Date: Sat, 22 Aug 2020 10:04:17 +1200 Subject: [PATCH 02/24] Fix webpack sourcemap path for debugging --- packages/cli/src/lib/bundler/config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 8049ce436d..be81b4bb59 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -198,6 +198,11 @@ export function createBackendConfig( chunkFilename: isDev ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js', + ...(isDev + ? { + devtoolModuleFilenameTemplate: 'file:///[absolute-resource-path]', + } + : {}), }, plugins: [ new StartServerPlugin('main.js'), From 290d28d856095ae8b39f0111b40d4ef70cc6c3fc Mon Sep 17 00:00:00 2001 From: Raghunandan Date: Sat, 22 Aug 2020 10:05:21 +0200 Subject: [PATCH 03/24] Update microsite i18n json --- microsite/i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/i18n/en.json b/microsite/i18n/en.json index d2593a13ce..698d86a26a 100644 --- a/microsite/i18n/en.json +++ b/microsite/i18n/en.json @@ -199,7 +199,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" From 950b316820c04518d30cf21ba10058052891df82 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 22 Aug 2020 10:08:06 +0200 Subject: [PATCH 04/24] cli: switch ts-jest to use isolatedModules --- packages/cli/config/jest.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index d95dd21489..cc7d45c139 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -37,7 +37,10 @@ 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'), + '\\.(js|jsx|ts|tsx)$': [ + require.resolve('ts-jest'), + { isolatedModules: true }, + ], '\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg)$': require.resolve( './jestFileTransform.js', ), From d7a10bcf127788d797e2ee2a469d2a76100434f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Sun, 23 Aug 2020 09:39:53 +0200 Subject: [PATCH 05/24] Update README.md (#2083) --- README.md | 10 ---------- 1 file changed, 10 deletions(-) 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. From 5f1c74452acbb294e32e21f8e2889343253155e0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 16:18:48 +0200 Subject: [PATCH 06/24] workflows: rename Frontend CI to just CI --- .github/workflows/{frontend.yml => ci.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{frontend.yml => ci.yml} (98%) diff --git a/.github/workflows/frontend.yml b/.github/workflows/ci.yml similarity index 98% rename from .github/workflows/frontend.yml rename to .github/workflows/ci.yml index dc65b987a8..105642e284 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: From 1358739063eae124dce5ba11064b891a1ade68ed Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 11:25:10 +0200 Subject: [PATCH 07/24] cli: move out e2e-test to separate package --- packages/cli/package.json | 7 +--- packages/e2e-test/.eslintrc.js | 7 ++++ packages/e2e-test/README.md | 24 +++++++++++++ packages/e2e-test/package.json | 35 +++++++++++++++++++ .../e2e-test => e2e-test/src}/.eslintrc.js | 0 .../{cli/e2e-test => e2e-test/src}/helpers.js | 0 .../cli-e2e-test.js => e2e-test/src/index.js} | 0 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 packages/e2e-test/.eslintrc.js create mode 100644 packages/e2e-test/README.md create mode 100644 packages/e2e-test/package.json rename packages/{cli/e2e-test => e2e-test/src}/.eslintrc.js (100%) rename packages/{cli/e2e-test => e2e-test/src}/helpers.js (100%) rename packages/{cli/e2e-test/cli-e2e-test.js => e2e-test/src/index.js} (100%) diff --git a/packages/cli/package.json b/packages/cli/package.json index e5a8eafc81..0291e720d2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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 --" }, @@ -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", @@ -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/e2e-test/.eslintrc.js b/packages/e2e-test/.eslintrc.js new file mode 100644 index 0000000000..69bec6cd2a --- /dev/null +++ b/packages/e2e-test/.eslintrc.js @@ -0,0 +1,7 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], + ignorePatterns: ['templates/**'], + rules: { + 'no-console': 0, + }, +}; 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..d2f9dc9784 --- /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": "ts-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/.eslintrc.js b/packages/e2e-test/src/.eslintrc.js similarity index 100% rename from packages/cli/e2e-test/.eslintrc.js rename to packages/e2e-test/src/.eslintrc.js diff --git a/packages/cli/e2e-test/helpers.js b/packages/e2e-test/src/helpers.js similarity index 100% rename from packages/cli/e2e-test/helpers.js rename to packages/e2e-test/src/helpers.js diff --git a/packages/cli/e2e-test/cli-e2e-test.js b/packages/e2e-test/src/index.js similarity index 100% rename from packages/cli/e2e-test/cli-e2e-test.js rename to packages/e2e-test/src/index.js From 9052dfc5bf7d4fba51ca17fd271a2b3885c6e14a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 11:28:59 +0200 Subject: [PATCH 08/24] e2e-test: merge eslint rules --- packages/e2e-test/.eslintrc.js | 9 +++++++++ packages/e2e-test/src/.eslintrc.js | 29 ----------------------------- 2 files changed, 9 insertions(+), 29 deletions(-) delete mode 100644 packages/e2e-test/src/.eslintrc.js diff --git a/packages/e2e-test/.eslintrc.js b/packages/e2e-test/.eslintrc.js index 69bec6cd2a..ce2592e03b 100644 --- a/packages/e2e-test/.eslintrc.js +++ b/packages/e2e-test/.eslintrc.js @@ -3,5 +3,14 @@ module.exports = { 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/src/.eslintrc.js b/packages/e2e-test/src/.eslintrc.js deleted file mode 100644 index 274c7426b8..0000000000 --- a/packages/e2e-test/src/.eslintrc.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - rules: { - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - optionalDependencies: true, - peerDependencies: true, - bundledDependencies: true, - }, - ], - }, -}; From 0d240696ebad85b2e2e78b346d08d9e283dab565 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 12:00:37 +0200 Subject: [PATCH 09/24] e2e-test: port to typscript --- packages/e2e-test/package.json | 4 +- .../e2e-test/src/{helpers.js => helpers.ts} | 48 ++++++------- packages/e2e-test/src/{index.js => index.ts} | 68 +++++++++++-------- packages/e2e-test/src/types.d.ts | 18 +++++ 4 files changed, 82 insertions(+), 56 deletions(-) rename packages/e2e-test/src/{helpers.js => helpers.ts} (84%) rename packages/e2e-test/src/{index.js => index.ts} (86%) create mode 100644 packages/e2e-test/src/types.d.ts diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index d2f9dc9784..389f4b6f93 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -13,9 +13,9 @@ "backstage" ], "license": "Apache-2.0", - "main": "src/index.js", + "main": "src/index.ts", "scripts": { - "start": "ts-node .", + "start": "yarn ts-node --transpile-only --compiler-options '{\"module\":\"CommonJS\"}' .", "lint": "backstage-cli lint", "test": "backstage-cli test", "test:e2e": "yarn start" diff --git a/packages/e2e-test/src/helpers.js b/packages/e2e-test/src/helpers.ts similarity index 84% rename from packages/e2e-test/src/helpers.js rename to packages/e2e-test/src/helpers.ts index 580f53cbe8..783b7e3988 100644 --- a/packages/e2e-test/src/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,7 +91,7 @@ function handleError(err) { * .cancel() is available * @returns {Promise} Promise of resolution */ -function waitFor(fn) { +export function waitFor(fn: () => boolean) { return new Promise(resolve => { const handle = setInterval(() => { if (fn()) { @@ -97,7 +103,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 +119,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 +169,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/e2e-test/src/index.js b/packages/e2e-test/src/index.ts similarity index 86% rename from packages/e2e-test/src/index.js rename to packages/e2e-test/src/index.ts index c4004f7c46..a455964f44 100644 --- a/packages/e2e-test/src/index.js +++ b/packages/e2e-test/src/index.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,8 @@ const { waitFor, waitForExit, print, -} = require('./helpers'); -const pgtools = require('pgtools'); +} from './helpers'; +import pgtools from 'pgtools'; async function main() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); @@ -59,7 +59,7 @@ 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); @@ -121,13 +121,20 @@ 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) { +async function pinYarnVersion(dir: string) { const repoRoot = resolvePath(__dirname, '../../..'); const yarnRc = await fs.readFile(resolvePath(repoRoot, '.yarnrc'), 'utf8'); const yarnRcLines = yarnRc.split('\n'); const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarn-path')); - const [, localYarnPath] = yarnPathLine.match(/"(.*)"/); + 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 = resolvePath(repoRoot, localYarnPath); await fs.writeFile(resolvePath(dir, '.yarnrc'), `yarn-path "${yarnPath}"\n`); @@ -136,7 +143,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 +162,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 +217,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 +243,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 +278,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 +314,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 +333,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 +355,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 +396,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/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'; From 831ce12b7d3b31325688fc001f4737aa3da078b9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 12:21:17 +0200 Subject: [PATCH 10/24] e2e-test: refactor dep detection and use cli-common to resolve paths --- packages/e2e-test/src/index.ts | 60 +++++++++++++--------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/index.ts index a455964f44..46992ab966 100644 --- a/packages/e2e-test/src/index.ts +++ b/packages/e2e-test/src/index.ts @@ -31,6 +31,9 @@ import { print, } from './helpers'; import pgtools from 'pgtools'; +import { findPaths } from '@backstage/cli-common'; + +const paths = findPaths(__dirname); async function main() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); @@ -63,34 +66,22 @@ 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/'), - ); - - 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/'), - ); + // We grab the needed dependencies from the create app template + const createAppDeps: string[] = []; + for (const pkgPath of ['packages/app', 'packages/backend']) { + const path = paths.resolveOwnRoot( + 'packages/create-app/templates/default-app', + pkgPath, + 'package.json.hbs', + ); + const appPkgTemplate = await fs.readFile(path, 'utf8'); + const { dependencies: allDeps } = JSON.parse( + handlebars.compile(appPkgTemplate)({ version: '0.0.0' }), + ); + createAppDeps.push( + ...Object.keys(allDeps).filter(name => name.startsWith('@backstage/')), + ); + } print(`Preparing workspace`); await runPlain([ @@ -98,13 +89,8 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { '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'); @@ -122,9 +108,7 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { * Pin the yarn version in a directory to the one we're using in the Backstage repo */ async function pinYarnVersion(dir: string) { - const repoRoot = resolvePath(__dirname, '../../..'); - - const yarnRc = await fs.readFile(resolvePath(repoRoot, '.yarnrc'), 'utf8'); + const yarnRc = await fs.readFile(paths.resolveOwnRoot('.yarnrc'), 'utf8'); const yarnRcLines = yarnRc.split('\n'); const yarnPathLine = yarnRcLines.find(line => line.startsWith('yarn-path')); if (!yarnPathLine) { @@ -135,7 +119,7 @@ async function pinYarnVersion(dir: string) { throw new Error(`Invalid 'yarn-path' in ${yarnRc}`); } const [, localYarnPath] = match; - const yarnPath = resolvePath(repoRoot, localYarnPath); + const yarnPath = paths.resolveOwnRoot(localYarnPath); await fs.writeFile(resolvePath(dir, '.yarnrc'), `yarn-path "${yarnPath}"\n`); } From 0d09f12c4d51a0cbab012c4c86f0699c71993a2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 13:28:16 +0200 Subject: [PATCH 11/24] workflows: update e2e test workflows --- .github/workflows/{cli-win.yml => e2e-win.yml} | 14 ++++++++------ .github/workflows/{cli.yml => e2e.yml} | 14 +++++++------- 2 files changed, 15 insertions(+), 13 deletions(-) rename .github/workflows/{cli-win.yml => e2e-win.yml} (77%) rename .github/workflows/{cli.yml => e2e.yml} (87%) diff --git a/.github/workflows/cli-win.yml b/.github/workflows/e2e-win.yml similarity index 77% rename from .github/workflows/cli-win.yml rename to .github/workflows/e2e-win.yml index 4e7a186282..5f75f3d9c8 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: @@ -42,6 +43,7 @@ jobs: - name: yarn install run: yarn install --frozen-lockfile - 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 87% rename from .github/workflows/cli.yml rename to .github/workflows/e2e.yml index 158310b5ea..45b9d7e2d8 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 }} @@ -51,13 +50,14 @@ jobs: - name: yarn install run: yarn install --frozen-lockfile - 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 From 5fc0ce3e375b62704dd92d2bd66abdd447eac75b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 13:43:18 +0200 Subject: [PATCH 12/24] e2e-test: make waitFor time out --- packages/e2e-test/src/helpers.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/e2e-test/src/helpers.ts b/packages/e2e-test/src/helpers.ts index 783b7e3988..b59de75860 100644 --- a/packages/e2e-test/src/helpers.ts +++ b/packages/e2e-test/src/helpers.ts @@ -91,9 +91,14 @@ export function handleError(err: Error & { code?: unknown }) { * .cancel() is available * @returns {Promise} Promise of resolution */ -export function waitFor(fn: () => boolean) { - 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(); From b73612df6476122043a9da08a29b2eba7b7d73fb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 14:56:05 +0200 Subject: [PATCH 13/24] e2e-test: add minimal test --- packages/e2e-test/src/helpers.test.ts | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 packages/e2e-test/src/helpers.test.ts 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(); + }); +}); From e8e6deb0e46c48585a702837379900a133d7131f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 15:33:15 +0200 Subject: [PATCH 14/24] e2e-test: switch to using ts-node register to pass options --- packages/e2e-test/package.json | 4 +-- .../e2e-test/src/{index.ts => e2e-test.ts} | 0 packages/e2e-test/src/index.js | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) rename packages/e2e-test/src/{index.ts => e2e-test.ts} (100%) create mode 100644 packages/e2e-test/src/index.js diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 389f4b6f93..8e6c730431 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -13,9 +13,9 @@ "backstage" ], "license": "Apache-2.0", - "main": "src/index.ts", + "main": "src/index.js", "scripts": { - "start": "yarn ts-node --transpile-only --compiler-options '{\"module\":\"CommonJS\"}' .", + "start": "node .", "lint": "backstage-cli lint", "test": "backstage-cli test", "test:e2e": "yarn start" diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/e2e-test.ts similarity index 100% rename from packages/e2e-test/src/index.ts rename to packages/e2e-test/src/e2e-test.ts diff --git a/packages/e2e-test/src/index.js b/packages/e2e-test/src/index.js new file mode 100644 index 0000000000..90f8030a0e --- /dev/null +++ b/packages/e2e-test/src/index.js @@ -0,0 +1,25 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +require('ts-node').register({ + transpileOnly: true, + project: require('path').resolve(__dirname, '../../../tsconfig.json'), + compilerOptions: { + module: 'CommonJS', + }, +}); + +require('./e2e-test'); From 5b722c179fb43932cf6109a389dc8d6a57e209dd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 15:48:34 +0200 Subject: [PATCH 15/24] e2e-test: include local deps from plugin template --- packages/e2e-test/src/e2e-test.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/e2e-test.ts index 46992ab966..7377655497 100644 --- a/packages/e2e-test/src/e2e-test.ts +++ b/packages/e2e-test/src/e2e-test.ts @@ -35,6 +35,12 @@ 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-')); print(`CLI E2E test root: ${rootDir}\n`); @@ -67,20 +73,18 @@ async function buildDistWorkspace(workspaceName: string, rootDir: string) { await fs.ensureDir(workspaceDir); // We grab the needed dependencies from the create app template - const createAppDeps: string[] = []; - for (const pkgPath of ['packages/app', 'packages/backend']) { - const path = paths.resolveOwnRoot( - 'packages/create-app/templates/default-app', - pkgPath, - 'package.json.hbs', - ); - const appPkgTemplate = await fs.readFile(path, 'utf8'); - const { dependencies: allDeps } = JSON.parse( - handlebars.compile(appPkgTemplate)({ version: '0.0.0' }), - ); - createAppDeps.push( - ...Object.keys(allDeps).filter(name => name.startsWith('@backstage/')), + 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' }), ); + + Array() + .concat(Object.keys(dependencies), Object.keys(devDependencies)) + .filter(name => name.startsWith('@backstage/')) + .forEach(dep => createAppDeps.add(dep)); } print(`Preparing workspace`); From 4e5422bd34275f82a479dbc48c5ba8e46b3b62d0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 15:55:29 +0200 Subject: [PATCH 16/24] workflows: add Windows master build --- .github/workflows/master-win.yml | 47 ++++++++++++++++++++++++++++++++ .github/workflows/master.yml | 2 +- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/master-win.yml diff --git a/.github/workflows/master-win.yml b/.github/workflows/master-win.yml new file mode 100644 index 0000000000..074f944250 --- /dev/null +++ b/.github/workflows/master-win.yml @@ -0,0 +1,47 @@ +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 + - 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') }} + - 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: yarn install + run: yarn install --frozen-lockfile + +# 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..805095ec53 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -1,4 +1,4 @@ -name: Master Build +name: Main Master Build on: push: From ed733cf02967d29a549c77d887f1276a83868bc6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Aug 2020 18:01:13 +0200 Subject: [PATCH 17/24] docsgen: fix link generation on windows --- packages/docgen/src/docgen/ApiDocGenerator.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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, From ed5a0e702821d36ef6ffec0ef0582c37869f90ec Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Mon, 24 Aug 2020 00:34:16 -0400 Subject: [PATCH 18/24] docs: add wealthsimple to adopters list (#2090) --- ADOPTERS.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 | From 9939dace2f2de5bcb0685f5d84bd472187d6020a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Aug 2020 14:54:54 +0200 Subject: [PATCH 19/24] fix(e2e): attempt to unbreak e2e tests due to bad rollup-plugin-dts release --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 0291e720d2..b57ee8cb67 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -76,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", diff --git a/yarn.lock b/yarn.lock index 5a42953413..2ad2111e29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20008,10 +20008,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-dts@^1.4.6: - version "1.4.10" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.10.tgz#0373c4284a2ba4d2d72df69c289271a816bc2736" - integrity sha512-bL6MBXc8lK7D5b/tYbHaglxs4ZxMQTQilGA6Xm9KQBEj4h9ZwIDlAsvDooGjJ/cOw23r3POTRtSCEyTHxtzHJg== +rollup-plugin-dts@1.4.11: + version "1.4.11" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.11.tgz#aedf0b7bb91d51e20b755e2c18e840edfc7af7a1" + integrity sha512-yiScAMKgwH77b44a/IFGgjLsmwSlNfQhEM+eCb2uMrupQMPE1n/12wrnT431+v1u6wYMF1XuHqldh+v/7mTvYA== optionalDependencies: "@babel/code-frame" "^7.10.4" From 9bacbf14cb6eea3abb0a5fc38d7efe938d2297a5 Mon Sep 17 00:00:00 2001 From: Marc Bruggmann Date: Mon, 24 Aug 2020 15:12:33 +0200 Subject: [PATCH 20/24] Update the system model to focus more on the core entities. (#2011) * Update the system model to focus more on the core entities. Now that we have a way of labeling and annotating catalogue entities, we can focus the system model more on the entities themselves. I believe that also makes more sense as a model that is shared across many companies - it's easier to standardize on the core entities, and leave some room to implement further abstraction or categories to individual implementations. * Re-add system and domain conepts. --- .../software-model-core-entities.png | Bin 0 -> 17277 bytes .../features/software-catalog/system-model.md | 129 ++++++++++-------- 2 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 docs/features/software-catalog/software-model-core-entities.png 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 0000000000000000000000000000000000000000..b718b7527c3e5a8ac723f09d294457f8bda6ce44 GIT binary patch literal 17277 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 literal 0 HcmV?d00001 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 From b5ada28f261f0de18a07aa6821d9749dcb441573 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 Aug 2020 15:52:44 +0200 Subject: [PATCH 21/24] Added techdocs-ref to backstage entity --- catalog-info.yaml | 1 + plugins/github-actions/scripts/sample.yaml | 1 + 2 files changed, 2 insertions(+) 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/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 From 889e1fd4bb929228aedb04f9bf34768acbf87145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Mon, 24 Aug 2020 16:37:51 +0200 Subject: [PATCH 22/24] Docs: strategies for adopting Backstage (#2092) * Adopting guidelines * More updates * Tactics * Add metrics * Add MTTR --- docs/README.md | 1 + docs/assets/pop.png | Bin 0 -> 103686 bytes docs/overview/adopting.md | 144 ++++++++++++++++++++++++++++++++++++++ docs/plugins/index.md | 2 +- microsite/i18n/en.json | 5 +- microsite/sidebars.json | 3 +- mkdocs.yml | 1 + 7 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 docs/assets/pop.png create mode 100644 docs/overview/adopting.md 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/assets/pop.png b/docs/assets/pop.png new file mode 100644 index 0000000000000000000000000000000000000000..441126f33c08cda5006dcfd913f76b027904bbd5 GIT binary patch literal 103686 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ðYy$A#7{R;Q|C`ENYI;6OPe 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 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/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/microsite/i18n/en.json b/microsite/i18n/en.json index 698d86a26a..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" }, @@ -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/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/mkdocs.yml b/mkdocs.yml index 085d83b7cc..e227ab8f9d 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' From edab8b2ad89e7a90f61b9d6cff82371f20d2d00f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 24 Aug 2020 17:01:27 +0200 Subject: [PATCH 23/24] fix(template tags): fixes issues with tags specified with uppercase (#2049) * fix(template tags): fixes issues with tags specified with uppercase * adds new validator for metadata tags * fix(template tags): change tags to be lowercase * fix(template tags docs): update docs to include lowercased tags * fix(validator): change back to using isValidDnsLabel for tags * fix(software catalog docs): use lowercase tags in docs --- docs/features/software-catalog/descriptor-format.md | 4 ++-- docs/features/software-templates/adding-templates.md | 4 ++-- .../extending/create-your-own-templater.md | 4 ++-- .../sample-templates/create-react-app/template.yaml | 6 +++--- .../sample-templates/react-ssr-template/template.yaml | 4 ++-- .../sample-templates/springboot-grpc-template/template.yaml | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) 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-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index b0405cab79..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 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/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/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 From e94c1e321eeec57d93f42f6c5664758edbbb7363 Mon Sep 17 00:00:00 2001 From: Jeff Feng <46946747+fengypants@users.noreply.github.com> Date: Mon, 24 Aug 2020 12:54:18 -0400 Subject: [PATCH 24/24] Update siteConfig.js (#2100) Now that we have a Docs tab on backstage.io, seems like the GitHub menu item can go back to linking to the top of the repo. --- microsite/siteConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index f0c8ce5fa5..73a25cd931 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -35,7 +35,7 @@ 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', }, {